Compare commits

..

No commits in common. "master" and "V2.0.1" have entirely different histories.

174 changed files with 5024 additions and 9942 deletions

View File

@ -1,17 +0,0 @@
meta {
name: Ban Channel
type: http
seq: 1
}
put {
url: http://localhost:8166/api/channel/ban?user=[discord-user-id]&a=[discord-user-id]&channel=[discord-channel-id]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
a: [discord-user-id]
channel: [discord-channel-id]
}

View File

@ -1,17 +0,0 @@
meta {
name: Unban Channel
type: http
seq: 2
}
put {
url: http://localhost:8166/api/channel/unban?user=[discord-user-id]&a=[discord-user-id]&channel=[discord-channel-id]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
a: [discord-user-id]
channel: [discord-channel-id]
}

View File

@ -1,3 +0,0 @@
meta {
name: Channel Management [Admin]
}

View File

@ -1,16 +0,0 @@
meta {
name: Activate API Key
type: http
seq: 2
}
put {
url: http://localhost:8166/api/key/activate?user=[discord-user-id]&a=[discord-user-id]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
a: [discord-user-id]
}

View File

@ -1,16 +0,0 @@
meta {
name: Ban API Key
type: http
seq: 4
}
put {
url: http://localhost:8166/api/key/ban?user=[discord-user-id]&a=[discord-user-id]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
a: [discord-user-id]
}

View File

@ -1,16 +0,0 @@
meta {
name: Deactivate API Key
type: http
seq: 3
}
put {
url: http://localhost:8166/api/key/deactivate?user=[discord-user-id]&a=[discord-user-id]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
a: [discord-user-id]
}

View File

@ -1,16 +0,0 @@
meta {
name: Generate API Key
type: http
seq: 1
}
get {
url: http://localhost:8166/api/key?user=[discord-user-id]&a=[discord-user-id]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
a: [discord-user-id]
}

View File

@ -1,16 +0,0 @@
meta {
name: Unban API Key
type: http
seq: 5
}
put {
url: http://localhost:8166/api/key/unban?user=[discord-user-id]&a=[discord-user-id]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
a: [discord-user-id]
}

View File

@ -1,3 +0,0 @@
meta {
name: Key Management [Admin]
}

View File

@ -1,3 +0,0 @@
meta {
name: Admin Requests
}

View File

@ -1,16 +0,0 @@
meta {
name: Activate Channel
type: http
seq: 3
}
put {
url: http://localhost:8166/api/channel/activate?user=[discord-user-id]&channel=[discord-channel-id]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
channel: [discord-channel-id]
}

View File

@ -1,16 +0,0 @@
meta {
name: Add Channel
type: http
seq: 2
}
post {
url: http://localhost:8166/api/channel/add?user=[discord-user-id]&channel=[discord-channel-id]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
channel: [discord-channel-id]
}

View File

@ -1,16 +0,0 @@
meta {
name: Deactivate Channel
type: http
seq: 4
}
put {
url: http://localhost:8166/api/channel/deactivate?user=[discord-user-id]&channel=[discord-channel-id]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
channel: [discord-channel-id]
}

View File

@ -1,15 +0,0 @@
meta {
name: Get Channels
type: http
seq: 1
}
get {
url: http://localhost:8166/api/channel?user=[discord-user-id]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
}

View File

@ -1,3 +0,0 @@
meta {
name: Channel Management
}

View File

@ -1,17 +0,0 @@
meta {
name: Delete API Key
type: http
seq: 1
}
delete {
url: http://localhost:8166/api/key/delete?user=[discord-user-id]&email=[email-address]&code=[optional:delete-code]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
email: [email-address]
code: [optional:delete-code]
}

View File

@ -1,3 +0,0 @@
meta {
name: Key Management
}

View File

@ -1,33 +0,0 @@
meta {
name: Roll Dice
type: http
seq: 1
}
get {
url: http://localhost:8166/api/roll?user=[discord-user-id]&channel=[discord-channel-id]&rollstr=[artificer-roll-cmd]&documentation=All items below are optional. Flags do not need values.&nd=[no-details-flag]&snd=[super-no-details-flag]&hr=[hide-raw-roll-details-flag]&s=[spoiler-results-flag]&m-or-max=[max-roll-flag, cannot be used with n flag]&min=[min-roll-flag, cannot be used with n, sn, or max]&n=[nominal-roll-flag, cannot be used with sn, max or min flag]&sn=[simulated-nominal-flag, can pass number with it, cannot be used with max, min, n. or cc]&gms=[csv-of-discord-user-ids-to-be-dmed-results]&o=[order-rolls, must be a or d]&c=[count-flag]&cc=[confirm-crit-flag, cannot be used with sn]&rd=[roll-dist-flag]&nv-or-vn=[number-variables-flag]&cd=[custom-dice, format value as name:[side1,side2,...,sideN], use ; to separate multiple custom dice]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
channel: [discord-channel-id]
rollstr: [artificer-roll-cmd]
documentation: All items below are optional. Flags do not need values.
nd: [no-details-flag]
snd: [super-no-details-flag]
hr: [hide-raw-roll-details-flag]
s: [spoiler-results-flag]
m-or-max: [max-roll-flag, cannot be used with n flag]
min: [min-roll-flag, cannot be used with n, sn, or max]
n: [nominal-roll-flag, cannot be used with sn, max or min flag]
sn: [simulated-nominal-flag, can pass number with it, cannot be used with max, min, n. or cc]
gms: [csv-of-discord-user-ids-to-be-dmed-results]
o: [order-rolls, must be a or d]
c: [count-flag]
cc: [confirm-crit-flag, cannot be used with sn]
rd: [roll-dist-flag]
nv-or-vn: [number-variables-flag]
cd: [custom-dice, format value as name:[side1,side2,...,sideN], use ; to separate multiple custom dice]
}

View File

@ -1,3 +0,0 @@
meta {
name: Roll Requests
}

View File

@ -1,7 +0,0 @@
meta {
name: Authenticated
}
headers {
X-Api-Key: [YOUR-API-KEY-HERE]
}

View File

@ -1,11 +0,0 @@
meta {
name: Get Heatmap Image
type: http
seq: 2
}
get {
url: http://localhost:8166/api/heatmap.png
body: none
auth: inherit
}

View File

@ -1,16 +0,0 @@
meta {
name: Request API Key
type: http
seq: 1
}
get {
url: http://localhost:8166/api/key?user=[discord-user-id]&email=[email-address]
body: none
auth: inherit
}
params:query {
user: [discord-user-id]
email: [email-address]
}

View File

@ -1,3 +0,0 @@
meta {
name: Unauthenticated
}

View File

@ -1,12 +0,0 @@
{
"version": "1",
"name": "The Artificer API",
"type": "collection",
"ignore": [
".git"
],
"presets": {
"requestType": "http",
"requestUrl": "http://localhost:8166/api"
}
}

View File

@ -1,3 +0,0 @@
auth {
mode: none
}

View File

@ -12,4 +12,5 @@ sonar.exclusions=emojis
sonar.sourceEncoding=UTF-8 sonar.sourceEncoding=UTF-8
# Exclusions for copy-paste detection # Exclusions for copy-paste detection
#sonar.cpd.exclusions= # src/commands/rollHelp.ts, src/commands/rollDecorators.ts are excluded to get rid of the duplicate code compliant. Sonar does not like you initializing JSON in ts files.
sonar.cpd.exclusions=src/commands/rollHelp.ts,src/commands/rollDecorators.ts

48
.vscode/settings.json vendored
View File

@ -1,56 +1,12 @@
{ {
"deno.enable": true, "deno.enable": true,
"deno.lint": true, "deno.lint": true,
"deno.unstable": true,
"deno.import_intellisense_origins": { "deno.import_intellisense_origins": {
"https://deno.land": true "https://deno.land": true
}, },
"spellright.language": [ "spellright.language": [
"en" "en"
], ],
"spellright.documentTypes": [], "spellright.documentTypes": []
"cSpell.words": [
"artigen",
"channelid",
"CWOD",
"DEVMODE",
"Discordeno",
"Dists",
"dkdk",
"EMDAS",
"Exponentials",
"funciton",
"guildid",
"hidewarn",
"imagescript",
"indev",
"Inno",
"LOCALMODE",
"localtoken",
"longtext",
"mtsf",
"Mult",
"nojs",
"noodp",
"noydir",
"oldcnt",
"oper",
"ovad",
"PEMDAS",
"Rehost",
"Rehosts",
"resultid",
"rolldecorators",
"rollhelp",
"rollstr",
"rsfop",
"sproc",
"Tarantallegra",
"tinyint",
"unauthorised",
"unban",
"xcwody",
"xdydz",
"xdydzracsq",
"xovady"
]
} }

View File

@ -1,10 +0,0 @@
## Imports
- Should be grouped the following order:
- `@` (third party)
- `/` (root directory)
- `artigen`
- `commands`
- `db`
- `endpoints`
- `src`
- Should be alphabetical by import source file name

View File

@ -3,7 +3,7 @@
### Public Bot Information ### Public Bot Information
Publicly available versions of `The Artificer#8166` (Discord ID: `789045930011656223`) (herein referred to as _The Bot_ or _Bot_) do not track or collect user information via Discord. Publicly available versions of `The Artificer#8166` (Discord ID: `789045930011656223`) (herein referred to as _The Bot_ or _Bot_) do not track or collect user information via Discord.
Upon inviting _The Bot_ to a user's guild, _The Bot_ sends the guild name, Discord Guild ID, and current count of guild members to Burn_E99 (herein referred to as _The Developer_) via a private Discord Guild. The guild name, Discord Guild ID, and current count of guild members are only used to roughly gage how popular _The Bot_ is and to determine if _The Bot_'s hosting solution needs to be improved. These pieces of information will never be sold or shared with anyone. Upon inviting _The Bot_ to a user's guild, _The Bot_ sends the guild name, Discord Guild ID, and current count of guild members to Burn_E99#1062 (herein referred to as _The Developer_) via a private Discord Guild. The guild name, Discord Guild ID, and current count of guild members are only used to roughly gage how popular _The Bot_ is and to determine if _The Bot_'s hosting solution needs to be improved. These pieces of information will never be sold or shared with anyone.
Like all Discord bots, _The Bot_ reads every message that it is allowed to, meaning if _The Bot_ is allowed to see a channel in a guild, it reads every new message sent in said channel. This is due to the way the Discord API itself is designed. _The Bot_ does not read any messages sent in the past. Like all Discord bots, _The Bot_ reads every message that it is allowed to, meaning if _The Bot_ is allowed to see a channel in a guild, it reads every new message sent in said channel. This is due to the way the Discord API itself is designed. _The Bot_ does not read any messages sent in the past.
* Messages that do not begin with _The Bot_'s command prefix are not saved or stored anywhere. Messages that do not begin with _The Bot_'s command prefix are ignored and not processed. * Messages that do not begin with _The Bot_'s command prefix are not saved or stored anywhere. Messages that do not begin with _The Bot_'s command prefix are ignored and not processed.
@ -39,11 +39,11 @@ If you would like to remove all of your submitted data, this can easily be done
If you have been banned from using _The API_, your API Key, and registration information (Discord User ID, and Email Address) will not be deleted as this data is considered necessary. If you have been banned from using _The API_, your API Key, and registration information (Discord User ID, and Email Address) will not be deleted as this data is considered necessary.
If you would like your Discord Guild ID to be removed from _The Bot_'s database, a Guild Owner or Administrator needs to run `[[api delete`. This will remove your Discord Guild's ID from _The Bot_'s database, reverting it back to the default setting of blocking _The API_. Additionally, _The Bot_ will automatically remove any data related to your Discord Guild when _The Bot_ is removed from your guild. If you would like your Discord Guild ID to be removed from _The Bot_'s database, a Guild Owner or Administrator needs to run `[[api delete`. This will remove your Discord Guild's ID from _The Bot_'s database, reverting it back to the default setting of blocking _The API_.
If your guild has been banned from using _The API_, the Discord Guild ID will not be deleted as this data is considered necessary. If your guild has been banned from using _The API_, the Discord Guild ID will not be deleted as this data is considered necessary.
The data described above is considered necessary to prevent users from abusing the API and ban evading by deleting and recreating their account. The data described above is considered necessary to prevent users from abusing the API and ban evading by deleting and recreating their account.
## Discord Command Data Deletion ## Discord Command Data Deletion
If you would like to ensure that all of your submitted reports are removed from _The Bot_'s private development server, please contact _The Developer_ via Discord (by sending a direct message to Burn_E99) or via email (<ean@milligan.dev>) with a message along the lines of `"Please remove all of my submitted reports from your development server."`. Submitted reports are deleted from the server as they are processed, which happens roughly once a week, but this can be accelerated if requested. If you would like to ensure that all of your submitted reports are removed from _The Bot_'s private development server, please contact _The Developer_ via Discord (by sending a direct message to Burn_E99#1062) or via email (<ean@milligan.dev>) with a message along the lines of `"Please remove all of my submitted reports from your development server."`. Submitted reports are deleted from the server as they are processed, which happens roughly once a week, but this can be accelerated if requested.

192
README.md
View File

@ -1,4 +1,4 @@
# The Artificer - A Dice Rolling Discord Bot | V3.0.0 - 2025/04/26 # The Artificer - A Dice Rolling Discord Bot | V2.0.1 - 2022/07/08
[![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)
@ -23,7 +23,9 @@ The Artificer comes with a few supplemental commands to the main rolling command
* `[[help` or `[[h` or `[[?` * `[[help` or `[[h` or `[[?`
* Provides a message similar to this available commands block. * Provides a message similar to this available commands block.
* `[[rollhelp` or `[[??` or `[[rh` or `[[hr` * `[[rollhelp` or `[[??` or `[[rh` or `[[hr`
* Opens the new help library. * Details on how to use the roll command, listed as `[[xdy...]]` below.
* `[[rolldecorators` or `[[???` or `[[rd` or `[[dr`
* Details on how to use decorators on the roll command.
* `[[api [subcommand]` * `[[api [subcommand]`
* Administrative tools for the bots's API. These commands may only be used by the Owner or Admins of your guild. * Administrative tools for the bots's API. These commands may only be used by the Owner or Admins of your guild.
* Available Subcommands: * Available Subcommands:
@ -52,82 +54,54 @@ The Artificer comes with a few supplemental commands to the main rolling command
* Prints out how many users, channels, and servers the bot is currently serving. * Prints out how many users, channels, and servers the bot is currently serving.
* `[[heatmap` or `[[hm` * `[[heatmap` or `[[hm`
* Heatmap of when the roll command is run the most. * Heatmap of when the roll command is run the most.
* `[[report` * `[[report` or `[[r [command that failed]`
* People aren't perfect, but this bot is trying to be. * People aren't perfect, but this bot is trying to be.
* If you encounter a command that errors out or returns something unexpected, please use this command to alert the developers of the problem. * If you encounter a command that errors out or returns something unexpected, please use this command to alert the developers of the problem.
* Example: * Example:
* `[[report [[2+2]] returned 5 when I expected it to return 4` will send the entire message after `[[report` to the devs via Discord. * `[[report [[2+2]] returned 5 when I expected it to return 4` will send the entire message after `[[report` to the devs via Discord.
* `[[opt-out` or `[[ignore-me`
* Adds you to an ignore list so the bot will never respond to you
* `[[opt-in` **Available via DM ONLY**
* Removes you from the ignore list
* `[[inline [subcommand]`
* Controls whether or not inline rolls can be done in a guild, defaults off. 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:
* `[[inline help`
* Provides a message similar to this subcommand description.
* `[[inline status`
* Shows the current status of inline rolls for this guild.
* `[[inline allow` or `[[inline enable`
* Allows inline rolls in the guild.
* `[[inline block` or `[[inline disable` or `[[inline delete`
* Blocks inline rolls in the guild.
* `[[xdydzracsq!]]` * `[[xdydzracsq!]]`
* This is the command the bot was built specifically for. * This is the command the bot was built specifically for.
* It looks a little complicated at first, but if you are familiar with the [Roll20 formatting](https://artificer.eanm.dev/roll20), this will be no different. * It looks a little complicated at first, but if you are familiar with the [Roll20 formatting](https://artificer.eanm.dev/roll20), this will no different.
* Any math (limited to exponential, multiplication, division, modulus, addition, and subtraction) will be correctly handled in PEMDAS order, so use parenthesis as needed. * Any math (limited to exponentials, multiplication, division, modulus, addition, and subtraction) will be correctly handled in PEMDAS order, so use parenthesis as needed.
* PI and e are available for use. * PI and e are available for use.
* Parameters for rolling: * Parameters for rolling:
| Parameter | Required? | Repeatable? | Description | | Paramater | Required? | Repeatable? | Description |
|---------------|-------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |---------------|-------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| x | Optional | No | number of dice to roll, if omitted, 1 is used | | x | Optional | No | number of dice to roll, if omitted, 1 is used, additionally, replace x with `F` to roll the dice as Fate dice |
| dy | Required | No | size of dice to roll, d20 = 20 sided die, replace y with `F` to roll the dice as Fate dice | | dy | Required | No | size of dice to roll, d20 = 20 sided die |
| dz or dlz | Optional | No | drops the lowest z dice, cannot be used with any other drop or keep options | | dz or dlz | Optional | No | drops the lowest z dice, cannot be used with any other drop or keep options |
| kz or khz | Optional | No | keeps the highest z dice, cannot be used with any other drop or keep options | | kz or khz | Optional | No | keeps the highest z dice, cannot be used with any other drop or keep options |
| dhz | Optional | No | drops the highest z dice, cannot be used with any other drop or keep options | | dhz | Optional | No | drops the highest z dice, cannot be used with any other drop or keep options |
| klz | Optional | No | keeps the lowest z dice, cannot be used with any other drop or keep options | | klz | Optional | No | keeps the lowest z dice, cannot be used with any other drop or keep options |
| ra or r=a | Optional | Yes | rerolls any rolls that match a, r3 will reroll every die that land on 3, throwing out old rolls, cannot be used with ro | | ra or r=a | Optional | Yes | rerolls any rolls that match a, r3 will reroll every die that land on 3, throwing out old rolls, cannot be used with ro |
| r<a | Optional | Yes | rerolls any rolls that are less than or equal to a, r3 will reroll every die that land on 3, 2, or 1, throwing out old rolls, cannot be used with ro | | r<a | Optional | Yes | rerolls any rolls that are less than or equal to a, r3 will reroll every die that land on 3, 2, or 1, throwing out old rolls, cannot be used with ro |
| r>a | Optional | Yes | rerolls any rolls that are greater than or equal to a, r3 will reroll every die that land on 3 or greater, throwing out old rolls, cannot be used with ro | | r>a | Optional | Yes | rerolls any rolls that are greater than or equal to a, r3 will reroll every die that land on 3 or greater, throwing out old rolls, cannot be used with ro |
| roa or ro=a | Optional | Yes | rerolls any rolls that match a, r3 will reroll each die that lands on 3 ONLY ONE TIME, throwing out old rolls, cannot be used with r | | roa or ro=a | Optional | Yes | rerolls any rolls that match a, r3 will reroll each die that lands on 3 ONLY ONE TIME, throwing out old rolls, cannot be used with r |
| ro<a | Optional | Yes | rerolls any rolls that are less than or equal to a, r3 will reroll each die that lands on 3, 2, or 1 ONLY ONE TIME, throwing out old rolls, cannot be used with r | | ro<a | Optional | Yes | rerolls any rolls that are less than or equal to a, r3 will reroll each die that lands on 3, 2, or 1 ONLY ONE TIME, throwing out old rolls, cannot be used with r |
| ro>a | Optional | Yes | rerolls any rolls that are greater than or equal to a, r3 will reroll each die that lands on 3 or greater ONLY ONE TIME, throwing out old rolls, cannot be used with r | | ro>a | Optional | Yes | rerolls any rolls that are greater than or equal to a, r3 will reroll each die that lands on 3 or greater ONLY ONE TIME, throwing out old rolls, cannot be used with r |
| csq or cs=q | Optional | Yes | changes crit score to q | | csq or cs=q | Optional | Yes | changes crit score to q |
| cs<q | Optional | Yes | changes crit score to be less than or equal to q | | cs<q | Optional | Yes | changes crit score to be less than or equal to q |
| cs>q | Optional | Yes | changes crit score to be greater than or equal to q | | cs>q | Optional | Yes | changes crit score to be greater than or equal to q |
| cfq or cf=q | Optional | Yes | changes crit fail to q | | cfq or cf=q | Optional | Yes | changes crit fail to q |
| cf<q | Optional | Yes | changes crit fail to be less than or equal to q | | cf<q | Optional | Yes | changes crit fail to be less than or equal to q |
| cf>q | Optional | Yes | changes crit fail to be greater than or equal to q | | cf>q | Optional | Yes | changes crit fail to be greater than or equal to q |
| ! | Optional | No | exploding, rolls another dy for every crit success | | ! | Optional | No | exploding, rolls another dy for every crit success |
| !o | Optional | No | exploding once, rolls another dy for each original crit success | | !o | Optional | No | exploding once, rolls another dy for each original crit success |
| !p | Optional | No | penetrating explosion, rolls one dy for each crit success, but subtracts one from each resulting explosion | | !p | Optional | No | penetrating explosion, rolls one dy for each crit success, but subtracts one from each resulting explosion |
| !! | Optional | No | compounding explosion, rolls one dy for each crit success, but adds the resulting explosion to the die that caused this explosion | | !! | Optional | No | compounding explosion, rolls one dy for each crit success, but adds the resulting explosion to the die that caused this explosion |
| !=u | Optional | Yes | exploding, rolls another dy for every die that lands on u | | !=u | Optional | Yes | exploding, rolls another dy for every die that lands on u |
| !>u | Optional | Yes | exploding, rolls another dy for every die that lands on u or greater | | !>u | Optional | Yes | exploding, rolls another dy for every die that lands on u or greater |
| !<u> | Optional | Yes | exploding, rolls another dy for every die that lands on u or less | | !<u> | Optional | Yes | exploding, rolls another dy for every die that lands on u or less |
| !o=u | Optional | Yes | exploding once, rolls another dy for each original die that landed on u | | !o=u | Optional | Yes | exploding once, rolls another dy for each original die that landed on u |
| !o>u | Optional | Yes | exploding once, rolls another dy for each original die that landed on u or greater | | !o>u | Optional | Yes | exploding once, rolls another dy for each original die that landed on u or greater |
| !o<u | Optional | Yes | exploding once, rolls another dy for each original die that landed on u or less | | !o<u | Optional | Yes | exploding once, rolls another dy for each original die that landed on u or less |
| !p=u | Optional | Yes | penetrating explosion, rolls one dy for each die that lands on u, but subtracts one from each resulting explosion | | !p=u | Optional | Yes | penetrating explosion, rolls one dy for each die that lands on u, but subtracts one from each resulting explosion |
| !p>u | Optional | Yes | penetrating explosion, rolls one dy for each die that lands on u or greater, but subtracts one from each resulting explosion | | !p>u | Optional | Yes | penetrating explosion, rolls one dy for each die that lands on u or greater, but subtracts one from each resulting explosion |
| !p<u | Optional | Yes | penetrating explosion, rolls one dy for each die that lands on u or under, but subtracts one from each resulting explosion | | !p<u | Optional | Yes | penetrating explosion, rolls one dy for each die that lands on u or under, but subtracts one from each resulting explosion |
| !!=u | Optional | Yes | compounding explosion, rolls one dy for each die that lands on u, but adds the resulting explosion to the die that caused this explosion | | !!=u | Optional | Yes | compounding explosion, rolls one dy for each die that lands on u, but adds the resulting explosion to the die that caused this explosion |
| !!>u | Optional | Yes | compounding explosion, rolls one dy for each die that lands on u or greater, but adds the resulting explosion to the die that caused this explosion | | !!>u | Optional | Yes | compounding explosion, rolls one dy for each die that lands on u or greater, but adds the resulting explosion to the die that caused this explosion |
| !!<u | Optional | Yes | compounding explosion, rolls one dy for each die that lands on u or under, but adds the resulting explosion to the die that caused this explosion | | !!<u | Optional | Yes | compounding explosion, rolls one dy for each die that lands on u or under, but adds the resulting explosion to the die that caused this explosion |
| m | Optional | No | matching dice, adds labels to any dice that match, cannot be combined with Target Number/Successes or Target Failures |
| mz | Optional | No | matching dice, adds labels to any dice that have z or more matches, cannot be combined with Target Number/Successes or Target Failures |
| mt | Optional | No | matching dice, adds labels to any dice that match, changes result to be the count of labels added, cannot be combined with Target Number/Successes or Target Failures |
| mtz | Optional | No | matching dice, adds labels to any dice that have z or more matches, changes result to be the count of labels added, cannot be combined with Target Number/Successes or Target Failures |
| s or sa | Optional | No | sort dice, sorts the list of dice for a roll in ascending order |
| sd | Optional | No | sort dice, sorts the list of dice for a roll in descending order |
| =z | Optional | Yes | target number/success, counts and marks dice as successful when they land on z, cannot be combined with the Dice Matching option |
| <z | Optional | Yes | target number/success, counts and marks dice as successful when they land on z or less, cannot be combined with the Dice Matching option |
| >z | Optional | Yes | target number/success, counts and marks dice as successful when they land on z or greater, cannot be combined with the Dice Matching option |
| fz or f=z | Optional | Yes | target failures, counts and marks dice as failed when they land on z, cannot be combined with the Dice Matching option |
| f<z | Optional | Yes | target failures, counts and marks dice as failed when they land on z or less, cannot be combined with the Dice Matching option |
| f>z | Optional | Yes | target failures, counts and marks dice as failed when they land on z or greater, cannot be combined with the Dice Matching option |
* If the parameter is Required, it must be provided at all times. * If the parameter is Required, it must be provided at all times.
* If the parameter is Repeatable, it may occur multiple times in the roll configuration. * If the parameter is Repeatable, it may occur multiple times in the roll configuration.
@ -148,23 +122,14 @@ The Artificer comes with a few supplemental commands to the main rolling command
* `-nd` - No Details - Suppresses all details of the requested roll * `-nd` - No Details - Suppresses all details of the requested roll
* `-snd` - Super No Details - Suppresses all details of the requested roll and hides no details message * `-snd` - Super No Details - Suppresses all details of the requested roll and hides no details message
* `-s` - Spoiler - Spoilers all details of the requested roll * `-s` - Spoiler - Spoilers all details of the requested roll
* `-m` or `-max` - Maximize Roll - Rolls the theoretical maximum roll, cannot be used with `-n`, `-min`, or `-sn` * `-m` - Maximize Roll - Rolls the theoretical maximum roll, cannot be used with -n
* `-min` - Minimize Roll - Rolls the theoretical minimum roll, cannot be used with `-m`, `-max`, `-n`, or `-sn` * `-n` - Nominal Roll - Rolls the theoretical nominal roll, cannot be used with -m
* `-n` - Nominal Roll - Rolls the theoretical nominal roll, cannot be used with `-m`, `-max`, `-min`, or `-sn` * `-gm @user1 @user2 ... @usern` - GM Roll - Rolls the requested roll in GM mode, suppressing all publicly shown results and details and sending the results directly to the specified GMs
* `-sn` or `-sn [number]` - Simulated Nominal - Rolls the requests roll many times to approximately simulate the nominal of complex rolls, can specify the amount or accept default amount by not specify the amount, cannot be used with `-m`, `-max`, `-min`, `-n`, or `-cc`
* `-gm @user1 @user2 ... @userN` - GM Roll - Rolls the requested roll in GM mode, suppressing all publicly shown results and details and sending the results directly to the specified GMs
* `-o a` or `-o d` - Order Roll - Rolls the requested roll and orders the results in the requested direction * `-o a` or `-o d` - Order Roll - Rolls the requested roll and orders the results in the requested direction
* `-ct` - Comma Totals - Adds commas to totals for readability
* `-cc` - Confirm Critical Hits - Automatically rerolls whenever a crit hits, cannot be used with `-sn`
* `-rd` - Roll Distribution - Shows a raw roll distribution of all dice in roll
* `-hr` - Hide Raw - Hide the raw input, showing only the results/details of the roll
* `-nv` or `-vn` - Number Variables - Adds `xN` before each roll command in the details section for debug reasons
* `-cd` - Custom Dice shapes - Allows a list of `name:[side1,side2,...,sideN]` separated by `;` to be passed to create special shaped dice
* The results have some formatting applied on them to provide details on what happened during this roll. * The results have some formatting applied on them to provide details on what happened during this roll.
* Critical successes will be **bolded** * Critical successes will be **bolded**
* Critical fails will be <ins>underlined</ins> * Critical fails will be <ins>underlined</ins>
* Rolls that were dropped or rerolled ~~crossed out~~ * Rolls that were dropped or rerolled ~~crossed out~~
* Rolls that exploded have an `!` added after them
## The Artificer API ## The Artificer API
The Artificer features an API that allows authenticated users to roll dice into Discord from third party applications (such as Excel macros). The API has a couple endpoints exposed to all authenticated users allowing management of channels that your API key can send rolls to. APIs requiring administrative access are not listed below. The Artificer features an API that allows authenticated users to roll dice into Discord from third party applications (such as Excel macros). The API has a couple endpoints exposed to all authenticated users allowing management of channels that your API key can send rolls to. APIs requiring administrative access are not listed below.
@ -180,11 +145,66 @@ Every API request **requires** the header `X-Api-Key` with the value set to the
* `429` - Too Many Requests - API rate limit exceeded, please slow down. * `429` - Too Many Requests - API rate limit exceeded, please slow down.
* `500` - Internal Server Error - Something broke, if this continues to happen, please submit a GitHub issue. * `500` - Internal Server Error - Something broke, if this continues to happen, please submit a GitHub issue.
Official API URL: `https://artificer.eanm.dev/api/` API URL: `https://artificer.eanm.dev/api/`
API Documentation can be found in the `.bruno` folder, which can be viewed in [Bruno](https://www.usebruno.com/). Available Endpoints and Methods Required:
API Key management via a basic GUI is available on the [API Tools](https://artificer.eanm.dev/) website. * `/api/roll` - `GET`
* Required query parameters:
* `user` - Your Discord User ID.
* `channel` - The Discord Channel ID that the bot is to send the results into.
* `rollstr` - A roll string formatted identically to the roll command detailed in the "Available Commands" section.
* Optional query parameters (these parameters do not require values unless specified):
* `c` - Count - Shows the Count Embed, containing the count of successful rolls, failed rolls, rerolls, drops, and explosions
* `nd` - No Details - Suppresses all details of the requested roll.
* `snd` - Super No Details - Suppresses all details of the requested roll and hides no details message.
* `s` - Spoiler - Spoilers all details of the requested roll.
* `m` - Maximize Roll - Rolls the theoretical maximum roll, cannot be used with Nominal roll.
* `n` - Nominal Roll - Rolls the theoretical nominal roll, cannot be used with Maximise roll.
* `gm` - GM Roll - Rolls the requested roll in GM mode, suppressing all publicly shown results and details and sending the results directly to the specified GMs. Takes a comma separated list of Discord User IDs.
* `o` - Order Roll - Rolls the requested roll and orders the results in the requested direction. Takes a single character: `a` or `d`.
* Returns:
* `200` - OK - Results of the roll should be found in Discord, but also are returned as a string via the API.
* `/api/channel` - `GET`
* Required query parameters:
* `user` - Your Discord ID.
* Returns:
* `200` - OK - JSON Array as a string containing allowed channels with their active and banned statuses.
* `/api/channel/add` - `POST`
* Required query parameters:
* `user` - Your Discord ID.
* `channel` - The Discord Channel ID you wish to whitelist for your user ID/API Key combo.
* Returns:
* `200` - OK - Nothing to be returned.
* `/api/channel/activate` - `PUT`
* Required query parameters:
* `user` - Your Discord ID.
* `channel` - The Discord Channel ID you wish to reactivate.
* Returns:
* `200` - OK - Nothing to be returned.
* `/api/channel/deactivate` - `PUT`
* Required query parameters:
* `user` - Your Discord ID.
* `channel` - The Discord Channel ID you wish to deactivate.
* Returns:
* `200` - OK - Nothing to be returned.
* `/api/key` - `GET`
* This endpoint does not require the `X-Api-Key` header.
* Required query parameters:
* `user` - Your Discord ID.
* `email` - An email address you can be reached at. The API Key will be sent to this address.
* Returns:
* `200` - OK - Nothing to be returned. API Key will be emailed to you within 24 hours.
* `/api/key/delete` - `DELETE`
* Required query parameters:
* `user` - Your Discord ID.
* `email` - An email address you can be reached at. This must match the email you registered with. The delete code will be sent to this address.
* `code` - Run this endpoint first without this field. Once you recieve the email containing the delete code, run this API a second time with this field
* Returns:
* `424` - Failed dependancy - You will be emailed a delete code to rerun this endpoint with.
* `200` - OK - Everything relating to your API key was successfully removed.
API Key management via a basic GUI is availble on the [API Tools](https://artificer.eanm.dev/) website.
## Problems? Feature requests? ## Problems? Feature requests?
If you run into any errors or problems with the bot, or think you have a good idea to add to the bot, please submit a new GitHub issue detailing it. If you don't have a GitHub account, a report command (detailed above) is provided for use in Discord. If you run into any errors or problems with the bot, or think you have a good idea to add to the bot, please submit a new GitHub issue detailing it. If you don't have a GitHub account, a report command (detailed above) is provided for use in Discord.
@ -192,11 +212,11 @@ If you run into any errors or problems with the bot, or think you have a good id
--- ---
## Self Hosting The Artificer ## Self Hosting The Artificer
The Artificer is built on [Deno](https://deno.land/) `v2.2.7` using [Discordeno](https://discordeno.mod.land/) `v12.0.1`. If you choose to run this yourself, you will need to rename `config.example.ts` to `config.ts` and edit some values. You will need to create a new [Discord Application](https://discord.com/developers/applications) and copy the newly generated token into the `"token"` key. If you want to utilize some of the bots dev features, you will need to fill in the keys `"logChannel"` and `"reportChannel"` with text channel IDs and `"devServer"` with a guild ID. The Artificer is built on [Deno](https://deno.land/) `v1.22.0` using [Discordeno](https://discordeno.mod.land/) `v12.0.1`. If you choose to run this yourself, you will need to rename `config.example.ts` to `config.ts` and edit some values. You will need to create a new [Discord Application](https://discord.com/developers/applications) and copy the newly generated token into the `"token"` key. If you want to utilize some of the bots dev features, you will need to fill in the keys `"logChannel"` and `"reportChannel"` with text channel IDs and `"devServer"` with a guild ID.
You will also need to install and setup a MySQL database with a user for the bot to use to add/modify the database. This user must have the "DB Manager" admin rights and "REFERENCES" Global Privileges. Once the DB is installed and a user is setup, run the provided `db\initialize.ts` to create the schema and tables. After this, run `db\populateDefaults.ts` to insert some needed values into the tables. You will also need to install and setup a MySQL database with a user for the bot to use to add/modify the database. This user must have the "DB Manager" admin rights and "REFERENCES" Global Privileges. Once the DB is installed and a user is setup, run the provided `db\initialize.ts` to create the schema and tables. After this, run `db\populateDefaults.ts` to insert some needed values into the tables.
Once everything is set up, starting the bot can simply be done with the command in `start.command`. Once everything is set up, starting the bot can simply be done with `deno run --allow-net .\mod.ts`.
If you choose to run version `1.1.0` or newer, ensure you disable the API in `config.ts` or verify you have properly secured your instance of The Artificer. If you enable the API, you should manually generate a 25 char nanoid and place it in `config.api.adminKey` and copy your `userid` and place it in `config.api.admin` before running `db\populateDefaults.ts`. If you choose to run version `1.1.0` or newer, ensure you disable the API in `config.ts` or verify you have properly secured your instance of The Artificer. If you enable the API, you should manually generate a 25 char nanoid and place it in `config.api.adminKey` and copy your `userid` and place it in `config.api.admin` before running `db\populateDefaults.ts`.

View File

@ -10,12 +10,11 @@ pidfile="/var/dbots/TheArtificer/artificer.pid"
artificer_root="/var/dbots/TheArtificer" artificer_root="/var/dbots/TheArtificer"
artificer_write="./logs/,./src/endpoints/gets/heatmap.png" artificer_write="./logs/,./src/endpoints/gets/heatmap.png"
artificer_read="./src/artigen/,./src/endpoints/gets/heatmap-base.png,./src/endpoints/gets/heatmap.png,./config.ts,./flags.ts" artificer_read="./src/solver/,./src/endpoints/gets/heatmap-base.png,./src/endpoints/gets/heatmap.png"
artificer_log="/var/log/artificer.log"
artificer_chdir="${artificer_root}" artificer_chdir="${artificer_root}"
command="/usr/sbin/daemon" command="/usr/sbin/daemon"
command_args="-f -R 5 -P ${pidfile} -o ${artificer_log} /usr/local/bin/deno run --allow-write=${artificer_write} --allow-read=${artificer_read} --allow-net --allow-import ${artificer_root}/mod.ts" command_args="-f -R 5 -P ${pidfile} /usr/local/bin/deno run --allow-write=${artificer_write} --allow-read=${artificer_read} --allow-net ${artificer_root}/mod.ts"
load_rc_config artificer load_rc_config artificer
run_rc_command "$1" run_rc_command "$1"

View File

@ -6,7 +6,7 @@ After=network.target
[Service] [Service]
Type=simple Type=simple
PIDFile=/run/deno.pid PIDFile=/run/deno.pid
ExecStart=/root/.deno/bin/deno run --allow-write=./logs/,./src/endpoints/gets/heatmap.png --allow-read=./src/artigen/,./src/endpoints/gets/heatmap-base.png,./src/endpoints/gets/heatmap.png,./config.ts,./flags.ts --allow-net --allow-import .\mod.ts ExecStart=/root/.deno/bin/deno run --allow-write=./logs/,./src/endpoints/gets/heatmap.png --allow-read=./src/solver/,./src/endpoints/gets/heatmap-base.png --allow-net .\mod.ts
RestartSec=60 RestartSec=60
Restart=on-failure Restart=on-failure

View File

@ -1,85 +1,63 @@
export const config = { export const config = {
name: 'The Artificer', // Name of the bot 'name': 'The Artificer', // Name of the bot
maxFileSize: 8388290, // Max file size bot can send 'version': '2.0.1', // Version of the bot
version: '3.0.0', // 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 'postfix': ']]', // Postfix for rolling command
postfix: ']]', // Postfix for rolling command 'limits': { // Limits for the bot functions
limits: { 'maxLoops': 5000000, // Determines how long the bot will attempt a roll, number of loops before it kills a roll. Increase this at your own risk, originally was set to 5 Million before rollWorkers were added, increased to 10 Million since multiple rolls can be handled concurrently
// Limits for the bot functions '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)
maxLoops: 1000000, // Determines how long the bot will attempt a roll, number of loops before it kills a roll. Increase this at your own risk. 'workerTimeout': 300000, // Maximum time before the bot kills a worker thread in ms
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: 300000, // Maximum time before the bot kills a worker thread in ms 'api': { // Setting for the built-in API
simulatedNominal: 100000, // Number of loops to run for simulating a nominal 'enable': false, // Leave this off if you have no intention of using this/supporting it
}, 'publicDomain': 'http://example.com/', // Public domain that the API is behind, should end with a /
api: { 'port': 8080, // Port for the API to listen on
// Setting for the built-in API 'supportURL': 'your_support_url_for_api_abuse', // Fill this in with the way you wish to be contacted when somebody needs to report API key abuse
enable: false, // Leave this off if you have no intention of using this/supporting it 'rateLimitTime': 10000, // Time range for how often the API rate limits will be lifted (time in ms)
publicDomain: 'http://example.com/', // Public domain that the API is behind, should end with a / 'rateLimitCnt': 10, // Amount of requests that can be made (successful or not) during above time range before getting rate limited
port: 8080, // Port for the API to listen on 'admin': 0n, // Discord user ID of the bot admin, this user will be the user that can ban/unban user/channel combos and API keys
supportURL: 'your_support_url_for_api_abuse', // Fill this in with the way you wish to be contacted when somebody needs to report API key abuse 'adminKey': 'your_25char_api_token', // API Key generated by nanoid that is 25 char long, this gets pre-populated into all_keys
rateLimitTime: 10000, // Time range for how often the API rate limits will be lifted (time in ms) 'email': 0n, // Temporary set up for email, this will be adjusted to an actual email using deno-smtp in the future.
rateLimitCnt: 10, // Amount of requests that can be made (successful or not) during above time range before getting rate limited },
admin: 0n, // Discord user ID of the bot admin, this user will be the user that can ban/unban user/channel combos and API keys 'db': { // Settings for the MySQL database, this is required for use with the API, if you do not want to set this up, you will need to rip all code relating to the DB out of the bot
adminKey: 'your_25char_api_token', // API Key generated by nanoid that is 25 char long, this gets pre-populated into all_keys 'host': '', // IP address for the db, usually localhost
email: 0n, // Temporary set up for email, this will be adjusted to an actual email using deno-smtp in the future. 'localhost': '', // IP address for a secondary OPTIONAL local testing DB, usually also is localhost, but depends on your dev environment
}, 'port': 3306, // Port for the db
db: { 'username': '', // Username for the account that will access your DB, this account will need "DB Manager" admin rights and "REFERENCES" Global Privalages
// Settings for the MySQL database, this is required for use with the API, if you do not want to set this up, you will need to rip all code relating to the DB out of the bot 'password': '', // Password for the account, user account may need to be authenticated with the "Standard" Authentication Type if this does not work out of the box
host: '', // IP address for the db, usually localhost 'name': '', // Name of the database Schema to use for the bot
localhost: '', // IP address for a secondary OPTIONAL local testing DB, usually also is localhost, but depends on your dev environment },
port: 3306, // Port for the db 'logRolls': false, // Enables logging of roll commands, this should be left disabled for privacy, but exists to allow verification of rolls before deployment, all API rolls will always be logged no matter what this is set to
username: '', // Username for the account that will access your DB, this account will need "DB Manager" admin rights and "REFERENCES" Global Privileges 'logChannel': 0n, // Discord channel ID where the bot should put startup messages and other error messages needed
password: '', // Password for the account, user account may need to be authenticated with the "Standard" Authentication Type if this does not work out of the box 'reportChannel': 0n, // Discord channel ID where reports will be sent when using the built-in report command
name: '', // Name of the database Schema to use for the bot 'devServer': 0n, // Discord guild ID where testing of indev features/commands will be handled, used in conjuction with the DEVMODE bool in mod.ts
}, 'emojis': [ // Array of objects containing all emojis that the bot can send on your behalf, empty this array if you don't want any of them
links: { { // Emoji object, duplicate for each emoji
// Links that are used in the bot 'name': 'emoji_name', // Name of emoji in discord
sourceCode: 'https://github.com/Burn-E99/TheArtificer', // Link to the repository 'aliases': ['alias_1', 'alias_2', 'alias_n'], // Commands that will activate this emoji
supportServer: '', // Invite link to the Discord support server 'id': 'the_emoji_id', // Discord emoji ID for this emoji
roll20Formatting: 'https://help.roll20.net/hc/en-us/articles/360037773133-Dice-Reference', // Link to Roll20 Dice Reference 'animated': false, // Tells the bot this emoji is animated so it sends correctly
mathDocs: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math', // Link to the MDN docs for Math 'deleteSender': false, // Tells the bot to attempt to delete the sender's message after sending the emoji
homePage: '', // Link to the bot's home/ad page },
privacyPolicy: '', // Link to the current Privacy Policy ],
termsOfService: '', // Link to the current Terms of Service 'botLists': [ // Array of objects containing all bot lists that stats should be posted to
}, { // Bot List object, duplicate for each bot list
logRolls: false, // Enables logging of roll commands, this should be left disabled for privacy, but exists to allow verification of rolls before deployment, all API rolls will always be logged no matter what this is set to 'name': 'Bot List Name', // Name of bot list, not used
logChannel: 0n, // Discord channel ID where the bot should put startup messages and other error messages needed 'enabled': true, // Should statistics be posted to this list?
reportChannel: 0n, // Discord channel ID where reports will be sent when using the built-in report command 'apiUrl': 'https://example.com/api/bots/?{bot_id}/stats', // API URL, use ?{bot_id} in place of the bot id so that it can be dynamically replaced
devServer: 0n, // Discord guild ID where testing of indev features/commands will be handled, used in conjunction with the DEVMODE bool in mod.ts 'headers': [ // Array of headers that need to be added to the request
emojis: [ { // Header Object, duplicate for every header needed
// Array of objects containing all emojis that the bot can send on your behalf, empty this array if you don't want any of them 'header': 'header_name', // Name of header needed, usually Authorization is needed
{ 'value': 'header_value', // Value for the header
// Emoji object, duplicate for each emoji },
name: 'emoji_name', // Name of emoji in discord ],
aliases: ['alias_1', 'alias_2', 'alias_n'], // Commands that will activate this emoji 'body': { // Data payload to send to the bot list, will be turned into a string and any ?{} will be replaced with the required value, currently only has ?{server_count}
id: 'the_emoji_id', // Discord emoji ID for this emoji 'param_name': '?{param_value}', // Add more params as needed
animated: false, // Tells the bot this emoji is animated so it sends correctly },
deleteSender: false, // Tells the bot to attempt to delete the sender's message after sending the emoji },
}, ],
],
botLists: [
// Array of objects containing all bot lists that stats should be posted to
{
// Bot List object, duplicate for each bot list
name: 'Bot List Name', // Name of bot list, not used
enabled: true, // Should statistics be posted to this list?
apiUrl: 'https://example.com/api/bots/?{bot_id}/stats', // API URL, use ?{bot_id} in place of the bot id so that it can be dynamically replaced
headers: [
// Array of headers that need to be added to the request
{
// Header Object, duplicate for every header needed
header: 'header_name', // Name of header needed, usually Authorization is needed
value: 'header_value', // Value for the header
},
],
body: {
// Data payload to send to the bot list, will be turned into a string and any ?{} will be replaced with the required value, currently only has ?{server_count}
param_name: '?{param_value}', // Add more params as needed
},
},
],
}; };
export default config; export default config;

View File

@ -1,12 +1,11 @@
// This file will create all tables for the artificer schema // This file will create all tables for the artificer schema
// DATA WILL BE LOST IF DB ALREADY EXISTS, RUN AT OWN RISK // DATA WILL BE LOST IF DB ALREADY EXISTS, RUN AT OWN RISK
import config from '~config';
import dbClient from 'db/client.ts'; import config from '../config.ts';
import { dbClient } from '../src/db.ts';
console.log('Attempting to create DB'); console.log('Attempting to create DB');
await dbClient.execute(`CREATE SCHEMA IF NOT EXISTS ${config.db.name};`); await dbClient.execute(`CREATE SCHEMA IF NOT EXISTS ${config.db.name};`);
console.log('test');
await dbClient.execute(`USE ${config.db.name}`); await dbClient.execute(`USE ${config.db.name}`);
console.log('DB created'); console.log('DB created');
@ -20,32 +19,8 @@ await dbClient.execute(`DROP PROCEDURE IF EXISTS INC_HEATMAP;`);
await dbClient.execute(`DROP TABLE IF EXISTS roll_time_heatmap;`); await dbClient.execute(`DROP TABLE IF EXISTS roll_time_heatmap;`);
await dbClient.execute(`DROP PROCEDURE IF EXISTS INC_CNT;`); await dbClient.execute(`DROP PROCEDURE IF EXISTS INC_CNT;`);
await dbClient.execute(`DROP TABLE IF EXISTS command_cnt;`); 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 allow_inline;`);
console.log('Tables dropped'); console.log('Tables dropped');
// Holds guilds that have explicitly allowed inline rolls
console.log('Attempting to create table allow_inline');
await dbClient.execute(`
CREATE TABLE allow_inline (
guildid bigint unsigned NOT NULL,
PRIMARY KEY (guildid),
UNIQUE KEY allow_inline_guildid_UNIQUE (guildid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`);
console.log('Table created');
// Table to hold list of users who want to be ignored by the bot
console.log('Attempting to create table ignore_list');
await dbClient.execute(`
CREATE TABLE ignore_list (
userid bigint unsigned NOT NULL,
PRIMARY KEY (userid),
UNIQUE KEY ignore_list_userid_UNIQUE (userid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`);
console.log('Table created');
// Light telemetry on how many commands have been run // Light telemetry on how many commands have been run
console.log('Attempting to create table command_cnt'); console.log('Attempting to create table command_cnt');
await dbClient.execute(` await dbClient.execute(`

View File

@ -1,48 +1,28 @@
// This file will populate the tables with default values // This file will populate the tables with default values
import config from '~config';
import dbClient from 'db/client.ts'; import config from '../config.ts';
import { dbClient } from '../src/db.ts';
console.log('Attempting to populate DB Admin API key'); console.log('Attempting to populate DB Admin API key');
await dbClient.execute('INSERT INTO all_keys(userid,apiKey) values(?,?)', [config.api.admin, config.api.adminKey]).catch((e) => { await dbClient.execute('INSERT INTO all_keys(userid,apiKey) values(?,?)', [config.api.admin, config.api.adminKey]).catch((e) => {
console.log('Failed to insert into database', e); console.log('Failed to insert into database', e);
}); });
console.log('Insertion done'); console.log('Inesrtion done');
console.log('Attempting to insert default commands into command_cnt'); console.log('Attempting to insert default commands into command_cnt');
const commands = [ const commands = ['ping', 'rip', 'rollhelp', 'help', 'info', 'version', 'report', 'stats', 'roll', 'emojis', 'api', 'privacy', 'mention', 'audit', 'heatmap', 'rollDecorators'];
'api',
'audit',
'emojis',
'heatmap',
'help',
'info',
'inline',
'mention',
'opt-in',
'opt-out',
'ping',
'privacy',
'rip',
'report',
'roll',
'rolldecorators',
'rollhelp',
'stats',
'version',
];
for (const command of commands) { for (const command of commands) {
await dbClient.execute('INSERT INTO command_cnt(command) values(?)', [command]).catch((e) => { await dbClient.execute('INSERT INTO command_cnt(command) values(?)', [command]).catch((e) => {
console.log(`Failed to insert ${command} into database`, e); console.log(`Failed to insert ${command} into database`, e);
}); });
} }
console.log('Insertion done'); console.log('Insertion done');
console.log('Attempting to insert default hours into roll_time_heatmap'); console.log('Attempting to insert default hours into roll_time_heatmap');
for (let i = 0; i <= 23; i++) { for (let i = 0; i <= 23; i++) {
await dbClient.execute('INSERT INTO roll_time_heatmap(hour) values(?)', [i]).catch((e) => { await dbClient.execute('INSERT INTO roll_time_heatmap(hour) values(?)', [i]).catch((e) => {
console.log(`Failed to insert hour ${i} into database`, e); console.log(`Failed to insert hour ${i} into database`, e);
}); });
} }
console.log('Insertion done'); console.log('Insertion done');

View File

@ -1,11 +1,14 @@
{ {
"compilerOptions": { "compilerOptions": {
"allowJs": true,
"lib": ["deno.worker"], "lib": ["deno.worker"],
"strict": true "strict": true
}, },
"lint": { "lint": {
"include": ["src/", "db/", "mod.ts", "deps.ts", "config.ts", "config.example.ts"], "files": {
"exclude": [], "include": ["src/", "db/", "mod.ts", "deps.ts", "config.ts", "config.example.ts"],
"exclude": []
},
"rules": { "rules": {
"tags": ["recommended"], "tags": ["recommended"],
"include": ["ban-untagged-todo"], "include": ["ban-untagged-todo"],
@ -13,30 +16,16 @@
} }
}, },
"fmt": { "fmt": {
"include": ["src/", "db/", "mod.ts", "deps.ts", "config.ts", "config.example.ts"], "files": {
"exclude": [], "include": ["src/", "db/", "mod.ts", "deps.ts", "config.ts", "config.example.ts"],
"lineWidth": 200, "exclude": []
"indentWidth": 2, },
"singleQuote": true, "options": {
"proseWrap": "preserve" "useTabs": true,
}, "lineWidth": 200,
"imports": { "indentWidth": 2,
"@discordeno": "https://deno.land/x/discordeno@12.0.1/mod.ts", "singleQuote": true,
"@Log4Deno": "https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.1/mod.ts", "proseWrap": "preserve"
"@mysql": "https://deno.land/x/mysql@v2.12.1/mod.ts", }
"@std/http": "jsr:@std/http@1.0.15",
"@nanoid": "https://deno.land/x/nanoid@v3.0.0/mod.ts",
"@imagescript": "https://deno.land/x/imagescript@1.3.0/mod.ts",
"~config": "./config.ts",
"~flags": "./flags.ts",
"artigen/": "./src/artigen/",
"commands/": "./src/commands/",
"db/": "./src/db/",
"embeds/": "./src/embeds/",
"endpoints/": "./src/endpoints/",
"events/": "./src/events/",
"utils/": "./src/utils/",
"src/api.ts": "./src/api.ts",
"src/events.ts": "./src/events.ts"
} }
} }

843
deno.lock
View File

@ -1,843 +0,0 @@
{
"version": "4",
"specifiers": {
"jsr:@std/cli@^1.0.17": "1.0.17",
"jsr:@std/encoding@^1.0.10": "1.0.10",
"jsr:@std/fmt@^1.0.7": "1.0.7",
"jsr:@std/html@^1.0.3": "1.0.3",
"jsr:@std/http@1.0.15": "1.0.15",
"jsr:@std/io@0.225.2": "0.225.2",
"jsr:@std/media-types@^1.1.0": "1.1.0",
"jsr:@std/net@^1.0.4": "1.0.4",
"jsr:@std/path@^1.0.9": "1.0.9",
"jsr:@std/streams@^1.0.9": "1.0.9"
},
"jsr": {
"@std/cli@1.0.17": {
"integrity": "e15b9abe629e17be90cc6216327f03a29eae613365f1353837fa749aad29ce7b"
},
"@std/encoding@1.0.10": {
"integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
},
"@std/fmt@1.0.7": {
"integrity": "2a727c043d8df62cd0b819b3fb709b64dd622e42c3b1bb817ea7e6cc606360fb"
},
"@std/html@1.0.3": {
"integrity": "7a0ac35e050431fb49d44e61c8b8aac1ebd55937e0dc9ec6409aa4bab39a7988"
},
"@std/http@1.0.15": {
"integrity": "435a4934b4e196e82a8233f724da525f7b7112f3566502f28815e94764c19159",
"dependencies": [
"jsr:@std/cli",
"jsr:@std/encoding",
"jsr:@std/fmt",
"jsr:@std/html",
"jsr:@std/media-types",
"jsr:@std/net",
"jsr:@std/path",
"jsr:@std/streams"
]
},
"@std/io@0.225.2": {
"integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7"
},
"@std/media-types@1.1.0": {
"integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
},
"@std/net@1.0.4": {
"integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852"
},
"@std/path@1.0.9": {
"integrity": "260a49f11edd3db93dd38350bf9cd1b4d1366afa98e81b86167b4e3dd750129e"
},
"@std/streams@1.0.9": {
"integrity": "a9d26b1988cdd7aa7b1f4b51e1c36c1557f3f252880fa6cc5b9f37078b1a5035"
}
},
"redirects": {
"https://deno.land/std/hash/mod.ts": "https://deno.land/std@0.224.0/hash/mod.ts"
},
"remote": {
"https://deno.land/std@0.104.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
"https://deno.land/std@0.104.0/async/deadline.ts": "1d6ac7aeaee22f75eb86e4e105d6161118aad7b41ae2dd14f4cfd3bf97472b93",
"https://deno.land/std@0.104.0/async/debounce.ts": "b2f693e4baa16b62793fd618de6c003b63228db50ecfe3bd51fc5f6dc0bc264b",
"https://deno.land/std@0.104.0/async/deferred.ts": "ce81070ad3ba3294f3f34c032af884ccde1a20922b648f6eaee54bd8fd951a1e",
"https://deno.land/std@0.104.0/async/delay.ts": "9de1d8d07d1927767ab7f82434b883f3d8294fb19cad819691a2ad81a728cf3d",
"https://deno.land/std@0.104.0/async/mod.ts": "78425176fabea7bd1046ce3819fd69ce40da85c83e0f174d17e8e224a91f7d10",
"https://deno.land/std@0.104.0/async/mux_async_iterator.ts": "62abff3af9ff619e8f2adc96fc70d4ca020fa48a50c23c13f12d02ed2b760dbe",
"https://deno.land/std@0.104.0/async/pool.ts": "353ce4f91865da203a097aa6f33de8966340c91b6f4a055611c8c5d534afd12f",
"https://deno.land/std@0.104.0/async/tee.ts": "6b8f1322b6dd2396202cfbe9cde9cab158d1e962cfd9197b0a97c6657bee79ce",
"https://deno.land/std@0.104.0/bytes/bytes_list.ts": "a13287edb03f19d27ba4927dec6d6de3e5bd46254cd4aee6f7e5815810122673",
"https://deno.land/std@0.104.0/bytes/mod.ts": "1ae1ccfe98c4b979f12b015982c7444f81fcb921bea7aa215bf37d84f46e1e13",
"https://deno.land/std@0.104.0/encoding/base64.ts": "eecae390f1f1d1cae6f6c6d732ede5276bf4b9cd29b1d281678c054dc5cc009e",
"https://deno.land/std@0.104.0/encoding/hex.ts": "5bc7df19af498c315cdaba69e2fce1b2aef5fc57344e8c21c08991aa8505a260",
"https://deno.land/std@0.104.0/fmt/colors.ts": "d2f8355f00a74404668fc5a1e4a92983ce1a9b0a6ac1d40efbd681cb8f519586",
"https://deno.land/std@0.104.0/fs/exists.ts": "b0d2e31654819cc2a8d37df45d6b14686c0cc1d802e9ff09e902a63e98b85a00",
"https://deno.land/std@0.104.0/hash/_wasm/hash.ts": "313a4820227f1c45fa7204d9c28731b4f8ce97cdcc5f1e7e4efcdf2d70540d32",
"https://deno.land/std@0.104.0/hash/_wasm/wasm.js": "792f612fbb9998e267f9ae3f82ed72444305cb9c77b5bbf7ff6517fd3b606ed1",
"https://deno.land/std@0.104.0/hash/hasher.ts": "57a9ec05dd48a9eceed319ac53463d9873490feea3832d58679df6eec51c176b",
"https://deno.land/std@0.104.0/hash/mod.ts": "dd339a26b094032f38d71311b85745e8d19f2085364794c1877057e057902dd9",
"https://deno.land/std@0.104.0/io/buffer.ts": "3ead6bb11276ebcf093c403f74f67fd2205a515dbbb9061862c468ca56f37cd8",
"https://deno.land/std@0.104.0/io/bufio.ts": "6024117aa37f8d21a116654bd5ca5191d803f6492bbc744e3cee5054d0e900d1",
"https://deno.land/std@0.104.0/io/types.d.ts": "89a27569399d380246ca7cdd9e14d5e68459f11fb6110790cc5ecbd4ee7f3215",
"https://deno.land/std@0.104.0/io/util.ts": "85c33d61b20fd706acc094fe80d4c8ae618b04abcf3a96ca2b47071842c1c8ac",
"https://deno.land/std@0.104.0/log/handlers.ts": "8c7221a2408b4097e186b018f3f1a18865d20b98761aa1dccaf1ee3d57298355",
"https://deno.land/std@0.104.0/log/levels.ts": "088a883039ece5fa0da5f74bc7688654045ea7cb01bf200b438191a28d728eae",
"https://deno.land/std@0.104.0/log/logger.ts": "6b2dd8cbe6f407100b9becfe61595d7681f8ce3692412fad843de84d617a038e",
"https://deno.land/std@0.104.0/log/mod.ts": "91711789b28803082b1bdfb123d2c9685a7e01767f2e79c0a82706063ad964d8",
"https://deno.land/std@0.104.0/testing/_diff.ts": "5d3693155f561d1a5443ac751ac70aab9f5d67b4819a621d4b96b8a1a1c89620",
"https://deno.land/std@0.104.0/testing/asserts.ts": "e4311d45d956459d4423bc267208fe154b5294989da2ed93257b6a85cae0427e",
"https://deno.land/std@0.145.0/http/http_status.ts": "897575a7d6bc2b9123f6a38ecbc0f03d95a532c5d92029315dc9f508e12526b8",
"https://deno.land/std@0.155.0/encoding/base64.ts": "c57868ca7fa2fbe919f57f88a623ad34e3d970d675bdc1ff3a9d02bba7409db2",
"https://deno.land/std@0.155.0/encoding/hex.ts": "4cc5324417cbb4ac9b828453d35aed45b9cc29506fad658f1f138d981ae33795",
"https://deno.land/std@0.155.0/hash/_wasm/hash.ts": "b408d3859efa8f755fabe63042df9872353bbd3a9c72943e4a6cda1de53e3aa6",
"https://deno.land/std@0.155.0/hash/_wasm/lib/deno_hash.generated.mjs": "1591bee0464c0bdcf29fb21951d0cccf1a93d7f47ea7b67bcf90f151a66b1cff",
"https://deno.land/std@0.155.0/hash/hasher.ts": "42fa30957370d69d22800a901ee147a2539b2b8cf3a9000737a4b0fb2317bb8f",
"https://deno.land/std@0.155.0/hash/mod.ts": "30903d6c4623e4bda798415a9fa14d6c1f2343abb3257dffe9a7f3b19009d967",
"https://deno.land/std@0.77.0/fmt/colors.ts": "c5665c66f1a67228f21c5989bbb04b36d369b98dd7ceac06f5e26856c81c2531",
"https://deno.land/std@0.99.0/encoding/base64.ts": "eecae390f1f1d1cae6f6c6d732ede5276bf4b9cd29b1d281678c054dc5cc009e",
"https://deno.land/x/bytes_formater@v1.4.0/deps.ts": "4f98f74e21145423b873a5ca6ead66dc3e674fa81e230a0a395f9b86aafeceea",
"https://deno.land/x/bytes_formater@v1.4.0/format.ts": "657c41b9f180c3ed0f934dcf75f77b09b6a610be98bb07525bffe2acfd5af4d5",
"https://deno.land/x/bytes_formater@v1.4.0/mod.ts": "c6bf35303f53d74e9134eb13f666fb388fb4c62c6b12b17542bbadade250a864",
"https://deno.land/x/discordeno@12.0.1/mod.ts": "9c4187b459f479e23a77b0e7f0e24507a10729c7865b5c18113e79b1fbf0effa",
"https://deno.land/x/discordeno@12.0.1/src/bot.ts": "e6e6599f7b6682bfd02c1f9a5d7ebb9f09dd7a0185629f016f3d7b537890c03f",
"https://deno.land/x/discordeno@12.0.1/src/cache.ts": "66e797d36ace1eda8a55a92469b94ba107046f597bda91c44d917b1593d8bb19",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/CHANNEL_CREATE.ts": "fec8d87f88da7e590ed5e97212b7d9162e1e86b6cdb3f3acdb6035c15f9c6b8e",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/CHANNEL_DELETE.ts": "8f847b5b283594dc7787e6e4d3e3c2fef2bae371738a6750e4474f09c4f0c285",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/CHANNEL_PINS_UPDATE.ts": "0d7e9a5b366c606f9fe3902d73c3b09b109db1db8bc2739cb1fc79253316321f",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/CHANNEL_UPDATE.ts": "3bcace4468fd9ff669b17971f2f886eef7916c32343c428dc36b42ae92a958bc",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/STAGE_INSTANCE_CREATE.ts": "6c5d120242cbc9f2e235388ba4daab079f1b74b8bb703d8afb3e9c1335f91d26",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/STAGE_INSTANCE_DELETE.ts": "78c4aaeb943073b62bda4104dfcf1f7ca602c7cb31790d81edb4ee20c4f1e488",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/STAGE_INSTANCE_UPDATE.ts": "5b7ac722bc4fe7f1b6ac564c1449775fb7fb1161bef7c5def4b4584bcffec87d",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/THREAD_CREATE.ts": "9b94fb4e4ca33b5d324cbda3f2015a1b103318c0f6946a5cdf1bfcf1c95e5061",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/THREAD_DELETE.ts": "070cea7c578316304bee8c01462b53a66ce9ab0b70e2785cd408118a5c70ad41",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/THREAD_LIST_SYNC.ts": "fba2fb864f41a52bdcf8d1dff33fdbe71da6a730b4320d394fef7bdb27f28225",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/THREAD_MEMBERS_UPDATE.ts": "1b1a43d3727810122d160388fbe2f4cadbbbad25d8305cfdc1e3900daf3ccde5",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/THREAD_MEMBER_UPDATE.ts": "940ccdb96ea63e3fbb48230cf7ae375daa6d0b2bc78c626bff25931d6cf3763f",
"https://deno.land/x/discordeno@12.0.1/src/handlers/channels/THREAD_UPDATE.ts": "bdd4e334d21a4ed67fa34b2cb085ce358c164ddfa7e449e5dbb21ce6c361e7df",
"https://deno.land/x/discordeno@12.0.1/src/handlers/commands/APPLICATION_COMMAND_CREATE.ts": "75fb3459d3604d02cbb55be23e3b4546256ae7d8ca1547a67b50ceefa476a818",
"https://deno.land/x/discordeno@12.0.1/src/handlers/commands/APPLICATION_COMMAND_DELETE.ts": "8d2510e86703bfbea9870b03965b0b7dc0e832253fb0a772c627ec99f5cafeca",
"https://deno.land/x/discordeno@12.0.1/src/handlers/commands/APPLICATION_COMMAND_UPDATE.ts": "2bf54489340ce4f0c474dadbc8e0bf05fc8a45a26b900e37e844eabcdc69bc73",
"https://deno.land/x/discordeno@12.0.1/src/handlers/emojis/GUILD_EMOJIS_UPDATE.ts": "39dec5a4466b68346956f6dfbc31976d3fdbac98d6c72cf5ad426fc42d6e31d7",
"https://deno.land/x/discordeno@12.0.1/src/handlers/guilds/GUILD_BAN_ADD.ts": "089000344218dc668990f860425c4c203a9e01ffa661e70bfa3a4567be2022f4",
"https://deno.land/x/discordeno@12.0.1/src/handlers/guilds/GUILD_BAN_REMOVE.ts": "ab6a1dcd14c03fcb1f5b4a2aa3ad6b6372d297d5672d369c29e3981df6407904",
"https://deno.land/x/discordeno@12.0.1/src/handlers/guilds/GUILD_CREATE.ts": "5ed4e59d9cc7f5bcf0ea2bc9a053afd41695f081bec1de00a2ff5b43eadb19d8",
"https://deno.land/x/discordeno@12.0.1/src/handlers/guilds/GUILD_DELETE.ts": "5305167a22cdcf8251f07bb9c2080fa9fde9bdca38b4730c952362fc9e309ae3",
"https://deno.land/x/discordeno@12.0.1/src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts": "e2d47d0a75183de2eb3294d0e153810f12a42bf7c3b6bf4817d486cdd41fc4ad",
"https://deno.land/x/discordeno@12.0.1/src/handlers/guilds/GUILD_UPDATE.ts": "4e157898729a5d7742fe2bde8b77498ef36f4926a0d3a63d232900bb2cd2cd42",
"https://deno.land/x/discordeno@12.0.1/src/handlers/integrations/INTEGRATION_CREATE.ts": "7a3afd2d12deb2a89e93fd72efc160455dc401e432baa75f5299b561d60c1f85",
"https://deno.land/x/discordeno@12.0.1/src/handlers/integrations/INTEGRATION_DELETE.ts": "e6a656f76b3ef037917eaaada601fd8a61c61fd9315bfb8458437d4c193d26f7",
"https://deno.land/x/discordeno@12.0.1/src/handlers/integrations/INTEGRATION_UPDATE.ts": "89dfc73b3b886711054bc88b208980e2e7186e12f1d352d7c0c1c48c077e4bb1",
"https://deno.land/x/discordeno@12.0.1/src/handlers/interactions/INTERACTION_CREATE.ts": "3d48b47da224a470bb7ae879711df921b768e43143007a3734f9c6746561cda8",
"https://deno.land/x/discordeno@12.0.1/src/handlers/invites/INVITE_CREATE.ts": "c51679f41f961e7869a30bf9fafa708b01524a99c1c7486f359f4302b86d84cb",
"https://deno.land/x/discordeno@12.0.1/src/handlers/members/GUILD_MEMBERS_CHUNK.ts": "c7c7bed2365caf30fdece1ae32ce7270a055ea4fb1694fcf460804b760fba408",
"https://deno.land/x/discordeno@12.0.1/src/handlers/members/GUILD_MEMBER_ADD.ts": "c4f9ac7c0f090332b56cf90053ce6cddae67b98754d6e6907f4125acc753aa9c",
"https://deno.land/x/discordeno@12.0.1/src/handlers/members/GUILD_MEMBER_REMOVE.ts": "f34e8f91627bea60cb3d023c1a550f6858e8eddc466746649c38b43b2bf23796",
"https://deno.land/x/discordeno@12.0.1/src/handlers/members/GUILD_MEMBER_UPDATE.ts": "48fb15630943a31c15a1a045431c6b3aaee05105101a02fe56d845ef18da2dec",
"https://deno.land/x/discordeno@12.0.1/src/handlers/messages/MESSAGE_CREATE.ts": "4437069dc5d8ceaf452ccffd866aafdf9e587afb4c9abab288df2b97f253b9cd",
"https://deno.land/x/discordeno@12.0.1/src/handlers/messages/MESSAGE_DELETE.ts": "da354383e44b14c4d244ec88b6ee393f534fea0d40dabbc6e490254f0dbae4cd",
"https://deno.land/x/discordeno@12.0.1/src/handlers/messages/MESSAGE_DELETE_BULK.ts": "a43060040ae5dcdc79a3f9463bf14cde8dd48a42a1d742e12a027cddc58c126c",
"https://deno.land/x/discordeno@12.0.1/src/handlers/messages/MESSAGE_REACTION_ADD.ts": "7c4abaf9ce52f6457f6bfb463b6ed566a04748da3b48fead9d20be3e4b8bf926",
"https://deno.land/x/discordeno@12.0.1/src/handlers/messages/MESSAGE_REACTION_REMOVE.ts": "dbf4a75010d5917548b6e5040deffc41d01abee2e8b7b9eebbd91ff4ef891f50",
"https://deno.land/x/discordeno@12.0.1/src/handlers/messages/MESSAGE_REACTION_REMOVE_ALL.ts": "b5b86d94c3817931ebf57faf97378b8a419bd2602ede8185c2ef2b93771679ca",
"https://deno.land/x/discordeno@12.0.1/src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts": "de08173125fa46bdf699ae281171e8b7c0ed7a74c6bf5c60be0aa4fdb4cdaa9f",
"https://deno.land/x/discordeno@12.0.1/src/handlers/messages/MESSAGE_UPDATE.ts": "3d2851365db5670765e7a003e9bbdc48e9358a228e703e2f47fa83b95aa94880",
"https://deno.land/x/discordeno@12.0.1/src/handlers/misc/PRESENCE_UPDATE.ts": "ebda9c759d838d8956821eef66ea31771504875b70f8d8d9d75593e31e01c69c",
"https://deno.land/x/discordeno@12.0.1/src/handlers/misc/READY.ts": "54489a1b8ba6acda0f9a28e9866ef2cd988907a53fed6fc440426f551c58b8c8",
"https://deno.land/x/discordeno@12.0.1/src/handlers/misc/TYPING_START.ts": "f07569cb512bc257b7c583645c46b5c2e9335047bff236a7aecb3216a535afd3",
"https://deno.land/x/discordeno@12.0.1/src/handlers/misc/USER_UPDATE.ts": "9c0c510373c3172459b3d2cfbb3932e42764f363fab1fcc298b17b05b9f994b5",
"https://deno.land/x/discordeno@12.0.1/src/handlers/mod.ts": "5ae55898810bc13551ad39320697617305ee86231bd2e860e1b9cd4f2c891248",
"https://deno.land/x/discordeno@12.0.1/src/handlers/roles/GUILD_ROLE_CREATE.ts": "aaecb932fb457a7cf4250b2ab009d6419cdf0a40329ed7b1b4246a41c4220736",
"https://deno.land/x/discordeno@12.0.1/src/handlers/roles/GUILD_ROLE_DELETE.ts": "69eebf63a3ee4e20dd7979e6f8e88a234b5c4f1f3c1d54563d2e55a56c6cfa19",
"https://deno.land/x/discordeno@12.0.1/src/handlers/roles/GUILD_ROLE_UPDATE.ts": "618858c94a002aefc109eb81c329ea6703acf974fa852db8769bcf8a52acb61f",
"https://deno.land/x/discordeno@12.0.1/src/handlers/voice/VOICE_SERVER_UPDATE.ts": "6e18af2344c424fff1e5e57da13ccb409a5017b9ae1384acca94d527c6dcf093",
"https://deno.land/x/discordeno@12.0.1/src/handlers/voice/VOICE_STATE_UPDATE.ts": "f158e0c276d0da4cd7bc644c9660991496916b4d8d588983e6e161a8a1099e35",
"https://deno.land/x/discordeno@12.0.1/src/handlers/webhooks/WEBHOOKS_UPDATE.ts": "1d71dd9c1d40b94cee07c77b1b60b1331b57ad7c197457127d39bfc03bb38c9e",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/category_children.ts": "0fb31fdd539eb44f2e706705f223453f288fcd1ed2c6e633448f284c22e5eba7",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/channel_overwrite_has_permission.ts": "5822972c765624836d375a10e842fee1dfa961de9aab40202952f09b5eb57f63",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/clone_channel.ts": "f3eaf9adb9e2cb60069ef88f59318c6f8a8421c4af7d5def1d6f394ef3e750e4",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/create_channel.ts": "20bcec542db2b4cf0d6108397175aa5faff943869c03475484cdfd0c78ef6631",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/create_stage_instance.ts": "bb4a3085c9c6ee4b1e196d4e500cdbdafd79e45bdfd3dbdfcb5218bd29040125",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/delete_channel.ts": "a874f24796732fb1a6ee6bcb45c4d872ec91cd3f4e4149286eb7b333bf69fd77",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/delete_channel_overwrite.ts": "b4209e21e9e6151e689152ee647f71f24569350ae05329005ff44bc40671fd80",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/delete_stage_instance.ts": "fffc6a7ef5cfe845415d6900c603316b2b040033fbc5774a36ef041824e65eff",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/edit_channel.ts": "7e67a788911e945da4c7e69583b09d42b117fe7c6b3a7a7b83654c36b5b03db9",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/edit_channel_overwrite.ts": "7d2f4211e5c7e7cc922fd275ed02ce89862a42805119969ced62e0365bd68c17",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/follow_channel.ts": "7f026a98c2abe9859187c97667d6d1bcb2362db77f9d7fb1a9bd569666abb15e",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/get_channel.ts": "3ef55b1bc144044a2b7f3dbb05f26022660f411a5dc97f2b8475febdcd109e34",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/get_channel_webhooks.ts": "ffc82d1c1458217740732e7192e3752df87f12a9b30460e18adce8fb21271fd9",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/get_channels.ts": "c13f60fedc9c061f1600cecf6b1dc6aaadad7914c08102ecbbc9732cef4660a2",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/get_pins.ts": "857541de1ac959616bf78b4c7d3b632f2c1979393f1d82a7445f68df57da94a0",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/get_stage_instance.ts": "1ec9530f228392fd34df7d84e7b51d01970c3a0c63929c96e86a3d658b3ac264",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/is_channel_synced.ts": "133146877efcf30a6d0ec22941598a9b79a88577b67e2bd6e5e08e09dc9f7b9e",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/start_typing.ts": "66b038dd9719b96bfb7edc749792fc292eb7eddb31a29107b6c87ff3f8773eff",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/swap_channels.ts": "334bb7895b7d13b8391494fa5f54f04f8b4d63e07b5f71cb165440a1409efa0a",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/add_to_thread.ts": "3f1f03e17c3a5f51a62bb9ca57c3fb9216b883a7fd85550c44302e5d0c655ac2",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/archive_thread.ts": "8b897ee76ca157f13669bf88ac08f3c24a9ffd6ec2331bf0651a3b0da9d8d6b0",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/delete_thread.ts": "d0a1f20b39aa3997914314ef1f3d15b11dbba6017e07db316dfcbecbb37ef2c8",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/edit_thread.ts": "23fed3e7b9c074545f533dca625efd2edfafe3d3b5947d9545ced8803b85d85c",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/get_active_threads.ts": "b24a49abe8478a1205139326877f1a6e43e311df948af79d021099cf343431c9",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/get_archived_threads.ts": "eeac865293099f526821e92cd10a6d1c1f26ad48edaf9db64508ce2f10d90bc6",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/get_thread_members.ts": "19bf0cb9e8e965be89d17f178ed4060bd55aed369282eb132d7649e50ba55377",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/join_thread.ts": "de17812814fce15b8d0fcfb894daad0b314fc23190948d739f5e0741c4a2e2e7",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/leave_thread.ts": "9b836d29ebf6445858412e41b1f988bb5abea6102ce67720842d3028a45c8208",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/lock_thread.ts": "a57269bcbb690e3ac0f1334ed04541a2facee448a5a9d02c861fc0e5a5e10769",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/remove_thread_member.ts": "407d9a159454922cf0739af745b12a26bd231c5d2039e392480aa573287f48fe",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/start_private_thread.ts": "0819cd57f5cecdc78f6f2807ed9ba9b454a6dc90ec7f3942ee966b303b42c2cc",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/start_thread.ts": "1a779e446ff3c759aaffa718766c07884071bc4314bffa18f355d93874006697",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/unarchive_thread.ts": "4b009ecbbebe6c16e114871448f3bd0f8d310918277f2bd42f1ced9b9603c29b",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/threads/unlock_thread.ts": "b501f253fcea7026615ba229a52f4aecb4bf8ca8612d9e7c51d099ea41ca5b75",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/update_stage_instance.ts": "12406d73065cf93ee504912e688098e463df9ee10aea834b99e15e79afeb0676",
"https://deno.land/x/discordeno@12.0.1/src/helpers/channels/update_voice_state.ts": "4cd4fefdcc87268697c3720b55408e382c16d9b2a55d939514418e6930377f6b",
"https://deno.land/x/discordeno@12.0.1/src/helpers/discovery/add_discovery_subcategory.ts": "169489feecbe2c28c62d76795b2e16485e3d53790ab812c79351283ae2a829e1",
"https://deno.land/x/discordeno@12.0.1/src/helpers/discovery/edit_discovery.ts": "3b9f8de70580a44bc6f2adda67b51d76be021c10f53129f636bdc0f7a98ee5cc",
"https://deno.land/x/discordeno@12.0.1/src/helpers/discovery/get_discovery_categories.ts": "48ef7d7949253857e9885b05324dbb2f5d3fa1f1b8fae7754de1ef2141fdb0b1",
"https://deno.land/x/discordeno@12.0.1/src/helpers/discovery/remove_discovery_subcategory.ts": "db0a888310293f1873fa8efd21d9c19e9a6ad24ce40b2fbfaac74e60d3af137b",
"https://deno.land/x/discordeno@12.0.1/src/helpers/discovery/valid_discovery_term.ts": "32893a940cc09ceeba02c874d985ba5022e9542083a5f138413b9ede9959816c",
"https://deno.land/x/discordeno@12.0.1/src/helpers/emojis/create_emoji.ts": "9ed9b494268ad448a86f02ae9dfd687d6447fd16e8805736dce04f87efbdbc0c",
"https://deno.land/x/discordeno@12.0.1/src/helpers/emojis/delete_emoji.ts": "7c6288ead0f9278c8aff367742d29595b1764608b738d92c7208b4591b96188f",
"https://deno.land/x/discordeno@12.0.1/src/helpers/emojis/edit_emoji.ts": "c98c84b9c39f2948603ddef352595334ed4dba8be635f9e4f4cc90c6f412fbb1",
"https://deno.land/x/discordeno@12.0.1/src/helpers/emojis/emoji_url.ts": "735c3643393c56e1e70095f0e85d04419ba590386361d2b946b221238e26bd92",
"https://deno.land/x/discordeno@12.0.1/src/helpers/emojis/get_emoji.ts": "6fdba22a4996821d8dc7ea6bafcde539446ca0c4d2d10d684a70e98d95631354",
"https://deno.land/x/discordeno@12.0.1/src/helpers/emojis/get_emojis.ts": "d452a40382ff6d574dd13dfef57b2a93b132591aa4c9c108cceb37565e4393f5",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/create_guild.ts": "5b70957ae4f54c35e010ff7911a1e25e10ae2319b0e7f5cbf687c6d1f6ceda1b",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/delete_guild.ts": "e56fa70b8fcd058ec54cfba768c8d7a57ae9c0be162c9d0033a468fa3f2a3f47",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/edit_guild.ts": "b5e301618be4ede122c837fb23460b0c0d9d8f7999ae2590afed76879c7f5854",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/edit_welcome_screen.ts": "ca58ca7e93ba76c76130c044fb30913215838d3ac7b88015a65d8cb01a9e43cc",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/edit_widget.ts": "62f65cb43e0eeecd8d19f0f7fdd8d9d1bcd74fe247d8e3019e5729eb82776f0c",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_audit_logs.ts": "0e5d1ad3b1d885618917de31514f9b63fee656280e879036f60c9d79fd5119de",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_available_voice_regions.ts": "88c5fe74ff0a0f6d43c4a175e0f3a14ce36a4dc03364c09939cd8fdd7e694f8d",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_ban.ts": "0e3b11ae76d6f54544bbd6a5a7080440d6f54d77ab384a9b3129b5af1e843a54",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_bans.ts": "fa8c63ad187ded434114d9abeb700641f39a81d373686172b8f46579a3ab56b7",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_guild.ts": "8430056349e6f0bbf12b0af396a0a06e9255b981fef39cc2bc4eb4b651f08ceb",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_guild_preview.ts": "e9112fd138ab45960e6f231c5fc0b516db51b2a55afcb3dbe8f1f83d483b8cee",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_prune_count.ts": "3933aee627217101318feb87b1d8a98a8922de661d52ba746e576216bf50a378",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_vainty_url.ts": "17cf2c9779339ca24173156eae6abb1856bbab10891251e4d049863e134c0734",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_voice_regions.ts": "334992d26170f88c03de210ac518c4aae3686c2af0eca5b4a1d2b6e5025c9f24",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_welcome_screen.ts": "761cf61217fb39df6d67d85c3a95ed5cdf0092a3ae4a29ed602d2e9c9ce2ff2a",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_widget.ts": "c257afeb870524a55f61636904fa88deceb282e4bd607f8e3a49950cbc4f8ee5",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_widget_image_url.ts": "e6f5b3cac78ebc1e3185ae3468f85dd6ee382980d693db09aec2054955d65011",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/get_widget_settings.ts": "74d54e2b8122c0d69187efdf91f520a50ef44b737196b361d15786150352d646",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/guild_banner_url.ts": "b007011aafc5361634aa2a6e777a742d0e7b593525ff7bbd00c2eaa274349db1",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/guild_icon_url.ts": "141c06d69d6dcce5bb9db0f6b73f7f62df3c8cf35eaab04fd5ef334736af8de2",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/guild_splash_url.ts": "9e79c487e4823ce7d3581d81a27c5e9793880c22e5004d09a785623e4bab9499",
"https://deno.land/x/discordeno@12.0.1/src/helpers/guilds/leave_guild.ts": "45c49840acce6594f24f769b54c1a42347e42d667f5b7eb4c2afb8e2c401cafb",
"https://deno.land/x/discordeno@12.0.1/src/helpers/integrations/delete_integration.ts": "96f2b8ec2a19cb457918b200eeac5367264f87f1c6d1f1be961bc719711144e4",
"https://deno.land/x/discordeno@12.0.1/src/helpers/integrations/get_integrations.ts": "f66654d7fa9819795be333ce94e656527d6d1b5ffcdfe29fdc31141ae5b0fa4a",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/batch_edit_slash_command_permissions.ts": "35ca0659b9aabebe7dd66b2d8fb6cae0c548e8b472a567ac38be5ffce4dcf53f",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/create_slash_command.ts": "fbff47db4b6c92e5399a136580825b47985f9dad82124df607030f60bf1e6169",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/delete_slash_command.ts": "8547541f1268277baa87588ddaac1b2adef2701fe74a69e1d9f8d2ce4659163b",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/delete_slash_response.ts": "60c68996279c34f9b7b8808c15e044889e10ce4c495b1816b1ae8eff9b37642e",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/edit_slash_command_permissions.ts": "fb72c20ee87a36cb2f055ce5744932d920d431220f920808aaec9f49e6f08f14",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/edit_slash_response.ts": "d96e54cf48582c36fe902f3e010c2ca01c0db681dae35f7f6e0ca67a0a46a978",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/get_slash_command.ts": "84bd3ebf8caaf59bc14b0e6aead31c128e8661d4c35da1abc1b0ef7e1bac19e9",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/get_slash_command_permission.ts": "74cf4a456bb34578636b36d117fc335ad009198bd7f74f6a83e0033ee3cbd35f",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/get_slash_command_permissions.ts": "6fd06dfd2a780316e11d1f4adaaff35fc7272402bc9d76477bfbe3369f3b8a24",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/get_slash_commands.ts": "0e86834b2023d126910ca0e3b333e4d5ac4a4defd1364e042637794eb7964dca",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/upsert_slash_command.ts": "d9bc6a75bfe2474e6a161afa2169e026634c8025e212740e99b28e62929a23cf",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/commands/upsert_slash_commands.ts": "6611c9a9c9250a868418518b6573e8c064b04fd75368ac0f15e73b6bce4f7d5a",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/get_original_interaction_response.ts": "332f140a6b452de6beb7ea48aa09f95c13f1ecb509f896c72d9cfad491e6e1aa",
"https://deno.land/x/discordeno@12.0.1/src/helpers/interactions/send_interaction_response.ts": "4c4bf8d6d5162a9e0201f31f4df6752de55832a31c75531914754623b2f372cc",
"https://deno.land/x/discordeno@12.0.1/src/helpers/invites/create_invite.ts": "765eea67041f56541fb48632cf5d347868a5fd39a4f73d3fb37d1954c694613e",
"https://deno.land/x/discordeno@12.0.1/src/helpers/invites/delete_invite.ts": "11698d3f83f58711193ca808d375cb65258ab7f0e16ae991c9f2a74316901fad",
"https://deno.land/x/discordeno@12.0.1/src/helpers/invites/get_channel_invites.ts": "7cf2f0e5e4da1aacd033fd6175328388a7e72b1a0267d499115021635fb09463",
"https://deno.land/x/discordeno@12.0.1/src/helpers/invites/get_invite.ts": "d22b11c29bcd5b25db1e1453db90f32657475df6fbbed39a8ed7fd559e428caf",
"https://deno.land/x/discordeno@12.0.1/src/helpers/invites/get_invites.ts": "f3cfbe95c70816ce16761e8c4e84efe9ca9552bc35d02f3c728173db5533d7ea",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/avatar_url.ts": "3dee52c11f4f8cd348a42739042d13df4d1fc42d68cf54d5f94d55bd83d462a9",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/ban_member.ts": "ae61b1e4344e9c3cf329f4e7dd9b681612ddfb1f83d4805164ef584bc9d0a43b",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/disconnect_member.ts": "627793c1e96cb5d65e65af2fb63bef8ee85928a39aabca7e8ab3776b6af3b580",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/edit_bot_nickname.ts": "b3b52953f43a845d97933132002ba418c98084a1dbd547614c3a6680f58a3865",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/edit_member.ts": "2ba50dd60ff9d19090bd5c144012bef97e62269ff20a87cca807595ae6dd5547",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/fetch_members.ts": "58af09cd92f52787067950878eed03be9f0670f12962328160604bdf51939067",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/get_member.ts": "fd10f1b2cb47c5142e3b74705917b985e65d42ebce2e70080f8c03b0cf89eaf7",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/get_members.ts": "48ca56063d6e0e180a6eacda2ab62394ed8f6ba40175e9da45b25ec04273bbb7",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/kick_member.ts": "864aa3a236e2f41bbb9f6728cc00e1077e2a20fdefcba185c759d21a8de4ab43",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/move_member.ts": "b0d5c0a49c25f2f5496fe88d55e8afce8e060f5c4dc5d29ac311e48a2fc39018",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/prune_members.ts": "de6e140a57a4a539004527a8076ca1e46ec884810765199c182726b11b8b9e2d",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/send_direct_message.ts": "c925a8a8f89dc5e4dc87e26d3559645c563b1a4337b9a75fb587ee006691e556",
"https://deno.land/x/discordeno@12.0.1/src/helpers/members/unban_member.ts": "06e4ff90ca09add6aae2c60f458a0f9772a8705296ee028df5132dc28969102b",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/add_reaction.ts": "5c3dddd8b353ecef9d2f9fb31fe3503b6ed1de79d50b2739acbe3db81de219bd",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/add_reactions.ts": "ee9e2ba5e7db0d4e9c9fa33b881342dad97420cede1a6b03cdc81e19e4071403",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/delete_message.ts": "3831e5cf8d73e4c40d770d1a5f717d6cf11cb128b352b672d3cd0bfb55d89994",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/delete_messages.ts": "fdaf1b176881e0a069224483c0b54ca3616137e9428c0d8e18e355a4b65cf884",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/edit_message.ts": "63f1d500fdddbca69dd677290ef439a58614f43365ef6745a0d1c260a498903e",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/get_message.ts": "e5bf023bce011d7555db493d93e94fdc7f1afa7939bfbd4c9599b2377b74d114",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/get_messages.ts": "87ea81f5c4be6d2aabec159d955b16df4842f0f9c91e4810904cc3d517654565",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/get_reactions.ts": "a0f76748432b28d495fe8f0cfd5248c72c705fda433bc4da5bbc5e8ed2929f41",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/pin_message.ts": "028c996ab47a363a28226ce8b67e67e35d005f0cbef52ea882adf92e91329955",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/publish_message.ts": "102672b11320509d3838f8afa8c1d9d3ce9edc5d69e66205b2f24f40178ac8d8",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/remove_all_reactions.ts": "c28540d1195b48a20fd229b33f3d959b3b4d715037a7d4742d4c3c614c701c5b",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/remove_reaction.ts": "61a3a2f1da42f7bd9b82a351d77d7263a23dd04fab35905b1157ae669b1c2ee6",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/remove_reaction_emoji.ts": "8ef57f33a451787030ebb6af9b62a3d1f71571112d4c05b1e1d70358b48108c9",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/send_message.ts": "913e70ad461d6751b8d78f5a1d4c1d6b9c5c4b2204eeffce02f129375c82ea37",
"https://deno.land/x/discordeno@12.0.1/src/helpers/messages/unpin_message.ts": "95243713f747c12b75ce678d9af8e36fc90abd64e97c4aa9211ee71c21f93c13",
"https://deno.land/x/discordeno@12.0.1/src/helpers/misc/edit_bot_profile.ts": "02c09719e48bdc03d61797071f5bf1a2005be1e3b8515231b9f23f02b4a36dbb",
"https://deno.land/x/discordeno@12.0.1/src/helpers/misc/edit_bot_status.ts": "d5831a96857d99a229bb5bd07012f5d5edb8b8d6f3a6c6c817fe9c9ca237f360",
"https://deno.land/x/discordeno@12.0.1/src/helpers/misc/get_gateway_bot.ts": "605ac54a0131ea34b0025801227a75932a2f02674f2676327ccb7368abe7a86a",
"https://deno.land/x/discordeno@12.0.1/src/helpers/misc/get_user.ts": "7a0d89e781e3d844a378d9e9da495e1f102495ad79caaa42cf9a0e7f9daa1deb",
"https://deno.land/x/discordeno@12.0.1/src/helpers/mod.ts": "0907bd7da6a747d146c35c1b79805bcecd233836c55689b134cb7bd6f127ef83",
"https://deno.land/x/discordeno@12.0.1/src/helpers/oauth/get_application.ts": "a702482471f8c800979c27c0664e5eee5a0d2f1c35a2774c38badc0a7a2bae7a",
"https://deno.land/x/discordeno@12.0.1/src/helpers/roles/add_role.ts": "05e864036e4258bf3d7f9ef2f5a9d597b19c15fbe1c507dc628566b3cb2f8c75",
"https://deno.land/x/discordeno@12.0.1/src/helpers/roles/create_role.ts": "15254863165ec1f0c074d60b476dd158e7a0dd0f75b9673a069263bdc708fd43",
"https://deno.land/x/discordeno@12.0.1/src/helpers/roles/delete_role.ts": "2caa0642b1fa10df4adb07c8e14209b0ed5d99cea5836295777dfa9797944c9d",
"https://deno.land/x/discordeno@12.0.1/src/helpers/roles/edit_role.ts": "d7923baab2f6a523d699e3981c5fb940b6eca13e60a5af75fc0e8ff5c94dfa80",
"https://deno.land/x/discordeno@12.0.1/src/helpers/roles/get_roles.ts": "8ef3b45dc0f023f412ca082fd9c06ea60cfdebb04cdcb2dbf6cb7528f9fe1e1f",
"https://deno.land/x/discordeno@12.0.1/src/helpers/roles/remove_role.ts": "8644de72fbca1849b2213b12a1b85f2e605bf5ee261b6538901e02d0548f588c",
"https://deno.land/x/discordeno@12.0.1/src/helpers/templates/create_guild_from_template.ts": "29d917f56fdda4c5dac0de3b81d7c2e4bed91747b6bafdc71e1fe87f70aedf01",
"https://deno.land/x/discordeno@12.0.1/src/helpers/templates/create_guild_template.ts": "8a4055a9b5df992067861571b97faa6c6b7f5228251a06fdfee1511b0db5dbea",
"https://deno.land/x/discordeno@12.0.1/src/helpers/templates/delete_guild_template.ts": "5d6f7d2530ba75eeaa2660b2d1baded98079fb41831cd2afd9eb3503bca7e488",
"https://deno.land/x/discordeno@12.0.1/src/helpers/templates/edit_guild_template.ts": "6692ebccef9ed38de14f898fb2356f1e55073b5c17cf4f18971d9f7d7bbe9101",
"https://deno.land/x/discordeno@12.0.1/src/helpers/templates/get_guild_templates.ts": "5ae82f165f0d564aba25e21b298e849f005d5b4859178fd62423ddb1fe0940f1",
"https://deno.land/x/discordeno@12.0.1/src/helpers/templates/get_template.ts": "1791848df73e713f347a471614651fbd22ee88ba58010f731081bd1679224408",
"https://deno.land/x/discordeno@12.0.1/src/helpers/templates/sync_guild_template.ts": "d95d81a838d6005f9dc6ad72a380fc348b026856e6ea77e47e0955e5825e87b6",
"https://deno.land/x/discordeno@12.0.1/src/helpers/type_guards/is_button.ts": "7a0ddea4a26263f9e882dcbca8f3bfa034104edc9db6b12e237f5e0d435959bb",
"https://deno.land/x/discordeno@12.0.1/src/helpers/type_guards/is_select_menu.ts": "e706b8340d04f75e427396698f2d5c13a5dad112101d4811704aff5648a2069e",
"https://deno.land/x/discordeno@12.0.1/src/helpers/type_guards/is_slash_command.ts": "97d065ba4ccebf4a6abffded9c111f0a7eb90a96c7253524c4092014a12f42ff",
"https://deno.land/x/discordeno@12.0.1/src/helpers/voice/connect_to_voice_channel.ts": "7a08c4d18fc695ace20d39db8eaf84c6bdb0f4215bbd98174b1bc7ed538aa7a1",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/create_webhook.ts": "b9d00409f4105808bd7803ff92c7a8c00968973a3bb1ea2955812d21ba41a403",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/delete_webhook.ts": "4eb549d5b00c4213abe78523bf03b77c2f94dd552abeaa0414dd970254f3a526",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/delete_webhook_message.ts": "bfc382da4f2713a425e584498c9d8bbf3dace96e8769759ccda58b6291cb399c",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/delete_webhook_with_token.ts": "eac6ba2e42b7ad72d908b69a3aff4ff72926357edbc054c733ddee480112b14f",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/edit_webhook.ts": "c6477c54ebf529788056f115e074710cc1c854b212351c4d21a8c857f2826d73",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/edit_webhook_message.ts": "b89fd5290a8b5f7d1ced488f721d4a0fa3ec80aacef2b569a41a54e0414ea8f2",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/edit_webhook_with_token.ts": "753c9c74d118fe70544b8b40c4a36b7cabd81ad5ef4a0f13617ab9b0fe54bdc2",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/get_webhook.ts": "5fb19856722706d460a4569adfa848dfba9137833224ded5519eefaa31da5fe5",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/get_webhook_message.ts": "50ea7e933e4e6359bdb9d9da20f326ed8d087ae4be04528dcdf1c5d0cf31672e",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/get_webhook_with_token.ts": "d223b13c74f67f676d58190a59221fed96cca6bcddfaa9f97f02e38bc7655aa5",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/get_webhooks.ts": "9faadcc66e6683d584fecc2bed15513c6e99c165e845066ac7be408d1d212569",
"https://deno.land/x/discordeno@12.0.1/src/helpers/webhooks/send_webhook.ts": "943a76a1776bcd30e2856b7888faa86a1b515a812c601a5bc049851a8e6bd5bf",
"https://deno.land/x/discordeno@12.0.1/src/rest/check_rate_limits.ts": "2b3074e044a95fccc8e85fa0385db2f1fd610fcb1163654cb0e777a1ba96bff7",
"https://deno.land/x/discordeno@12.0.1/src/rest/cleanup_queues.ts": "80b1db9af5db6630b2b043f5fc8db9657d92a82129ec6be8328d27572714c58d",
"https://deno.land/x/discordeno@12.0.1/src/rest/create_request_body.ts": "c71c4c14e4e38bc09b72df0ad11ab25bcf98619fab851b496bd1b3faa45cea61",
"https://deno.land/x/discordeno@12.0.1/src/rest/mod.ts": "fea5de6652d41d45d6fd5a0915bdd0ae78b680e92b35e7c4fc14f84f2ca7d7c8",
"https://deno.land/x/discordeno@12.0.1/src/rest/process_queue.ts": "c25be6b2cc2a14b94985bb8cc736d855a3c7da15b6755fdd000ad7a6b8712353",
"https://deno.land/x/discordeno@12.0.1/src/rest/process_rate_limited_paths.ts": "58b0d5060249556fd7703e16df16a3ce5f6e03f118d06519588b0c548a9e22fe",
"https://deno.land/x/discordeno@12.0.1/src/rest/process_request.ts": "367071166eeb1ecf4a0d170f7bf74f945b412c8012cfd09eb4da730b6aeab7d0",
"https://deno.land/x/discordeno@12.0.1/src/rest/process_request_headers.ts": "14a53f0f63eff1a473a5f9b0e737fd7ebcf268c081b537c407bc854b55422be2",
"https://deno.land/x/discordeno@12.0.1/src/rest/rest.ts": "1dd7ef78441e2d70a9c852e1324d000a05906e614bcd6834d35a6113976e7866",
"https://deno.land/x/discordeno@12.0.1/src/rest/run_method.ts": "3b2f778e21b9b5cf55f32b22a9b9a1556aa2636f54e7ea6b377f0e5f5db8d115",
"https://deno.land/x/discordeno@12.0.1/src/rest/simplify_url.ts": "a4348ba808cb3a80d4a9c6b5e1a0cb5572a61cbb9409b280d5d84ee9d1d85679",
"https://deno.land/x/discordeno@12.0.1/src/structures/channel.ts": "caa28ae30b9af52110614e46d521b345f6d25ca2102247344ee8fc2bb0739058",
"https://deno.land/x/discordeno@12.0.1/src/structures/guild.ts": "7a7f7f3a1829478e5c83d53a68ac4437c257c437ab0c6623815a9d6a951f7880",
"https://deno.land/x/discordeno@12.0.1/src/structures/member.ts": "5e08c642dc0913f8a181cb86526b3cced7e2623529d814b9c6d2ca4a8705c9d8",
"https://deno.land/x/discordeno@12.0.1/src/structures/message.ts": "bca3ee1269a6cf959b23a9a98622251bbc6b9297c074be5f11cddae76d8c9374",
"https://deno.land/x/discordeno@12.0.1/src/structures/mod.ts": "5b80755c8336d40bdef87a376ccc8ded6fad4d34e973ca257e10f3142c3c5dd0",
"https://deno.land/x/discordeno@12.0.1/src/structures/role.ts": "509ed6dfa484f2dfeca65e7fb251e8d3c79f76d4bbe5fe07d44a1cfd9d5e592f",
"https://deno.land/x/discordeno@12.0.1/src/structures/voice_state.ts": "9f0a7351ec8dbc85879149482b1be51c48c8ae6b0fcfc81296d6659cab560d0d",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/activity.ts": "7d34ddfc8a25f6cdc897ea0756b86346830aec1b1e66b696b73503e5c00db75d",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/activity_assets.ts": "a8ad6474142762a5723f18b0f4c2f5086770d84343b8ce441b07e57926f62717",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/activity_button.ts": "c7643944b5d695ad699937e5915a563a62fef4195ec0a5305d25cedfcd4a651d",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/activity_emoji.ts": "7287e91f18aa4b9d936d9061445868a0070e8018fba57a8e43984c6df13da4d5",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/activity_flags.ts": "23b96eb169832d16f607e952894ab0a010c879a924fb854c8948b7cd1f8e597d",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/activity_party.ts": "660fc738165352dd67a7bf251d740b207c41ad208df97d1bc0a07d6c10125277",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/activity_secrets.ts": "b185dc3a22cc0cb2e804648898e65c11886706378728ab0b2dcd008ae9711392",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/activity_timestamps.ts": "57d62ce975a75af7fe7dd93e29b38abb56251b14618c4308132ca4808a2633f5",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/activity_types.ts": "282b9999c9da66e3ed55054532cc61419055810eeed5da2c9a23dc6f2b5b7e29",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/client_status.ts": "29df68543c1bb257d36ef28a5d6a0bf9285992068b588965e66f932fec66e36c",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/mod.ts": "71dfc42746756ba7b9000aa807af2ef8ba95b8665e9353ec0a10d3d69ad843fb",
"https://deno.land/x/discordeno@12.0.1/src/types/activity/presence_update.ts": "594dee9c8259d72f8d85e53cf40e10534c17a269bc81096460272d80b05c422e",
"https://deno.land/x/discordeno@12.0.1/src/types/applications/application.ts": "a0912a6e15aeeceaca6ddd8899ebad223fbb64f185843abb94d0dcd28fc767ba",
"https://deno.land/x/discordeno@12.0.1/src/types/applications/application_flags.ts": "f66b131a2a993542b9234f31a41a1f9dc4943edfb104487bea91337df81b6b15",
"https://deno.land/x/discordeno@12.0.1/src/types/applications/mod.ts": "86c8bfecb4e6b86b6ca3db67edcfbc69d041d4c1f0a1903f8c358b2f0f5f7798",
"https://deno.land/x/discordeno@12.0.1/src/types/audit_log/audit_log.ts": "7429ed118f04206a2a9569c9b7f8c0b7284f84f44a1283287fa489fed18083a5",
"https://deno.land/x/discordeno@12.0.1/src/types/audit_log/audit_log_change.ts": "8cddaeb5259f559ba0537bb7c4dd0513266ce50d4b2698ffcc0edf1c227bce53",
"https://deno.land/x/discordeno@12.0.1/src/types/audit_log/audit_log_entry.ts": "d48c22916e75f7f516765ab601d7a0fbc34fb90b22cc989939d75841b06a72a6",
"https://deno.land/x/discordeno@12.0.1/src/types/audit_log/audit_log_events.ts": "d00d7524a61064fc41bd5c0ff9f844d581558137d08969b75635854ea1239424",
"https://deno.land/x/discordeno@12.0.1/src/types/audit_log/get_guild_audit_log.ts": "b83d2a874c8c0dcb818bb7db500fb0077dcd7332fc973b68a05951152dc5a86a",
"https://deno.land/x/discordeno@12.0.1/src/types/audit_log/mod.ts": "c36386f4de474cac0401ea20834330f7a0df1bfd4c02a52db26ab9ebe799dcfa",
"https://deno.land/x/discordeno@12.0.1/src/types/audit_log/optional_audit_entry_info.ts": "d9df543a55bcb8966ceaa718250fa7d48b52c7e8d6f90da471ffbec4e4b90f59",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/channel.ts": "bba726d092188a98731321b73c4ba2f99cbd69c979b5266e3876c9c98e3e7333",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/channel_mention.ts": "b0f7bf18ac590cf83d4587f49b189e0b547f10b3b71b4af701ff0946671208ef",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/channel_pins_update.ts": "bc1cec14468a56e714bcf5b0c203572d3e30b8fde4e95838fc7ca91730aa751d",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/channel_types.ts": "a2ebf2c2bb84cc5d49ce398076ee068e13a5a00a4c624bdab7f3b19504f3704e",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/followed_channel.ts": "416745de34a86022f060f8041d2e396f50c7e065e78f32abf7c7c4bceb9dc2a3",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/mod.ts": "a016d83f6110abb77571187a879e16014b9ab4369a1287aff7c0d974e4da9909",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/modify_channel.ts": "f300ae9039327f670312276729e76c5b3f7ae8ba2ec532e02ad44023f9b52c12",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/overwrite.ts": "89b33391f5f9983b79ea1bf16a696e7a85d015f79956383b7bcab242112f2303",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/overwrite_types.ts": "9b4f40855fd6312f572d55259bbbc7aac01ac7193314c9df2c5691e874c14529",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/privacy_level.ts": "dc0802046b643efda6a5a4765b86c0c0cda34fd23be4ee23687d02b7b2d78b52",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/stage_instance.ts": "bfb6785506c807d20f0ed9798c9232e70af4e26236ae545789a51733118d3011",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/threads/list_active_threads.ts": "5a6110b349c2a9dfe83d41f5bedd2724bf886e7a271ab868b4880c1f32b8cf6f",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/threads/list_public_archived_threads.ts": "5557064faaf97fd0dd32d5c7aa283311207c7390cadb7e3dd4068769eaebe2ef",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/threads/modify_thread.ts": "f7a73eb72c3dbf728fae57880a06437a1c8ce44e7d87a4513773f87f39630fe1",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/threads/start_thread.ts": "69396fef03efd6e815d1beb01d50fff5a6043cb0a06a9f4ff082ac134ca29476",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/threads/thread_list_sync.ts": "4e2dd6faf75daac0bbcd2404f950a82487340281959dff3cd0b4a2a1b6783f05",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/threads/thread_member.ts": "451c25c7a62e1e69759437ae739c228eeec077c41ae8254ad55aebaf51e01ced",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/threads/thread_members_update.ts": "69ab90c9ed33d69bf8aa6d2d3f269b83549c330b94f155a9c28fceb1bcf45716",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/threads/thread_metadata.ts": "ed472ebe61e1d79723ff1ece7d4e8b7eaa95ceed2f94ba77f60b34775445f2d3",
"https://deno.land/x/discordeno@12.0.1/src/types/channels/video_quality_modes.ts": "c11a4973d8b0f41bb0cb8c4e9565a80db9afdf8d3c682eecd0a9e5b882811c64",
"https://deno.land/x/discordeno@12.0.1/src/types/codes/gateway_close_event_codes.ts": "d27dd7ef8984cd4094a14fe5cc1f30fc8cb723a526ed6c29d0693998173b9289",
"https://deno.land/x/discordeno@12.0.1/src/types/codes/gateway_opcodes.ts": "eb488801d0f437db2d42852c650381b0f2671bb15a48e59debaa76a32e601ddb",
"https://deno.land/x/discordeno@12.0.1/src/types/codes/http_response_codes.ts": "b9929169cfc6346f6eb19b6a73796fde04b33f246472b54e8738e47a5040148b",
"https://deno.land/x/discordeno@12.0.1/src/types/codes/json_error_codes.ts": "601b5ec55329dfd433b48507a38009e89fdc5a95de6f5ffa6bbddfbba645dde6",
"https://deno.land/x/discordeno@12.0.1/src/types/codes/mod.ts": "f9834bf16db285dae92edf3d07c586047165da9c317996774e697a076ce282b8",
"https://deno.land/x/discordeno@12.0.1/src/types/codes/rpc_close_event_codes.ts": "8180e76199f03bcc0957057b8566ce16193a27b44146fd753272df35735f0370",
"https://deno.land/x/discordeno@12.0.1/src/types/codes/rpc_error_codes.ts": "b87d01aa923f108074b8c5b07bb593aa9c8d237aec3b1892b75e0d0354bda7f9",
"https://deno.land/x/discordeno@12.0.1/src/types/codes/voice_close_event_codes.ts": "6818e3842b3d5cbbbbc0c94f3e2991607a256d9a417c2588aadda4f18d64e80d",
"https://deno.land/x/discordeno@12.0.1/src/types/codes/voice_opcodes.ts": "013c29e3ee462ba0e64e06514a43d0ef54b289e1269e9ce091c0959de663c0b2",
"https://deno.land/x/discordeno@12.0.1/src/types/discordeno/create_slash_command.ts": "5de59d5b75e2c6a57fa260f5ef8dfa657bc1e1bc93ff771e4c01c661c7b69344",
"https://deno.land/x/discordeno@12.0.1/src/types/discordeno/debug_arg.ts": "3e9f547bc0b678c1358c395f4ed49683de40b85af064f8b0fd5761227851cc14",
"https://deno.land/x/discordeno@12.0.1/src/types/discordeno/edit_webhook_message.ts": "45e0375cc986c94c7f77a8d3a0b8fe1557374ec83943cdafca5fe52aeef7cd24",
"https://deno.land/x/discordeno@12.0.1/src/types/discordeno/errors.ts": "2614c86f6860b9e569657adec7f0541c9822e0be38df01f8c71cedd63a0aed72",
"https://deno.land/x/discordeno@12.0.1/src/types/discordeno/event_handlers.ts": "4175f7bc193a5111de7de6e5d74fc43ff084d4113279907676f9f53c0481a03b",
"https://deno.land/x/discordeno@12.0.1/src/types/discordeno/file_content.ts": "2931569529bfeeac82eb7a679b6f9f179a222708d8599b7b066b870213abbf08",
"https://deno.land/x/discordeno@12.0.1/src/types/discordeno/guild_member.ts": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"https://deno.land/x/discordeno@12.0.1/src/types/discordeno/guild_update_change.ts": "ce1bb0f144e3d463fd9642b7c8134489e1b1cc8e55650dd919040cb85c60051b",
"https://deno.land/x/discordeno@12.0.1/src/types/discordeno/interaction_response.ts": "e715eb95ca793b40c4064397b954511403130c6fc006559bfb2d3076c7d6062a",
"https://deno.land/x/discordeno@12.0.1/src/types/discordeno/mod.ts": "a0cce1a613a3f922ae8c2a6acfff482ee7b80557842d2d1c2c817209168013bd",
"https://deno.land/x/discordeno@12.0.1/src/types/discovery/add_guild_discovery_subcategory.ts": "b0edda1c3398d35099adf71ebf7c521bea8c0ffd44ecb4e9389f53024ee09bec",
"https://deno.land/x/discordeno@12.0.1/src/types/discovery/discovery_category.ts": "ae4fe9210bc495ccff1c227af097cb6a729e92c6b4d867f1ec51c71a3416b0f5",
"https://deno.land/x/discordeno@12.0.1/src/types/discovery/discovery_metadata.ts": "fed5d2337581204e5f7e9858f43ed0cae70f209bee43e2898f24c243732f1151",
"https://deno.land/x/discordeno@12.0.1/src/types/discovery/discovery_name.ts": "830761102dc603a82b58912d642abed965f2f46c96498646783b8594b592a1ca",
"https://deno.land/x/discordeno@12.0.1/src/types/discovery/mod.ts": "5239cb22070b6a149e8053486d5609fd75111c32c2b8301d3270d3912e66f214",
"https://deno.land/x/discordeno@12.0.1/src/types/discovery/modify_guild_discovery_metadata.ts": "2d565323109179f9dfbbc5f95d530b66aa868c6730d52f6973e7b999e47a8881",
"https://deno.land/x/discordeno@12.0.1/src/types/discovery/validate_discovery_search_term.ts": "3399ac74193a8693fa93215c5b3cdcecf3833fc0f6c2fdecfe490a31a4f39454",
"https://deno.land/x/discordeno@12.0.1/src/types/discovery/validate_discovery_search_term_params.ts": "6f9c2bebd75c0d02f5e1f0c41fdcfcba2fe137671a24f3669d721278f6c97b63",
"https://deno.land/x/discordeno@12.0.1/src/types/embeds/embed.ts": "2245fa9fe3b5be80cdebf0dfb64bd9987b024821a27a96e7b5eb6e172c551ae7",
"https://deno.land/x/discordeno@12.0.1/src/types/embeds/embed_author.ts": "abe9d10e39b07998119b08486c1d6365bcdb001ba8a495e6e715c41b05454ff2",
"https://deno.land/x/discordeno@12.0.1/src/types/embeds/embed_field.ts": "4b3d50a51f33459abe5cf1d84028e49e8f5cd8f53c7daddda634b11bc11a546b",
"https://deno.land/x/discordeno@12.0.1/src/types/embeds/embed_footer.ts": "c70d2ee9fc5f386946bfca8d8e5cdf7609e210f818a9e1c4c67b49366a23b40e",
"https://deno.land/x/discordeno@12.0.1/src/types/embeds/embed_image.ts": "450db6360abc614a7e0edae6fd43f2bd13b4423c8acf619e47e5e25af5f48e8d",
"https://deno.land/x/discordeno@12.0.1/src/types/embeds/embed_provider.ts": "916f675f139ed08d394ff0ff133ab5964aa55362c1315f44d5e16ce80534801b",
"https://deno.land/x/discordeno@12.0.1/src/types/embeds/embed_thumbnail.ts": "5daf8217e6c4f58af1a042d48d35d17683714e8ce6e3d95d6b9bde5643d28c02",
"https://deno.land/x/discordeno@12.0.1/src/types/embeds/embed_types.ts": "84dfb33b9be7ea713b5a68b347cbf09e4f3f7335a74a1ba53febd97fa890c31d",
"https://deno.land/x/discordeno@12.0.1/src/types/embeds/embed_video.ts": "b2608d208249fe39b5a0b4f79053a827c380541271a96d48b0fe25954f2f2656",
"https://deno.land/x/discordeno@12.0.1/src/types/embeds/mod.ts": "6f843bd4b1a08f2dbc38d88866d01c3f9acb3d5dc7acb06be54ee0fe484712c1",
"https://deno.land/x/discordeno@12.0.1/src/types/emojis/create_guild_emoji.ts": "1f746dcf5e00483a891ef6e25736bf57e506083867a16c90ac41357352b85518",
"https://deno.land/x/discordeno@12.0.1/src/types/emojis/emoji.ts": "816fee9d303e87d3e8021411119888605e9bc40969e50bed76592f97dbf6ab66",
"https://deno.land/x/discordeno@12.0.1/src/types/emojis/guild_emojis_update.ts": "40dac4e55b8ead25aa6bbccec02cdb83c7207ffe076f1678888e1b4093db0ac0",
"https://deno.land/x/discordeno@12.0.1/src/types/emojis/mod.ts": "90df31a90a51a7b618fa6b2a1c34a740d6ec85a40b4468165439dc14fcef744b",
"https://deno.land/x/discordeno@12.0.1/src/types/emojis/modify_guild_emoji.ts": "bc2751feb17c2312bb0374523c44d52530256f1f9688270bbfa9435350d5849a",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/gateway_intents.ts": "3a4b00bc646f190ef2f67877b415150a87f5881ccbfc50877742c151b3ce822f",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/gateway_payload.ts": "112ed8d384debe78712eedba4b28d0af5fcc7ba148ee513d60f2825df9308802",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/gateway_url_params.ts": "5b7c1856e40b30c77eab74469712a71f0d8681f7219b0a04c469e05e9407d021",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/get_gateway_bot.ts": "3aa13ea2be2ed7ef9fccf444ab02b7915d13eab16ccf6531f612f34b6cd8bd07",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/hello.ts": "2ef6d751aec82fd5b3038b1b52e6bcb7513e79d24ef4d88b1a40992962fcf3bb",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/identify.ts": "c5d35c10f0937107ff5d278a3fad9978d2be2e77a0ca9cd0d6e05baf77af7909",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/identify_connection_properties.ts": "4241f2cc6e232c43ed02df4372dab925d3726a4d0a9e6b68db5aa75b8a77d64e",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/mod.ts": "b1fae244819a0805e8be9ec9aebd0c296573a98455d052e65025d88dc9f18c34",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/ready.ts": "c8bbc0f85ff571611725d52cbdf85d729f212756879e9dd6e94ff0e43a9f84dc",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/resume.ts": "2cfa1ee13ee921457a3b98b1f0a062db85cbd2f489ad795bc75f31bd071cc109",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/session_start_limit.ts": "910e0ec21753a6f29c6ea0505bc8471d0a6094c28baf7717ddf98743855dad89",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/status_types.ts": "49c689add16773ca7ccc7fe943d9fbaee82bb65ac717ff1cf6ef760befbad51c",
"https://deno.land/x/discordeno@12.0.1/src/types/gateway/status_update.ts": "14a3154743691211509db934b48be742a79f8fd72ea519427d2614b41749bee0",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/ban.ts": "ccfdb4af12838bc026410a39381bece7ee04853282f0c1a382344afd22827a91",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/begin_guild_prune.ts": "c8c9214fcbc520b13bcdccdb02b30e7d46aad478f6807aa8059b6ca43058785b",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/create_guild.ts": "fb38d21e0f01386750119052bb40a56d33db0daf55d12d272d0863be8ba465b2",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/create_guild_ban.ts": "61864ac8b829bae7c32445bbe7bd393d9592d0bf9fb7eca21218e3574f0beea7",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/create_guild_channel.ts": "3b4d0779209075eaa3d9c3fa77fc047427cae8b791b1027291678e3334cd2b24",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/create_guild_role.ts": "caf03c3222f4191c23689f4a307967815501e64651a61f0bc56a0abb752d396d",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/default_message_notification_levels.ts": "2b5157428c451aec689a450bb9a79995b1c3276cde520e10fc79e0aff2aefe1b",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/explicit_content_filter_levels.ts": "929cf4cb47e1c8a6e855c2d2ad801d6f97ac4abf7f0a89af17f3b4f0cbc6352e",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/get_guild.ts": "188e6988722482043f827a5c1b8b631fc3374242e65e67ec189027c0a7e3d303",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/get_guild_prune_count.ts": "23669a9c61615895cf05f1ec6f47d5cb5bfd1f560285eb1a6087231e8740159a",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/get_guild_widget_image.ts": "05c418983d58b6a588864cff52d9cac7df32aa9d2b87b10068ab5a9a6cccbeae",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/get_guild_widget_image_style_options.ts": "00641321ee414e4ce1fc6d34bf29fe87fa55bbc73ff203ba96560b1145276f83",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/guild.ts": "8fc1dcf92ba29eb7a3103459072e0179152d38fb59c95855c50a838744327712",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/guild_ban_add_remove.ts": "b24aaf96aa23d04c215f5c65f8808d1e752ee816a427eaa63cf44ab11d4b6dab",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/guild_ban_remove.ts": "7e0c5d90f0a18ac503a8a186b58e3d0a926d1f601955926ab3374b9e3cfc9ddc",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/guild_features.ts": "8d6b08fb9d13254a7886c1f7f90a0bbbeda73e1a71e8d54becd9e135be1b7ab2",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/guild_nsfw_level.ts": "fbe0ccf872bba3bcdb0a204f9f6abe49fa2cb203c2c4dc1b78426319d5218cdc",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/guild_preview.ts": "dae116abf051f416fc08b099076d66c0b3a33cc0d84f8dac0b3c868ec5e980bd",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/guild_role_create.ts": "6c838e78586f2259e1151a496f8fc18701879af23dd493a24d0320970fc0f50e",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/guild_role_delete.ts": "494b620f7a24b15f9473f818f11b5d0c8c2b5f4177f55350e52a9651e3d869cb",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/guild_role_update.ts": "93c870994c7fa5d4be6dbfd66913757352e5a3f68c141201a35a1b6f183f592a",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/guild_widget.ts": "03d47a8ba30be209e709d6e00651a1c1d36d4a8fc6de3d873ea136f29a0e2856",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/guild_widget_details.ts": "9ae4fd54600e48bdad73e212ca792e16a7ff9e063458024a18362ad36adc1dc7",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/mfa_levels.ts": "c9fc5a0d2dc3de72df455f0c49e02718fc7b8db6fe1fa772e399c7b3ee87f45b",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/mod.ts": "eeb1aa95c6953b653a59a82c25bbc062638f55e68b3e8429f3a38922a1730e0c",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/modify_guild.ts": "60d23520096cab852fc52ec5ad934415252692f89920077f79218ea2d82448f8",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/modify_guild_channel_position.ts": "35f1efce25f0b22da6f628bce4664c097095ba7d652b35cd2e1b252c00df88b5",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/modify_guild_member.ts": "882aac23edfbd6f5d27717032ea3313dd6e4924f6fc0d60923f9cfdc561bf563",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/modify_guild_role.ts": "428a137c7974570002fec90ab0bba7c814075b194f7de7081ba90fa2ee44346f",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/modify_guild_role_positions.ts": "f0cb8ff1ac284a20dd0b4d36c6c1dbb957273f60f62addd2fbec033b7e60d06b",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/modify_guild_welcome_screen.ts": "634214924569b6d3ee450a111cfcfa98314d1ef0bf06b28e5c594867f32334c5",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/premium_tiers.ts": "6bd44db4e168d6db9914dc0135a3b82f180f87f80ad335448e9b46fa579f1ded",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/system_channel_flags.ts": "e7838b8537a3b171b1d9c7a71a7225493663a6f5776704703d1efcdf3dff1a51",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/unavailable_guild.ts": "2937f55cbfc3d021dd386a7b0723ad3d35abda9e2f8280f2b23052651bb84e41",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/update_others_voice_state.ts": "6e796bb876100211e6bf97bdc069c58a632cf54878f4b36cdbe0a836e081da84",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/update_self_voice_state.ts": "2a5ffe02555eac543757181380c435fc7bc9103df63cfb2cfb1274b20afa8a8f",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/verification_levels.ts": "ead22d89fba2cd26095bfd2b09f1c062ddb782229a87ea70cce403f04c492eb1",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/welcome_screen.ts": "4d3d83350611c1537d3abe44dc7d265b8e451b01feb36c0ca4e38ae136b8116f",
"https://deno.land/x/discordeno@12.0.1/src/types/guilds/welcome_screen_channel.ts": "6039e1cfe646f6c6f4850c4b9eaf63e2451ae0feadbc132dd4b7ee4c253dfee0",
"https://deno.land/x/discordeno@12.0.1/src/types/integrations/guild_integrations_update.ts": "e2d995180d9dd8a946fb3929468c129c98ef432512d3dfc311ee0649c87fc716",
"https://deno.land/x/discordeno@12.0.1/src/types/integrations/integration.ts": "8ab702ce5089238647ea5750c24f88922a59076532e3fe78e91b10d229c61c04",
"https://deno.land/x/discordeno@12.0.1/src/types/integrations/integration_account.ts": "920889feb3ce7b75779d5341077f757b52ab016b1e1fad38dd209c523c0cf65f",
"https://deno.land/x/discordeno@12.0.1/src/types/integrations/integration_application.ts": "9ef6f1baed66f520b716cd1941691475604a7a7e48528722c37f6491652d6f49",
"https://deno.land/x/discordeno@12.0.1/src/types/integrations/integration_create_update.ts": "db77df8027bc362da8fa19f91a1e9307a2152e99abb741dd68ac1360582d10d7",
"https://deno.land/x/discordeno@12.0.1/src/types/integrations/integration_delete.ts": "2053d99b869c3e76a23ed3ef5f0c3c0b43aae6faf3120f9152e608f62a758d87",
"https://deno.land/x/discordeno@12.0.1/src/types/integrations/integration_expire_behaviors.ts": "0b618900855a81136ef5cab1e37ab1be0cd5e66087d5498a42948c1cecc3cfb3",
"https://deno.land/x/discordeno@12.0.1/src/types/integrations/mod.ts": "b6db017298dc714f5584ec702e5b9da47971ff98cf0ea33fc533c4e68afded2f",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/application_command.ts": "d38d90c519b7f17cec1abbcd858c21a3795a17646480e501fea9d64949227833",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/application_command_callback_data.ts": "aee22774c1a419fa42afd62d4110beeaaa68b761ba8dab797e3901a14b75b7d4",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/application_command_create_update_delete.ts": "294af565659fe37e54e2bea32708b631051d818f3c7808858f48ab7dc227da1e",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/application_command_interaction_data.ts": "28de54255bf58298777dd446c857505de7c705570e199846b5319fe28cdfb945",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/application_command_interaction_data_option.ts": "0466622a878c795366fe4dd7b25724d2d227d65f675e69d9fb1e86d02ade53d8",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/application_command_interaction_data_resolved.ts": "aab04cf520a6996d44c6c558d0fd9ea7e0ba65f7a8225a683b93273d9ee0e2c0",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/application_command_option.ts": "11ebc266c3e09b8b18ae9c831da89dabed6df5a25cbbf83d8cb94e8d307cc781",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/application_command_option_choice.ts": "794e07b054b4d9f114f83c616dbe324a54b73177075d9604e0603309c2844a70",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/application_command_option_types.ts": "33d8f107acd6632d79b0c08b4c58d40be5b539f99190b1a05ca31c3320c0faec",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/application_command_permission_types.ts": "30573c358f36b352f0967dc08c34f5158a1e737854064157cac5cd79977e4dcf",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/application_command_permissions.ts": "c5f973d8f54ff4afe219d9bab15a0ee78978ff87513537d802af75f5a915bb63",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/create_global_application_command.ts": "decb10c281bc32e835d314cbea79e2462634bb55544be10b30a148d7923d96bf",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/create_guild_application_command.ts": "496ff874f52f109a0288fbc79b95d6cf44c7c511b38e604c3981845388c18d31",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/edit_global_application_command.ts": "ac88b35c2d38f79d8e560c708e88e552632e7e241d18254dcd6a74364bfc0708",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/edit_guild_application_command.ts": "acb83c3e78ef5314c8dac4c807e6fffd76200768b298a5be90db485fc8013fbe",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/commands/guild_application_command_permissions.ts": "dd1a0f12c790653ad172e92c93a4230025e8d01b006aa304fc96f75e401fdfaf",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/interaction.ts": "ce9284e9cf04b2feac0c571c0f4b45fb562c8fac9994eacdf70e20315aff137c",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/interaction_guild_member.ts": "b7a0e56bdfa52e00d0b6b64afe1d266d6ee51f4910acb8dcc4b066d8da2a28cb",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/interaction_response.ts": "e3859eae90a97d193c373fe51faf33f85246f21712726a3046784562a3e36bef",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/interaction_response_types.ts": "cb05edea31d2ebe4d7f6dd158e298a147d6b1d3faa5f686b04ee3a1826378be7",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/interaction_types.ts": "4d1177f31fe6d122840f5fd328256e3f136eac57447e6d68333ce114ba33eb33",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/message_interaction.ts": "27456f496e8d43792d94758a3e231756ea0c034fa1b62ab15a657a7773c63286",
"https://deno.land/x/discordeno@12.0.1/src/types/interactions/mod.ts": "bbcd6803c8adea202efb9891b54fba800aad936932a69bb8517a2cdc2904afda",
"https://deno.land/x/discordeno@12.0.1/src/types/invites/create_channel_invite.ts": "9ef8243ca3f89e3de3d2285e94a951fcee9783f72c45a2c46854b11217a24054",
"https://deno.land/x/discordeno@12.0.1/src/types/invites/get_invite.ts": "b9bc12f9ee9af506f291403b61f8aeddad8f79073b16dac4bfa7271500d39598",
"https://deno.land/x/discordeno@12.0.1/src/types/invites/invite.ts": "ce20d72d2dcf6321ade1b51500cc563eb6316bf57b12d2180cb57d180f992cf2",
"https://deno.land/x/discordeno@12.0.1/src/types/invites/invite_create.ts": "92857bbd6284eddb26bcc7ebc13b2092bc2a9b5c23aab516296699425a88e629",
"https://deno.land/x/discordeno@12.0.1/src/types/invites/invite_delete.ts": "e7d0a649622fdf44e81a01ed18c0742dd7929ed31ebd6b8845cc1fbc456dabfa",
"https://deno.land/x/discordeno@12.0.1/src/types/invites/invite_metadata.ts": "f8ae3244b1999f1f7d61f79314e85ac7b097d2fa23cbdfdc1f13bde12a9d394f",
"https://deno.land/x/discordeno@12.0.1/src/types/invites/invite_stage_instance.ts": "4cb5d0be6793fe6ea6be8dd12509625d514db06aad3df258531fc847ccbaf344",
"https://deno.land/x/discordeno@12.0.1/src/types/invites/invite_target_types.ts": "a9070da68cf943f580d3d870189fff9a28d6c62ff4b3fc1180a705c13447d8e7",
"https://deno.land/x/discordeno@12.0.1/src/types/invites/mod.ts": "b2e3427f9697f42bac218b21f0ee7536987ac1f0c758ba3467473f8ca3b3fcdc",
"https://deno.land/x/discordeno@12.0.1/src/types/invites/target_types.ts": "aa7f83c9a122a0a550aaf4f2171d4f42d3484075e3b0ec3883934e4db0941d65",
"https://deno.land/x/discordeno@12.0.1/src/types/members/guild_member.ts": "493c685559556b004e4322746b7606dff7c53d6767fa5c24d6c2651a9f7471e1",
"https://deno.land/x/discordeno@12.0.1/src/types/members/guild_member_add.ts": "a7942bca9ff7526b7ee11386933136e5ab32e19e725770f67064d48ce36e939f",
"https://deno.land/x/discordeno@12.0.1/src/types/members/guild_member_remove.ts": "00175234a47ff2c3c721ae262503842e8108acd58ad0f5af79fad236d63dd451",
"https://deno.land/x/discordeno@12.0.1/src/types/members/guild_member_update.ts": "ce45dd4cd6f8ef03ff7cfa17edc27fbd3a11776e976aab1e4c51231c0b339513",
"https://deno.land/x/discordeno@12.0.1/src/types/members/guild_members_chunk.ts": "531828ac31da39623f392972a4f4809a7dd953208156590d0e3b137ca0892b0e",
"https://deno.land/x/discordeno@12.0.1/src/types/members/list_guild_members.ts": "9f1abb6cb8dc70f85a3dec745a4f05c9ec644e25434233418bc6b8ce265f4546",
"https://deno.land/x/discordeno@12.0.1/src/types/members/mod.ts": "524dd0d6e9a10b373a985eabeefdf413445acd7adc88368cfe18734245a9931d",
"https://deno.land/x/discordeno@12.0.1/src/types/members/modify_current_user_nick.ts": "eb38b7b85a3b4da7ae551becb1679a030fcdbbc4caeb75a45dc2e761877ef22f",
"https://deno.land/x/discordeno@12.0.1/src/types/members/request_guild_members.ts": "085f3a9ef7da76338448f9d4304e294804a6ee8a5eeec36d77c91e19af6c0dd2",
"https://deno.land/x/discordeno@12.0.1/src/types/members/search_guild_members.ts": "a1ab4682962b4a4afdfe70fd60a74470cabdfbe1c3dba085664cd4a543dcaf2d",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/allowed_mentions.ts": "c505cd9fdadd0c603416b5b8bd9c4b6a250d67da3df15c3209fdd580024de1e6",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/allowed_mentions_types.ts": "1c17804d29947d9ae98c37ce7a2676ec06b8e360ded811d3a23e93e56d3a493f",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/attachment.ts": "916820d2f91c5cc4b8a8e4d8263d5712c6a12ff362e6db7d26b992064774f908",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/components/action_row.ts": "ba1d4c9a1bdee1bf23544d5d643081341c3144a5b505b3b668a2f58ed6f30ab0",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/components/button_component.ts": "c8baf276a704ed053443d1ab2b26fde4c926f5048999772c17345256dd671cf2",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/components/button_data.ts": "a1e4a5970a698c059ea295f98b0999de8b3d458d6f91bae371f333cefda4d1be",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/components/button_styles.ts": "18f6d3b59252f234e6d0e2170f5049cbd324fb8e1a53bafca89705bfd4068274",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/components/message_component_types.ts": "1aec772a66fe12c560c01122dcc000afa9f3742d1b3b8fa5d55e25fd8e7efad4",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/components/message_components.ts": "c60ae3a19be3918fab45f018b55d681e60626a6a2fa1d35a11b5df1ccce0c9f3",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/components/select_data.ts": "499b8584ef21f2454530c6494d8d78f6711d77b993bde52387561c232456fc70",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/components/select_menu.ts": "be9cd262916289a098fc2d0d7cc899c8f310b311149792480e0f0f35463b6404",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/components/select_option.ts": "5f94b74b32ae9d1bd43e9f8a963cfc21497643f4ed1b7553478d711ad30a9010",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/create_message.ts": "49d180f1dcc333c9a400d0e127f0f1cdffd91596f5d010458688a687731e0095",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/edit_message.ts": "5074649ac579092445e21d5c3985b6b62f8419ab602cc0dcbf15c493466f6451",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/get_messages.ts": "2b4568641b0dfad59cdb10344c885c719ca3bdefee4d005c369c477d07ef18f4",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message.ts": "205a1f9b4cba8cb140f5cd8ba6ba5a25716afbd7b0e3f760b2a68f71909f9b1b",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_activity.ts": "0b96b7ed761a083486a12f32c67428dfcc7c96ad1d37cb2499aa2126007a9cdd",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_activity_types.ts": "922960fdc6781246d850f34b0add70a72fffe4fb7a04a43171b54054548dbf06",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_delete.ts": "34fcd76b0a07a2346b46af61d61d97f3279c9a32036c6207cfc3ff83f2d58b85",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_delete_bulk.ts": "270109bfe8cf35176a5422ae7aac2cc900ae5cfd324bd561080ac47159be8ee2",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_flags.ts": "d7a7fa48eab2038bbd8c0018b5416a8e1dce26a19fd2bb2835e55264a1bb5b5e",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_get_reactions.ts": "6e04a198966cc949a353f137d88d62a738e99d9551401471ecdd127ad02867dc",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_reaction_add.ts": "12b57c3ab5c5308d6018eb418e977ed77e9230467c29765638d9fd999bed2c60",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_reaction_remove.ts": "c6c513f938cb8383b40fb805cbd944dcf08bc379d8f6661c38453ac450ebef27",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_reaction_remove_all.ts": "f56bdcf33ae2aca7e555b6fc113072641ecadd6b18ac6626644c01b6d5c0108e",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_reaction_remove_emoji.ts": "40221a12988a518f05f7239bcaef4ef4db38d9de01f4743b2c9914b4cfea10ed",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_reference.ts": "8a69a2dff5e9fbe225661f46c9b55154be4cba91fbc82e8098727dc87e2d0ed4",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_sticker.ts": "02b808c381e2ee4b4fb7678577017a9328b79c628cca03a166dbd86234b836e8",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_sticker_format_types.ts": "6666bb24bdc9a62e2a568b97697f2b82bb4fae11abb5b2b3a61ea20e8026963a",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_sticker_item.ts": "8e7316f65769f72a34059aa6fe4581f24e88f41b092f95c23c1c1521198ad8d4",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/message_types.ts": "1a234933822c666f6e87306afa91f9872ff6aabdb8b4ad576a730230f85cd593",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/mod.ts": "b125e93de534a1027e973216b299d00845a95f5e97488aaffe5c0aca5f6691cd",
"https://deno.land/x/discordeno@12.0.1/src/types/messages/reaction.ts": "ffd87364779c6da9ab38dc09b649c6da9d8bab165571e6710845ec34192fc3ac",
"https://deno.land/x/discordeno@12.0.1/src/types/misc/image_format.ts": "da094996f9d9af0bf46ac02e4b8d2c344cfbd0e048dd4e08e11ff984fcb308e1",
"https://deno.land/x/discordeno@12.0.1/src/types/misc/image_size.ts": "16f080d4f0054bdce8beeba9467519a93d59ffda1541f1644772d9bf64b27813",
"https://deno.land/x/discordeno@12.0.1/src/types/misc/mod.ts": "d7ad3f45fff9ad8db7c7240c9b0ac1dca557983e718a43e05a41a65e2667e31c",
"https://deno.land/x/discordeno@12.0.1/src/types/misc/typing_start.ts": "45a83766a4dc967b6fe7316963fdd4b0deff7bb64b3483e3ebe3838b79ca6e15",
"https://deno.land/x/discordeno@12.0.1/src/types/mod.ts": "be001875d2268dd04922dda282f1931c4cb2f4d57fe7e8a99467f411a400569c",
"https://deno.land/x/discordeno@12.0.1/src/types/oauth2/bot_auth_query.ts": "e34275d1f448368030c689b1a1a3091dacd4cca3fe6cce7b7122cf42024c98d1",
"https://deno.land/x/discordeno@12.0.1/src/types/oauth2/get_current_authorization_information.ts": "45835141b355314d717a81fbb3005f216e1c0bba85d4c1868d5bef01f37a12e6",
"https://deno.land/x/discordeno@12.0.1/src/types/oauth2/mod.ts": "572c24b2f90280a3ffb3eeb1902563ccb78f2c82d4c11b3a87348d86cf4e4c20",
"https://deno.land/x/discordeno@12.0.1/src/types/oauth2/scopes.ts": "657d9a843a1dfebb50ef4eb78af602410fae62cfe82511f8fb4e32e5cfca34e1",
"https://deno.land/x/discordeno@12.0.1/src/types/permissions/bitwise_permission_flags.ts": "cace984189ca2765774d33b62b3e891ee939c8807c84986ec1278fabd7c8e25b",
"https://deno.land/x/discordeno@12.0.1/src/types/permissions/mod.ts": "84759ae831e43d8c37a0ed7a47c463fb50ed0b33910ff9db4187a07adcc34648",
"https://deno.land/x/discordeno@12.0.1/src/types/permissions/permission_strings.ts": "e608ac09f427d78fbc2b91701e932d28688e2f93a8a2571068f2e3d11325681c",
"https://deno.land/x/discordeno@12.0.1/src/types/permissions/role.ts": "f927430b1d470bbb691cc996711bce666b562d776e454b63b4bd2f463b284b19",
"https://deno.land/x/discordeno@12.0.1/src/types/permissions/role_tags.ts": "76c6cdcdcadd0c8d957236d9d7b3f90abf5b53b5a0753afe1b35614c4a0f3abb",
"https://deno.land/x/discordeno@12.0.1/src/types/teams/mod.ts": "3fb43926589c0c330781195d8f7dd0d6f664931eafaf3933be4f7fe52d2aee3d",
"https://deno.land/x/discordeno@12.0.1/src/types/teams/team.ts": "8ff5d101459e2d809ee8bfa4cb16c85fb531c1959c03865b614694c111872342",
"https://deno.land/x/discordeno@12.0.1/src/types/teams/team_member.ts": "7a0475093edb4883e0cbc824de207e2ad915552d1ec09243674755572e91900d",
"https://deno.land/x/discordeno@12.0.1/src/types/teams/team_membership_states.ts": "e4dc48d9fdb525c1c8ca3f412f2b0458bd4a37aa4737336bc5c9ab99a07d8886",
"https://deno.land/x/discordeno@12.0.1/src/types/templates/create_guild_from_template.ts": "ae1f0d5f146a49cfba34679c1b5754767603951c64d43c7bbc8bb607660d1104",
"https://deno.land/x/discordeno@12.0.1/src/types/templates/mod.ts": "c8df6c5ccb7626fdb563866040b8049245a86a052f9e426dc03886650bf291a9",
"https://deno.land/x/discordeno@12.0.1/src/types/templates/modify_guild_template.ts": "8fdf4fdedd83112990b3c260d096a6c5f953c7b85fed7ebbdfb71e5d0f68e6de",
"https://deno.land/x/discordeno@12.0.1/src/types/templates/template.ts": "53474bf645309ce5fd1a4100a6a01fb7e2567620249f188606bc49667c81fe25",
"https://deno.land/x/discordeno@12.0.1/src/types/users/connection.ts": "498cba9fab0689b78017b1500f31b7fca899bab011141aab12705195167a37ad",
"https://deno.land/x/discordeno@12.0.1/src/types/users/create_dm.ts": "b4eb45c208985c13c7124f011d662a260c14a41f2ab5357213aedadd35b3f0fa",
"https://deno.land/x/discordeno@12.0.1/src/types/users/create_group_dm.ts": "3e37eabd020900e37b965f17ab768273cb48f4167833bbd2c2120a0ac874317e",
"https://deno.land/x/discordeno@12.0.1/src/types/users/mod.ts": "5803018de389174b4c94576862b8a121769934d53e55c3c3c7baca5fae939f9a",
"https://deno.land/x/discordeno@12.0.1/src/types/users/modify_current_user.ts": "2561dc9274960be675a3f85c55b864ebca92094edc169c974b0ebff4bdb9afc2",
"https://deno.land/x/discordeno@12.0.1/src/types/users/premium_types.ts": "a3532d25bb31b38c78e3d93ef695e0bab5d5f69ed5b281053b85103522fdba65",
"https://deno.land/x/discordeno@12.0.1/src/types/users/user.ts": "69212e83c0dd958e3bed597851a4466c028bd1074cf732b0d9f989fea93a0e17",
"https://deno.land/x/discordeno@12.0.1/src/types/users/user_flags.ts": "b38c1a86c6322f56d442a541148d05e0ccea284ec66698d29858f652d3d3e5a1",
"https://deno.land/x/discordeno@12.0.1/src/types/users/visibility_types.ts": "ef9bb46cf7cf2ce0adec7c2d91705775fb316f5e1a974a0a3e114a5cd65fa96b",
"https://deno.land/x/discordeno@12.0.1/src/types/util.ts": "2c0e6c61f97e728935ede97d6eb9f3fbe4cba1816747a864e73015a2ae30a5c3",
"https://deno.land/x/discordeno@12.0.1/src/types/voice/mod.ts": "8609e6f0b8d1ad036d9da55413ece8fb16ac5833e2276f84f1e7cf86fe5aa363",
"https://deno.land/x/discordeno@12.0.1/src/types/voice/update_voice_state.ts": "0b02d394084b064c7cb9f77757a6552d998f4893917c9a82fa6db830530bedce",
"https://deno.land/x/discordeno@12.0.1/src/types/voice/voice_region.ts": "d7c7d1215f4a5084bf62f30051a8a2700cd94fd690cf1648e9898c2457d92d6c",
"https://deno.land/x/discordeno@12.0.1/src/types/voice/voice_server_update.ts": "1cf51a829af01123e5d527d377734245dd3c0586a6ee4e7d493dc772c5c61415",
"https://deno.land/x/discordeno@12.0.1/src/types/voice/voice_state.ts": "19641e7e7d208eda81a44d12c44ed886f8836bc5cd27a6442b2d5bbccbaceae0",
"https://deno.land/x/discordeno@12.0.1/src/types/webhooks/create_webhook.ts": "3d417ac1b94f714468ae770e046a2a1898e3d03edcc3df2ce66aab03a93781ec",
"https://deno.land/x/discordeno@12.0.1/src/types/webhooks/discord_webhook_types.ts": "39ecbca5fc26146cd7fdb30f0ae76aa225df6d19eed294ba0f66dbaae40516ee",
"https://deno.land/x/discordeno@12.0.1/src/types/webhooks/edit_webhook_message.ts": "8e02812d2f0b858d96a3c1989f7dfa14752ff11a07add9b878a79385e2cfa8ba",
"https://deno.land/x/discordeno@12.0.1/src/types/webhooks/execute_webhook.ts": "86a96e649e0ef42548a9770a9daafc8f35ebc9537029f54978fb1ebd5d103100",
"https://deno.land/x/discordeno@12.0.1/src/types/webhooks/mod.ts": "88cbe7bf62e98bce36ca8ea9cc1b92f52a8b0f4f8c9ae3dd9e1ac4b27db2d270",
"https://deno.land/x/discordeno@12.0.1/src/types/webhooks/modify_webhook.ts": "750df9aa85922874eec8e8e4470885055798c262ecc1efcd9942292e56099f66",
"https://deno.land/x/discordeno@12.0.1/src/types/webhooks/webhook.ts": "1a6afa12056d68c959e8007dec58930a03ee281968b0cf484f600450b38f3e9c",
"https://deno.land/x/discordeno@12.0.1/src/types/webhooks/webhooks_update.ts": "5a55d12468c71de8d9977631d62840671469edfcf1f074e9e9662a16d53b5db2",
"https://deno.land/x/discordeno@12.0.1/src/util/bigint.ts": "0e5bae16f4e9c2cb579ed7fb4b93ef3b3f496b24730217f7862c47a890a6bfd9",
"https://deno.land/x/discordeno@12.0.1/src/util/cache_members.ts": "f9777e7b89269dd6c2e5e4f392da9cacc861617da148624b056df6df0a6c79dd",
"https://deno.land/x/discordeno@12.0.1/src/util/calculate_shard_id.ts": "d067462feb3b5689d621da80494cdc94cc97d61781d7ffa16f22abc44f6de3b0",
"https://deno.land/x/discordeno@12.0.1/src/util/collection.ts": "8836093375b54fe14291c68822ad9caa38477721eb7ba274adf787a5c07e4212",
"https://deno.land/x/discordeno@12.0.1/src/util/constants.ts": "b6af21db7f958fdc6f74e11ced5295a57683651068594c68232a6e78532fb151",
"https://deno.land/x/discordeno@12.0.1/src/util/deps.ts": "fbd56407f7b637905afa607108068f30179b0c5c9fa051f2b6f3d8db7c37cf93",
"https://deno.land/x/discordeno@12.0.1/src/util/dispatch_requirements.ts": "3620e6a477cc1c0afc765a8cd3d785680203fe6fbbdb92be0ab39791baee51b8",
"https://deno.land/x/discordeno@12.0.1/src/util/hash.ts": "16b43830850d0f388655c7a797812e61e5134ea7c334678d66eb35d43f373a19",
"https://deno.land/x/discordeno@12.0.1/src/util/loop_object.ts": "ba4812378503d215c07df1d6ff3a08d40ca86acb7a2bc48526452298bb8d6900",
"https://deno.land/x/discordeno@12.0.1/src/util/mod.ts": "0208f1161e21ac6a29032eb3458d564dbfefab7055902267a60f942064a46208",
"https://deno.land/x/discordeno@12.0.1/src/util/permissions.ts": "333c95b78fb941689fdec4c8b5ab4e4ca8b76243dee89a5796462e16a39868c2",
"https://deno.land/x/discordeno@12.0.1/src/util/transformers/channel_to_thread.ts": "ac6ae14343ae805a1982dccfee80946614882a37fbbfe3fffc5b098d173588d5",
"https://deno.land/x/discordeno@12.0.1/src/util/transformers/mod.ts": "d258185f899f4fdf2604f42150912b1319e2eff49bc899b6a19774f81cb0d34e",
"https://deno.land/x/discordeno@12.0.1/src/util/transformers/thread_member_modified.ts": "44e958ed826ed24c8d8adb978242a150e72cd22ef649502d60739758d1739204",
"https://deno.land/x/discordeno@12.0.1/src/util/transformers/thread_members_update_modified.ts": "de27753db80eee6f70e15fb2c1743d321f8a00a3c8894b2f174068d85b529663",
"https://deno.land/x/discordeno@12.0.1/src/util/utils.ts": "96b33045aa38c849276416b8397ae001fb1a206bc02e751e5b0aea7ea5c80f32",
"https://deno.land/x/discordeno@12.0.1/src/util/validate_length.ts": "7c610911d72082f9cfe2c455737cd37d8ce8f323483f0ef65fdfea6a993984b5",
"https://deno.land/x/discordeno@12.0.1/src/ws/close_ws.ts": "3870cde9dab402106dd50d11667041cccb9893a361c812a6e87b11ab3e058658",
"https://deno.land/x/discordeno@12.0.1/src/ws/create_shard.ts": "7d6ac3eb08c1c3241bf3d00e66718b1934444d87f31b2abfd91d68099bde0824",
"https://deno.land/x/discordeno@12.0.1/src/ws/deps.ts": "dde42348b5949fcadd6ff96450e3ad2702d3e98c14678d15857db72bc21584a4",
"https://deno.land/x/discordeno@12.0.1/src/ws/events.ts": "10a4b57f2a58dccfdcd6ece10bb88af5b72aeb809112aa75b2bce7806c8d7046",
"https://deno.land/x/discordeno@12.0.1/src/ws/handle_discord_payload.ts": "79c07721351cb9339313a152869d115614187552fcb9f0aca0b465ad4c963b94",
"https://deno.land/x/discordeno@12.0.1/src/ws/handle_on_message.ts": "fe33770d5dad1cf8e93ccb6e5c8aa46f8ed8483bccbd0cf1524a8a3af199689e",
"https://deno.land/x/discordeno@12.0.1/src/ws/heartbeat.ts": "2371362027532261f3dea99500bf85d48e0164115e73b593ef6d83818c7ee758",
"https://deno.land/x/discordeno@12.0.1/src/ws/identify.ts": "7c179f0fd8be1f6311e8713940567043a20668899e7cb54a7f150ea1b0cb2aaf",
"https://deno.land/x/discordeno@12.0.1/src/ws/mod.ts": "3f868f73c514d2bf2ff8908ccfec9d804ec4b8ebf248e985c4ae8a07f374cd37",
"https://deno.land/x/discordeno@12.0.1/src/ws/process_queue.ts": "16087e8b5bd9f15c4ec482f4aeb7725c924505e140156e22e45c34d25b5e962c",
"https://deno.land/x/discordeno@12.0.1/src/ws/resharder.ts": "d5c1f0aeae8609cdcc63e266accb1536b6858edb8b08096bb00ae36d52ac8676",
"https://deno.land/x/discordeno@12.0.1/src/ws/resume.ts": "665fd5d13644b60d4e9485cc1b6f371088ec671479a8dc9fc66066411c9f751f",
"https://deno.land/x/discordeno@12.0.1/src/ws/send_shard_message.ts": "ab48f1d03d6b485d2bab2c2bb03181d8dafd11924f2591a692b92d30f0510e2b",
"https://deno.land/x/discordeno@12.0.1/src/ws/spawn_shards.ts": "cad1fbe2155c6141c1a40db547397f413c9791d6531d657702c59b6ed4392dbd",
"https://deno.land/x/discordeno@12.0.1/src/ws/start_gateway.ts": "a8214b3abb5cbbf385cf0b5486aae1dd0b444842ce1c57b33dec634a2ac5ae85",
"https://deno.land/x/discordeno@12.0.1/src/ws/start_gateway_options.ts": "21a8eb91a408f7576cc17cacfc1746e4f668dfb61b8e4414eaf38ea04506605e",
"https://deno.land/x/discordeno@12.0.1/src/ws/tell_cluster_to_identify.ts": "fd495d6cd39b9051da7185b752d7975454b036a9522140b3a4f307b5cc261304",
"https://deno.land/x/discordeno@12.0.1/src/ws/ws.ts": "b059ceb6e9e41e9c7c3cf4758f0cf196be66c528806ffcdd2f9865bec5c5db7f",
"https://deno.land/x/god_crypto@v0.2.0/mod.ts": "45a249678300e1d3af935c05f622288605788d5b2e14fea989c9fc4cb1eefd22",
"https://deno.land/x/god_crypto@v0.2.0/rsa.ts": "b9be695ea1a859e0591b514ad3eeda4a926cec3445f1da3c8382ee5801b1d3ef",
"https://deno.land/x/god_crypto@v0.2.0/src/basic_encoding_rule.ts": "722203fdcd263920b9841a039c21a89ac09196ba77235bfb75d3fe4a1c790c98",
"https://deno.land/x/god_crypto@v0.2.0/src/eme_oaep.ts": "063983a123709b9db1fbb99e34919d70864269da106efabaece4cfab35aecfe0",
"https://deno.land/x/god_crypto@v0.2.0/src/helper.ts": "f6fb1fa7d4a84e64ce9cee195b38a004c57be1d8e8e175918877e34b041c6cf7",
"https://deno.land/x/god_crypto@v0.2.0/src/math.ts": "d1dfdc8f84a77a07139de0542d5d20279f99e3cd1afc092b22a8cd8bb54e3696",
"https://deno.land/x/god_crypto@v0.2.0/src/primitives.ts": "16609ae5c228fea7eea74a9832aa321ac3b079ab6cd44b0ea143e0019f377856",
"https://deno.land/x/god_crypto@v0.2.0/src/rsa.ts": "315707c7c4cc32bb2085a06df8f84f90f1433d1a555082dab7e7d4171122d3e4",
"https://deno.land/x/imagescript@1.3.0/ImageScript.js": "cf90773c966031edd781ed176c598f7ed495e7694cd9b86c986d2d97f783cca0",
"https://deno.land/x/imagescript@1.3.0/mod.ts": "18a6cb83c55e690c873505f6fe867364c678afb64934fe7aef593a6b92f79995",
"https://deno.land/x/imagescript@1.3.0/png/src/crc.mjs": "5cf50de181d61dd00e66a240d811018ba5070afa8bba302f393604404604de84",
"https://deno.land/x/imagescript@1.3.0/png/src/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
"https://deno.land/x/imagescript@1.3.0/png/src/png.mjs": "96ef0ceff1b5a6cd9304749e5f187b4ab238509fb5f9a8be8ee934240271ed8d",
"https://deno.land/x/imagescript@1.3.0/png/src/zlib.mjs": "9867dc3fab1d31b664f9344b0d7e977f493d9c912a76c760d012ed2b89f7061c",
"https://deno.land/x/imagescript@1.3.0/utils/buffer.js": "952cb1beb8827e50a493a5d1f29a4845e8c648789406d389dd51f51205ba02d8",
"https://deno.land/x/imagescript@1.3.0/utils/crc32.js": "573d6222b3605890714ebc374e687ec2aa3e9a949223ea199483e47ca4864f7d",
"https://deno.land/x/imagescript@1.3.0/utils/png.js": "fbed9117e0a70602645d70df9c103ff6e79c03e987bd5c1685dcb4200729b6de",
"https://deno.land/x/imagescript@1.3.0/utils/wasm/font.js": "9e75d842608c057045698d6a7cdf5ffd27241b5cdea0391c89a1917b31294524",
"https://deno.land/x/imagescript@1.3.0/utils/wasm/gif.js": "8b86f7b96486bb8ff50fbc7c7487f86cb5cef85e6acd71e1def78a1aa2f12e4f",
"https://deno.land/x/imagescript@1.3.0/utils/wasm/jpeg.js": "75295e2fcf96b4f7bb894b3844fdaa8140d63169d28b466b5d5be89d59a7b6e6",
"https://deno.land/x/imagescript@1.3.0/utils/wasm/png.js": "0659536a8dd8f892c8346e268b2754b4414fad0ec1e9794dfcde1ba1c804ee02",
"https://deno.land/x/imagescript@1.3.0/utils/wasm/svg.js": "f5c8a9d1977b51a7c07549ceb6bbbaca9497321a193f28b3dc229a42d91bcf14",
"https://deno.land/x/imagescript@1.3.0/utils/wasm/tiff.js": "c2d7bdaef094df25aae1752e75167f485e89275d76a1379e39d8949580b7af4f",
"https://deno.land/x/imagescript@1.3.0/utils/wasm/zlib.js": "749875f83abffe24d3b977475a0cbd5f9b52bee1fbdbef61ec183cbfc17805f6",
"https://deno.land/x/imagescript@1.3.0/v2/framebuffer.mjs": "add44ff184636659714b3c6d4b896f628545451abffbc30b5bcc2e8d9a73d012",
"https://deno.land/x/imagescript@1.3.0/v2/ops/blur.mjs": "80716f1ffab8a2aeb54a036f583bf51a2b9dd37e005adc000add803df8e8a12f",
"https://deno.land/x/imagescript@1.3.0/v2/ops/color.mjs": "5e72cdcbf97dc939a2795223f01e3cb0544c0c56b03ea2aa026050df58348814",
"https://deno.land/x/imagescript@1.3.0/v2/ops/crop.mjs": "69431fa6f687fd9f0c31eff0ec27d7ac925275005e53a37f0c3fab4cc4d9a9ea",
"https://deno.land/x/imagescript@1.3.0/v2/ops/fill.mjs": "cf1b9488314753fbc9ebf03410ac74c2a34ea5a69fb6892cd6e8366cd1930d93",
"https://deno.land/x/imagescript@1.3.0/v2/ops/flip.mjs": "825a34a66567dcf15e76a719f1bf2f66fb106503cd69942292b1b0ae05c5718e",
"https://deno.land/x/imagescript@1.3.0/v2/ops/index.mjs": "423ba687119be2bba8cec72890577d3afa3621b6b8108912242fe937a183f2aa",
"https://deno.land/x/imagescript@1.3.0/v2/ops/iterator.mjs": "c2adf3d90ce00719a02c48c97634574176a3501ff026676259bd71aa8f5d69b9",
"https://deno.land/x/imagescript@1.3.0/v2/ops/overlay.mjs": "7e6e2c2ffd25006d52597ab8babc5f8f503d388a3fdf2fbc0eaea02799a020c9",
"https://deno.land/x/imagescript@1.3.0/v2/ops/resize.mjs": "814e78ebce8eaf8f1f918688db7b52a141405e06a36ed4b25d04413d69e7d17b",
"https://deno.land/x/imagescript@1.3.0/v2/ops/rotate.mjs": "a1b65616717bd2eed8db406affea3263b4674dada46b56441ef38167a187455d",
"https://deno.land/x/imagescript@1.3.0/v2/util/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
"https://deno.land/x/imagescript@v1.2.13/ImageScript.js": "08d66a68ead9dd8a12f83a0161a3bcd82437b65addde67d0e0bb9d05f593af1e",
"https://deno.land/x/imagescript@v1.2.13/mod.ts": "18a6cb83c55e690c873505f6fe867364c678afb64934fe7aef593a6b92f79995",
"https://deno.land/x/imagescript@v1.2.13/png/src/crc.mjs": "5cf50de181d61dd00e66a240d811018ba5070afa8bba302f393604404604de84",
"https://deno.land/x/imagescript@v1.2.13/png/src/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
"https://deno.land/x/imagescript@v1.2.13/png/src/png.mjs": "96ef0ceff1b5a6cd9304749e5f187b4ab238509fb5f9a8be8ee934240271ed8d",
"https://deno.land/x/imagescript@v1.2.13/png/src/zlib.mjs": "9867dc3fab1d31b664f9344b0d7e977f493d9c912a76c760d012ed2b89f7061c",
"https://deno.land/x/imagescript@v1.2.13/utils/buffer.js": "952cb1beb8827e50a493a5d1f29a4845e8c648789406d389dd51f51205ba02d8",
"https://deno.land/x/imagescript@v1.2.13/utils/crc32.js": "573d6222b3605890714ebc374e687ec2aa3e9a949223ea199483e47ca4864f7d",
"https://deno.land/x/imagescript@v1.2.13/utils/png.js": "fbed9117e0a70602645d70df9c103ff6e79c03e987bd5c1685dcb4200729b6de",
"https://deno.land/x/imagescript@v1.2.13/utils/wasm/font.js": "9e75d842608c057045698d6a7cdf5ffd27241b5cdea0391c89a1917b31294524",
"https://deno.land/x/imagescript@v1.2.13/utils/wasm/gif.js": "8b86f7b96486bb8ff50fbc7c7487f86cb5cef85e6acd71e1def78a1aa2f12e4f",
"https://deno.land/x/imagescript@v1.2.13/utils/wasm/jpeg.js": "75295e2fcf96b4f7bb894b3844fdaa8140d63169d28b466b5d5be89d59a7b6e6",
"https://deno.land/x/imagescript@v1.2.13/utils/wasm/png.js": "0659536a8dd8f892c8346e268b2754b4414fad0ec1e9794dfcde1ba1c804ee02",
"https://deno.land/x/imagescript@v1.2.13/utils/wasm/svg.js": "f5c8a9d1977b51a7c07549ceb6bbbaca9497321a193f28b3dc229a42d91bcf14",
"https://deno.land/x/imagescript@v1.2.13/utils/wasm/zlib.js": "749875f83abffe24d3b977475a0cbd5f9b52bee1fbdbef61ec183cbfc17805f6",
"https://deno.land/x/imagescript@v1.2.13/v2/framebuffer.mjs": "add44ff184636659714b3c6d4b896f628545451abffbc30b5bcc2e8d9a73d012",
"https://deno.land/x/imagescript@v1.2.13/v2/ops/blur.mjs": "80716f1ffab8a2aeb54a036f583bf51a2b9dd37e005adc000add803df8e8a12f",
"https://deno.land/x/imagescript@v1.2.13/v2/ops/color.mjs": "5e72cdcbf97dc939a2795223f01e3cb0544c0c56b03ea2aa026050df58348814",
"https://deno.land/x/imagescript@v1.2.13/v2/ops/crop.mjs": "69431fa6f687fd9f0c31eff0ec27d7ac925275005e53a37f0c3fab4cc4d9a9ea",
"https://deno.land/x/imagescript@v1.2.13/v2/ops/fill.mjs": "cf1b9488314753fbc9ebf03410ac74c2a34ea5a69fb6892cd6e8366cd1930d93",
"https://deno.land/x/imagescript@v1.2.13/v2/ops/flip.mjs": "825a34a66567dcf15e76a719f1bf2f66fb106503cd69942292b1b0ae05c5718e",
"https://deno.land/x/imagescript@v1.2.13/v2/ops/index.mjs": "423ba687119be2bba8cec72890577d3afa3621b6b8108912242fe937a183f2aa",
"https://deno.land/x/imagescript@v1.2.13/v2/ops/iterator.mjs": "c2adf3d90ce00719a02c48c97634574176a3501ff026676259bd71aa8f5d69b9",
"https://deno.land/x/imagescript@v1.2.13/v2/ops/overlay.mjs": "7e6e2c2ffd25006d52597ab8babc5f8f503d388a3fdf2fbc0eaea02799a020c9",
"https://deno.land/x/imagescript@v1.2.13/v2/ops/resize.mjs": "814e78ebce8eaf8f1f918688db7b52a141405e06a36ed4b25d04413d69e7d17b",
"https://deno.land/x/imagescript@v1.2.13/v2/ops/rotate.mjs": "a1b65616717bd2eed8db406affea3263b4674dada46b56441ef38167a187455d",
"https://deno.land/x/imagescript@v1.2.13/v2/util/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
"https://deno.land/x/mysql@v2.10.2/deps.ts": "a8b61ff81c7e55cc045a38a5a39f597454291cfc81a3739127d857611c4ad9da",
"https://deno.land/x/mysql@v2.10.2/mod.ts": "c751574b2b41bb0926f0eb4f29c70aa9a435dc039a370e1fb238dc495fea2dcf",
"https://deno.land/x/mysql@v2.10.2/src/auth.ts": "d4b9a4db2368ffde77b24fda35e8a52baedb4081f88ffa4e212264accc88b2f2",
"https://deno.land/x/mysql@v2.10.2/src/auth_plugin/caching_sha2_password.ts": "adaf27da89c242ab351351e9320b631b565dad85daff9a6c299382a3ed4ed526",
"https://deno.land/x/mysql@v2.10.2/src/auth_plugin/crypt.ts": "419f5616dcf2554bd5f37259d148af74525504512604823aae26daba53e50a57",
"https://deno.land/x/mysql@v2.10.2/src/auth_plugin/index.ts": "8617e520ad854e38470aeefd07becdb3397c4cde16c2397dd48d5c10fdd5ab09",
"https://deno.land/x/mysql@v2.10.2/src/buffer.ts": "0b9fe1d8d2fbf390e1db2a5e691a7633fb38a727bdef02911e6dc92bf0ed398f",
"https://deno.land/x/mysql@v2.10.2/src/client.ts": "9a486419dfeb5f4d15d9fa56705e3cfbab6134bbbe08783a515e7e38f5cbca65",
"https://deno.land/x/mysql@v2.10.2/src/connection.ts": "4a7e2348eda63119dcee8f1636210144c3c6796c0906bc10c8db1db398c8ed9d",
"https://deno.land/x/mysql@v2.10.2/src/constant/capabilities.ts": "bf6b357b793da4d6e3f192a45d2368767902d7cb92affde2b393c3e08ed530f9",
"https://deno.land/x/mysql@v2.10.2/src/constant/charset.ts": "253d7233679c774df623d1f974ebb358f3678c18fd6a623e25983311d97d959b",
"https://deno.land/x/mysql@v2.10.2/src/constant/errors.ts": "923bab27d524e43199fa21fdfcbe025580ca76d8b32254ad9505765c502f238a",
"https://deno.land/x/mysql@v2.10.2/src/constant/mysql_types.ts": "79c50de8eb5919b897e81e2ff2366ee1ffdbb4297f711e15003bdb787bbc8e6c",
"https://deno.land/x/mysql@v2.10.2/src/constant/packet.ts": "a1e7e00ce30c551c5f95c05d233b8d83f8e1fc865de97be3b317058e173630a9",
"https://deno.land/x/mysql@v2.10.2/src/deferred.ts": "35d087619d919961e849e382c33b2bfea15b4119f55eca2d9c9047f30512a2cb",
"https://deno.land/x/mysql@v2.10.2/src/logger.ts": "9fe85e361d3972f3105e33930dd4a069456c625b5b0cd7efc322418964edc470",
"https://deno.land/x/mysql@v2.10.2/src/packets/builders/auth.ts": "d9752c7e95aae3f3ace81df03c19907ed8a9dfe9c19399796e80a24cb83ab2ed",
"https://deno.land/x/mysql@v2.10.2/src/packets/builders/query.ts": "caf426a72ebe545ff5bab14c8b7b5e412dd8827c091322959cdf4e9aa89ef900",
"https://deno.land/x/mysql@v2.10.2/src/packets/packet.ts": "d7800cc142226f7dfd3c5f647f03cd3ef308f9d8551b4edb2e1bfb9c758d33b6",
"https://deno.land/x/mysql@v2.10.2/src/packets/parsers/err.ts": "4110c4ddc2ae8358d6661fa2522f8eda2e603900d1e433e3684765ed50e88ed8",
"https://deno.land/x/mysql@v2.10.2/src/packets/parsers/handshake.ts": "88f7ee34e9e0ef089bc5fdefacaccf256ef002b2f7a8ad684e35327682039e73",
"https://deno.land/x/mysql@v2.10.2/src/packets/parsers/result.ts": "8ab16f1adae67415eefcc17803b0eb828c1f4c6a24c55f25949f418e862d3ec8",
"https://deno.land/x/mysql@v2.10.2/src/pool.ts": "53d094f574d4685f6d884ab6f2680ba1704d69e0f37700bd976fb2cf0b4d59a6",
"https://deno.land/x/mysql@v2.10.2/src/util.ts": "83d38e87cc3901da00ac44bfcd53c0e8d24525262f5c7647c912dccf3ed2dbb5",
"https://deno.land/x/mysql@v2.10.3/deps.ts": "a8b61ff81c7e55cc045a38a5a39f597454291cfc81a3739127d857611c4ad9da",
"https://deno.land/x/mysql@v2.10.3/mod.ts": "c751574b2b41bb0926f0eb4f29c70aa9a435dc039a370e1fb238dc495fea2dcf",
"https://deno.land/x/mysql@v2.10.3/src/auth.ts": "d4b9a4db2368ffde77b24fda35e8a52baedb4081f88ffa4e212264accc88b2f2",
"https://deno.land/x/mysql@v2.10.3/src/auth_plugin/caching_sha2_password.ts": "3b5cc23222f733093cfdfdf3878f5488769a54ec1df849a1800a63c48bd9b72f",
"https://deno.land/x/mysql@v2.10.3/src/auth_plugin/crypt.ts": "6c96e8b2ff19c1b035f3a706013fd559ed6bf2558bf9f220872279d4aa1d4c45",
"https://deno.land/x/mysql@v2.10.3/src/auth_plugin/index.ts": "8617e520ad854e38470aeefd07becdb3397c4cde16c2397dd48d5c10fdd5ab09",
"https://deno.land/x/mysql@v2.10.3/src/buffer.ts": "0b9fe1d8d2fbf390e1db2a5e691a7633fb38a727bdef02911e6dc92bf0ed398f",
"https://deno.land/x/mysql@v2.10.3/src/client.ts": "9a486419dfeb5f4d15d9fa56705e3cfbab6134bbbe08783a515e7e38f5cbca65",
"https://deno.land/x/mysql@v2.10.3/src/connection.ts": "86f0e2f3f4c34f64d0f827fe285eb86786dbf1e8a764b9c61c1ea9378d678a47",
"https://deno.land/x/mysql@v2.10.3/src/constant/capabilities.ts": "bf6b357b793da4d6e3f192a45d2368767902d7cb92affde2b393c3e08ed530f9",
"https://deno.land/x/mysql@v2.10.3/src/constant/charset.ts": "253d7233679c774df623d1f974ebb358f3678c18fd6a623e25983311d97d959b",
"https://deno.land/x/mysql@v2.10.3/src/constant/errors.ts": "923bab27d524e43199fa21fdfcbe025580ca76d8b32254ad9505765c502f238a",
"https://deno.land/x/mysql@v2.10.3/src/constant/mysql_types.ts": "79c50de8eb5919b897e81e2ff2366ee1ffdbb4297f711e15003bdb787bbc8e6c",
"https://deno.land/x/mysql@v2.10.3/src/constant/packet.ts": "a1e7e00ce30c551c5f95c05d233b8d83f8e1fc865de97be3b317058e173630a9",
"https://deno.land/x/mysql@v2.10.3/src/deferred.ts": "35d087619d919961e849e382c33b2bfea15b4119f55eca2d9c9047f30512a2cb",
"https://deno.land/x/mysql@v2.10.3/src/logger.ts": "9fe85e361d3972f3105e33930dd4a069456c625b5b0cd7efc322418964edc470",
"https://deno.land/x/mysql@v2.10.3/src/packets/builders/auth.ts": "d9752c7e95aae3f3ace81df03c19907ed8a9dfe9c19399796e80a24cb83ab2ed",
"https://deno.land/x/mysql@v2.10.3/src/packets/builders/query.ts": "caf426a72ebe545ff5bab14c8b7b5e412dd8827c091322959cdf4e9aa89ef900",
"https://deno.land/x/mysql@v2.10.3/src/packets/packet.ts": "d7800cc142226f7dfd3c5f647f03cd3ef308f9d8551b4edb2e1bfb9c758d33b6",
"https://deno.land/x/mysql@v2.10.3/src/packets/parsers/err.ts": "4110c4ddc2ae8358d6661fa2522f8eda2e603900d1e433e3684765ed50e88ed8",
"https://deno.land/x/mysql@v2.10.3/src/packets/parsers/handshake.ts": "88f7ee34e9e0ef089bc5fdefacaccf256ef002b2f7a8ad684e35327682039e73",
"https://deno.land/x/mysql@v2.10.3/src/packets/parsers/result.ts": "8ab16f1adae67415eefcc17803b0eb828c1f4c6a24c55f25949f418e862d3ec8",
"https://deno.land/x/mysql@v2.10.3/src/pool.ts": "53d094f574d4685f6d884ab6f2680ba1704d69e0f37700bd976fb2cf0b4d59a6",
"https://deno.land/x/mysql@v2.10.3/src/util.ts": "83d38e87cc3901da00ac44bfcd53c0e8d24525262f5c7647c912dccf3ed2dbb5",
"https://deno.land/x/mysql@v2.11.0/deps.ts": "68635959a41bb08bc87db007679fb8449febc55d48202dff20b93cc23ef5820d",
"https://deno.land/x/mysql@v2.11.0/mod.ts": "c751574b2b41bb0926f0eb4f29c70aa9a435dc039a370e1fb238dc495fea2dcf",
"https://deno.land/x/mysql@v2.11.0/src/auth.ts": "129ea08b180d3e90e567c3f71e60432bb266304c224e17ea39d604bbcc1160d8",
"https://deno.land/x/mysql@v2.11.0/src/auth_plugin/caching_sha2_password.ts": "aab89e272382e6f408406f860ae6e79628275f4511e27a565049033543c4bdec",
"https://deno.land/x/mysql@v2.11.0/src/auth_plugin/crypt.ts": "8798819cce1171d95cfee8edda15fe6a652068cad4dc91f81b6e91cf90a13617",
"https://deno.land/x/mysql@v2.11.0/src/auth_plugin/index.ts": "8617e520ad854e38470aeefd07becdb3397c4cde16c2397dd48d5c10fdd5ab09",
"https://deno.land/x/mysql@v2.11.0/src/buffer.ts": "59f7e08e196f1b7e58cf5c3cf8ae8f4d0d47d1ae31430076fc468d974d3b59e7",
"https://deno.land/x/mysql@v2.11.0/src/client.ts": "9a486419dfeb5f4d15d9fa56705e3cfbab6134bbbe08783a515e7e38f5cbca65",
"https://deno.land/x/mysql@v2.11.0/src/connection.ts": "0ca035bbba2865f93900d8817f9d3a080e4c01bca3a45dc8318276a14b1d9459",
"https://deno.land/x/mysql@v2.11.0/src/constant/capabilities.ts": "bf6b357b793da4d6e3f192a45d2368767902d7cb92affde2b393c3e08ed530f9",
"https://deno.land/x/mysql@v2.11.0/src/constant/charset.ts": "253d7233679c774df623d1f974ebb358f3678c18fd6a623e25983311d97d959b",
"https://deno.land/x/mysql@v2.11.0/src/constant/errors.ts": "923bab27d524e43199fa21fdfcbe025580ca76d8b32254ad9505765c502f238a",
"https://deno.land/x/mysql@v2.11.0/src/constant/mysql_types.ts": "79c50de8eb5919b897e81e2ff2366ee1ffdbb4297f711e15003bdb787bbc8e6c",
"https://deno.land/x/mysql@v2.11.0/src/constant/packet.ts": "a1e7e00ce30c551c5f95c05d233b8d83f8e1fc865de97be3b317058e173630a9",
"https://deno.land/x/mysql@v2.11.0/src/deferred.ts": "35d087619d919961e849e382c33b2bfea15b4119f55eca2d9c9047f30512a2cb",
"https://deno.land/x/mysql@v2.11.0/src/logger.ts": "9fe85e361d3972f3105e33930dd4a069456c625b5b0cd7efc322418964edc470",
"https://deno.land/x/mysql@v2.11.0/src/packets/builders/auth.ts": "d9752c7e95aae3f3ace81df03c19907ed8a9dfe9c19399796e80a24cb83ab2ed",
"https://deno.land/x/mysql@v2.11.0/src/packets/builders/query.ts": "caf426a72ebe545ff5bab14c8b7b5e412dd8827c091322959cdf4e9aa89ef900",
"https://deno.land/x/mysql@v2.11.0/src/packets/packet.ts": "d7800cc142226f7dfd3c5f647f03cd3ef308f9d8551b4edb2e1bfb9c758d33b6",
"https://deno.land/x/mysql@v2.11.0/src/packets/parsers/authswitch.ts": "aa34f21336c4907b3ae968108fcdad8f1c43a303088efd83d972e6c7b258c166",
"https://deno.land/x/mysql@v2.11.0/src/packets/parsers/err.ts": "4110c4ddc2ae8358d6661fa2522f8eda2e603900d1e433e3684765ed50e88ed8",
"https://deno.land/x/mysql@v2.11.0/src/packets/parsers/handshake.ts": "88f7ee34e9e0ef089bc5fdefacaccf256ef002b2f7a8ad684e35327682039e73",
"https://deno.land/x/mysql@v2.11.0/src/packets/parsers/result.ts": "8ab16f1adae67415eefcc17803b0eb828c1f4c6a24c55f25949f418e862d3ec8",
"https://deno.land/x/mysql@v2.11.0/src/pool.ts": "978ba2813b3886d68be007678360ad43c54dab14b1aea1c07fcdb41222fcc432",
"https://deno.land/x/mysql@v2.11.0/src/util.ts": "83d38e87cc3901da00ac44bfcd53c0e8d24525262f5c7647c912dccf3ed2dbb5",
"https://deno.land/x/mysql@v2.12.0/deps.ts": "68635959a41bb08bc87db007679fb8449febc55d48202dff20b93cc23ef5820d",
"https://deno.land/x/mysql@v2.12.0/mod.ts": "3246c9c259434563be69cc95d5b792f8aac7ef5d10b8a6c6589aa54ebf1bd266",
"https://deno.land/x/mysql@v2.12.0/src/auth.ts": "129ea08b180d3e90e567c3f71e60432bb266304c224e17ea39d604bbcc1160d8",
"https://deno.land/x/mysql@v2.12.0/src/auth_plugin/caching_sha2_password.ts": "aab89e272382e6f408406f860ae6e79628275f4511e27a565049033543c4bdec",
"https://deno.land/x/mysql@v2.12.0/src/auth_plugin/crypt.ts": "8798819cce1171d95cfee8edda15fe6a652068cad4dc91f81b6e91cf90a13617",
"https://deno.land/x/mysql@v2.12.0/src/auth_plugin/index.ts": "8617e520ad854e38470aeefd07becdb3397c4cde16c2397dd48d5c10fdd5ab09",
"https://deno.land/x/mysql@v2.12.0/src/buffer.ts": "59f7e08e196f1b7e58cf5c3cf8ae8f4d0d47d1ae31430076fc468d974d3b59e7",
"https://deno.land/x/mysql@v2.12.0/src/client.ts": "982200909f18f5fdc275a66988630ec46cdf15126379485aa050d0bd83492009",
"https://deno.land/x/mysql@v2.12.0/src/connection.ts": "482f75a6ea5536e34653e200afbf86023367f7fc11174cca22d151693f45fa0c",
"https://deno.land/x/mysql@v2.12.0/src/constant/capabilities.ts": "2324c0e46ac43f59b7b03bdd878d7a14ecc5202b9e133c7e8769345a8290f2a1",
"https://deno.land/x/mysql@v2.12.0/src/constant/charset.ts": "253d7233679c774df623d1f974ebb358f3678c18fd6a623e25983311d97d959b",
"https://deno.land/x/mysql@v2.12.0/src/constant/errors.ts": "923bab27d524e43199fa21fdfcbe025580ca76d8b32254ad9505765c502f238a",
"https://deno.land/x/mysql@v2.12.0/src/constant/mysql_types.ts": "79c50de8eb5919b897e81e2ff2366ee1ffdbb4297f711e15003bdb787bbc8e6c",
"https://deno.land/x/mysql@v2.12.0/src/constant/packet.ts": "a1e7e00ce30c551c5f95c05d233b8d83f8e1fc865de97be3b317058e173630a9",
"https://deno.land/x/mysql@v2.12.0/src/deferred.ts": "35d087619d919961e849e382c33b2bfea15b4119f55eca2d9c9047f30512a2cb",
"https://deno.land/x/mysql@v2.12.0/src/logger.ts": "eb5feb3efdb9fd4887f6eccd5c06b5702591ac032af9857a12bbae86ceefe21b",
"https://deno.land/x/mysql@v2.12.0/src/packets/builders/auth.ts": "0b53dd5fa0269427aa54c3f6909bd830ffb426009061df89df262c504d6c9b70",
"https://deno.land/x/mysql@v2.12.0/src/packets/builders/client_capabilities.ts": "1000f2c1a20e0e119b9a416eb4ea4553cc1c5655d289a66e9077bf7a5993d52d",
"https://deno.land/x/mysql@v2.12.0/src/packets/builders/query.ts": "caf426a72ebe545ff5bab14c8b7b5e412dd8827c091322959cdf4e9aa89ef900",
"https://deno.land/x/mysql@v2.12.0/src/packets/builders/tls.ts": "2abb4a2fa74c47914372b221cb6f178f6015df54421daf0e10e54d80d7156498",
"https://deno.land/x/mysql@v2.12.0/src/packets/packet.ts": "d7800cc142226f7dfd3c5f647f03cd3ef308f9d8551b4edb2e1bfb9c758d33b6",
"https://deno.land/x/mysql@v2.12.0/src/packets/parsers/authswitch.ts": "aa34f21336c4907b3ae968108fcdad8f1c43a303088efd83d972e6c7b258c166",
"https://deno.land/x/mysql@v2.12.0/src/packets/parsers/err.ts": "4110c4ddc2ae8358d6661fa2522f8eda2e603900d1e433e3684765ed50e88ed8",
"https://deno.land/x/mysql@v2.12.0/src/packets/parsers/handshake.ts": "88f7ee34e9e0ef089bc5fdefacaccf256ef002b2f7a8ad684e35327682039e73",
"https://deno.land/x/mysql@v2.12.0/src/packets/parsers/result.ts": "8ab16f1adae67415eefcc17803b0eb828c1f4c6a24c55f25949f418e862d3ec8",
"https://deno.land/x/mysql@v2.12.0/src/pool.ts": "978ba2813b3886d68be007678360ad43c54dab14b1aea1c07fcdb41222fcc432",
"https://deno.land/x/mysql@v2.12.0/src/util.ts": "83d38e87cc3901da00ac44bfcd53c0e8d24525262f5c7647c912dccf3ed2dbb5",
"https://deno.land/x/mysql@v2.12.1/deps.ts": "68635959a41bb08bc87db007679fb8449febc55d48202dff20b93cc23ef5820d",
"https://deno.land/x/mysql@v2.12.1/mod.ts": "3246c9c259434563be69cc95d5b792f8aac7ef5d10b8a6c6589aa54ebf1bd266",
"https://deno.land/x/mysql@v2.12.1/src/auth.ts": "129ea08b180d3e90e567c3f71e60432bb266304c224e17ea39d604bbcc1160d8",
"https://deno.land/x/mysql@v2.12.1/src/auth_plugin/caching_sha2_password.ts": "aab89e272382e6f408406f860ae6e79628275f4511e27a565049033543c4bdec",
"https://deno.land/x/mysql@v2.12.1/src/auth_plugin/crypt.ts": "8798819cce1171d95cfee8edda15fe6a652068cad4dc91f81b6e91cf90a13617",
"https://deno.land/x/mysql@v2.12.1/src/auth_plugin/index.ts": "8617e520ad854e38470aeefd07becdb3397c4cde16c2397dd48d5c10fdd5ab09",
"https://deno.land/x/mysql@v2.12.1/src/buffer.ts": "59f7e08e196f1b7e58cf5c3cf8ae8f4d0d47d1ae31430076fc468d974d3b59e7",
"https://deno.land/x/mysql@v2.12.1/src/client.ts": "30912964986667a2ce108c14f7153dd38e8089e55f8068e8d07697f75f2ac22f",
"https://deno.land/x/mysql@v2.12.1/src/connection.ts": "1d104c05441f8c94ee73123497fbbae28499f3badb0d9fef8cc82540688ada6e",
"https://deno.land/x/mysql@v2.12.1/src/constant/capabilities.ts": "2324c0e46ac43f59b7b03bdd878d7a14ecc5202b9e133c7e8769345a8290f2a1",
"https://deno.land/x/mysql@v2.12.1/src/constant/charset.ts": "253d7233679c774df623d1f974ebb358f3678c18fd6a623e25983311d97d959b",
"https://deno.land/x/mysql@v2.12.1/src/constant/errors.ts": "923bab27d524e43199fa21fdfcbe025580ca76d8b32254ad9505765c502f238a",
"https://deno.land/x/mysql@v2.12.1/src/constant/mysql_types.ts": "79c50de8eb5919b897e81e2ff2366ee1ffdbb4297f711e15003bdb787bbc8e6c",
"https://deno.land/x/mysql@v2.12.1/src/constant/packet.ts": "a1e7e00ce30c551c5f95c05d233b8d83f8e1fc865de97be3b317058e173630a9",
"https://deno.land/x/mysql@v2.12.1/src/deferred.ts": "35d087619d919961e849e382c33b2bfea15b4119f55eca2d9c9047f30512a2cb",
"https://deno.land/x/mysql@v2.12.1/src/logger.ts": "eb5feb3efdb9fd4887f6eccd5c06b5702591ac032af9857a12bbae86ceefe21b",
"https://deno.land/x/mysql@v2.12.1/src/packets/builders/auth.ts": "0b53dd5fa0269427aa54c3f6909bd830ffb426009061df89df262c504d6c9b70",
"https://deno.land/x/mysql@v2.12.1/src/packets/builders/client_capabilities.ts": "1000f2c1a20e0e119b9a416eb4ea4553cc1c5655d289a66e9077bf7a5993d52d",
"https://deno.land/x/mysql@v2.12.1/src/packets/builders/query.ts": "caf426a72ebe545ff5bab14c8b7b5e412dd8827c091322959cdf4e9aa89ef900",
"https://deno.land/x/mysql@v2.12.1/src/packets/builders/tls.ts": "2abb4a2fa74c47914372b221cb6f178f6015df54421daf0e10e54d80d7156498",
"https://deno.land/x/mysql@v2.12.1/src/packets/packet.ts": "d7800cc142226f7dfd3c5f647f03cd3ef308f9d8551b4edb2e1bfb9c758d33b6",
"https://deno.land/x/mysql@v2.12.1/src/packets/parsers/authswitch.ts": "aa34f21336c4907b3ae968108fcdad8f1c43a303088efd83d972e6c7b258c166",
"https://deno.land/x/mysql@v2.12.1/src/packets/parsers/err.ts": "4110c4ddc2ae8358d6661fa2522f8eda2e603900d1e433e3684765ed50e88ed8",
"https://deno.land/x/mysql@v2.12.1/src/packets/parsers/handshake.ts": "88f7ee34e9e0ef089bc5fdefacaccf256ef002b2f7a8ad684e35327682039e73",
"https://deno.land/x/mysql@v2.12.1/src/packets/parsers/result.ts": "8ab16f1adae67415eefcc17803b0eb828c1f4c6a24c55f25949f418e862d3ec8",
"https://deno.land/x/mysql@v2.12.1/src/pool.ts": "978ba2813b3886d68be007678360ad43c54dab14b1aea1c07fcdb41222fcc432",
"https://deno.land/x/mysql@v2.12.1/src/util.ts": "83d38e87cc3901da00ac44bfcd53c0e8d24525262f5c7647c912dccf3ed2dbb5",
"https://deno.land/x/nanoid@v3.0.0/customAlphabet.ts": "1cfd7cfd2f07ca8d78a7e7855fcc9f59abf01ef2a127484ef94328fadf940ead",
"https://deno.land/x/nanoid@v3.0.0/customRandom.ts": "af56e19038c891a4b4ef2be931554c27579bd407ee5bbea5cb64f6ee1347cbe3",
"https://deno.land/x/nanoid@v3.0.0/mod.ts": "3ead610e40c58d8fdca21d5da9ec661445a2b82526e19c34d05de5f90be8a1be",
"https://deno.land/x/nanoid@v3.0.0/nanoid.ts": "8d119bc89a0f34e7bbe0c2dbdc280d01753e431af553d189663492310a31085d",
"https://deno.land/x/nanoid@v3.0.0/random.ts": "4da71d5f72f2bfcc6a4ee79b5d4e72f48dcf4fe4c3835fd5ebab08b9f33cd598",
"https://deno.land/x/nanoid@v3.0.0/urlAlphabet.ts": "8b1511deb1ecb23c66202b6000dc10fb68f9a96b5550c6c8cef5009324793431",
"https://deno.land/x/sql_builder@v1.9.1/util.ts": "b9855dc435972704cf82655019f4ec168ac83550ab4db596c5f6b6d201466384",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V1.1.1/deps.ts": "4932522dd8d38cc322df6508d4f2e55e5fb0ec15e54fcdc81e2bf10051021608",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V1.1.1/mod.ts": "d9c38a41a405cf5732c9233c2391a1d7f5a12d0e464aace6f8f596fabf5f21ba",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V1.1.1/src/logger.ts": "f6ba6f7fe254fc3227a3ad48fd7c2c3aaaec8c350f0246fb3eeff075c21dc7e5",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.0.0/deps.ts": "9a1b2d559fc8c33ae1aeed899aa821f53f9d094e9df40bd4b51b099c58961cd7",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.0.0/mod.ts": "d9c38a41a405cf5732c9233c2391a1d7f5a12d0e464aace6f8f596fabf5f21ba",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.0.0/src/logger.ts": "a1924f1f02b35a7501161349de90b60a3aa329e12f1033fdb212b598542897c4",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.0/deps.ts": "3ab026026d146ca5e7160b16146d5665e45487a62749a7970f8e00c0c934874d",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.0/mod.ts": "d9c38a41a405cf5732c9233c2391a1d7f5a12d0e464aace6f8f596fabf5f21ba",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.0/src/logger.ts": "78072b8257a25b4e6adc03d5b92d64ef68b215159a732832fe6020bdebce2ec7",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.1/deps.ts": "3ab026026d146ca5e7160b16146d5665e45487a62749a7970f8e00c0c934874d",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.1/mod.ts": "d9c38a41a405cf5732c9233c2391a1d7f5a12d0e464aace6f8f596fabf5f21ba",
"https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.1/src/logger.ts": "b3a39724d58102dfbcdcd640a829cbfe1f083065060f68003f9c8fd49fdd658a",
"https://unpkg.com/@evan/wasm@0.0.65/target/zlib/deno.js": "36cd3f1edd2f3a6d6fd4c2376f701c2748338c132703810d4866cfa52b5e7bf9"
},
"workspace": {
"dependencies": [
"jsr:@std/http@1.0.15"
]
}
}

26
deps.ts Normal file
View File

@ -0,0 +1,26 @@
// All external dependancies are to be loaded here to make updating dependancy versions much easier
export {
botId,
cache,
cacheHandlers,
DiscordActivityTypes,
editBotNickname,
editBotStatus,
hasGuildPermissions,
Intents,
sendDirectMessage,
sendMessage,
startBot,
} from 'https://deno.land/x/discordeno@12.0.1/mod.ts';
export type { CreateMessage, DiscordenoGuild, DiscordenoMessage, EmbedField } from 'https://deno.land/x/discordeno@12.0.1/mod.ts';
export { Client } from 'https://deno.land/x/mysql@v2.10.2/mod.ts';
export { Status, STATUS_TEXT } from 'https://deno.land/std@0.145.0/http/http_status.ts';
export { nanoid } from 'https://deno.land/x/nanoid@v3.0.0/mod.ts';
export { initLog, log, LogTypes as LT } from 'https://raw.githubusercontent.com/Burn-E99/Log4Deno/V1.1.1/mod.ts';
export * as is from 'https://deno.land/x/imagescript@v1.2.13/mod.ts';

View File

@ -1,6 +1,6 @@
// DEVMODE is to prevent users from accessing parts of the bot that are currently broken // DEVMODE is to prevent users from accessing parts of the bot that are currently broken
export const DEVMODE = false; export const DEVMODE = false;
// DEBUG is used to toggle the cmdPrompt and show debug log messages // DEBUG is used to toggle the cmdPrompt
export const DEBUG = false; export const DEBUG = false;
// LOCALMODE is used to run a different bot token for local testing // LOCALMODE is used to run a differnt bot token for local testing
export const LOCALMODE = false; export const LOCALMODE = false;

307
mod.ts
View File

@ -3,35 +3,302 @@
* *
* December 21, 2020 * December 21, 2020
*/ */
import { Intents, startBot } from '@discordeno';
import { initLog } from '@Log4Deno';
import config from '~config'; import config from './config.ts';
import { DEBUG, LOCALMODE } from '~flags'; import { DEBUG, DEVMODE, LOCALMODE } from './flags.ts';
import {
import api from 'src/api.ts'; // Discordeno deps
import eventHandlers from 'src/events.ts'; botId,
cache,
// Extend the BigInt prototype to support JSON.stringify DiscordActivityTypes,
interface BigIntX extends BigInt { DiscordenoGuild,
// Convert to BigInt to string form in JSON.stringify DiscordenoMessage,
toJSON: () => string; editBotNickname,
} editBotStatus,
(BigInt.prototype as BigIntX).toJSON = function () { initLog,
return this.toString(); Intents,
}; // Log4Deno deps
log,
LT,
// Discordeno deps
sendMessage,
startBot,
} from './deps.ts';
import api from './src/api.ts';
import { dbClient } from './src/db.ts';
import commands from './src/commands/_index.ts';
import intervals from './src/intervals.ts';
import { successColor, warnColor } from './src/commandUtils.ts';
import utils from './src/utils.ts';
// Initialize logging client with folder to use for logs, needs --allow-write set on Deno startup // Initialize logging client with folder to use for logs, needs --allow-write set on Deno startup
initLog('logs', DEBUG); initLog('logs', DEBUG);
// Start up the Discord Bot // Start up the Discord Bot
startBot({ startBot({
token: LOCALMODE ? config.localtoken : config.token, token: LOCALMODE ? config.localtoken : config.token,
intents: [Intents.GuildMessages, Intents.DirectMessages, Intents.Guilds], intents: [Intents.GuildMessages, Intents.DirectMessages, Intents.Guilds],
eventHandlers, eventHandlers: {
ready: () => {
log(LT.INFO, `${config.name} Logged in!`);
editBotStatus({
activities: [{
name: 'Booting up . . .',
type: DiscordActivityTypes.Game,
createdAt: new Date().getTime(),
}],
status: 'online',
});
// Interval to rotate the status text every 30 seconds to show off more commands
setInterval(async () => {
log(LT.LOG, 'Changing bot status');
try {
// Wrapped in try-catch due to hard crash possible
editBotStatus({
activities: [{
name: await intervals.getRandomStatus(),
type: DiscordActivityTypes.Game,
createdAt: new Date().getTime(),
}],
status: 'online',
});
} catch (e) {
log(LT.ERROR, `Failed to update status: ${JSON.stringify(e)}`);
}
}, 30000);
// Interval to update bot list stats every 24 hours
LOCALMODE ? log(LT.INFO, 'updateListStatistics not running') : setInterval(() => {
log(LT.LOG, 'Updating all bot lists statistics');
intervals.updateListStatistics(botId, cache.guilds.size);
}, 86400000);
// Interval to update hourlyRates every hour
setInterval(() => {
log(LT.LOG, 'Updating all command hourlyRates');
intervals.updateHourlyRates();
}, 3600000);
// Interval to update heatmap.png every hour
setInterval(() => {
log(LT.LOG, 'Updating heatmap.png');
intervals.updateHeatmapPng();
}, 3600000);
// setTimeout added to make sure the startup message does not error out
setTimeout(() => {
LOCALMODE && editBotNickname(config.devServer, `LOCAL - ${config.name}`);
LOCALMODE ? log(LT.INFO, 'updateListStatistics not running') : intervals.updateListStatistics(botId, cache.guilds.size);
intervals.updateHourlyRates();
intervals.updateHeatmapPng();
editBotStatus({
activities: [{
name: 'Booting Complete',
type: DiscordActivityTypes.Game,
createdAt: new Date().getTime(),
}],
status: 'online',
});
sendMessage(config.logChannel, {
embeds: [{
title: `${config.name} is now Online`,
color: successColor,
fields: [
{
name: 'Version:',
value: `${config.version}`,
inline: true,
},
],
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:88', 'Startup', e));
}, 1000);
},
guildCreate: (guild: DiscordenoGuild) => {
log(LT.LOG, `Handling joining guild ${JSON.stringify(guild)}`);
sendMessage(config.logChannel, {
embeds: [{
title: 'New Guild Joined!',
color: successColor,
fields: [
{
name: 'Name:',
value: `${guild.name}`,
inline: true,
},
{
name: 'Id:',
value: `${guild.id}`,
inline: true,
},
{
name: 'Member Count:',
value: `${guild.memberCount}`,
inline: true,
},
],
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:95', 'Join Guild', e));
},
guildDelete: (guild: DiscordenoGuild) => {
log(LT.LOG, `Handling leaving guild ${JSON.stringify(guild)}`);
sendMessage(config.logChannel, {
embeds: [{
title: 'Removed from Guild',
color: warnColor,
fields: [
{
name: 'Name:',
value: `${guild.name}`,
inline: true,
},
{
name: 'Id:',
value: `${guild.id}`,
inline: true,
},
{
name: 'Member Count:',
value: `${guild.memberCount}`,
inline: true,
},
],
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('mod.ts:99', 'Leave Guild', e));
dbClient.execute('DELETE FROM allowed_guilds WHERE guildid = ? AND banned = 0', [guild.id]).catch((e) => utils.commonLoggers.dbError('mod.ts:100', 'delete from', e));
},
debug: DEVMODE ? (dmsg) => log(LT.LOG, `Debug Message | ${JSON.stringify(dmsg)}`) : undefined,
messageCreate: (message: DiscordenoMessage) => {
// Ignore all other bots
if (message.isBot) return;
// Ignore all messages that are not commands
if (message.content.indexOf(config.prefix) !== 0) {
// Handle @bot messages
if (message.mentionedUserIds[0] === botId && (message.content.trim().startsWith(`<@${botId}>`) || message.content.trim().startsWith(`<@!${botId}>`))) {
commands.handleMentions(message);
}
// return as we are done handling this command
return;
}
log(LT.LOG, `Handling ${config.prefix}command message: ${JSON.stringify(message)}`);
// Split into standard command + args format
const args = message.content.slice(config.prefix.length).trim().split(/[ \n]+/g);
const command = args.shift()?.toLowerCase();
// All commands below here
switch (command) {
case 'ping':
// [[ping
// Its a ping test, what else do you want.
commands.ping(message);
break;
case 'rip':
case 'memory':
// [[rip [[memory
// Displays a short message I wanted to include
commands.rip(message);
break;
case 'rollhelp':
case 'rh':
case 'hr':
case '??':
// [[rollhelp or [[rh or [[hr or [[??
// Help command specifically for the roll command
commands.rollHelp(message);
break;
case 'rolldecorators':
case 'rd':
case 'dr':
case '???':
// [[rollDecorators or [[rd or [[dr or [[???
// Help command specifically for the roll command decorators
commands.rollDecorators(message);
break;
case 'help':
case 'h':
case '?':
// [[help or [[h or [[?
// Help command, prints from help file
commands.help(message);
break;
case 'info':
case 'i':
// [[info or [[i
// Info command, prints short desc on bot and some links
commands.info(message);
break;
case 'privacy':
// [[privacy
// Privacy command, prints short desc on bot's privacy policy
commands.privacy(message);
break;
case 'version':
case 'v':
// [[version or [[v
// Returns version of the bot
commands.version(message);
break;
case 'report':
case 'r':
// [[report or [[r (command that failed)
// Manually report a failed roll
commands.report(message, args);
break;
case 'stats':
case 's':
// [[stats or [[s
// Displays stats on the bot
commands.stats(message);
break;
case 'api':
// [[api arg
// API sub commands
commands.api(message, args);
break;
case 'audit':
// [[audit arg
// Audit sub commands
commands.audit(message, args);
break;
case 'heatmap':
case 'hm':
// [[heatmap or [[hm
// Audit sub commands
commands.heatmap(message);
break;
default:
// Non-standard commands
if (command?.startsWith('xdy')) {
// [[xdydz (aka someone copy pasted the template as a roll)
// Help command specifically for the roll command
commands.rollHelp(message);
} else if (command && (`${command}${args.join('')}`).indexOf(config.postfix) > -1) {
// [[roll]]
// Dice rolling commence!
commands.roll(message, args, command);
} else if (command) {
// [[emoji or [[emojialias
// Check if the unhandled command is an emoji request
commands.emoji(message, command);
}
break;
}
},
},
}); });
// Start up the command prompt for debug usage
if (DEBUG) {
utils.cmdPrompt(config.logChannel, config.name);
}
// Start up the API for rolling from third party apps (like excel macros) // Start up the API for rolling from third party apps (like excel macros)
if (config.api.enable) { if (config.api.enable) {
api.start(); api.start();
} }

View File

@ -3,196 +3,220 @@
* *
* December 21, 2020 * December 21, 2020
*/ */
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config'; import config from '../config.ts';
import {
import dbClient from 'db/client.ts'; // Log4Deno deps
log,
import endpoints from 'endpoints/_index.ts'; LT,
import stdResp from 'endpoints/stdResponses.ts'; } from '../deps.ts';
import { dbClient } from './db.ts';
import endpoints from './endpoints/_index.ts';
import stdResp from './endpoints/stdResponses.ts';
// start() returns nothing // start() returns nothing
// start initializes and runs the entire API for the bot // start initializes and runs the entire API for the bot
const start = () => { const start = async (): Promise<void> => {
log(LT.INFO, `HTTP api running at: http://localhost:${config.api.port}/`); const server = Deno.listen({ port: config.api.port });
log(LT.INFO, `HTTP api running at: http://localhost:${config.api.port}/`);
// rateLimitTime holds all users with the last time they started a rate limit timer // rateLimitTime holds all users with the last time they started a rate limit timer
const rateLimitTime = new Map<string, number>(); const rateLimitTime = new Map<string, number>();
// rateLimitCnt holds the number of times the user has called the api in the current rate limit timer // rateLimitCnt holds the number of times the user has called the api in the current rate limit timer
const rateLimitCnt = new Map<string, number>(); const rateLimitCnt = new Map<string, number>();
// Catching every request made to the server // Catching every request made to the server
Deno.serve({ port: config.api.port }, async (request) => { for await (const conn of server) {
log(LT.LOG, `Handling request: ${JSON.stringify(request.headers)} | ${JSON.stringify(request.method)} | ${JSON.stringify(request.url)}`); (async () => {
// Check if user is authenticated to be using this API const httpConn = Deno.serveHttp(conn);
let authenticated = false; for await (const requestEvent of httpConn) {
let rateLimited = false; const request = requestEvent.request;
let updateRateLimitTime = false; log(LT.LOG, `Handling request: ${JSON.stringify(request.headers)} | ${JSON.stringify(request.method)} | ${JSON.stringify(request.url)}`);
let apiUserid = 0n; // Check if user is authenticated to be using this API
let apiUseridStr = ''; let authenticated = false;
let apiUserEmail = ''; let rateLimited = false;
let apiUserDelCode = ''; let updateRateLimitTime = false;
let apiUserid = 0n;
let apiUseridStr = '';
let apiUserEmail = '';
let apiUserDelCode = '';
// Check the requests API key // Check the requests API key
if (request.headers.has('X-Api-Key')) { if (request.headers.has('X-Api-Key')) {
// Get the userid and flags for the specific key // Get the userid and flags for the specific key
const dbApiQuery = await dbClient.query('SELECT userid, email, deleteCode FROM all_keys WHERE apiKey = ? AND active = 1 AND banned = 0', [ const dbApiQuery = await dbClient.query('SELECT userid, email, deleteCode FROM all_keys WHERE apiKey = ? AND active = 1 AND banned = 0', [request.headers.get('X-Api-Key')]);
request.headers.get('X-Api-Key'),
]);
// If only one user returned, is not banned, and is currently active, mark as authenticated // If only one user returned, is not banned, and is currently active, mark as authenticated
if (dbApiQuery.length === 1) { if (dbApiQuery.length === 1) {
apiUserid = BigInt(dbApiQuery[0].userid); apiUserid = BigInt(dbApiQuery[0].userid);
apiUserEmail = dbApiQuery[0].email; apiUserEmail = dbApiQuery[0].email;
apiUserDelCode = dbApiQuery[0].deleteCode; apiUserDelCode = dbApiQuery[0].deleteCode;
authenticated = true; authenticated = true;
// Rate limiting inits // Rate limiting inits
apiUseridStr = apiUserid.toString(); apiUseridStr = apiUserid.toString();
const apiTimeNow = new Date().getTime(); const apiTimeNow = new Date().getTime();
// Check if user has sent a request recently // Check if user has sent a request recently
if (rateLimitTime.has(apiUseridStr) && (rateLimitTime.get(apiUseridStr) || 0) + config.api.rateLimitTime > apiTimeNow) { if (rateLimitTime.has(apiUseridStr) && (((rateLimitTime.get(apiUseridStr) || 0) + config.api.rateLimitTime) > apiTimeNow)) {
// Get current count // Get current count
const currentCnt = rateLimitCnt.get(apiUseridStr) || 0; const currentCnt = rateLimitCnt.get(apiUseridStr) || 0;
if (currentCnt < config.api.rateLimitCnt) { if (currentCnt < config.api.rateLimitCnt) {
// Limit not yet exceeded, update count // Limit not yet exceeded, update count
rateLimitCnt.set(apiUseridStr, currentCnt + 1); rateLimitCnt.set(apiUseridStr, currentCnt + 1);
} else { } else {
// Limit exceeded, prevent API use // Limit exceeded, prevent API use
rateLimited = true; rateLimited = true;
} }
} else { } else {
// Update the maps // Update the maps
updateRateLimitTime = true; updateRateLimitTime = true;
rateLimitCnt.set(apiUseridStr, 1); rateLimitCnt.set(apiUseridStr, 1);
} }
} }
} }
if (!rateLimited) { if (!rateLimited) {
// Get path and query as a string // Get path and query as a string
const [urlPath, tempQ] = request.url.split('?'); const [urlPath, tempQ] = request.url.split('?');
const path = urlPath.split('api')[1]; const path = urlPath.split('api')[1];
// Turn the query into a map (if it exists) // Turn the query into a map (if it exists)
const query = new Map<string, string>(); const query = new Map<string, string>();
if (tempQ !== undefined) { if (tempQ !== undefined) {
tempQ.split('&').forEach((e: string) => { tempQ.split('&').forEach((e: string) => {
log(LT.LOG, `Parsing request query ${request} ${e}`); log(LT.LOG, `Parsing request query ${request} ${e}`);
const [option, params] = e.split('='); const [option, params] = e.split('=');
query.set(option.toLowerCase(), params); query.set(option.toLowerCase(), params);
}); });
} }
if (path) { if (path) {
if (authenticated) { if (authenticated) {
// Update rate limit details // Handle the authenticated request
if (updateRateLimitTime) { switch (request.method) {
const apiTimeNow = new Date().getTime(); case 'GET':
rateLimitTime.set(apiUseridStr, apiTimeNow); switch (path.toLowerCase()) {
} case '/key':
case '/key/':
endpoints.get.apiKeyAdmin(requestEvent, query, apiUserid);
break;
case '/channel':
case '/channel/':
endpoints.get.apiChannel(requestEvent, query, apiUserid);
break;
case '/roll':
case '/roll/':
endpoints.get.apiRoll(requestEvent, query, apiUserid);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Get'));
break;
}
break;
case 'POST':
switch (path.toLowerCase()) {
case '/channel/add':
case '/channel/add/':
endpoints.post.apiChannelAdd(requestEvent, query, apiUserid);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Post'));
break;
}
break;
case 'PUT':
switch (path.toLowerCase()) {
case '/key/ban':
case '/key/ban/':
case '/key/unban':
case '/key/unban/':
case '/key/activate':
case '/key/activate/':
case '/key/deactivate':
case '/key/deactivate/':
endpoints.put.apiKeyManage(requestEvent, query, apiUserid, path);
break;
case '/channel/ban':
case '/channel/ban/':
case '/channel/unban':
case '/channel/unban/':
endpoints.put.apiChannelManageBan(requestEvent, query, apiUserid, path);
break;
case '/channel/activate':
case '/channel/activate/':
case '/channel/deactivate':
case '/channel/deactivate/':
endpoints.put.apiChannelManageActive(requestEvent, query, apiUserid, path);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Put'));
break;
}
break;
case 'DELETE':
switch (path.toLowerCase()) {
case '/key/delete':
case '/key/delete/':
endpoints.delete.apiKeyDelete(requestEvent, query, apiUserid, apiUserEmail, apiUserDelCode);
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.NotFound('Auth Del'));
break;
}
break;
default:
// Alert API user that they messed up
requestEvent.respondWith(stdResp.MethodNotAllowed('Auth'));
break;
}
// Handle the authenticated request // Update rate limit details
switch (request.method) { if (updateRateLimitTime) {
case 'GET': const apiTimeNow = new Date().getTime();
switch (path.toLowerCase()) { rateLimitTime.set(apiUseridStr, apiTimeNow);
case '/key': }
case '/key/': } else if (!authenticated) {
return endpoints.get.apiKeyAdmin(query, apiUserid); // Handle the unathenticated request
case '/channel': switch (request.method) {
case '/channel/': case 'GET':
return endpoints.get.apiChannel(query, apiUserid); switch (path.toLowerCase()) {
case '/roll': case '/key':
case '/roll/': case '/key/':
return endpoints.get.apiRoll(query, apiUserid); endpoints.get.apiKey(requestEvent, query);
default: break;
// Alert API user that they messed up case '/heatmap.png':
return stdResp.NotFound('Auth Get'); endpoints.get.heatmapPng(requestEvent);
} break;
break; default:
case 'POST': // Alert API user that they messed up
switch (path.toLowerCase()) { requestEvent.respondWith(stdResp.NotFound('NoAuth Get'));
case '/channel/add': break;
case '/channel/add/': }
return endpoints.post.apiChannelAdd(query, apiUserid); break;
default: default:
// Alert API user that they messed up // Alert API user that they messed up
return stdResp.NotFound('Auth Post'); requestEvent.respondWith(stdResp.MethodNotAllowed('NoAuth'));
} break;
break; }
case 'PUT': }
switch (path.toLowerCase()) { } else {
case '/key/ban': requestEvent.respondWith(stdResp.Forbidden('What are you trying to do?'));
case '/key/ban/': }
case '/key/unban': } else if (authenticated && rateLimited) {
case '/key/unban/': // Alert API user that they are doing this too often
case '/key/activate': requestEvent.respondWith(stdResp.TooManyRequests('Slow down, servers are expensive and this bot is free to use.'));
case '/key/activate/': } else {
case '/key/deactivate': // Alert API user that they shouldn't be doing this
case '/key/deactivate/': requestEvent.respondWith(stdResp.Forbidden('Why are you here?'));
return endpoints.put.apiKeyManage(query, apiUserid, path); }
case '/channel/ban': }
case '/channel/ban/': })();
case '/channel/unban': }
case '/channel/unban/':
return endpoints.put.apiChannelManageBan(query, apiUserid, path);
case '/channel/activate':
case '/channel/activate/':
case '/channel/deactivate':
case '/channel/deactivate/':
return endpoints.put.apiChannelManageActive(query, apiUserid, path);
default:
// Alert API user that they messed up
return stdResp.NotFound('Auth Put');
}
break;
case 'DELETE':
switch (path.toLowerCase()) {
case '/key/delete':
case '/key/delete/':
return endpoints.delete.apiKeyDelete(query, apiUserid, apiUserEmail, apiUserDelCode);
default:
// Alert API user that they messed up
return stdResp.NotFound('Auth Del');
}
break;
default:
// Alert API user that they messed up
return stdResp.MethodNotAllowed('Auth');
}
} else {
// Handle the unauthenticated request
switch (request.method) {
case 'GET':
switch (path.toLowerCase()) {
case '/key':
case '/key/':
return endpoints.get.apiKey(query);
case '/heatmap.png':
return endpoints.get.heatmapPng();
default:
// Alert API user that they messed up
return stdResp.NotFound('NoAuth Get');
}
break;
default:
// Alert API user that they messed up
return stdResp.MethodNotAllowed('NoAuth');
}
}
} else {
return stdResp.Forbidden('What are you trying to do?');
}
} else if (authenticated && rateLimited) {
// Alert API user that they are doing this too often
return stdResp.TooManyRequests('Slow down, servers are expensive and this bot is free to use.');
} else {
// Alert API user that they shouldn't be doing this
return stdResp.Forbidden('Why are you here?');
}
});
}; };
export default { start }; export default { start };

View File

@ -1,3 +0,0 @@
# artigen - The Artificer's Dice and Math Engine
artigen is the core engine powering The Artificer.

View File

@ -1,42 +0,0 @@
import { Embed, FileContent } from '@discordeno';
import { CountDetails, RollDistributionMap } from 'artigen/dice/dice.d.ts';
// ReturnData is the temporary internal type used before getting turned into SolvedRoll
export interface ReturnData {
origIdx?: number;
rollTotal: number;
rollPreFormat: string;
rollPostFormat: string;
rollDetails: string;
containsCrit: boolean;
containsFail: boolean;
initConfig: string;
isComplex: boolean;
}
// SolvedRoll is the complete solved and formatted roll, or the error said roll created
export interface SolvedRoll {
error: boolean;
errorMsg: string;
errorCode: string;
line1: string;
line2: string;
line3: string;
counts: CountDetails;
rollDistributions: RollDistributionMap;
}
interface basicArtigenEmbed {
charCount: number;
embed: Embed;
}
export interface ArtigenEmbedNoAttachment extends basicArtigenEmbed {
hasAttachment: false;
}
export interface ArtigenEmbedWithAttachment extends basicArtigenEmbed {
hasAttachment: true;
attachment: FileContent;
}

View File

@ -1,160 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { SolvedRoll } from 'artigen/artigen.d.ts';
import { tokenizeCmd } from 'artigen/cmdTokenizer.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { reduceCountDetails } from 'artigen/utils/counter.ts';
import { cmdSplitRegex, escapeCharacters } from 'artigen/utils/escape.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { assertPrePostBalance } from 'artigen/utils/parenBalance.ts';
import { reduceRollDistMaps } from 'artigen/utils/rollDist.ts';
import { compareTotalRolls, compareTotalRollsReverse } from 'artigen/utils/sortFuncs.ts';
import { translateError } from 'artigen/utils/translateError.ts';
// runCmd(rollRequest)
// runCmd handles converting rollRequest into a computer readable format for processing, and finally executes the solving
export const runCmd = (rollRequest: QueuedRoll): SolvedRoll => {
const returnMsg: SolvedRoll = {
error: false,
errorCode: '',
errorMsg: '',
line1: '',
line2: '',
line3: '',
counts: {
total: 0,
successful: 0,
failed: 0,
rerolled: 0,
dropped: 0,
exploded: 0,
},
rollDistributions: new Map<string, number[]>(),
};
// Whole processor lives in a try-catch to catch artigen's intentional error conditions
try {
loggingEnabled && log(LT.LOG, `rollRequest received! ${JSON.stringify(rollRequest)}`);
// filter removes all null/empty strings since we don't care about them
const sepCmds = rollRequest.rollCmd.split(cmdSplitRegex).filter((x) => x);
loggingEnabled && log(LT.LOG, `Split cmd into parts ${JSON.stringify(sepCmds)}`);
// Verify prefix/postfix balance
assertPrePostBalance(sepCmds);
// Send the split roll into the command tokenizer to get raw response data
const [tempReturnData, tempCountDetails, tempRollDists] = tokenizeCmd(sepCmds, rollRequest.modifiers, true);
loggingEnabled && log(LT.LOG, `Return data is back ${JSON.stringify(tempReturnData)} ${JSON.stringify(tempCountDetails)} ${JSON.stringify(tempRollDists)}`);
// Remove any floating spaces from originalCommand
// Escape any | and ` chars in originalCommand to prevent spoilers and code blocks from acting up
const rawCmd = escapeCharacters(rollRequest.originalCommand.trim(), '|').replace(/`/g, '');
let line1 = '';
let line2 = '';
let line3 = '';
// The ': ' is used by generateRollEmbed to split line 2 up
const resultStr = tempReturnData.length > 1 ? 'Results: ' : 'Result: ';
line2 = resultStr;
// If a theoretical roll is requested, mark the output as such, else use default formatting
const theoreticalBools = [
rollRequest.modifiers.maxRoll,
rollRequest.modifiers.minRoll,
rollRequest.modifiers.nominalRoll,
rollRequest.modifiers.simulatedNominal > 0,
];
if (theoreticalBools.includes(true)) {
const theoreticalTexts = ['Theoretical Maximum', 'Theoretical Minimum', 'Theoretical Nominal', 'Simulated Nominal'];
const theoreticalText = theoreticalTexts[theoreticalBools.indexOf(true)];
line1 = ` requested the ${theoreticalText.toLowerCase()} of:\n\`${rawCmd}\``;
line2 = `${theoreticalText} ${resultStr}`;
} else if (rollRequest.modifiers.order === 'a') {
line1 = ` requested the following rolls to be ordered from least to greatest:\n\`${rawCmd}\``;
tempReturnData.sort(compareTotalRolls);
} else if (rollRequest.modifiers.order === 'd') {
line1 = ` requested the following rolls to be ordered from greatest to least:\n\`${rawCmd}\``;
tempReturnData.sort(compareTotalRollsReverse);
} else {
line1 = ` rolled:\n\`${rawCmd}\``;
}
// List number of iterations on simulated nominals
if (rollRequest.modifiers.simulatedNominal) line2 += `Iterations performed per roll: \`${rollRequest.modifiers.simulatedNominal}\`\n`;
// Reduce counts to a single object
returnMsg.counts = reduceCountDetails(tempCountDetails);
// If a regular nominal and roll looks somewhat complex, alert user simulatedNominal exists
if (rollRequest.modifiers.nominalRoll && tempReturnData.filter((data) => data.isComplex).length) {
line2 +=
"One or more of the rolls requested appear to be more complex than what the Nominal calculator is intended for. For a better approximation of this roll's nominal value, please rerun this roll with the `-sn` flag.\n";
}
// Fill out all of the details and results now
tempReturnData.forEach((e, i) => {
loopCountCheck();
loggingEnabled && log(LT.LOG, `Parsing roll ${rollRequest.rollCmd} | Making return text ${JSON.stringify(e)}`);
let preFormat = '';
let postFormat = '';
if (!rollRequest.modifiers.simulatedNominal) {
// If the roll contained a crit success or fail, set the formatting around it
if (e.containsCrit) {
preFormat = `**${preFormat}`;
postFormat = `${postFormat}**`;
}
if (e.containsFail) {
preFormat = `__${preFormat}`;
postFormat = `${postFormat}__`;
}
}
// Populate line2 (the results) and line3 (the details) with their data
if (rollRequest.modifiers.order === '') {
line2 += `${e.rollPreFormat ? escapeCharacters(e.rollPreFormat, '|*_~`') : ' '}${preFormat}${rollRequest.modifiers.commaTotals ? e.rollTotal.toLocaleString() : e.rollTotal}${postFormat}${
e.rollPostFormat ? escapeCharacters(e.rollPostFormat, '|*_~`') : ''
}`;
} else {
// If order is on, turn rolls into csv without formatting
line2 += `${preFormat}${rollRequest.modifiers.commaTotals ? e.rollTotal.toLocaleString() : e.rollTotal}${postFormat}, `;
}
const varNum = `\`x${i}\`: `;
const rollDetails = rollRequest.modifiers.noDetails || rollRequest.modifiers.simulatedNominal > 0 ? ' = ' : ` = ${e.rollDetails} = `;
line3 += `${rollRequest.modifiers.numberVariables && i + 1 !== tempReturnData.length ? varNum : ''}\`${
e.initConfig.replaceAll(
' ',
'',
)
}\`${rollDetails}${preFormat}${rollRequest.modifiers.commaTotals ? e.rollTotal.toLocaleString() : e.rollTotal}${postFormat}\n`;
});
// If order is on, remove trailing ", "
if (rollRequest.modifiers.order !== '') {
line2 = line2.substring(0, line2.length - 2);
}
// Fill in the return block
returnMsg.line1 = line1;
returnMsg.line2 = line2;
returnMsg.line3 = line3;
// Reduce rollDist maps into a single map
returnMsg.rollDistributions = reduceRollDistMaps(tempRollDists);
} catch (e) {
// Fill in the return block
const solverError = e as Error;
loggingEnabled && log(LT.ERROR, `Error hit: ${solverError.message} | ${rollRequest.rollCmd}`);
returnMsg.error = true;
[returnMsg.errorCode, returnMsg.errorMsg] = translateError(solverError);
}
return returnMsg;
};

View File

@ -1,235 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
import { ReturnData } from 'artigen/artigen.d.ts';
import { CountDetails, RollDistributionMap, RollModifiers } from 'artigen/dice/dice.d.ts';
import { handleGroup } from 'artigen/dice/groupHandler.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { tokenizeMath } from 'artigen/math/mathTokenizer.ts';
import { reduceCountDetails } from 'artigen/utils/counter.ts';
import { closeInternal, closeInternalGrp, internalGrpWrapRegex, internalWrapRegex, mathSplitRegex, openInternal, openInternalGrp } from 'artigen/utils/escape.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { assertGroupBalance, getMatchingGroupIdx, getMatchingInternalGrpIdx, getMatchingInternalIdx, getMatchingPostfixIdx } from 'artigen/utils/parenBalance.ts';
import { basicReducer } from 'artigen/utils/reducers.ts';
// tokenizeCmd expects a string[] of items that are either config.prefix/config.postfix or some text that contains math and/or dice rolls
export const tokenizeCmd = (
cmd: string[],
modifiers: RollModifiers,
topLevel: boolean,
previousResults: number[] = [],
): [ReturnData[], CountDetails[], RollDistributionMap[]] => {
loggingEnabled && log(LT.LOG, `Tokenizing command ${JSON.stringify(cmd)}`);
const returnData: ReturnData[] = [];
const countDetails: CountDetails[] = [];
const rollDists: RollDistributionMap[] = [];
// Wrapped commands still exist, unwrap them
while (cmd.includes(config.prefix)) {
loopCountCheck();
const openIdx = cmd.indexOf(config.prefix);
const closeIdx = getMatchingPostfixIdx(cmd, openIdx);
const currentCmd = cmd.slice(openIdx + 1, closeIdx);
const simulatedLoopCount = modifiers.simulatedNominal || 1;
loggingEnabled &&
log(
LT.LOG,
`Setting previous results: topLevel:${topLevel} ${topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults} simulatedLoopCount:${simulatedLoopCount}`,
);
const simulatedData: ReturnData[] = [];
for (let i = 0; i < simulatedLoopCount; i++) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `In simLoop:${i} "${currentCmd}" of ${JSON.stringify(cmd)}`);
// Handle any nested commands
const [tempData, tempCounts, tempDists] = tokenizeCmd(currentCmd, modifiers, false, topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults);
const data = tempData[0];
loggingEnabled && log(LT.LOG, `Data back from tokenizeCmd, "${currentCmd}" of "${JSON.stringify(cmd)}" ${JSON.stringify(data)}`);
// Only run this on first loop
if (topLevel && i === 0) {
// Handle saving any formatting between dice
if (openIdx !== 0) {
data.rollPreFormat = cmd.slice(0, openIdx).join('');
}
// Chop off all formatting between cmds along with the processed cmd
cmd.splice(0, closeIdx + 1);
}
// Store results
modifiers.simulatedNominal ? simulatedData.push(data) : returnData.push(data);
countDetails.push(...tempCounts);
rollDists.push(...tempDists);
// Handle ConfirmCrit if its on
if (topLevel && modifiers.confirmCrit && reduceCountDetails(tempCounts).successful) {
loggingEnabled && log(LT.LOG, `ConfirmCrit on ${JSON.stringify(currentCmd)}`);
let done = false;
while (!done) {
loopCountCheck();
// Keep running the same roll again until its not successful
const [ccTempData, ccTempCounts, ccTempDists] = tokenizeCmd(
currentCmd,
modifiers,
false,
topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults,
);
const ccData = ccTempData[0];
ccData.rollPreFormat = '\nAuto-Confirming Crit: ';
loggingEnabled &&
log(LT.LOG, `ConfirmCrit on ${JSON.stringify(currentCmd)} | Rolled again ${JSON.stringify(ccData)} ${JSON.stringify(ccTempCounts)}`);
// Store CC results
returnData.push(ccData);
countDetails.push(...ccTempCounts);
rollDists.push(...ccTempDists);
done = reduceCountDetails(ccTempCounts).successful === 0;
}
}
}
// Turn the simulated return data into a single usable payload
if (modifiers.simulatedNominal) {
loggingEnabled && log(LT.LOG, `SN on, condensing array into single item ${JSON.stringify(simulatedData)}`);
returnData.push({
rollTotal: simulatedData.map((data) => data.rollTotal).reduce(basicReducer, 0) / simulatedData.length,
rollPreFormat: simulatedData[0].rollPreFormat,
rollPostFormat: simulatedData[0].rollPostFormat,
rollDetails: simulatedData[0].rollDetails,
containsCrit: simulatedData.some((data) => data.containsCrit),
containsFail: simulatedData.some((data) => data.containsFail),
initConfig: simulatedData[0].initConfig,
isComplex: simulatedData[0].isComplex,
});
loggingEnabled && log(LT.LOG, `SN on, returnData updated ${JSON.stringify(returnData)}`);
}
// Finally, if we are handling a nested [[cmd]], fill in the rollTotal correctly
if (!topLevel) {
cmd.splice(openIdx, closeIdx - openIdx + 1, `${openInternal}${Math.round(returnData[returnData.length - 1].rollTotal)}${closeInternal}`);
}
}
if (topLevel) {
if (cmd.length) {
loggingEnabled && log(LT.LOG, `Adding leftover formatting to last returnData ${JSON.stringify(cmd)}`);
returnData[returnData.length - 1].rollPostFormat = cmd.join('');
}
return [returnData, countDetails, rollDists];
} else {
// Check for any groups and handle them
const groupParts = cmd
.join('')
.split(/([{}])/g)
.filter((x) => x);
const groupResults: ReturnData[] = [];
if (groupParts.includes('{')) {
assertGroupBalance(groupParts);
}
while (groupParts.includes('{')) {
loggingEnabled && log(LT.LOG, `Handling Groups | Current cmd: ${JSON.stringify(groupParts)}`);
const openIdx = groupParts.indexOf('{');
const closeIdx = getMatchingGroupIdx(groupParts, openIdx);
const currentGrp = groupParts.slice(openIdx + 1, closeIdx);
// Try to find and "eat" any modifiers from the next groupPart
let thisGrpMods = '';
const possibleMods = groupParts[closeIdx + 1]?.trim() ?? '';
if (possibleMods.match(/^[dk<>=f].*/g)) {
const items = groupParts[closeIdx + 1].split(mathSplitRegex).filter((x) => x);
thisGrpMods = items.shift() ?? '';
groupParts[closeIdx + 1] = items.join('');
}
const [tempData, tempCounts, tempDists] = handleGroup(currentGrp, thisGrpMods, modifiers, previousResults);
const data = tempData[0];
log(LT.LOG, `Solved Group is back ${JSON.stringify(data)} | ${JSON.stringify(returnData)} ${JSON.stringify(tempCounts)} ${JSON.stringify(tempDists)}`);
countDetails.push(...tempCounts);
rollDists.push(...tempDists);
// Merge result back into groupParts
groupParts.splice(openIdx, closeIdx - openIdx + 1, `${openInternalGrp}${groupResults.length}${closeInternalGrp}`);
groupResults.push(data);
}
const cmdForMath = groupParts.join('');
loggingEnabled && log(LT.LOG, `Tokenizing math ${cmdForMath}`);
// Solve the math and rolls for this cmd
const [tempData, tempCounts, tempDists] = tokenizeMath(cmdForMath, modifiers, previousResults, groupResults);
const data = tempData[0];
loggingEnabled &&
log(
LT.LOG,
`Solved math is back ${JSON.stringify(data)} | ${JSON.stringify(returnData)} ${JSON.stringify(groupResults)} ${
JSON.stringify(
tempCounts,
)
} ${JSON.stringify(tempDists)}`,
);
// Merge counts
countDetails.push(...tempCounts);
rollDists.push(...tempDists);
// Handle merging group data into initConfig first since a group could "smuggle" a returnData in it
const tempInitConf = data.initConfig.split(internalGrpWrapRegex).filter((x) => x);
loggingEnabled && log(LT.LOG, `Split solved math into tempInitConf ${JSON.stringify(tempInitConf)}`);
while (tempInitConf.includes(openInternalGrp)) {
loopCountCheck();
const openIdx = tempInitConf.indexOf(openInternalGrp);
const closeIdx = getMatchingInternalGrpIdx(tempInitConf, openIdx);
// Take first groupResult out of array
const dataToMerge = groupResults.shift();
// Replace the found pair with the nested tempInitConfig and result
tempInitConf.splice(openIdx, closeIdx - openIdx + 1, `${dataToMerge?.initConfig}`);
loggingEnabled && log(LT.LOG, `Current tempInitConf state ${JSON.stringify(tempInitConf)}`);
}
// Handle merging returnData into tempData
const initConf = tempInitConf
.join('')
.split(internalWrapRegex)
.filter((x) => x);
loggingEnabled && log(LT.LOG, `Split tempInitConfig into initConf ${JSON.stringify(initConf)}`);
while (initConf.includes(openInternal)) {
loopCountCheck();
const openIdx = initConf.indexOf(openInternal);
const closeIdx = getMatchingInternalIdx(initConf, openIdx);
// Take first returnData out of array
const dataToMerge = returnData.shift();
// Replace the found pair with the nested initConfig and result
initConf.splice(openIdx, closeIdx - openIdx + 1, `${config.prefix}${dataToMerge?.initConfig}=${dataToMerge?.rollTotal}${config.postfix}`);
loggingEnabled && log(LT.LOG, `Current initConf state ${JSON.stringify(initConf)}`);
}
// Join all parts/remainders
data.initConfig = initConf.join('');
loggingEnabled && log(LT.LOG, `ReturnData merged into solved math ${JSON.stringify(data)} | ${JSON.stringify(countDetails)}`);
return [[data], countDetails, rollDists];
}
};

View File

@ -1,165 +0,0 @@
import { SolvedStep } from 'artigen/math/math.d.ts';
// Available Roll Types
type RollType = '' | 'custom' | 'roll20' | 'fate' | 'cwod' | 'ova';
// RollSet is used to preserve all information about a calculated roll
export interface RollSet {
type: RollType;
rollGrpIdx?: number;
origIdx: number;
roll: number;
size: number;
dropped: boolean;
rerolled: boolean;
exploding: boolean;
critHit: boolean;
critFail: boolean;
isComplex: boolean;
matchLabel: string;
success: boolean;
fail: boolean;
}
// CountDetails is the object holding the count data for creating the Count Embed
export interface CountDetails {
total: number;
successful: number;
failed: number;
rerolled: number;
dropped: number;
exploded: number;
}
// RollDistribution is used for storing the raw roll distribution
// use rollDistKey to generate the key
export type RollDistributionMap = Map<string, number[]>;
export type CustomDiceShapes = Map<string, number[]>;
// RollFormat is the return structure for the rollFormatter
export interface FormattedRoll {
solvedStep: SolvedStep;
countDetails: CountDetails;
rollDistributions: RollDistributionMap;
}
// RollModifiers is the structure to keep track of the decorators applied to a roll command
export interface RollModifiers {
noDetails: boolean;
superNoDetails: boolean;
hideRaw: boolean;
spoiler: string;
maxRoll: boolean;
minRoll: boolean;
nominalRoll: boolean;
simulatedNominal: number;
gmRoll: boolean;
gms: string[];
order: string;
count: boolean;
commaTotals: boolean;
confirmCrit: boolean;
rollDist: boolean;
numberVariables: boolean;
customDiceShapes: CustomDiceShapes;
apiWarn: string;
valid: boolean;
error: Error;
}
// Basic conf interfaces
interface CountConf {
on: boolean;
count: number;
}
interface RangeConf {
on: boolean;
range: number[];
}
interface GroupRangeConf extends RangeConf {
// minValue carries the minimum number for the specified option to trigger
// ex: if set to 4, 4 and greater will trigger the option
minValue: number | null;
// maxValue carries the minimum number for the specified option to trigger
// ex: if set to 4, 4 and less will trigger the option
maxValue: number | null;
}
// Sort interface
interface SortDisabled {
on: false;
direction: '';
}
interface SortEnabled {
on: true;
direction: 'a' | 'd';
}
// D% configuration
export interface DPercentConf {
on: boolean;
sizeAdjustment: number;
critVal: number;
}
interface BaseConf {
drop: CountConf;
keep: CountConf;
dropHigh: CountConf;
keepLow: CountConf;
}
// GroupConf carries the machine readable group configuration the user specified
export interface GroupConf extends BaseConf {
success: GroupRangeConf;
fail: GroupRangeConf;
}
// RollConf carries the machine readable roll configuration the user specified
export interface RollConf extends BaseConf {
type: RollType;
customType: string | null;
dieCount: number;
dieSize: number;
dPercent: DPercentConf;
reroll: {
on: boolean;
once: boolean;
nums: number[];
};
critScore: RangeConf;
critFail: RangeConf;
exploding: {
on: boolean;
once: boolean;
compounding: boolean;
penetrating: boolean;
nums: number[];
};
match: {
on: boolean;
minCount: number;
returnTotal: boolean;
};
sort: SortDisabled | SortEnabled;
success: RangeConf;
fail: RangeConf;
}
export interface SumOverride {
on: boolean;
value: number;
}
export interface ExecutedRoll {
rollSet: RollSet[];
countSuccessOverride: boolean;
countFailOverride: boolean;
sumOverride: SumOverride;
}
export interface GroupResultFlags {
dropped: boolean;
success: boolean;
failed: boolean;
}

View File

@ -1,357 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { ExecutedRoll, RollModifiers, RollSet, SumOverride } from 'artigen/dice/dice.d.ts';
import { generateRoll } from 'artigen/dice/randomRoll.ts';
import { getRollConf } from 'artigen/dice/getRollConf.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { compareOrigIdx, compareRolls, compareRollsReverse } from 'artigen/utils/sortFuncs.ts';
import { flagRoll } from 'artigen/utils/diceFlagger.ts';
import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { generateRollVals } from 'artigen/utils/rollValCounter.ts';
// roll(rollStr, modifiers) returns RollSet
// roll parses and executes the rollStr
export const executeRoll = (rollStr: string, modifiers: RollModifiers): ExecutedRoll => {
/* Roll Capabilities
* Deciphers and rolls a single dice roll set
*
* Check the README.md of this project for details on the roll options. I gave up trying to keep three places updated at once.
*/
// Make entire roll lowercase for ease of parsing
rollStr = rollStr.toLowerCase();
// Turn the rollStr into a machine readable rollConf
const rollConf = getRollConf(rollStr, modifiers.customDiceShapes);
// Roll the roll
const rollSet: RollSet[] = [];
/* Roll will contain objects of the following format:
* {
* origIdx: 0,
* roll: 0,
* dropped: false,
* rerolled: false,
* exploding: false,
* critHit: false,
* critFail: false
* }
*
* Each of these is defined as following:
* {
* origIdx: The original index of the roll
* roll: The resulting roll on this die in the set
* dropped: This die is to be dropped as it was one of the dy lowest dice
* rerolled: This die has been rerolled as it matched rz, it is replaced by the very next die in the set
* exploding: This die was rolled as the previous die exploded (was a crit hit)
* critHit: This die matched csq[-u], max die value used if cs not used
* critFail: This die rolled a nat 1, a critical failure
* }
*/
// Initialize a template rollSet to copy multiple times
const getTemplateRoll = (): RollSet => ({
type: rollConf.type,
origIdx: 0,
roll: 0,
size: 0,
dropped: false,
rerolled: false,
exploding: false,
critHit: false,
critFail: false,
isComplex: rollConf.drop.on ||
rollConf.keep.on ||
rollConf.dropHigh.on ||
rollConf.keepLow.on ||
rollConf.critScore.on ||
rollConf.critFail.on ||
rollConf.exploding.on ||
rollConf.success.on ||
rollConf.fail.on,
matchLabel: '',
success: false,
fail: false,
});
// Initial rolling, not handling reroll or exploding here
for (let i = 0; i < rollConf.dieCount; i++) {
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
loopCountCheck();
// Copy the template to fill out for this iteration
const rolling = getTemplateRoll();
// If maximizeRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll
rolling.roll = generateRoll(rollConf, modifiers);
rolling.size = rollConf.dieSize;
// Set origIdx of roll
rolling.origIdx = i;
flagRoll(rollConf, rolling, modifiers.customDiceShapes);
loggingEnabled && log(LT.LOG, `${getLoopCount()} Roll done ${JSON.stringify(rolling)}`);
// Push the newly created roll and loop again
rollSet.push(rolling);
}
// If needed, handle rerolling and exploding dice now
if (rollConf.reroll.on || rollConf.exploding.on) {
let minMaxOverride = 0;
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])}`);
// If loopCount gets too high, stop trying to calculate infinity
loopCountCheck();
// 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 we need to reroll this roll, flag its been replaced and...
rollSet[i].rerolled = true;
// Copy the template to fill out for this iteration
const newReroll = getTemplateRoll();
newReroll.size = rollConf.dieSize;
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
mmMaxLoop: for (let m = rollConf.dieSize - 1; m > 0; m--) {
loopCountCheck();
if (!rollConf.reroll.nums.includes(m)) {
minMaxOverride = m;
break mmMaxLoop;
}
}
} 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
mmMinLoop: for (let m = rollConf.dPercent.on ? 1 : 2; m <= rollConf.dieSize; m++) {
loopCountCheck();
if (!rollConf.reroll.nums.includes(m)) {
minMaxOverride = m;
break mmMinLoop;
}
}
}
if (modifiers.maxRoll || modifiers.minRoll) {
newReroll.roll = minMaxOverride;
} else {
// If nominalRoll is on, set the roll to the average roll of dieSize, otherwise generate a new random roll
newReroll.roll = generateRoll(rollConf, modifiers);
}
flagRoll(rollConf, newReroll, modifiers.customDiceShapes);
loggingEnabled && log(LT.LOG, `${getLoopCount()} Roll done ${JSON.stringify(newReroll)}`);
// Slot this new roll in after the current iteration so it can be processed in the next loop
rollSet.splice(i + 1, 0, newReroll);
} else if (
rollConf.exploding.on &&
!rollSet[i].rerolled &&
(rollConf.exploding.nums.length ? rollConf.exploding.nums.includes(rollSet[i].roll) : rollSet[i].critHit) &&
(!rollConf.exploding.once || !rollSet[i].exploding)
) {
// If we have exploding.nums set, use those to determine the exploding range, and make sure if !o is on, make sure we don't repeatedly explode
// If it exploded, we keep both, so no flags need to be set
// Copy the template to fill out for this iteration
const newExplodingRoll = getTemplateRoll();
// If maximizeRoll is on, set the roll to the dieSize, else if nominalRoll is on, set the roll to the average roll of dieSize, else generate a new random roll
newExplodingRoll.roll = generateRoll(rollConf, modifiers);
newExplodingRoll.size = rollConf.dieSize;
// Always mark this roll as exploding
newExplodingRoll.exploding = true;
flagRoll(rollConf, newExplodingRoll, modifiers.customDiceShapes);
loggingEnabled && log(LT.LOG, `${getLoopCount()} Roll done ${JSON.stringify(newExplodingRoll)}`);
// Slot this new roll in after the current iteration so it can be processed in the next loop
rollSet.splice(i + 1, 0, newExplodingRoll);
}
}
}
// If penetrating is on, do the decrements
if (rollConf.exploding.penetrating) {
for (const penRoll of rollSet) {
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
loopCountCheck();
// If the die was from an explosion, decrement it by one
if (penRoll.exploding) {
penRoll.roll--;
}
}
}
// Handle compounding explosions
if (rollConf.exploding.compounding) {
for (let i = 0; i < rollSet.length; 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
loopCountCheck();
// Compound the exploding rolls, including the exploding flag and
if (rollSet[i].exploding) {
rollSet[i - 1].roll = rollSet[i - 1].roll + rollSet[i].roll;
rollSet[i - 1].exploding = true;
rollSet[i - 1].critFail = rollSet[i - 1].critFail || rollSet[i].critFail;
rollSet[i - 1].critHit = rollSet[i - 1].critHit || rollSet[i].critHit;
rollSet.splice(i, 1);
i--;
}
}
}
// If we need to handle the drop/keep flags
if (rollConf.drop.on || rollConf.keep.on || rollConf.dropHigh.on || rollConf.keepLow.on) {
// Count how many rerolled dice there are if the reroll flag was on
let rerollCount = 0;
if (rollConf.reroll.on) {
for (let j = 0; j < rollSet.length; j++) {
// If loopCount gets too high, stop trying to calculate infinity
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[j])}`);
rollSet[j].origIdx = j;
if (rollSet[j].rerolled) {
rerollCount++;
}
}
}
// Order the rolls from least to greatest (by RollSet.roll)
rollSet.sort(compareRolls);
// Determine how many valid rolls there are to drop from (may not be equal to dieCount due to exploding)
const validRolls = rollSet.length - rerollCount;
let dropCount = 0;
// For normal drop and keep, simple subtraction is enough to determine how many to drop
// Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop
if (rollConf.drop.on) {
dropCount = rollConf.drop.count;
if (dropCount > validRolls) {
dropCount = validRolls;
}
} else if (rollConf.keep.on) {
dropCount = validRolls - rollConf.keep.count;
if (dropCount < 0) {
dropCount = 0;
}
} // For inverted drop and keep, order must be flipped to greatest to least before the simple subtraction can determine how many to drop
// Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop
else if (rollConf.dropHigh.on) {
rollSet.reverse();
dropCount = rollConf.dropHigh.count;
if (dropCount > validRolls) {
dropCount = validRolls;
}
} else if (rollConf.keepLow.on) {
rollSet.reverse();
dropCount = validRolls - rollConf.keepLow.count;
if (dropCount < 0) {
dropCount = 0;
}
}
// Now its time to drop all dice needed
let i = 0;
while (dropCount > 0 && i < rollSet.length) {
// If loopCount gets too high, stop trying to calculate infinity
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Dropping dice ${dropCount} ${JSON.stringify(rollSet[i])}`);
// Skip all rolls that were rerolled
if (!rollSet[i].rerolled) {
rollSet[i].dropped = true;
dropCount--;
}
i++;
}
// Finally, return the rollSet to its original order
rollSet.sort(compareOrigIdx);
}
// Handle OVA dropping/keeping
if (rollConf.type === 'ova') {
const rollVals: Array<number> = generateRollVals(rollConf, rollSet, rollStr, false);
// Find max value, using lastIndexOf to use the greatest die size max in case of duplicate maximums
const maxRoll = rollVals.lastIndexOf(Math.max(...rollVals)) + 1;
// Drop all dice that are not a part of the max
for (const ovaRoll of rollSet) {
loopCountCheck();
loggingEnabled &&
log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | checking if this roll should be dropped ${ovaRoll.roll} | to keep: ${maxRoll}`);
if (ovaRoll.roll !== maxRoll) {
ovaRoll.dropped = true;
ovaRoll.critFail = false;
ovaRoll.critHit = false;
}
}
}
const sumOverride: SumOverride = {
on: rollConf.match.returnTotal,
value: 0,
};
if (rollConf.match.on) {
const rollVals: Array<number> = generateRollVals(rollConf, rollSet, rollStr, true).map((count) => (count >= rollConf.match.minCount ? count : 0));
const labels = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
let labelIdx = 0;
const rollLabels: Array<string> = rollVals.map((count) => {
loopCountCheck();
if (labelIdx >= labels.length) {
throw new Error(`TooManyLabels_${labels.length}`);
}
if (count) {
return labels[labelIdx++];
}
return '';
});
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | current match state: ${rollVals} | ${rollLabels}`);
// Apply labels
for (const roll of rollSet) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | trying to add a label to ${JSON.stringify(roll)}`);
if (rollLabels[roll.roll - 1]) {
roll.matchLabel = rollLabels[roll.roll - 1];
} else if (rollConf.match.returnTotal) {
roll.dropped = true;
}
}
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | labels added: ${JSON.stringify(rollSet)}`);
if (rollConf.match.returnTotal) {
sumOverride.value = rollVals.filter((count) => count !== 0).length;
}
}
if (rollConf.sort.on) {
rollSet.sort(rollConf.sort.direction === 'a' ? compareRolls : compareRollsReverse);
}
return {
rollSet,
sumOverride,
countSuccessOverride: rollConf.success.on,
countFailOverride: rollConf.fail.on,
};
};

View File

@ -1,102 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { ExecutedRoll, FormattedRoll, RollModifiers } from 'artigen/dice/dice.d.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { rollCounter } from 'artigen/utils/counter.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { createRollDistMap } from 'artigen/utils/rollDist.ts';
// generateFormattedRoll(executedRoll, modifiers) returns one SolvedStep
// generateFormattedRoll handles creating and formatting the completed rolls into the SolvedStep format
export const formatRoll = (executedRoll: ExecutedRoll, modifiers: RollModifiers): FormattedRoll => {
let tempTotal = 0;
let tempDetails = '[';
let tempCrit = false;
let tempFail = false;
let tempComplex = false;
// 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)}`);
executedRoll.rollSet.forEach((e) => {
loopCountCheck();
loggingEnabled && log(LT.LOG, `At ${JSON.stringify(e)}`);
let preFormat = '';
let postFormat = '';
if (!e.dropped && !e.rerolled) {
// If the roll was not dropped or rerolled, add it to the stepTotal and flag the critHit/critFail
tempTotal += e.roll;
if (e.critHit) {
tempCrit = true;
}
if (e.critFail) {
tempFail = true;
}
if (e.isComplex) {
tempComplex = true;
}
}
// If the roll was a crit hit or fail, or dropped/rerolled, add the formatting needed
if (e.critHit) {
// Bold for crit success
preFormat = `**${preFormat}`;
postFormat = `${postFormat}**`;
}
if (e.critFail) {
// Underline for crit fail
preFormat = `__${preFormat}`;
postFormat = `${postFormat}__`;
}
if (e.dropped || e.rerolled) {
// Strikethrough for dropped/rerolled rolls
preFormat = `~~${preFormat}`;
postFormat = `${postFormat}~~`;
}
if (e.exploding) {
// Add ! to indicate the roll came from an explosion
postFormat = `!${postFormat}`;
}
let rollLabel = '';
if (e.matchLabel) {
rollLabel = `${e.matchLabel}:`;
}
// Finally add this to the roll's details
tempDetails += `${preFormat}${rollLabel}${e.roll}${postFormat} + `;
});
// After the looping is done, remove the extra " + " from the details and cap it with the closing ]
tempDetails = tempDetails.substring(0, tempDetails.length - 3);
if (executedRoll.countSuccessOverride) {
const successCnt = executedRoll.rollSet.filter((e) => !e.dropped && !e.rerolled && e.success).length;
tempDetails += `, ${successCnt} Success${successCnt !== 1 ? 'es' : ''}`;
executedRoll.sumOverride.on = true;
executedRoll.sumOverride.value += successCnt;
}
if (executedRoll.countFailOverride) {
const failCnt = executedRoll.rollSet.filter((e) => !e.dropped && !e.rerolled && e.fail).length;
tempDetails += `, ${failCnt} Fail${failCnt !== 1 ? 's' : ''}`;
executedRoll.sumOverride.on = true;
if (executedRoll.rollSet[0]?.type !== 'cwod') {
executedRoll.sumOverride.value -= failCnt;
}
}
tempDetails += ']';
return {
solvedStep: {
total: executedRoll.sumOverride.on ? executedRoll.sumOverride.value : tempTotal,
details: tempDetails,
containsCrit: tempCrit,
containsFail: tempFail,
isComplex: tempComplex,
},
countDetails: modifiers.count || modifiers.confirmCrit ? rollCounter(executedRoll.rollSet) : rollCounter([]),
rollDistributions: modifiers.rollDist ? createRollDistMap(executedRoll.rollSet) : new Map<string, number[]>(),
};
};

View File

@ -1,73 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { GroupConf } from 'artigen/dice/dice.d.ts';
import { getRollConf } from 'artigen/dice/getRollConf.ts';
import { GroupOptions } from 'artigen/dice/rollOptions.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
// Wrapper to abuse getRollConf, produces a GroupConf by making the groupStr into a rollStr by adding a 1d1 onto it
export const getGroupConf = (groupStr: string, rawStr: string): GroupConf => {
const numberMatches = rawStr.match(/\d+/g) ?? ['1'];
let biggest = parseInt(numberMatches.length ? numberMatches[0] : '1');
for (const num of numberMatches) {
loopCountCheck();
const curNum = parseInt(num);
loggingEnabled && log(LT.LOG, `Finding biggest number to use as die size, ${curNum} ${biggest}`);
if (curNum > biggest) {
biggest = curNum;
}
}
loggingEnabled && log(LT.LOG, `Abusing getRollConf with "1d${biggest} ${groupStr}"`);
const fakeRollConf = getRollConf(`1d${biggest}${groupStr}`);
loggingEnabled && log(LT.LOG, `Abused rollConf back for ${groupStr}: ${JSON.stringify(fakeRollConf)}`);
// Apply > to minValue and < to maxValue for success and fail
const groupSplit = groupStr.split(/(\d+)/g).filter((x) => x);
loggingEnabled && log(LT.LOG, `Handling success/fail gt/lt ${JSON.stringify(groupSplit)}`);
let minSuccess: number | null = null;
let maxSuccess: number | null = null;
let minFail: number | null = null;
let maxFail: number | null = null;
while (groupSplit.length) {
loopCountCheck();
const option = groupSplit.shift() ?? '';
const value = parseInt(groupSplit.shift() ?? '');
if (!isNaN(value)) {
switch (option) {
case GroupOptions.SuccessLt:
maxSuccess = maxSuccess && value < maxSuccess ? maxSuccess : value;
break;
case GroupOptions.SuccessGtr:
minSuccess = minSuccess && value > minSuccess ? minSuccess : value;
break;
case GroupOptions.FailLt:
maxFail = maxFail && value < maxFail ? maxFail : value;
break;
case GroupOptions.FailGtr:
minFail = minFail && value > minFail ? minFail : value;
break;
}
}
}
loggingEnabled && log(LT.LOG, `Parsed GT/LT: minSuccess: ${minSuccess} maxSuccess: ${maxSuccess} minFail: ${minFail} maxFail: ${maxFail}`);
return {
drop: fakeRollConf.drop,
keep: fakeRollConf.keep,
dropHigh: fakeRollConf.dropHigh,
keepLow: fakeRollConf.keepLow,
success: { ...fakeRollConf.success, minValue: minSuccess, maxValue: maxSuccess },
fail: { ...fakeRollConf.fail, minValue: minFail, maxValue: maxFail },
};
};

View File

@ -1,235 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
import { RollModifiers } from 'artigen/dice/dice.d.ts';
export const reservedCharacters = ['d', '%', '^', '*', '(', ')', '{', '}', '/', '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
export const Modifiers = Object.freeze({
Count: '-c',
NoDetails: '-nd',
SuperNoDetails: '-snd',
HideRaw: '-hr',
Spoiler: '-s',
Max: '-max',
MaxShorthand: '-m',
Min: '-min',
Nominal: '-n',
SimulatedNominal: '-sn',
GM: '-gm',
Order: '-o',
CommaTotals: '-ct',
ConfirmCrit: '-cc',
RollDistribution: '-rd',
NumberVariables: '-nv',
VariablesNumber: '-vn',
CustomDiceShapes: '-cd',
});
// args will look like this: ['-sn', ' ', '10'] as spaces/newlines are split on their own
export const getModifiers = (args: string[]): [RollModifiers, string[]] => {
const modifiers: RollModifiers = {
noDetails: false,
superNoDetails: false,
hideRaw: false,
spoiler: '',
maxRoll: false,
minRoll: false,
nominalRoll: false,
simulatedNominal: 0,
gmRoll: false,
gms: [],
order: '',
count: false,
commaTotals: false,
confirmCrit: false,
rollDist: false,
numberVariables: false,
customDiceShapes: new Map<string, number[]>(),
apiWarn: '',
valid: true,
error: new Error(),
};
// Check if any of the args are command flags and pull those out into the modifiers object
for (let i = 0; i < args.length; i++) {
log(LT.LOG, `Checking ${args.join(' ')} for command modifiers ${i} | ${args[i]}`);
let defaultCase = false;
switch (args[i].toLowerCase()) {
case Modifiers.Count:
modifiers.count = true;
break;
case Modifiers.NoDetails:
modifiers.noDetails = true;
break;
case Modifiers.SuperNoDetails:
modifiers.superNoDetails = true;
break;
case Modifiers.HideRaw:
modifiers.hideRaw = true;
break;
case Modifiers.Spoiler:
modifiers.spoiler = '||';
break;
case Modifiers.Max:
case Modifiers.MaxShorthand:
modifiers.maxRoll = true;
break;
case Modifiers.Min:
modifiers.minRoll = true;
break;
case Modifiers.Nominal:
modifiers.nominalRoll = true;
break;
case Modifiers.SimulatedNominal:
if (args[i + 2] && parseInt(args[i + 2]).toString() === args[i + 2]) {
// Shift the ["-sn", " "] out so the next item is the amount
args.splice(i, 2);
modifiers.simulatedNominal = parseInt(args[i]);
} else {
modifiers.simulatedNominal = 10000;
}
break;
case Modifiers.ConfirmCrit:
modifiers.confirmCrit = true;
break;
case Modifiers.GM:
modifiers.gmRoll = true;
// -gm is a little more complex, as we must get all of the GMs that need to be DMd
log(LT.LOG, `Finding all GMs, checking args ${JSON.stringify(args)}`);
while (i + 2 < args.length && args[i + 2].startsWith('<@')) {
// Keep looping thru the rest of the args until one does not start with the discord mention code
modifiers.gms.push(args[i + 2].replace(/!/g, ''));
args.splice(i + 1, 2);
}
if (modifiers.gms.length < 1) {
// If -gm is on and none were found, throw an error
modifiers.error.name = 'NoGMsFound';
modifiers.error.message = 'Must specify at least one GM by @mentioning them';
modifiers.valid = false;
return [modifiers, args];
}
log(LT.LOG, `Found all GMs, ${modifiers.gms}`);
break;
case Modifiers.Order:
// Shift the -o out of the array so the next item is the direction
args.splice(i, 2);
if (!args[i] || (args[i].toLowerCase()[0] !== 'd' && args[i].toLowerCase()[0] !== 'a')) {
// If -o is on and asc or desc was not specified, error out
modifiers.error.name = 'NoOrderFound';
modifiers.error.message = 'Must specify `a` or `d` to order the rolls ascending or descending';
modifiers.valid = false;
return [modifiers, args];
}
modifiers.order = args[i].toLowerCase()[0];
break;
case Modifiers.CommaTotals:
modifiers.commaTotals = true;
break;
case Modifiers.RollDistribution:
modifiers.rollDist = true;
break;
case Modifiers.NumberVariables:
case Modifiers.VariablesNumber:
modifiers.numberVariables = true;
break;
case Modifiers.CustomDiceShapes: {
// Shift the -cd out of the array so the dice shapes are next
args.splice(i, 2);
const cdSyntaxMessage =
'Must specify at least one custom dice shape using the `name:[side1,side2,...,sideN]` syntax. If multiple custom dice shapes are needed, use a `;` to separate the list.';
const shapes = (args[i] ?? '').split(';').filter((x) => x);
if (!shapes.length) {
modifiers.error.name = 'NoShapesSpecified';
modifiers.error.message = `No custom shaped dice found.\n\n${cdSyntaxMessage}`;
modifiers.valid = false;
return [modifiers, args];
}
for (const shape of shapes) {
const [name, rawSides] = shape.split(':').filter((x) => x);
if (!name || !rawSides || !rawSides.includes('[') || !rawSides.includes(']')) {
modifiers.error.name = 'InvalidShapeSpecified';
modifiers.error.message = `One of the custom dice is not formatted correctly.\n\n${cdSyntaxMessage}`;
modifiers.valid = false;
return [modifiers, args];
}
if (modifiers.customDiceShapes.has(name)) {
modifiers.error.name = 'ShapeAlreadySpecified';
modifiers.error.message = `Shape \`${name}\` is already specified, please give it a different name.\n\n${cdSyntaxMessage}`;
modifiers.valid = false;
return [modifiers, args];
}
if (reservedCharacters.some((char) => name.includes(char))) {
modifiers.error.name = 'InvalidCharacterInCDName';
modifiers.error.message = `Custom dice names cannot include any of the following characters:\n${
JSON.stringify(
reservedCharacters,
)
}\n\n${cdSyntaxMessage}`;
modifiers.valid = false;
return [modifiers, args];
}
const sides = rawSides
.replaceAll('[', '')
.replaceAll(']', '')
.split(',')
.filter((x) => x)
.map((side) => parseFloat(side));
if (!sides.length) {
modifiers.error.name = 'NoCustomSidesSpecified';
modifiers.error.message = `No sides found for \`${name}\`.\n\n${cdSyntaxMessage}`;
modifiers.valid = false;
return [modifiers, args];
}
modifiers.customDiceShapes.set(name, sides);
}
log(LT.LOG, `Generated Custom Dice: ${JSON.stringify(modifiers.customDiceShapes.entries().toArray())}`);
break;
}
default:
// Default case should not mess with the array
defaultCase = true;
break;
}
if (!defaultCase) {
args.splice(i, 1);
i--;
}
}
// maxRoll, minRoll, nominalRoll, simulatedNominal cannot be on at same time, throw an error
if ([modifiers.maxRoll, modifiers.minRoll, modifiers.nominalRoll, modifiers.simulatedNominal].filter((b) => b).length > 1) {
modifiers.error.name = 'MaxAndNominal';
modifiers.error.message = 'Can only use one of the following at a time:\n`maximize`, `minimize`, `nominal`, `simulatedNominal`';
modifiers.valid = false;
}
// simulatedNominal and confirmCrit cannot be used at same time, throw an error
if ([modifiers.confirmCrit, modifiers.simulatedNominal].filter((b) => b).length > 1) {
modifiers.error.name = 'SimNominalAndCC';
modifiers.error.message = 'Cannot use the following at the same time:\n`confirmCrit`, `simulatedNominal`';
modifiers.valid = false;
}
// simulatedNominal cannot be greater than config.limits.simulatedNominal
if (modifiers.simulatedNominal > config.limits.simulatedNominal) {
modifiers.error.name = 'SimNominalTooBig';
modifiers.error.message = `Number of iterations for \`simulatedNominal\` cannot be greater than \`${config.limits.simulatedNominal}\``;
modifiers.valid = false;
}
return [modifiers, args];
};

View File

@ -1,541 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { CustomDiceShapes, RollConf } from 'artigen/dice/dice.d.ts';
import { DiceOptions, NumberlessDiceOptions } from 'artigen/dice/rollOptions.ts';
import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { addToRange, gtrAddToRange, ltAddToRange } from 'artigen/utils/rangeAdder.ts';
const throwDoubleSepError = (sep: string): void => {
throw new Error(`DoubleSeparator_${sep}`);
};
// Converts a rollStr into a machine readable rollConf
export const getRollConf = (rollStr: string, customTypes: CustomDiceShapes = new Map<string, number[]>()): RollConf => {
// Split the roll on the die size (and the drop if its there)
const dPts = rollStr.split('d');
// Initialize the configuration to store the parsed data
const rollConf: RollConf = {
type: '',
customType: null,
dieCount: 0,
dieSize: 0,
dPercent: {
on: false,
sizeAdjustment: 0,
critVal: 0,
},
drop: {
on: false,
count: 0,
},
keep: {
on: false,
count: 0,
},
dropHigh: {
on: false,
count: 0,
},
keepLow: {
on: false,
count: 0,
},
reroll: {
on: false,
once: false,
nums: [],
},
critScore: {
on: false,
range: [],
},
critFail: {
on: false,
range: [],
},
exploding: {
on: false,
once: false,
compounding: false,
penetrating: false,
nums: [],
},
match: {
on: false,
minCount: 2,
returnTotal: false,
},
sort: {
on: false,
direction: '',
},
success: {
on: false,
range: [],
},
fail: {
on: false,
range: [],
},
};
// If the dPts is not long enough, throw error
if (dPts.length < 2) {
throw new Error(`YouNeedAD_${rollStr}`);
}
// Fill out the die count, first item will either be an int or empty string, short circuit execution will take care of replacing the empty string with a 1
const rawDC = dPts.shift() || '1';
if (rawDC.includes('.')) {
throw new Error('WholeDieCountSizeOnly');
}
const tempDC = rawDC.replace(/\D/g, '');
const numberlessRawDC = rawDC.replace(/\d/g, '');
if (!tempDC && !numberlessRawDC) {
throw new Error(`CannotParseDieCount_${rawDC}`);
}
// Rejoin all remaining parts
let remains = dPts.join('d');
loggingEnabled && log(LT.LOG, `Initial breaking of rollStr ${rawDC} ${tempDC} ${dPts} ${remains}`);
// Manual Parsing for custom roll types
if (rawDC.endsWith('cwo')) {
// CWOD dice parsing
rollConf.type = 'cwod';
// Get CWOD parts, setting count and getting difficulty
const cwodParts = rollStr.split('cwod');
rollConf.dieCount = parseInt(cwodParts[0] || '1');
rollConf.dieSize = 10;
// Use success to set the difficulty
rollConf.success.on = true;
rollConf.fail.on = true;
addToRange('cwod', rollConf.fail.range, 1);
const tempDifficulty = (cwodParts[1] ?? '').search(/\d/) === 0 ? cwodParts[1] : '';
let afterDifficultyIdx = tempDifficulty.search(/[^\d]/);
if (afterDifficultyIdx === -1) {
afterDifficultyIdx = tempDifficulty.length;
}
const difficulty = parseInt(tempDifficulty.slice(0, afterDifficultyIdx) || '10');
for (let i = difficulty; i <= rollConf.dieSize; i++) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling cwod ${rollStr} | Parsing difficulty ${i}`);
rollConf.success.range.push(i);
}
// Remove any garbage from the remains
remains = remains.slice(afterDifficultyIdx);
} else if (rawDC.endsWith('ova')) {
// OVA dice parsing
rollConf.type = 'ova';
// Get OVA parts, setting count and getting difficulty
const ovaParts = rollStr.split('ovad');
const tempOvaPart1 = (ovaParts[1] ?? '').search(/\d/) === 0 ? ovaParts[1] : '';
if (tempOvaPart1.search(/\d+\.\d/) === 0) {
throw new Error('WholeDieCountSizeOnly');
}
rollConf.dieCount = parseInt(ovaParts[0] || '1');
let afterOvaSizeIdx = tempOvaPart1.search(/[^\d]/);
if (afterOvaSizeIdx === -1) {
afterOvaSizeIdx = tempOvaPart1.length;
}
rollConf.dieSize = parseInt(tempOvaPart1.slice(0, afterOvaSizeIdx) || '6');
// Remove any garbage from the remains
remains = remains.slice(afterOvaSizeIdx);
} else if (remains.startsWith('f')) {
// fate dice setup
rollConf.type = 'fate';
rollConf.dieCount = parseInt(tempDC);
// dieSize set to 1 as 1 is max face value, a six sided die is used internally
rollConf.dieSize = 1;
// remove F from the remains
remains = remains.slice(1);
} else if (customTypes.has(numberlessRawDC)) {
// custom dice setup
rollConf.type = 'custom';
rollConf.customType = numberlessRawDC;
rollConf.dieCount = isNaN(parseInt(tempDC ?? '1')) ? 1 : parseInt(tempDC ?? '1');
rollConf.dieSize = Math.max(...(customTypes.get(numberlessRawDC) ?? []));
} else {
// roll20 dice setup
rollConf.type = 'roll20';
rollConf.dieCount = parseInt(tempDC);
// Finds the end of the die size/beginning of the additional options
let afterDieIdx = dPts[0].search(/[^%\d]/);
if (afterDieIdx === -1) {
afterDieIdx = dPts[0].length;
}
// Get the die size out of the remains and into the rollConf
const rawDS = remains.slice(0, afterDieIdx);
remains = remains.slice(afterDieIdx);
if (rawDS.startsWith('%')) {
rollConf.dieSize = 10;
rollConf.dPercent.on = true;
const percentCount = rawDS.match(/%/g)?.length ?? 1;
rollConf.dPercent.sizeAdjustment = Math.pow(10, percentCount - 1);
rollConf.dPercent.critVal = Math.pow(10, percentCount) - rollConf.dPercent.sizeAdjustment;
} else {
rollConf.dieSize = parseInt(rawDS);
}
if (remains.search(/\.\d/) === 0) {
throw new Error('WholeDieCountSizeOnly');
}
}
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsed Die Count: ${rollConf.dieCount}`);
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | Parsed Die Size: ${rollConf.dieSize}`);
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | remains: ${remains}`);
if (!rollConf.dieCount || !rollConf.dieSize) {
throw new Error(`YouNeedAD_${rollStr}`);
}
// Finish parsing the roll
if (remains.length > 0) {
// Determine if the first item is a drop, and if it is, add the d back in
if (remains.search(/\D/) > 0 || remains.indexOf('l') === 0 || remains.indexOf('h') === 0) {
remains = `d${remains}`;
}
// Loop until all remaining args are parsed
while (remains.length > 0) {
loopCountCheck();
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
let afterSepIdx = remains.search(/[-\d]/);
if (afterSepIdx < 0) {
afterSepIdx = remains.length;
}
// Determine if afterSepIdx needs to be moved up (cases like mt! or !mt)
const tempSep = remains.slice(0, afterSepIdx);
let noNumberAfter = false;
NumberlessDiceOptions.some((opt) => {
loopCountCheck();
if (tempSep.startsWith(opt) && tempSep !== opt) {
afterSepIdx = opt.length;
noNumberAfter = true;
return true;
}
return tempSep === opt;
});
// Save the rule name to tSep and remove it from remains
const tSep = remains.slice(0, afterSepIdx);
remains = remains.slice(afterSepIdx);
// Find the next non-number in the remains to be able to cut out the count/num
let afterNumIdx = noNumberAfter ? 0 : remains.search(/(?![-\d])/);
if (afterNumIdx < 0) {
afterNumIdx = remains.length;
}
// Save the count/num to tNum leaving it in remains for the time being
const tNum = parseInt(remains.slice(0, afterNumIdx));
loggingEnabled && log(LT.LOG, `${getLoopCount()} tSep: ${tSep} ${afterSepIdx}, tNum: ${tNum} ${afterNumIdx}`);
// Switch on rule name
switch (tSep) {
case DiceOptions.Drop:
case DiceOptions.DropLow:
if (rollConf.drop.on) {
// Ensure we do not override existing settings
throwDoubleSepError(tSep);
}
// Configure Drop (Lowest)
rollConf.drop.on = true;
rollConf.drop.count = tNum;
break;
case DiceOptions.Keep:
case DiceOptions.KeepHigh:
if (rollConf.keep.on) {
// Ensure we do not override existing settings
throwDoubleSepError(tSep);
}
// Configure Keep (Highest)
rollConf.keep.on = true;
rollConf.keep.count = tNum;
break;
case DiceOptions.DropHigh:
if (rollConf.dropHigh.on) {
// Ensure we do not override existing settings
throwDoubleSepError(tSep);
}
// Configure Drop (Highest)
rollConf.dropHigh.on = true;
rollConf.dropHigh.count = tNum;
break;
case DiceOptions.KeepLow:
if (rollConf.keepLow.on) {
// Ensure we do not override existing settings
throwDoubleSepError(tSep);
}
// Configure Keep (Lowest)
rollConf.keepLow.on = true;
rollConf.keepLow.count = tNum;
break;
case DiceOptions.RerollOnce:
case DiceOptions.RerollOnceEqu:
rollConf.reroll.once = true;
// falls through as ro/ro= functions the same as r/r= in this context
case DiceOptions.Reroll:
case DiceOptions.RerollEqu:
// Configure Reroll (this can happen multiple times)
rollConf.reroll.on = true;
addToRange(tSep, rollConf.reroll.nums, tNum);
break;
case DiceOptions.RerollOnceGtr:
rollConf.reroll.once = true;
// falls through as ro> functions the same as r> in this context
case DiceOptions.RerollGtr:
// Configure reroll for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.reroll.on = true;
gtrAddToRange(tSep, rollConf.reroll.nums, tNum, rollConf.dieSize);
break;
case DiceOptions.RerollOnceLt:
rollConf.reroll.once = true;
// falls through as ro< functions the same as r< in this context
case DiceOptions.RerollLt:
// Configure reroll for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.reroll.on = true;
ltAddToRange(tSep, rollConf.reroll.nums, tNum, rollConf.type);
break;
case DiceOptions.CritSuccess:
case DiceOptions.CritSuccessEqu:
// Configure CritScore for one number (this can happen multiple times)
rollConf.critScore.on = true;
addToRange(tSep, rollConf.critScore.range, tNum);
break;
case DiceOptions.CritSuccessGtr:
// Configure CritScore for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true;
gtrAddToRange(tSep, rollConf.critScore.range, tNum, rollConf.dieSize);
break;
case DiceOptions.CritSuccessLt:
// Configure CritScore for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true;
ltAddToRange(tSep, rollConf.critScore.range, tNum, rollConf.type);
break;
case DiceOptions.CritFail:
case DiceOptions.CritFailEqu:
// Configure CritFail for one number (this can happen multiple times)
rollConf.critFail.on = true;
addToRange(tSep, rollConf.critFail.range, tNum);
break;
case DiceOptions.CritFailGtr:
// Configure CritFail for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true;
gtrAddToRange(tSep, rollConf.critFail.range, tNum, rollConf.dieSize);
break;
case DiceOptions.CritFailLt:
// Configure CritFail for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true;
ltAddToRange(tSep, rollConf.critFail.range, tNum, rollConf.type);
break;
case DiceOptions.Exploding:
case DiceOptions.ExplodeOnce:
case DiceOptions.PenetratingExplosion:
case DiceOptions.CompoundingExplosion:
// Configure Exploding
rollConf.exploding.on = true;
if (afterNumIdx > 0) {
// User gave a number to explode on, save it
addToRange(tSep, rollConf.exploding.nums, tNum);
}
break;
case DiceOptions.ExplodingEqu:
case DiceOptions.ExplodeOnceEqu:
case DiceOptions.PenetratingExplosionEqu:
case DiceOptions.CompoundingExplosionEqu:
// Configure Exploding (this can happen multiple times)
rollConf.exploding.on = true;
addToRange(tSep, rollConf.exploding.nums, tNum);
break;
case DiceOptions.ExplodingGtr:
case DiceOptions.ExplodeOnceGtr:
case DiceOptions.PenetratingExplosionGtr:
case DiceOptions.CompoundingExplosionGtr:
// Configure Exploding for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.exploding.on = true;
gtrAddToRange(tSep, rollConf.exploding.nums, tNum, rollConf.dieSize);
break;
case DiceOptions.ExplodingLt:
case DiceOptions.ExplodeOnceLt:
case DiceOptions.PenetratingExplosionLt:
case DiceOptions.CompoundingExplosionLt:
// Configure Exploding for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.exploding.on = true;
ltAddToRange(tSep, rollConf.exploding.nums, tNum, rollConf.type);
break;
case DiceOptions.MatchingTotal:
rollConf.match.returnTotal = true;
// falls through as mt functions the same as m in this context
case DiceOptions.Matching:
if (rollConf.match.on) {
// Ensure we do not override existing settings
throwDoubleSepError(tSep);
}
rollConf.match.on = true;
if (afterNumIdx > 0) {
// User gave a number to work with, save it
rollConf.match.minCount = tNum;
}
break;
case DiceOptions.Sort:
case DiceOptions.SortAsc:
if (rollConf.sort.on) {
// Ensure we do not override existing settings
throwDoubleSepError(tSep);
}
rollConf.sort.on = true;
rollConf.sort.direction = 'a';
break;
case DiceOptions.SortDesc:
if (rollConf.sort.on) {
// Ensure we do not override existing settings
throwDoubleSepError(tSep);
}
rollConf.sort.on = true;
rollConf.sort.direction = 'd';
break;
case DiceOptions.SuccessEqu:
// Configure success (this can happen multiple times)
rollConf.success.on = true;
addToRange(tSep, rollConf.success.range, tNum);
break;
case DiceOptions.SuccessGtr:
// Configure success for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.success.on = true;
gtrAddToRange(tSep, rollConf.success.range, tNum, rollConf.dieSize);
break;
case DiceOptions.SuccessLt:
// Configure success for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.success.on = true;
ltAddToRange(tSep, rollConf.success.range, tNum, rollConf.type);
break;
case DiceOptions.Fail:
case DiceOptions.FailEqu:
// Configure fail (this can happen multiple times)
rollConf.fail.on = true;
addToRange(tSep, rollConf.fail.range, tNum);
break;
case DiceOptions.FailGtr:
// Configure fail for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.fail.on = true;
gtrAddToRange(tSep, rollConf.fail.range, tNum, rollConf.dieSize);
break;
case DiceOptions.FailLt:
// Configure fail for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.fail.on = true;
ltAddToRange(tSep, rollConf.fail.range, tNum, rollConf.type);
break;
default:
// Throw error immediately if unknown op is encountered
throw new Error(`UnknownOperation_${tSep}`);
}
// Followup switch to avoid weird duplicated code
switch (tSep) {
case DiceOptions.ExplodeOnce:
case DiceOptions.ExplodeOnceLt:
case DiceOptions.ExplodeOnceGtr:
case DiceOptions.ExplodeOnceEqu:
rollConf.exploding.once = true;
break;
case DiceOptions.PenetratingExplosion:
case DiceOptions.PenetratingExplosionLt:
case DiceOptions.PenetratingExplosionGtr:
case DiceOptions.PenetratingExplosionEqu:
rollConf.exploding.penetrating = true;
break;
case DiceOptions.CompoundingExplosion:
case DiceOptions.CompoundingExplosionLt:
case DiceOptions.CompoundingExplosionGtr:
case DiceOptions.CompoundingExplosionEqu:
rollConf.exploding.compounding = true;
break;
}
// Finally slice off everything else parsed this loop
remains = remains.slice(afterNumIdx);
}
}
loggingEnabled && log(LT.LOG, `RollConf before cleanup: ${JSON.stringify(rollConf)}`);
// Verify the parse, throwing errors for every invalid config
if (rollConf.dieCount < 0) {
throw new Error('NoZerosAllowed_base');
}
if (rollConf.dieCount === 0 || rollConf.dieSize === 0) {
throw new Error('NoZerosAllowed_base');
}
// Since only one drop or keep option can be active, count how many are active to throw the right error
let dkdkCnt = 0;
[rollConf.drop.on, rollConf.keep.on, rollConf.dropHigh.on, rollConf.keepLow.on].forEach((e) => {
loggingEnabled && log(LT.LOG, `Handling ${rollConf.type} ${rollStr} | Checking if drop/keep is on ${e}`);
if (e) {
dkdkCnt++;
}
});
if (dkdkCnt > 1) {
throw new Error('FormattingError_dk');
}
if (rollConf.match.on && (rollConf.success.on || rollConf.fail.on)) {
throw new Error('FormattingError_mtsf');
}
if (rollConf.drop.on && rollConf.drop.count === 0) {
throw new Error('NoZerosAllowed_drop');
}
if (rollConf.keep.on && rollConf.keep.count === 0) {
throw new Error('NoZerosAllowed_keep');
}
if (rollConf.dropHigh.on && rollConf.dropHigh.count === 0) {
throw new Error('NoZerosAllowed_dropHigh');
}
if (rollConf.keepLow.on && rollConf.keepLow.count === 0) {
throw new Error('NoZerosAllowed_keepLow');
}
// Filter rollConf num lists to only include valid numbers
const validNumFilter = (curNum: number) => {
if (rollConf.type === 'fate') {
return [-1, 0, 1].includes(curNum);
}
return curNum <= rollConf.dieSize && curNum > (rollConf.dPercent.on ? -1 : 0);
};
rollConf.reroll.nums = rollConf.reroll.nums.filter(validNumFilter);
rollConf.critScore.range = rollConf.critScore.range.filter(validNumFilter);
rollConf.critFail.range = rollConf.critFail.range.filter(validNumFilter);
rollConf.exploding.nums = rollConf.exploding.nums.filter(validNumFilter);
rollConf.success.range = rollConf.success.range.filter(validNumFilter);
rollConf.fail.range = rollConf.fail.range.filter(validNumFilter);
if (rollConf.reroll.on && rollConf.reroll.nums.length === (rollConf.type === 'fate' ? 3 : rollConf.dieSize)) {
throw new Error('NoRerollOnAllSides');
}
loggingEnabled && log(LT.LOG, `RollConf after cleanup: ${JSON.stringify(rollConf)}`);
return rollConf;
};

View File

@ -1,284 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { ReturnData } from 'artigen/artigen.d.ts';
import { CountDetails, GroupConf, GroupResultFlags, RollDistributionMap, RollModifiers } from 'artigen/dice/dice.d.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { tokenizeMath } from 'artigen/math/mathTokenizer.ts';
import { closeInternalGrp, internalGrpWrapRegex, mathSplitRegex, openInternalGrp } from 'artigen/utils/escape.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { getMatchingGroupIdx, getMatchingInternalGrpIdx } from 'artigen/utils/parenBalance.ts';
import { getGroupConf } from 'artigen/dice/getGroupConf.ts';
import { compareOrigIdx, compareTotalRolls } from 'artigen/utils/sortFuncs.ts';
import { applyFlags } from '../utils/groupResultFlagger.ts';
export const handleGroup = (
groupParts: string[],
groupModifiers: string,
modifiers: RollModifiers,
previousResults: number[],
): [ReturnData[], CountDetails[], RollDistributionMap[]] => {
let retData: ReturnData;
const returnData: ReturnData[] = [];
const countDetails: CountDetails[] = [];
const rollDists: RollDistributionMap[] = [];
const groupConf: GroupConf = getGroupConf(groupModifiers, groupParts.join(''));
const prevGrpReturnData: ReturnData[] = [];
// Nested groups still exist, unwrap them
while (groupParts.includes('{')) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `Handling Nested Groups | Current cmd: ${JSON.stringify(groupParts)}`);
const openIdx = groupParts.indexOf('{');
const closeIdx = getMatchingGroupIdx(groupParts, openIdx);
const currentGrp = groupParts.slice(openIdx + 1, closeIdx);
// Try to find and "eat" any modifiers from the next groupPart
let thisGrpMods = '';
const possibleMods = groupParts[closeIdx + 1]?.trim() ?? '';
if (possibleMods.match(/^[dk<>=f].*/g)) {
const items = groupParts[closeIdx + 1].split(mathSplitRegex).filter((x) => x);
thisGrpMods = items.shift() ?? '';
groupParts[closeIdx + 1] = items.join('');
}
const [tempData, tempCounts, tempDists] = handleGroup(currentGrp, thisGrpMods, modifiers, previousResults);
const data = tempData[0];
loggingEnabled && log(LT.LOG, `Solved Nested Group is back ${JSON.stringify(data)} | ${JSON.stringify(tempCounts)} ${JSON.stringify(tempDists)}`);
countDetails.push(...tempCounts);
rollDists.push(...tempDists);
// Merge result back into groupParts
groupParts.splice(openIdx, closeIdx - openIdx + 1, `${openInternalGrp}${prevGrpReturnData.length}${closeInternalGrp}`);
prevGrpReturnData.push(data);
}
// Handle the items in the groups
const commaParts = groupParts
.join('')
.split(',')
.filter((x) => x);
if (commaParts.length > 1) {
loggingEnabled && log(LT.LOG, `In multi-mode ${JSON.stringify(commaParts)} ${groupModifiers} ${JSON.stringify(groupConf)}`);
// Handle "normal operation" of group
const groupResults: ReturnData[] = [];
for (const part of commaParts) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `Solving commaPart: ${part}`);
const [tempData, tempCounts, tempDists] = tokenizeMath(part, modifiers, previousResults, prevGrpReturnData);
const data = tempData[0];
loggingEnabled && log(LT.LOG, `Solved Math for Group is back ${JSON.stringify(data)} | ${JSON.stringify(tempCounts)} ${JSON.stringify(tempDists)}`);
countDetails.push(...tempCounts);
rollDists.push(...tempDists);
groupResults.push(data);
}
if (groupModifiers.trim()) {
// Handle the provided modifiers
const getTemplateFlags = (): GroupResultFlags => ({ dropped: false, success: false, failed: false });
// Assign original indexes
const resultFlags: GroupResultFlags[] = [];
groupResults.forEach((rd, idx) => {
rd.origIdx = idx;
resultFlags.push(getTemplateFlags());
});
// Handle drop/keep options
if (groupConf.drop.on || groupConf.keep.on || groupConf.dropHigh.on || groupConf.keepLow.on) {
groupResults.sort(compareTotalRolls);
let dropCount = 0;
// For normal drop and keep, simple subtraction is enough to determine how many to drop
// Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop
if (groupConf.drop.on) {
dropCount = groupConf.drop.count;
if (dropCount > groupResults.length) {
dropCount = groupResults.length;
}
} else if (groupConf.keep.on) {
dropCount = groupResults.length - groupConf.keep.count;
if (dropCount < 0) {
dropCount = 0;
}
} // For inverted drop and keep, order must be flipped to greatest to least before the simple subtraction can determine how many to drop
// Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop
else if (groupConf.dropHigh.on) {
groupResults.reverse();
dropCount = groupConf.dropHigh.count;
if (dropCount > groupResults.length) {
dropCount = groupResults.length;
}
} else if (groupConf.keepLow.on) {
groupResults.reverse();
dropCount = groupResults.length - groupConf.keepLow.count;
if (dropCount < 0) {
dropCount = 0;
}
}
let i = 0;
while (dropCount > 0 && i < groupResults.length) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `Handling group dropping | Dropping ${dropCount}`);
resultFlags[groupResults[i].origIdx ?? -1].dropped = true;
dropCount--;
i++;
}
groupResults.sort(compareOrigIdx);
}
let successCnt = 0;
let failCnt = 0;
if (groupConf.success.on || groupConf.fail.on) {
groupResults.forEach((rd, idx) => {
loopCountCheck();
if (!resultFlags[idx].dropped) {
if (
groupConf.success.on &&
(groupConf.success.range.includes(rd.rollTotal) ||
(groupConf.success.minValue !== null && rd.rollTotal >= groupConf.success.minValue) ||
(groupConf.success.maxValue !== null && rd.rollTotal <= groupConf.success.maxValue))
) {
successCnt++;
resultFlags[idx].success = true;
}
if (
groupConf.fail.on &&
(groupConf.fail.range.includes(rd.rollTotal) ||
(groupConf.fail.minValue !== null && rd.rollTotal >= groupConf.fail.minValue) ||
(groupConf.fail.maxValue !== null && rd.rollTotal <= groupConf.fail.maxValue))
) {
failCnt++;
resultFlags[idx].failed = true;
}
}
});
}
loggingEnabled && log(LT.LOG, `Current Group Results: ${JSON.stringify(groupResults)}`);
loggingEnabled && log(LT.LOG, `Applying group flags: ${JSON.stringify(resultFlags)}`);
const data = groupResults.reduce(
(prev, cur, idx) => ({
rollTotal: resultFlags[idx].dropped ? prev.rollTotal : prev.rollTotal + cur.rollTotal,
rollPreFormat: '',
rollPostFormat: '',
rollDetails: `${prev.rollDetails}${prev.rollDetails ? ', ' : ''}${applyFlags(cur.rollDetails, resultFlags[idx])}`,
containsCrit: resultFlags[idx].dropped ? prev.containsCrit : prev.containsCrit || cur.containsCrit,
containsFail: resultFlags[idx].dropped ? prev.containsFail : prev.containsFail || cur.containsFail,
initConfig: `${prev.initConfig}${prev.initConfig ? ', ' : ''}${cur.initConfig}`,
isComplex: prev.isComplex || cur.isComplex,
}),
{
rollTotal: 0,
rollPreFormat: '',
rollPostFormat: '',
rollDetails: '',
containsCrit: false,
containsFail: false,
initConfig: '',
isComplex: false,
},
);
data.initConfig = `{${data.initConfig}}${groupModifiers.replaceAll(' ', '')}`;
if (groupConf.success.on || groupConf.fail.on) {
data.rollTotal = 0;
}
if (groupConf.success.on) {
data.rollTotal += successCnt;
data.rollDetails += `, ${successCnt} Success${successCnt !== 1 ? 'es' : ''}`;
}
if (groupConf.fail.on) {
data.rollTotal -= failCnt;
data.rollDetails += `, ${failCnt} Fail${failCnt !== 1 ? 's' : ''}`;
}
data.rollDetails = `{${data.rollDetails}}`;
retData = data;
} else {
// Sum mode
const data = groupResults.reduce(
(prev, cur) => ({
rollTotal: prev.rollTotal + cur.rollTotal,
rollPreFormat: '',
rollPostFormat: '',
rollDetails: `${prev.rollDetails}${prev.rollDetails ? ' + ' : ''}${cur.rollDetails}`,
containsCrit: prev.containsCrit || cur.containsCrit,
containsFail: prev.containsFail || cur.containsFail,
initConfig: `${prev.initConfig}${prev.initConfig ? ', ' : ''}${cur.initConfig}`,
isComplex: prev.isComplex || cur.isComplex,
}),
{
rollTotal: 0,
rollPreFormat: '',
rollPostFormat: '',
rollDetails: '',
containsCrit: false,
containsFail: false,
initConfig: '',
isComplex: false,
},
);
data.initConfig = `{${data.initConfig}}`;
data.rollDetails = `{${data.rollDetails}}`;
retData = data;
}
} else {
loggingEnabled && log(LT.LOG, `In single-mode ${JSON.stringify(commaParts)} ${groupModifiers} ${JSON.stringify(groupConf)}`);
const [tempData, tempCounts, tempDists] = tokenizeMath(
commaParts[0],
modifiers,
previousResults,
prevGrpReturnData,
groupModifiers.trim() ? groupConf : null,
);
const data = tempData[0];
loggingEnabled && log(LT.LOG, `Solved Math for Group is back ${JSON.stringify(data)} | ${JSON.stringify(tempCounts)} ${JSON.stringify(tempDists)}`);
countDetails.push(...tempCounts);
rollDists.push(...tempDists);
data.initConfig = `{${data.initConfig}}${groupModifiers.trim() ? groupModifiers.replaceAll(' ', '') : ''}`;
data.rollDetails = `{${data.rollDetails}}`;
retData = data;
}
// Handle merging back any nested groups to prevent an internalGrp marker from sneaking out
const initConf = retData.initConfig.split(internalGrpWrapRegex).filter((x) => x);
loggingEnabled && log(LT.LOG, `Split retData into initConf ${JSON.stringify(initConf)}`);
while (initConf.includes(openInternalGrp)) {
loopCountCheck();
const openIdx = initConf.indexOf(openInternalGrp);
const closeIdx = getMatchingInternalGrpIdx(initConf, openIdx);
// Take first groupResult out of array
const dataToMerge = prevGrpReturnData.shift();
// Replace the found pair with the nested initConfig and result
initConf.splice(openIdx, closeIdx - openIdx + 1, `${dataToMerge?.initConfig}`);
loggingEnabled && log(LT.LOG, `Current initConf state ${JSON.stringify(initConf)}`);
}
retData.initConfig = initConf.join('');
returnData.push(retData);
return [returnData, countDetails, rollDists];
};

View File

@ -1,41 +0,0 @@
import { DPercentConf, RollConf, RollModifiers } from 'artigen/dice/dice.d.ts';
import { basicReducer } from 'artigen/utils/reducers.ts';
// genBasicRoll(size, modifiers, dPercent) returns number
// genBasicRoll rolls a die of size size and returns the result
const genBasicRoll = (size: number, modifiers: RollModifiers, dPercent: DPercentConf): number => {
let result;
if (modifiers.maxRoll) {
result = size;
} else if (modifiers.minRoll) {
result = 1;
} else {
// Math.random * size will return a decimal number between 0 and size (excluding size), so add 1 and floor the result to not get 0 as a result
result = modifiers.nominalRoll ? size / 2 + 0.5 : Math.floor(Math.random() * size + 1);
}
return dPercent.on ? (result - 1) * dPercent.sizeAdjustment : result;
};
const getRollFromArray = (sides: number[], modifiers: RollModifiers): number => {
if (modifiers.nominalRoll) {
return sides.reduce(basicReducer, 0) / sides.length;
} else if (modifiers.maxRoll) {
return Math.max(...sides);
} else if (modifiers.minRoll) {
return Math.min(...sides);
}
return sides[genBasicRoll(sides.length, modifiers, <DPercentConf> { on: false }) - 1];
};
export const generateRoll = (rollConf: RollConf, modifiers: RollModifiers): number => {
switch (rollConf.type) {
case 'fate':
return getRollFromArray([-1, -1, 0, 0, 1, 1], modifiers);
case 'custom':
return getRollFromArray(modifiers.customDiceShapes.get(rollConf.customType ?? '') ?? [], modifiers);
default:
return genBasicRoll(rollConf.dieSize, modifiers, rollConf.dPercent);
}
};

View File

@ -1,69 +0,0 @@
export const GroupOptions = Object.freeze({
Drop: 'd',
DropLow: 'dl',
DropHigh: 'dh',
Keep: 'k',
KeepLow: 'kl',
KeepHigh: 'kh',
SuccessLt: '<',
SuccessGtr: '>',
SuccessEqu: '=',
Fail: 'f',
FailLt: 'f<',
FailGtr: 'f>',
FailEqu: 'f=',
});
export const DiceOptions = Object.freeze({
...GroupOptions,
Reroll: 'r',
RerollLt: 'r<',
RerollGtr: 'r>',
RerollEqu: 'r=',
RerollOnce: 'ro',
RerollOnceLt: 'ro<',
RerollOnceGtr: 'ro>',
RerollOnceEqu: 'ro=',
CritSuccess: 'cs',
CritSuccessLt: 'cs<',
CritSuccessGtr: 'cs>',
CritSuccessEqu: 'cs=',
CritFail: 'cf',
CritFailLt: 'cf<',
CritFailGtr: 'cf>',
CritFailEqu: 'cf=',
Exploding: '!',
ExplodingLt: '!<',
ExplodingGtr: '!>',
ExplodingEqu: '!=',
ExplodeOnce: '!o',
ExplodeOnceLt: '!o<',
ExplodeOnceGtr: '!o>',
ExplodeOnceEqu: '!o=',
PenetratingExplosion: '!p',
PenetratingExplosionLt: '!p<',
PenetratingExplosionGtr: '!p>',
PenetratingExplosionEqu: '!p=',
CompoundingExplosion: '!!',
CompoundingExplosionLt: '!!<',
CompoundingExplosionGtr: '!!>',
CompoundingExplosionEqu: '!!=',
Matching: 'm',
MatchingTotal: 'mt',
Sort: 's',
SortAsc: 'sa',
SortDesc: 'sd',
});
// Should be ordered such that 'mt' will be encountered before 'm'
export const NumberlessDiceOptions = [
DiceOptions.SortDesc,
DiceOptions.SortAsc,
DiceOptions.Sort,
DiceOptions.MatchingTotal,
DiceOptions.Matching,
DiceOptions.CompoundingExplosion,
DiceOptions.PenetratingExplosion,
DiceOptions.ExplodeOnce,
DiceOptions.Exploding,
];

View File

@ -1,45 +0,0 @@
import { closeLog, initLog } from '@Log4Deno';
import { runCmd } from 'artigen/artigen.ts';
import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
loggingEnabled && initLog('logs/worker', loggingEnabled);
// Extend the BigInt prototype to support JSON.stringify
interface BigIntX extends BigInt {
// Convert to BigInt to string form in JSON.stringify
toJSON: () => string;
}
(BigInt.prototype as BigIntX).toJSON = function () {
return this.toString();
};
// Alert rollQueue that this worker is ready
self.postMessage('ready');
// Handle the roll
self.onmessage = async (e: MessageEvent<QueuedRoll>) => {
const payload = e.data;
const returnMsg = runCmd(payload) || {
error: true,
errorCode: 'EmptyMessage',
errorMsg: 'Error: Empty message',
line1: '',
line2: '',
line3: '',
counts: {
total: 0,
successful: 0,
failed: 0,
rerolled: 0,
dropped: 0,
exploded: 0,
},
};
self.postMessage(returnMsg);
loggingEnabled && (await closeLog());
self.close();
};

View File

@ -1,5 +0,0 @@
let currentWorkers = 0;
export const addWorker = () => currentWorkers++;
export const removeWorker = () => currentWorkers--;
export const getWorkerCnt = () => currentWorkers;

View File

@ -1,238 +0,0 @@
import { botId, DiscordenoMessage, Embed, FileContent, sendDirectMessage, sendMessage } from '@discordeno';
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
import { DEVMODE } from '~flags';
import { SolvedRoll } from 'artigen/artigen.d.ts';
import { RollModifiers } from 'artigen/dice/dice.d.ts';
import { removeWorker } from 'artigen/managers/countManager.ts';
import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { generateCountDetailsEmbed, generateDMFailed, generateRollDistsEmbed, generateRollEmbed } from 'artigen/utils/embeds.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import dbClient from 'db/client.ts';
import { queries } from 'db/common.ts';
import stdResp from 'endpoints/stdResponses.ts';
import utils from 'utils/utils.ts';
import { infoColor1 } from 'embeds/colors.ts';
import { basicReducer } from 'artigen/utils/reducers.ts';
export const onWorkerComplete = async (workerMessage: MessageEvent<SolvedRoll>, workerTimeout: number, rollRequest: QueuedRoll) => {
let apiErroredOut = false;
try {
removeWorker();
clearTimeout(workerTimeout);
const returnMsg = workerMessage.data;
loggingEnabled && log(LT.LOG, `Roll came back from worker: ${returnMsg.line1.length} |&| ${returnMsg.line2.length} |&| ${returnMsg.line3.length} `);
loggingEnabled && log(LT.LOG, `Roll came back from worker: ${returnMsg.line1} |&| ${returnMsg.line2} |&| ${returnMsg.line3} `);
const pubEmbedDetails = generateRollEmbed(
rollRequest.apiRoll ? rollRequest.api.userId : rollRequest.dd.originalMessage.authorId,
returnMsg,
rollRequest.modifiers,
);
const gmEmbedDetails = generateRollEmbed(rollRequest.apiRoll ? rollRequest.api.userId : rollRequest.dd.originalMessage.authorId, returnMsg, {
...rollRequest.modifiers,
gmRoll: false,
});
let pubRespCharCount = pubEmbedDetails.charCount;
let gmRespCharCount = gmEmbedDetails.charCount;
const pubEmbeds: Embed[] = [pubEmbedDetails.embed];
const gmEmbeds: Embed[] = [gmEmbedDetails.embed];
const pubAttachments: FileContent[] = pubEmbedDetails.hasAttachment ? [pubEmbedDetails.attachment] : [];
const gmAttachments: FileContent[] = gmEmbedDetails.hasAttachment ? [gmEmbedDetails.attachment] : [];
// Handle adding count embed to correct list
if (rollRequest.modifiers.count) {
const countEmbed = generateCountDetailsEmbed(returnMsg.counts);
if (rollRequest.modifiers.gmRoll) {
gmEmbeds.push(countEmbed.embed);
gmRespCharCount += countEmbed.charCount;
} else {
pubEmbeds.push(countEmbed.embed);
pubRespCharCount += countEmbed.charCount;
}
}
// Handle adding rollDist embed to correct list
if (rollRequest.modifiers.rollDist) {
const rollDistEmbed = generateRollDistsEmbed(returnMsg.rollDistributions);
if (rollRequest.modifiers.gmRoll) {
gmEmbeds.push(rollDistEmbed.embed);
rollDistEmbed.hasAttachment && gmAttachments.push(rollDistEmbed.attachment);
gmRespCharCount += rollDistEmbed.charCount;
} else {
pubEmbeds.push(rollDistEmbed.embed);
rollDistEmbed.hasAttachment && pubAttachments.push(rollDistEmbed.attachment);
pubRespCharCount += rollDistEmbed.charCount;
}
}
loggingEnabled && log(LT.LOG, `Embeds are generated: ${pubRespCharCount} ${JSON.stringify(pubEmbeds)} |&| ${gmRespCharCount} ${JSON.stringify(gmEmbeds)}`);
// If there was an error, report it to the user in hopes that they can determine what they did wrong
if (returnMsg.error) {
if (rollRequest.apiRoll) {
rollRequest.api.resolve(stdResp.InternalServerError(returnMsg.errorMsg));
} else {
rollRequest.dd.myResponse.edit({ embeds: pubEmbeds });
}
if (rollRequest.apiRoll || (DEVMODE && config.logRolls)) {
// If enabled, log rolls so we can see what went wrong
dbClient
.execute(queries.insertRollLogCmd(rollRequest.apiRoll ? 1 : 0, 1), [
rollRequest.originalCommand,
returnMsg.errorCode,
rollRequest.apiRoll ? null : rollRequest.dd.myResponse.id,
])
.catch((e) => utils.commonLoggers.dbError('rollQueue.ts:82', 'insert into', e));
}
return;
}
let newMsg: DiscordenoMessage | void = undefined;
// Determine if we are to send a GM roll or a normal roll
if (rollRequest.modifiers.gmRoll) {
if (rollRequest.apiRoll) {
newMsg = await sendMessage(rollRequest.api.channelId, {
content: rollRequest.modifiers.apiWarn,
embeds: pubEmbeds,
}).catch(() => {
apiErroredOut = true;
rollRequest.api.resolve(stdResp.InternalServerError('Message failed to send - location 0.'));
});
} else {
// Send the public embed to correct channel
rollRequest.dd.myResponse.edit({ embeds: pubEmbeds });
}
if (!apiErroredOut) {
// And message the full details to each of the GMs, alerting roller of every GM that could not be messaged
rollRequest.modifiers.gms.forEach(async (gm) => {
const gmId: bigint = BigInt(gm.startsWith('<') ? gm.substring(2, gm.length - 1) : gm);
log(LT.LOG, `Messaging GM ${gm} | ${gmId}`);
// Attempt to DM the GM and send a warning if it could not DM a GM
await sendDirectMessage(gmId, {
content: `Original GM Roll Request: ${rollRequest.apiRoll ? newMsg && newMsg.link : rollRequest.dd.myResponse.link}`,
embeds: gmEmbeds,
})
.then(async () => {
// Check if we need to attach a file and send it after the initial details sent
if (gmAttachments.length) {
await sendDirectMessage(gmId, {
file: gmAttachments,
}).catch(() => {
if (newMsg && rollRequest.apiRoll) {
newMsg.reply(generateDMFailed(gmId));
} else if (!rollRequest.apiRoll) {
rollRequest.dd.originalMessage.reply(generateDMFailed(gmId));
}
});
}
})
.catch(() => {
if (rollRequest.apiRoll && newMsg) {
newMsg.reply(generateDMFailed(gmId));
} else if (!rollRequest.apiRoll) {
rollRequest.dd.originalMessage.reply(generateDMFailed(gmId));
}
});
});
}
} else {
// Not a gm roll, so just send normal embed to correct channel
if (rollRequest.apiRoll) {
newMsg = await sendMessage(rollRequest.api.channelId, {
content: rollRequest.modifiers.apiWarn,
embeds: pubEmbeds,
}).catch(() => {
apiErroredOut = true;
rollRequest.api.resolve(stdResp.InternalServerError('Message failed to send - location 1.'));
});
} else {
newMsg = await rollRequest.dd.myResponse.edit({
embeds: pubEmbeds,
});
}
if (pubAttachments.length && newMsg) {
// Attachment requires you to send a new message
const respMessage: Embed[] = [
{
color: infoColor1,
description: `This message contains information for a previous roll.\nPlease click on "<@${botId}> *Click to see attachment*" above this message to see the previous roll.`,
},
];
if (pubAttachments.map((file) => file.blob.size).reduce(basicReducer, 0) < config.maxFileSize) {
// All attachments will fit in one message
newMsg.reply({
embeds: respMessage,
file: pubAttachments,
});
} else {
pubAttachments.forEach((file) => {
newMsg &&
newMsg.reply({
embeds: respMessage,
file,
});
});
}
}
}
if (rollRequest.apiRoll && !apiErroredOut) {
dbClient
.execute(queries.insertRollLogCmd(1, 0), [rollRequest.originalCommand, returnMsg.errorCode, newMsg ? newMsg.id : null])
.catch((e) => utils.commonLoggers.dbError('rollQueue.ts:155', 'insert into', e));
rollRequest.api.resolve(
stdResp.OK(
JSON.stringify(
rollRequest.modifiers.count
? {
counts: returnMsg.counts,
details: pubEmbedDetails,
}
: {
details: pubEmbedDetails,
},
),
),
);
}
} catch (e) {
log(LT.ERROR, `Unhandled rollRequest Error: ${JSON.stringify(e)}`);
if (!rollRequest.apiRoll) {
rollRequest.dd.myResponse.edit({
embeds: [
(
await generateRollEmbed(
rollRequest.dd.originalMessage.authorId,
<SolvedRoll> {
error: true,
errorMsg:
`Something weird went wrong, likely the requested roll is too complex and caused the response to be too large for Discord. Try breaking the request down into smaller messages and try again.\n\nIf this error continues to come up, please \`${config.prefix}report\` this to my developer.`,
errorCode: 'UnhandledWorkerComplete',
},
<RollModifiers> {},
)
).embed,
],
});
}
if (rollRequest.apiRoll && !apiErroredOut) {
rollRequest.api.resolve(stdResp.InternalServerError(JSON.stringify(e)));
}
}
};

View File

@ -1,10 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
export const onWorkerReady = (rollWorker: Worker, rollRequest: QueuedRoll) => {
loggingEnabled && log(LT.LOG, `Sending roll to worker: ${rollRequest.rollCmd}, ${JSON.stringify(rollRequest.modifiers)}`);
rollWorker.postMessage(rollRequest);
};

View File

@ -1,39 +0,0 @@
import { SolvedRoll } from 'artigen/artigen.d.ts';
import { RollModifiers } from 'artigen/dice/dice.d.ts';
import { removeWorker } from 'artigen/managers/countManager.ts';
import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { generateRollEmbed } from 'artigen/utils/embeds.ts';
import stdResp from 'endpoints/stdResponses.ts';
import utils from 'utils/utils.ts';
export const terminateWorker = async (rollWorker: Worker, rollRequest: QueuedRoll) => {
rollWorker.terminate();
removeWorker();
if (rollRequest.apiRoll) {
rollRequest.api.resolve(stdResp.RequestTimeout('Roll took too long to process, try breaking roll down into simpler parts'));
} else {
rollRequest.dd.myResponse
.edit({
embeds: [
(
await generateRollEmbed(
rollRequest.dd.originalMessage.authorId,
<SolvedRoll> {
error: true,
errorCode: 'TooComplex',
errorMsg: 'Error: Roll took too long to process, try breaking roll down into simpler parts',
},
<RollModifiers> {},
)
).embed,
],
})
.catch((e) => utils.commonLoggers.messageEditError('rollQueue.ts:51', rollRequest.dd.myResponse, e));
}
};

View File

@ -1,13 +0,0 @@
import config from '~config';
let loopCount = 0;
// Will ensure if maxLoops is 10, 10 loops will be allowed, 11 will not.
export const loopCountCheck = (): void => {
loopCount++;
if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded');
}
};
export const getLoopCount = (): number => loopCount;

View File

@ -1,26 +0,0 @@
import { DiscordenoMessage } from '@discordeno';
import { RollModifiers } from 'artigen/dice/dice.d.ts';
// QueuedRoll is the structure to track rolls we could not immediately handle
interface BaseQueuedRoll {
rollCmd: string;
modifiers: RollModifiers;
originalCommand: string;
}
export interface ApiQueuedRoll extends BaseQueuedRoll {
apiRoll: true;
api: {
resolve: (value: Response | PromiseLike<Response>) => void;
channelId: bigint;
userId: bigint;
};
}
export interface DDQueuedRoll extends BaseQueuedRoll {
apiRoll: false;
dd: {
myResponse: DiscordenoMessage;
originalMessage: DiscordenoMessage;
};
}
export type QueuedRoll = ApiQueuedRoll | DDQueuedRoll;

View File

@ -1,56 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
import { getWorkerCnt } from 'artigen/managers/countManager.ts';
import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { handleRollRequest } from 'artigen/managers/workerManager.ts';
import { rollingEmbed } from 'artigen/utils/embeds.ts';
import { infoColor2 } from 'embeds/colors.ts';
import utils from 'utils/utils.ts';
const rollQueue: Array<QueuedRoll> = [];
// Runs the roll or queues it depending on how many workers are currently running
export const sendRollRequest = (rollRequest: QueuedRoll) => {
if (rollRequest.apiRoll) {
handleRollRequest(rollRequest);
} else if (!rollQueue.length && getWorkerCnt() < config.limits.maxWorkers) {
handleRollRequest(rollRequest);
} else {
rollQueue.push(rollRequest);
rollRequest.dd.myResponse
.edit({
embeds: [
{
color: infoColor2,
title: `${config.name} currently has its hands full and has queued your roll.`,
description: `There are currently ${getWorkerCnt() + rollQueue.length} rolls ahead of this roll.
The results for this roll will replace this message when it is done.`,
},
],
})
.catch((e: Error) => utils.commonLoggers.messageEditError('rollQueue.ts:197', rollRequest.dd.myResponse, e));
}
};
// Checks the queue constantly to make sure the queue stays empty
setInterval(() => {
log(
LT.LOG,
`Checking rollQueue for items, rollQueue length: ${rollQueue.length}, currentWorkers: ${getWorkerCnt()}, config.limits.maxWorkers: ${config.limits.maxWorkers}`,
);
if (rollQueue.length && getWorkerCnt() < config.limits.maxWorkers) {
const rollRequest = rollQueue.shift();
if (rollRequest && !rollRequest.apiRoll) {
rollRequest.dd.myResponse.edit(rollingEmbed).catch((e: Error) => utils.commonLoggers.messageEditError('rollQueue.ts:208', rollRequest.dd.myResponse, e));
handleRollRequest(rollRequest);
} else if (rollRequest && rollRequest.apiRoll) {
handleRollRequest(rollRequest);
}
}
}, 1000);

View File

@ -1,23 +0,0 @@
import config from '~config';
import { addWorker } from 'artigen/managers/countManager.ts';
import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { onWorkerComplete } from 'artigen/managers/handler/workerComplete.ts';
import { onWorkerReady } from 'artigen/managers/handler/workerReady.ts';
import { terminateWorker } from 'artigen/managers/handler/workerTerminate.ts';
export const handleRollRequest = (rollRequest: QueuedRoll) => {
// Handle setting up and calling the rollWorker
addWorker();
const rollWorker = new Worker(new URL('./artigenWorker.ts', import.meta.url).href, { type: 'module' });
const workerTimeout = setTimeout(() => terminateWorker(rollWorker, rollRequest), config.limits.workerTimeout);
// Handle events from the worker
rollWorker.addEventListener('message', (workerMessage) => {
if (workerMessage.data === 'ready') {
return onWorkerReady(rollWorker, rollRequest);
}
onWorkerComplete(workerMessage, workerTimeout, rollRequest);
});
};

View File

@ -1,11 +0,0 @@
// SolvedStep is used to preserve information while math is being performed on the roll
export interface SolvedStep {
total: number;
details: string;
containsCrit: boolean;
containsFail: boolean;
isComplex: boolean;
}
// Joined type for mathConf as its a "WIP" variable and moved everything from string->number->SolvedStep
export type MathConf = string | number | SolvedStep;

View File

@ -1,219 +0,0 @@
/* The Artificer was built in memory of Babka
* With love, Ean
*
* December 21, 2020
*/
import { log, LogTypes as LT } from '@Log4Deno';
import { MathConf, SolvedStep } from 'artigen/math/math.d.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { legalMath, legalMathOperators } from 'artigen/utils/legalMath.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { getMatchingParenIdx } from 'artigen/utils/parenBalance.ts';
// mathSolver(conf, wrapDetails) returns one condensed SolvedStep
// mathSolver is a function that recursively solves the full roll and math
export const mathSolver = (conf: MathConf[], wrapDetails = false): SolvedStep => {
// Initialize PEMDAS
const signs = ['^', '**', '*', '/', '%', '+', '-'];
const stepSolve: SolvedStep = {
total: 0,
details: '',
containsCrit: false,
containsFail: false,
isComplex: false,
};
// If entering with a single number, note it now
let singleNum = false;
if (conf.length === 1) {
singleNum = true;
}
// Evaluate all parenthesis
while (conf.includes('(')) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`);
// Get first open parenthesis
let openParenIdx = conf.indexOf('(');
const closeParenIdx = getMatchingParenIdx(conf, openParenIdx);
// Call the solver on the items between openParenIdx and closeParenIdx (excluding the parens)
const parenSolve = mathSolver(conf.slice(openParenIdx + 1, closeParenIdx), true);
// Replace the items between openParenIdx and closeParenIdx (including the parens) with its solved equivalent
conf.splice(openParenIdx, closeParenIdx - openParenIdx + 1, parenSolve);
// Determine if previous idx is a Math operator and execute it
if (openParenIdx - 1 > -1 && legalMathOperators.includes(conf[openParenIdx - 1].toString())) {
// Update total and details of parenSolve
parenSolve.total = legalMath[legalMathOperators.indexOf(conf[openParenIdx - 1].toString())](parenSolve.total);
parenSolve.details = `${conf[openParenIdx - 1]}${parenSolve.details}`;
conf.splice(openParenIdx - 1, 2, parenSolve);
// shift openParenIdx as we have just removed something before it
openParenIdx--;
}
// Determining if we need to add in a multiplication sign to handle implicit multiplication (like "(4)2" = 8)
// Check if a number was directly before openParenIdx and slip in the "*" if needed
if (openParenIdx - 1 > -1 && !signs.includes(conf[openParenIdx - 1].toString())) {
conf.splice(openParenIdx, 0, '*');
// shift openParenIdx as we have just added something before it
openParenIdx++;
}
// Check if a number is directly after the closing paren and slip in the "*" if needed
// openParenIdx is used here as the conf array has already been collapsed down
if (openParenIdx + 1 < conf.length && !signs.includes(conf[openParenIdx + 1].toString())) {
conf.splice(openParenIdx + 1, 0, '*');
}
}
// Look for any implicit multiplication that may have been missed
// 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)}`);
for (let i = 1; i < conf.length; i++) {
loopCountCheck();
const prevConfAsStr = <string> conf[i - 1];
const curConfAsStr = <string> conf[i];
if (!signs.includes(curConfAsStr) && !signs.includes(prevConfAsStr)) {
// Both previous and current conf are operators, slip in the "*"
conf.splice(i, 0, '*');
}
}
// At this point, conf should be [num, op, num, op, num, op, num, etc]
// Evaluate all EMDAS by looping thru each tier of operators (exponential is the highest tier, addition/subtraction the lowest)
const allCurOps = [
['^', '**'],
['*', '/', '%'],
['+', '-'],
];
allCurOps.forEach((curOps) => {
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Evaluating ${JSON.stringify(curOps)}`);
// Iterate thru all operators/operands in the conf
for (let i = 0; i < conf.length; i++) {
loopCountCheck();
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
if (curOps.includes(conf[i].toString())) {
// Grab the operands from before and after the operator
const operand1 = conf[i - 1];
const operand2 = conf[i + 1];
// Init temp math to NaN to catch bad parsing
let oper1 = NaN;
let oper2 = NaN;
const subStepSolve: SolvedStep = {
total: NaN,
details: '',
containsCrit: false,
containsFail: false,
isComplex: false,
};
// If operand1 is a SolvedStep, populate our subStepSolve with its details and crit/fail flags
if (typeof operand1 === 'object') {
oper1 = operand1.total;
subStepSolve.details = `${operand1.details}\\${conf[i]}`;
subStepSolve.containsCrit = operand1.containsCrit;
subStepSolve.containsFail = operand1.containsFail;
subStepSolve.isComplex = operand1.isComplex;
} else {
// else parse it as a number and add it to the subStep details
if (operand1 || operand1 == 0) {
oper1 = parseFloat(operand1.toString());
subStepSolve.details = `${oper1.toString()}\\${conf[i]}`;
}
}
// If operand2 is a SolvedStep, populate our subStepSolve with its details without overriding what operand1 filled in
if (typeof operand2 === 'object') {
oper2 = operand2.total;
subStepSolve.details += operand2.details;
subStepSolve.containsCrit = subStepSolve.containsCrit || operand2.containsCrit;
subStepSolve.containsFail = subStepSolve.containsFail || operand2.containsFail;
subStepSolve.isComplex = subStepSolve.isComplex || operand2.isComplex;
} else {
// else parse it as a number and add it to the subStep details
oper2 = parseFloat(operand2.toString());
subStepSolve.details += oper2;
}
// Make sure neither operand is NaN before continuing
if (isNaN(oper1) || isNaN(oper2)) {
throw new Error('OperandNaN');
}
// Verify a second time that both are numbers before doing math, throwing an error if necessary
if (typeof oper1 === 'number' && typeof oper2 === 'number') {
// Finally do the operator on the operands, throw an error if the operator is not found
switch (conf[i]) {
case '^':
case '**':
subStepSolve.total = Math.pow(oper1, oper2);
break;
case '*':
subStepSolve.total = oper1 * oper2;
break;
case '/':
subStepSolve.total = oper1 / oper2;
break;
case '%':
subStepSolve.total = oper1 % oper2;
break;
case '+':
subStepSolve.total = oper1 + oper2;
break;
case '-':
subStepSolve.total = oper1 - oper2;
break;
default:
throw new Error('OperatorWhat');
}
} else {
throw new Error('EMDASNotNumber');
}
// Replace the two operands and their operator with our subStepSolve
conf.splice(i - 1, 3, subStepSolve);
// Because we are messing around with the array we are iterating thru, we need to back up one idx to make sure every operator gets processed
i--;
}
}
});
// If we somehow have more than one item left in conf at this point, something broke, throw an error
if (conf.length > 1) {
loggingEnabled && log(LT.LOG, `ConfWHAT? ${JSON.stringify(conf)}`);
throw new Error('ConfWhat');
} else if (singleNum && typeof conf[0] === 'number') {
// If we are only left with a number, populate the stepSolve with it
stepSolve.total = conf[0];
stepSolve.details = conf[0].toString();
} else {
// Else fully populate the stepSolve with what was computed
const tempConf = <SolvedStep> conf[0];
stepSolve.total = tempConf.total;
stepSolve.details = tempConf.details;
stepSolve.containsCrit = tempConf.containsCrit;
stepSolve.containsFail = tempConf.containsFail;
stepSolve.isComplex = tempConf.isComplex;
}
// If this was a nested call, add on parens around the details to show what math we've done
if (wrapDetails) {
stepSolve.details = `(${stepSolve.details})`;
}
// If our total has reached undefined for some reason, error out now
if (stepSolve.total === undefined) {
throw new Error('UndefinedStep');
}
return stepSolve;
};

View File

@ -1,334 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { ReturnData } from 'artigen/artigen.d.ts';
import { MathConf, SolvedStep } from 'artigen/math/math.d.ts';
import { CountDetails, ExecutedRoll, GroupConf, RollDistributionMap, RollModifiers, RollSet } from 'artigen/dice/dice.d.ts';
import { formatRoll } from 'artigen/dice/generateFormattedRoll.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { mathSolver } from 'artigen/math/mathSolver.ts';
import { closeInternalGrp, cmdSplitRegex, internalWrapRegex, mathSplitRegex, openInternalGrp } from 'artigen/utils/escape.ts';
import { legalMathOperators } from 'artigen/utils/legalMath.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { assertParenBalance } from 'artigen/utils/parenBalance.ts';
import { executeRoll } from 'artigen/dice/executeRoll.ts';
import { compareOrigIdx, compareRolls } from 'artigen/utils/sortFuncs.ts';
// minusOps are operators that will cause a negative sign to collapse into a number (in cases like + - 1)
const minusOps = ['(', '^', '**', '*', '/', '%', '+', '-'];
const allOps = [...minusOps, ')'];
export const tokenizeMath = (
cmd: string,
modifiers: RollModifiers,
previousResults: number[],
groupResults: ReturnData[],
groupConf: GroupConf | null = null,
): [ReturnData[], CountDetails[], RollDistributionMap[]] => {
const countDetails: CountDetails[] = [];
const rollDists: RollDistributionMap[] = [];
const executedRolls: Map<number, ExecutedRoll> = new Map();
loggingEnabled && log(LT.LOG, `Parsing roll ${cmd} | ${JSON.stringify(modifiers)} | ${JSON.stringify(previousResults)}`);
// Remove all spaces from the operation config and split it by any operator (keeping the operator in mathConf for fullSolver to do math on)
const mathConf: MathConf[] = cmd
.replace(cmdSplitRegex, '')
.replace(internalWrapRegex, '')
.replace(/ /g, '')
.split(mathSplitRegex)
.filter((x) => x);
loggingEnabled && log(LT.LOG, `Split roll into mathConf ${JSON.stringify(mathConf)}`);
// Verify balanced parens before doing anything
assertParenBalance(mathConf);
// Evaluate all rolls into stepSolve format and all numbers into floats
for (let i = 0; i < mathConf.length; i++) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `Parsing roll ${JSON.stringify(cmd)} | Evaluating rolls into math-able items ${JSON.stringify(mathConf[i])}`);
const curMathConfStr = mathConf[i].toString();
if (curMathConfStr.length === 0) {
// If its an empty string, get it out of here
mathConf.splice(i, 1);
i--;
} else if (mathConf[i] == parseFloat(curMathConfStr)) {
// If its a number, parse the number out
mathConf[i] = parseFloat(curMathConfStr);
} else if (curMathConfStr.startsWith(openInternalGrp)) {
const groupIdx = parseInt(curMathConfStr.substring(1, curMathConfStr.indexOf(closeInternalGrp)));
if (groupIdx >= groupResults.length) {
throw new Error('InternalGroupMachineBroke');
}
mathConf[i] = {
total: groupResults[groupIdx].rollTotal,
details: groupResults[groupIdx].rollDetails,
containsCrit: groupResults[groupIdx].containsCrit,
containsFail: groupResults[groupIdx].containsFail,
isComplex: groupResults[groupIdx].isComplex,
};
} else if (curMathConfStr.toLowerCase() === 'e') {
// If the operand is the constant e, create a SolvedStep for it
mathConf[i] = {
total: Math.E,
details: '*e*',
containsCrit: false,
containsFail: false,
isComplex: false,
};
} else if (curMathConfStr.toLowerCase() === 'lemon' || curMathConfStr.toLowerCase() === '🍋') {
mathConf[i] = {
total: 5,
details: '🍋',
containsCrit: false,
containsFail: false,
isComplex: false,
};
} else if (curMathConfStr.toLowerCase() === 'horse' || curMathConfStr.toLowerCase() === '🐴') {
mathConf[i] = {
total: Math.sqrt(3),
details: '🐴',
containsCrit: false,
containsFail: false,
isComplex: false,
};
} else if (curMathConfStr.toLowerCase() === 'fart' || curMathConfStr.toLowerCase() === '💩') {
mathConf[i] = {
total: 7,
details: '💩',
containsCrit: false,
containsFail: false,
isComplex: false,
};
} else if (curMathConfStr.toLowerCase() === 'sex' || curMathConfStr.toLowerCase() === '🍆🍑' || curMathConfStr.toLowerCase() === '🍑🍆') {
mathConf[i] = {
total: 69,
details: '( ͡° ͜ʖ ͡°)',
containsCrit: false,
containsFail: false,
isComplex: false,
};
} else if (curMathConfStr.toLowerCase() === 'inf' || curMathConfStr.toLowerCase() === 'infinity' || curMathConfStr.toLowerCase() === '∞') {
// If the operand is the constant Infinity, create a SolvedStep for it
mathConf[i] = {
total: Infinity,
details: '∞',
containsCrit: false,
containsFail: false,
isComplex: false,
};
} else if (curMathConfStr.toLowerCase() === 'pi' || curMathConfStr.toLowerCase() === '𝜋') {
// If the operand is the constant pi, create a SolvedStep for it
mathConf[i] = {
total: Math.PI,
details: '𝜋',
containsCrit: false,
containsFail: false,
isComplex: false,
};
} else if (curMathConfStr.toLowerCase() === 'pie' || curMathConfStr.toLowerCase() === '🥧') {
// If the operand is pie, pi*e, create a SolvedStep for e and pi (and the multiplication symbol between them)
mathConf[i] = {
total: Math.PI,
details: '𝜋',
containsCrit: false,
containsFail: false,
isComplex: false,
};
mathConf.splice(
i + 1,
0,
...[
'*',
{
total: Math.E,
details: '*e*',
containsCrit: false,
containsFail: false,
isComplex: false,
},
],
);
i += 2;
} else if (!legalMathOperators.includes(curMathConfStr) && legalMathOperators.some((mathOp) => curMathConfStr.endsWith(mathOp))) {
// Identify when someone does something weird like 4floor(2.5) and split 4 and floor
const matchedMathOp = legalMathOperators.filter((mathOp) => curMathConfStr.endsWith(mathOp))[0];
mathConf[i] = parseFloat(curMathConfStr.replace(matchedMathOp, ''));
mathConf.splice(i + 1, 0, ...['*', matchedMathOp]);
i += 2;
} else if (/(x\d+(\.\d*)?)/.test(curMathConfStr)) {
// Identify when someone is using a variable from previous commands
if (curMathConfStr.includes('.')) {
// Verify someone did not enter x1.1 as a variable
throw new Error(`IllegalVariable_${curMathConfStr}`);
}
const varIdx = parseInt(curMathConfStr.replaceAll('x', ''));
// Get the index from the variable and attempt to use it to query the previousResults
if (previousResults.length > varIdx) {
mathConf[i] = parseFloat(previousResults[varIdx].toString());
} else {
throw new Error(`IllegalVariable_${curMathConfStr}`);
}
} else if (![...allOps, ...legalMathOperators].includes(curMathConfStr)) {
// If nothing else has handled it by now, try it as a roll
const executedRoll = executeRoll(curMathConfStr, modifiers);
if (groupConf) {
executedRolls.set(i, executedRoll);
} else {
const formattedRoll = formatRoll(executedRoll, modifiers);
mathConf[i] = formattedRoll.solvedStep;
countDetails.push(formattedRoll.countDetails);
rollDists.push(formattedRoll.rollDistributions);
}
}
// Identify if we are in a state where the current number is a negative number
if (mathConf[i - 1] === '-' && ((!mathConf[i - 2] && mathConf[i - 2] !== 0) || minusOps.includes(<string> mathConf[i - 2]))) {
if (typeof mathConf[i] === 'string') {
// Current item is a mathOp, need to insert a "-1 *" before it
mathConf.splice(i - 1, 1, ...[parseFloat('-1'), '*']);
i += 2;
} else {
// Handle normally, just set current item to negative
if (typeof mathConf[i] === 'number') {
mathConf[i] = <number> mathConf[i] * -1;
} else {
(<SolvedStep> mathConf[i]).total = (<SolvedStep> mathConf[i]).total * -1;
(<SolvedStep> mathConf[i]).details = `-${(<SolvedStep> mathConf[i]).details}`;
}
mathConf.splice(i - 1, 1);
i--;
}
}
}
// Handle applying the group config
if (groupConf) {
loggingEnabled && log(LT.LOG, `Applying groupConf to executedRolls | ${JSON.stringify(groupConf)} ${JSON.stringify(executedRolls.entries().toArray())}`);
// Merge all rollSets into one array, adding the idx into each rollSet to allow separating them back out
const allRollSets: RollSet[] = [];
const executedRollArr = executedRolls.entries().toArray();
executedRollArr.forEach(([rollGroupIdx, executedRoll]) => {
executedRoll.rollSet.forEach((roll) => (roll.rollGrpIdx = rollGroupIdx));
allRollSets.push(...executedRoll.rollSet);
});
loggingEnabled && log(LT.LOG, `raw rollSets: ${JSON.stringify(allRollSets)}`);
// Handle drop or keep operations
if (groupConf.drop.on || groupConf.keep.on || groupConf.dropHigh.on || groupConf.keepLow.on) {
allRollSets.sort(compareRolls);
let dropCount = 0;
// For normal drop and keep, simple subtraction is enough to determine how many to drop
// Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop
if (groupConf.drop.on) {
dropCount = groupConf.drop.count;
if (dropCount > allRollSets.length) {
dropCount = allRollSets.length;
}
} else if (groupConf.keep.on) {
dropCount = allRollSets.length - groupConf.keep.count;
if (dropCount < 0) {
dropCount = 0;
}
} // For inverted drop and keep, order must be flipped to greatest to least before the simple subtraction can determine how many to drop
// Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop
else if (groupConf.dropHigh.on) {
allRollSets.reverse();
dropCount = groupConf.dropHigh.count;
if (dropCount > allRollSets.length) {
dropCount = allRollSets.length;
}
} else if (groupConf.keepLow.on) {
allRollSets.reverse();
dropCount = allRollSets.length - groupConf.keepLow.count;
if (dropCount < 0) {
dropCount = 0;
}
}
let i = 0;
while (dropCount > 0 && i < allRollSets.length) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `Handling group dropping | Dropping ${dropCount}, looking at ${JSON.stringify(allRollSets[i])}`);
if (!allRollSets[i].dropped && !allRollSets[i].rerolled) {
allRollSets[i].dropped = true;
allRollSets[i].success = false;
allRollSets[i].fail = false;
allRollSets[i].matchLabel = '';
dropCount--;
}
i++;
}
allRollSets.sort(compareOrigIdx);
}
// Handle marking new successes/fails
if (groupConf.success.on || groupConf.fail.on) {
allRollSets.forEach((rs) => {
loopCountCheck();
if (!rs.dropped && !rs.rerolled) {
if (groupConf.success.on && groupConf.success.range.includes(rs.roll)) {
rs.success = true;
rs.matchLabel = 'S';
}
if (groupConf.fail.on && groupConf.fail.range.includes(rs.roll)) {
rs.fail = true;
rs.matchLabel = 'F';
}
}
});
}
// Handle separating the rollSets back out, recalculating the success/fail count, assigning them to the correct mathConf slots
executedRollArr.forEach(([rollGroupIdx, executedRoll]) => {
// Update flags on executedRoll
executedRoll.countSuccessOverride = executedRoll.countSuccessOverride || groupConf.success.on;
executedRoll.countFailOverride = executedRoll.countFailOverride || groupConf.fail.on;
executedRoll.rollSet = allRollSets.filter((rs) => rs.rollGrpIdx === rollGroupIdx);
const formattedRoll = formatRoll(executedRoll, modifiers);
mathConf[rollGroupIdx] = formattedRoll.solvedStep;
countDetails.push(formattedRoll.countDetails);
rollDists.push(formattedRoll.rollDistributions);
});
}
// Now that mathConf is parsed, send it into the solver
loggingEnabled && log(LT.LOG, `Sending mathConf to solver ${JSON.stringify(mathConf)}`);
const tempSolved = mathSolver(mathConf);
loggingEnabled && log(LT.LOG, `SolvedStep back from mathSolver ${JSON.stringify(tempSolved)}`);
// Push all of this step's solved data into the temp array
return [
[
{
rollTotal: tempSolved.total,
rollPreFormat: '',
rollPostFormat: '',
rollDetails: tempSolved.details,
containsCrit: tempSolved.containsCrit,
containsFail: tempSolved.containsFail,
initConfig: cmd,
isComplex: tempSolved.isComplex,
},
],
countDetails,
rollDists,
];
};

View File

@ -1,49 +0,0 @@
import { CountDetails, RollSet } from 'artigen/dice/dice.d.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
export const rollCounter = (rollSet: RollSet[]): CountDetails => {
const countDetails = {
total: 0,
successful: 0,
failed: 0,
rerolled: 0,
dropped: 0,
exploded: 0,
};
rollSet.forEach((roll) => {
loopCountCheck();
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;
};
export const reduceCountDetails = (counts: CountDetails[]): CountDetails =>
counts.reduce(
(acc, cur) => {
loopCountCheck();
return {
total: acc.total + cur.total,
successful: acc.successful + cur.successful,
failed: acc.failed + cur.failed,
rerolled: acc.rerolled + cur.rerolled,
dropped: acc.dropped + cur.dropped,
exploded: acc.exploded + cur.exploded,
};
},
{
total: 0,
successful: 0,
failed: 0,
rerolled: 0,
dropped: 0,
exploded: 0,
},
);

View File

@ -1,35 +0,0 @@
import { CustomDiceShapes, RollConf, RollSet } from 'artigen/dice/dice.d.ts';
export const flagRoll = (rollConf: RollConf, rollSet: RollSet, customDiceShapes: CustomDiceShapes) => {
// If critScore arg is on, check if the roll should be a crit, if its off, check if the roll matches the die size
if (rollConf.critScore.on && rollConf.critScore.range.includes(rollSet.roll)) {
rollSet.critHit = true;
} else if (!rollConf.critScore.on) {
rollSet.critHit = rollSet.roll === (rollConf.dPercent.on ? rollConf.dPercent.critVal : rollConf.dieSize);
}
// If critFail arg is on, check if the roll should be a fail, if its off, check if the roll matches 1
if (rollConf.critFail.on && rollConf.critFail.range.includes(rollSet.roll)) {
rollSet.critFail = true;
} else if (!rollConf.critFail.on) {
if (rollConf.type === 'fate') {
rollSet.critFail = rollSet.roll === -1;
} else if (rollConf.type === 'custom') {
rollSet.critFail = rollSet.roll === Math.min(...(customDiceShapes.get(rollConf.customType ?? '') ?? []));
} else {
rollSet.critFail = rollSet.roll === (rollConf.dPercent.on ? 0 : 1);
}
}
// If success arg is on, check if roll should be successful
if (rollConf.success.on && rollConf.success.range.includes(rollSet.roll)) {
rollSet.success = true;
rollSet.matchLabel = 'S';
}
// If fail arg is on, check if roll should be failed
if (rollConf.fail.on && rollConf.fail.range.includes(rollSet.roll)) {
rollSet.fail = true;
rollSet.matchLabel = 'F';
}
};

View File

@ -1,274 +0,0 @@
import { CreateMessage, EmbedField } from '@discordeno';
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
import { ArtigenEmbedNoAttachment, ArtigenEmbedWithAttachment, SolvedRoll } from 'artigen/artigen.d.ts';
import { CountDetails, RollDistributionMap, RollModifiers } from 'artigen/dice/dice.d.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { failColor, infoColor1, infoColor2 } from 'embeds/colors.ts';
import { basicReducer } from 'artigen/utils/reducers.ts';
export const rollingEmbed: CreateMessage = {
embeds: [
{
color: infoColor1,
title: 'Rolling . . .',
},
],
};
export const generateDMFailed = (user: bigint): CreateMessage => ({
embeds: [
{
color: failColor,
title: `WARNING: <@${user}> could not be messaged.`,
description: 'If this issue persists, make sure direct messages are allowed from this server.',
},
],
});
export const generateRollError = (errorType: string, errorName: string, errorMsg: string): CreateMessage => ({
embeds: [
{
color: failColor,
title: 'Roll command encountered the following error:',
fields: [
{
name: errorType,
value: `${errorMsg}\n\nPlease try again. If the error is repeated, please report the issue using the \`${config.prefix}report\` command.`,
},
],
footer: {
text: errorName,
},
},
],
});
export const generateCountDetailsEmbed = (counts: CountDetails): ArtigenEmbedNoAttachment => {
const title = 'Roll Count Details:';
const fields: EmbedField[] = [
{
name: 'Total Rolls:',
value: `${counts.total}`,
inline: true,
},
{
name: 'Successful Rolls:',
value: `${counts.successful}`,
inline: true,
},
{
name: 'Failed Rolls:',
value: `${counts.failed}`,
inline: true,
},
{
name: 'Rerolled Dice:',
value: `${counts.rerolled}`,
inline: true,
},
{
name: 'Dropped Dice:',
value: `${counts.dropped}`,
inline: true,
},
{
name: 'Exploded Dice:',
value: `${counts.exploded}`,
inline: true,
},
];
return {
charCount: title.length + fields.map((field) => field.name.length + field.value.length).reduce(basicReducer, 0),
embed: {
color: infoColor1,
title,
fields,
},
hasAttachment: false,
};
};
const getDistName = (key: string) => {
const [type, size] = key.split('-');
switch (type) {
case 'fate':
return 'Fate dice';
case 'cwod':
return `CWOD d${size}`;
case 'ova':
return `OVA d${size}`;
case 'custom':
return `Custom d${size}`;
default:
return `d${size}`;
}
};
export const generateRollDistsEmbed = (rollDists: RollDistributionMap): ArtigenEmbedNoAttachment | ArtigenEmbedWithAttachment => {
const fields = rollDists
.entries()
.toArray()
.map(([key, distArr]) => {
const total = distArr.reduce(basicReducer, 0);
return {
name: `${getDistName(key)} (Total rolls: ${total}):`,
value: distArr
.map((cnt, dieIdx) => key.startsWith('custom') && cnt === 0 ? '' : `${key.startsWith('fate') ? dieIdx - 1 : dieIdx + 1}: ${cnt} (${((cnt / total) * 100).toFixed(1)}%)`)
.filter((x) => x)
.join('\n'),
inline: true,
};
});
const rollDistTitle = 'Roll Distributions:';
const totalSize = fields.map((field) => field.name.length + field.value.length).reduce(basicReducer, 0);
if (totalSize > 4000 || fields.length > 25 || fields.some((field) => field.name.length > 256 || field.value.length > 1024)) {
const rollDistBlob = new Blob([fields.map((field) => `# ${field.name}\n${field.value}`).join('\n\n') as BlobPart], { type: 'text' });
if (rollDistBlob.size > config.maxFileSize) {
const rollDistErrDesc =
'The roll distribution was too large to be included and could not be attached below. If you would like to see the roll distribution details, please send the rolls in multiple messages.';
return {
charCount: rollDistTitle.length + rollDistErrDesc.length,
embed: {
color: failColor,
title: rollDistTitle,
description: rollDistErrDesc,
},
hasAttachment: false,
};
} else {
const rollDistErrDesc = 'The roll distribution was too large to be included and has been attached below.';
return {
charCount: rollDistTitle.length + rollDistErrDesc.length,
embed: {
color: failColor,
title: rollDistTitle,
description: rollDistErrDesc,
},
hasAttachment: true,
attachment: {
name: 'rollDistributions.md',
blob: rollDistBlob,
},
};
}
}
return {
charCount: rollDistTitle.length + totalSize,
embed: {
color: infoColor1,
title: rollDistTitle,
fields,
},
hasAttachment: false,
};
};
export const generateRollEmbed = (
authorId: bigint,
returnDetails: SolvedRoll,
modifiers: RollModifiers,
): ArtigenEmbedNoAttachment | ArtigenEmbedWithAttachment => {
if (returnDetails.error) {
// Roll had an error, send error embed
const errTitle = 'Roll failed:';
const errDesc = `${returnDetails.errorMsg}`;
const errCode = `Code: ${returnDetails.errorCode}`;
return {
charCount: errTitle.length + errDesc.length + errCode.length,
embed: {
color: failColor,
title: errTitle,
description: errDesc,
footer: {
text: errCode,
},
},
hasAttachment: false,
};
}
const line1Details = modifiers.hideRaw ? '' : `<@${authorId}>${returnDetails.line1}\n\n`;
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)
const desc = `${line1Details}${line1Details ? '\n' : ''}Results have been messaged to the following GMs: ${
modifiers.gms
.map((gm) => (gm.startsWith('<') ? gm : `<@${gm}>`))
.join(' ')
}`;
return {
charCount: desc.length,
embed: {
color: infoColor2,
description: desc,
},
hasAttachment: false,
};
}
// Roll is normal, make normal embed
const line2Details = returnDetails.line2.split(': ');
let details = '';
if (!modifiers.superNoDetails) {
details = `**Details:**\n${modifiers.spoiler}${returnDetails.line3}${modifiers.spoiler}`;
loggingEnabled && log(LT.LOG, `${returnDetails.line3} |&| ${details}`);
}
const baseDesc = `${line1Details}**${line2Details.shift()}:**\n${line2Details.join(': ')}`;
// Embed desc limit is 4096
if (baseDesc.length + details.length < 4000) {
// Response is valid size
const desc = `${baseDesc}\n\n${details}`;
return {
charCount: desc.length,
embed: {
color: infoColor2,
description: desc,
},
hasAttachment: false,
};
}
// Response is too big, collapse it into a .txt file and send that instead.
const b = new Blob([`${baseDesc}\n\n${details}` as BlobPart], { type: 'text' });
details = `${baseDesc}\n\nDetails have been omitted from this message for being over 4000 characters.`;
if (b.size > config.maxFileSize) {
// blob is too big, don't attach it
details +=
'\n\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.';
return {
charCount: details.length,
embed: {
color: infoColor2,
description: details,
},
hasAttachment: false,
};
}
// blob is small enough, attach it
details += '\n\nFull details have been attached to this messaged as a `.txt` file for verification purposes.';
return {
charCount: details.length,
embed: {
color: infoColor2,
description: details,
},
hasAttachment: true,
attachment: {
blob: b,
name: 'rollDetails.txt',
},
};
};

View File

@ -1,40 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
// escapeCharacters(str, esc) returns str
// escapeCharacters escapes all characters listed in esc
export const escapeCharacters = (str: string, esc: string): string => {
// Loop thru each esc char one at a time
for (const e of esc) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `Escaping character ${e} | ${str}, ${esc}`);
// Create a new regex to look for that char that needs replaced and escape it
const tempRgx = new RegExp(`[${e}]`, 'g');
str = str.replace(tempRgx, `\\${e}`);
}
return str;
};
// escapePrefixPostfix(str) returns str
// Escapes all characters that need escaped in a regex string to allow prefix/postfix to be configurable
const escapePrefixPostfix = (str: string): string => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
export const cmdSplitRegex = new RegExp(`(${escapePrefixPostfix(config.prefix)})|(${escapePrefixPostfix(config.postfix)})`, 'g');
// breaks the string on the following: (\*\*) ** for exponents ([+()*/^] for basic algebra (?<![d%])% for breaking on d%%%% dice correctly (?<![rsfop!=<>])- for breaking on - correctly with fate dice) (x\d+(\.\d*)?) x# for variables
export const mathSplitRegex = /(\*\*)|([+()*/^]|(?<![d%])%|(?<![rsfop!=<>])-)|(x\d+(\.\d*)?)/g;
// Internal is used for recursive text replacement, these will always be the top level as they get replaced with config.prefix/postfix when exiting each level
export const openInternal = '\u2045';
export const closeInternal = '\u2046';
export const internalWrapRegex = new RegExp(`([${openInternal}${closeInternal}])`, 'g');
// Internal Group is used for marking handled groups
export const openInternalGrp = '\u2e20';
export const closeInternalGrp = '\u2e21';
export const internalGrpWrapRegex = new RegExp(`([${openInternalGrp}${closeInternalGrp}])`, 'g');

View File

@ -1,13 +0,0 @@
import { GroupResultFlags } from 'artigen/dice/dice.d.ts';
export const applyFlags = (rollDetails: string, flags: GroupResultFlags): string => {
if (flags.dropped) {
return `~~${rollDetails.replaceAll('~', '')}~~`;
} else if (flags.success) {
return `S:${rollDetails}`;
} else if (flags.failed) {
return `F:${rollDetails}`;
} else {
return rollDetails;
}
};

View File

@ -1,11 +0,0 @@
type MathFunction = (arg: number) => number;
export const legalMath: MathFunction[] = [];
(Object.getOwnPropertyNames(Math) as (keyof Math)[]).forEach((propName) => {
const mathProp = Math[propName];
if (typeof mathProp === 'function' && mathProp.length === 1) {
legalMath.push(mathProp as MathFunction);
}
});
export const legalMathOperators = legalMath.map((oper) => oper.name);

View File

@ -1 +0,0 @@
export const loggingEnabled = false;

View File

@ -1,65 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { MathConf } from 'artigen/math/math.d.ts';
import { closeInternal, closeInternalGrp, openInternal, openInternalGrp } from 'artigen/utils/escape.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
const checkBalance = (conf: MathConf[], openStr: string, closeStr: string, errorType: string, getMatching: boolean, openIdx: number): number => {
let parenCnt = 0;
// 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++) {
loopCountCheck();
loggingEnabled &&
log(
LT.LOG,
`${getMatching ? 'Looking for matching' : 'Checking'} ${openStr}/${closeStr} ${getMatching ? '' : 'balance '}on ${
JSON.stringify(
conf,
)
} | at ${JSON.stringify(conf[i])}`,
);
if (conf[i] === openStr) {
parenCnt++;
} else if (conf[i] === closeStr) {
parenCnt--;
}
// If parenCnt ever goes below 0, that means too many closing paren appeared before opening parens
if (parenCnt < 0) {
throw new Error(`Unbalanced${errorType}`);
}
// When parenCnt reaches 0 again, we will have found the matching closing parenthesis and can safely exit the for loop
if (getMatching && parenCnt === 0) {
loggingEnabled && log(LT.LOG, `Matching ${openStr}/${closeStr} found at "${i}" | ${JSON.stringify(conf[i])}`);
return i;
}
}
// If the parenCnt is not 0, then we do not have balanced parens and need to error out now
// If getMatching flag is set and we have exited the loop, we did not find a matching paren
if (parenCnt !== 0 || getMatching) {
throw new Error(`Unbalanced${errorType}`);
}
// getMatching flag not set, this value is unused
return 0;
};
// assertXBalance verifies the entire conf has balanced X
export const assertGroupBalance = (conf: MathConf[]) => checkBalance(conf, '{', '}', 'Group', false, 0);
export const assertParenBalance = (conf: MathConf[]) => checkBalance(conf, '(', ')', 'Paren', false, 0);
export const assertPrePostBalance = (conf: MathConf[]) => checkBalance(conf, config.prefix, config.postfix, 'PrefixPostfix', false, 0);
// getMatchingXIdx gets the matching X, also partially verifies the conf has balanced X
export const getMatchingGroupIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, '{', '}', 'Group', true, openIdx);
export const getMatchingInternalIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, openInternal, closeInternal, 'Internal', true, openIdx);
export const getMatchingInternalGrpIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, openInternalGrp, closeInternalGrp, 'InternalGrp', true, openIdx);
export const getMatchingParenIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, '(', ')', 'Paren', true, openIdx);
export const getMatchingPostfixIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, config.prefix, config.postfix, 'PrefixPostfix', true, openIdx);

View File

@ -1,26 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { RollType } from 'artigen/dice/dice.d.ts';
import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
// Add tNum to range
export const addToRange = (tSep: string, range: Array<number>, tNum: number) => {
loggingEnabled && log(LT.LOG, `${getLoopCount()} addToRange on ${tSep} attempting to add: ${tNum}`);
!range.includes(tNum) && range.push(tNum);
};
const internalAddMultipleToRange = (tSep: string, range: Array<number>, start: number, end: number) => {
for (let i = start; i <= end; i++) {
loopCountCheck();
addToRange(tSep, range, i);
}
};
// Add numbers less than or equal to tNum to range
export const ltAddToRange = (tSep: string, range: Array<number>, tNum: number, rollType: RollType) => internalAddMultipleToRange(tSep, range, rollType === 'fate' ? -1 : 0, tNum);
// Add numbers greater than or equal to tNum to range
export const gtrAddToRange = (tSep: string, range: Array<number>, tNum: number, dieSize: number) => internalAddMultipleToRange(tSep, range, tNum, dieSize);

View File

@ -1 +0,0 @@
export const basicReducer = (prev: number, cur: number) => prev + cur;

View File

@ -1,42 +0,0 @@
import { RollDistributionMap, RollSet, RollType } from 'artigen/dice/dice.d.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
// Used to generate consistent keys for rollDistributions
export const rollDistKey = (type: RollType, size: number) => `${type}-${size}`;
// Converts a RollSet into a RollDistMap
export const createRollDistMap = (rollSet: RollSet[]): RollDistributionMap => {
const rollDistMap = new Map<string, number[]>();
rollSet.forEach((roll) => {
loopCountCheck();
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]++;
rollDistMap.set(rollDistKey(roll.type, roll.size), tempArr);
});
return rollDistMap;
};
// Collapses an array of RollDistMaps into a single RollDistMap
export const reduceRollDistMaps = (rollDistArr: RollDistributionMap[]): RollDistributionMap =>
rollDistArr.reduce((acc, cur) => {
loopCountCheck();
cur
.entries()
.toArray()
.forEach(([key, value]) => {
loopCountCheck();
const tempArr = acc.get(key) ?? new Array<number>(value.length).fill(0);
for (let i = 0; i < tempArr.length; i++) {
loopCountCheck();
tempArr[i] += value[i];
}
acc.set(key, tempArr);
});
return acc;
}, new Map<string, number[]>());

View File

@ -1,25 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { RollConf, RollSet } from 'artigen/dice/dice.d.ts';
import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
// Can either count or sum each die
export const generateRollVals = (rollConf: RollConf, rollSet: RollSet[], rollStr: string, count: boolean): Array<number> => {
const rollVals = new Array(rollConf.dieSize).fill(0);
// Count up all rolls
for (const ovaRoll of rollSet) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | incrementing rollVals for ${JSON.stringify(ovaRoll)}`);
if (!ovaRoll.dropped && !ovaRoll.rerolled) {
rollVals[ovaRoll.roll - 1] += count ? 1 : ovaRoll.roll;
}
}
loggingEnabled && log(LT.LOG, `${getLoopCount()} Handling ${rollConf.type} ${rollStr} | rollVals ${rollVals}`);
return rollVals;
};

View File

@ -1,51 +0,0 @@
import { ReturnData } from 'artigen/artigen.d.ts';
import { RollSet } from 'artigen/dice/dice.d.ts';
const internalCompareRolls = (a: RollSet, b: RollSet, dir: 1 | -1): number => {
if (a.roll < b.roll) {
return -1 * dir;
}
if (a.roll > b.roll) {
return 1 * dir;
}
return 0;
};
// compareRolls(a, b) returns -1|0|1
// compareRolls is used to order an array of RollSets by RollSet.roll
export const compareRolls = (a: RollSet, b: RollSet): number => internalCompareRolls(a, b, 1);
// compareRolls(a, b) returns -1|0|1
// compareRolls is used to order an array of RollSets by RollSet.roll reversed
export const compareRollsReverse = (a: RollSet, b: RollSet): number => internalCompareRolls(a, b, -1);
const internalCompareTotalRolls = (a: ReturnData, b: ReturnData, dir: 1 | -1): number => {
if (a.rollTotal < b.rollTotal) {
return -1 * dir;
}
if (a.rollTotal > b.rollTotal) {
return 1 * dir;
}
return 0;
};
// compareTotalRolls(a, b) returns -1|0|1
// compareTotalRolls is used to order an array of RollSets by RollSet.roll
export const compareTotalRolls = (a: ReturnData, b: ReturnData): number => internalCompareTotalRolls(a, b, 1);
// compareTotalRollsReverse(a, b) returns 1|0|-1
// compareTotalRollsReverse is used to order an array of RollSets by RollSet.roll reversed
export const compareTotalRollsReverse = (a: ReturnData, b: ReturnData): number => internalCompareTotalRolls(a, b, -1);
// compareRolls(a, b) returns -1|0|1
// compareRolls is used to order an array of RollSet or ReturnData by X.origIdx
export const compareOrigIdx = (a: RollSet | ReturnData, b: RollSet | ReturnData): number => {
if ((a.origIdx ?? 0) < (b.origIdx ?? 0)) {
return -1;
}
if ((a.origIdx ?? 0) > (b.origIdx ?? 0)) {
return 1;
}
return 0;
};

View File

@ -1,136 +0,0 @@
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
export const translateError = (solverError: Error): [string, string] => {
// Welp, the unthinkable happened, we hit an error
// Split on _ for the error messages that have more info than just their name
const errorSplits = solverError.message.split('_');
const errorName = errorSplits.shift();
const errorDetails = errorSplits.join('_');
let errorMsg = '';
// Translate the errorName to a specific errorMsg
switch (errorName) {
case 'WholeDieCountSizeOnly':
errorMsg = 'Error: Die Size and Die Count must be positive whole numbers';
break;
case 'YouNeedAD':
errorMsg = `Error: Attempted to parse \`${errorDetails}\` as a dice configuration, `;
if (errorDetails.includes('d')) {
errorMsg += '`d` was found, but the die size and/or count were missing or zero when they should be a positive whole number';
} else {
errorMsg += '`d` was not found in the dice config for specifying die size and/or count';
}
break;
case 'CannotParseDieCount':
errorMsg = `Formatting Error: Cannot parse \`${errorDetails}\` as a number`;
break;
case 'DoubleSeparator':
errorMsg = `Formatting Error: \`${errorDetails}\` should only be specified once per roll, remove all but one and repeat roll`;
break;
case 'FormattingError':
errorMsg = 'Formatting Error: ';
switch (errorDetails) {
case 'dk':
errorMsg += 'Cannot use Keep and Drop at the same time, remove all but one and repeat roll';
break;
case 'mtsf':
errorMsg += 'Cannot use Match with CWOD Dice, or the Success or Fail options, remove all but one and repeat roll';
break;
default:
errorMsg += `Unhandled - ${errorDetails}`;
break;
}
break;
case 'NoMaxWithDash':
errorMsg = 'Formatting Error: CritScore range specified without a maximum, remove - or add maximum to correct';
break;
case 'UnknownOperation':
errorMsg = `Error: Unknown Operation ${errorDetails}`;
if (errorDetails === '-') {
errorMsg += '\nNote: Negative numbers are not supported';
} else if (errorDetails === ' ') {
errorMsg += `\nNote: Every roll must be closed by ${config.postfix}`;
}
break;
case 'NoZerosAllowed':
errorMsg = 'Formatting Error: ';
switch (errorDetails) {
case 'base':
errorMsg += 'Die Size and Die Count';
break;
case 'drop':
errorMsg += 'Drop (`d` or `dl`)';
break;
case 'keep':
errorMsg += 'Keep (`k` or `kh`)';
break;
case 'dropHigh':
errorMsg += 'Drop Highest (`dh`)';
break;
case 'keepLow':
errorMsg += 'Keep Lowest (`kl`)';
break;
case 'reroll':
errorMsg += 'Reroll (`r`)';
break;
case 'critScore':
errorMsg += 'Crit Score (`cs`)';
break;
case 'critFail':
errorMsg += 'Crit Fail (`cf`)';
break;
default:
errorMsg += `Unhandled - ${errorDetails}`;
break;
}
errorMsg += ' cannot be zero';
break;
case 'NoRerollOnAllSides':
errorMsg = 'Error: Cannot reroll all sides of a die, must have at least one side that does not get rerolled';
break;
case 'CritScoreMinGtrMax':
errorMsg = 'Formatting Error: CritScore maximum cannot be greater than minimum, check formatting and flip min/max';
break;
case 'Invalid string length':
case 'MaxLoopsExceeded':
errorMsg = 'Error: Roll is too complex or reaches infinity';
break;
case 'UnbalancedParen':
errorMsg = 'Formatting Error: At least one of the equations contains unbalanced `(`/`)`';
break;
case 'UnbalancedPrefixPostfix':
errorMsg = `Formatting Error: At least one of the equations contains unbalanced \`${config.prefix}\`/\`${config.postfix}\``;
break;
case 'EMDASNotNumber':
errorMsg = 'Error: One or more operands is not a number';
break;
case 'ConfWhat':
errorMsg = 'Error: Not all values got processed, please report the command used';
break;
case 'OperatorWhat':
errorMsg = 'Error: Something really broke with the Operator, try again';
break;
case 'OperandNaN':
errorMsg = 'Error: One or more operands reached NaN, check input';
break;
case 'UndefinedStep':
errorMsg = 'Error: Roll became undefined, one or more operands are not a roll or a number, check input';
break;
case 'IllegalVariable':
errorMsg = `Error: \`${errorDetails}\` is not a valid variable`;
break;
case 'TooManyLabels':
errorMsg = `Error: ${config.name} can only support a maximum of \`${errorDetails}\` labels when using the dice matching options (\`m\` or \`mt\`)`;
break;
default:
log(LT.ERROR, `Unhandled Parser Error: ${errorName}, ${errorDetails}`);
errorMsg = `Unhandled Error: ${solverError.message}\nCheck input and try again, if issue persists, please use \`${config.prefix}report\` to alert the devs of the issue`;
break;
}
return [solverError.message, errorMsg];
};

316
src/commandUtils.ts Normal file
View File

@ -0,0 +1,316 @@
import config from '../config.ts';
import { CountDetails, SolvedRoll } from './solver/solver.d.ts';
import { RollModifiers } from './mod.d.ts';
export const failColor = 0xe71212;
export const warnColor = 0xe38f28;
export const successColor = 0x0f8108;
export const infoColor1 = 0x313bf9;
export const infoColor2 = 0x6805e9;
export const rollingEmbed = {
embeds: [{
color: infoColor1,
title: 'Rolling . . .',
}],
};
export const generatePing = (time: number) => ({
embeds: [{
color: infoColor1,
title: time === -1 ? 'Ping?' : `Pong! Latency is ${time}ms.`,
}],
});
export const generateReport = (msg: string) => ({
embeds: [{
color: infoColor2,
title: 'USER REPORT:',
description: msg || 'No message',
}],
});
export const generateStats = (guildCount: number, channelCount: number, memberCount: number, rollCount: bigint, utilityCount: bigint, rollRate: number, utilityRate: number) => ({
embeds: [{
color: infoColor2,
title: 'The Artificer\'s Statistics:',
timestamp: new Date().toISOString(),
fields: [
{
name: 'Guilds:',
value: `${guildCount}`,
inline: true,
},
{
name: 'Channels:',
value: `${channelCount}`,
inline: true,
},
{
name: 'Active Members:',
value: `${memberCount}`,
inline: true,
},
{
name: 'Roll Commands:',
value: `${rollCount}
(${rollRate.toFixed(2)} per hour)`,
inline: true,
},
{
name: 'Utility Commands:',
value: `${utilityCount}
(${utilityRate.toFixed(2)} per hour)`,
inline: true,
},
],
}],
});
export const generateApiFailed = (args: string) => ({
embeds: [{
color: failColor,
title: `Failed to ${args} API rolls for this guild.`,
description: 'If this issue persists, please report this to the developers.',
}],
});
export const generateApiStatus = (banned: boolean, active: boolean) => {
const apiStatus = active ? 'allowed' : 'blocked from being used';
return {
embeds: [{
color: infoColor1,
title: `The Artificer's API is ${config.api.enable ? 'currently enabled' : 'currently disabled'}.`,
description: banned ? 'API rolls are banned from being used in this guild.\n\nThis will not be reversed.' : `API rolls are ${apiStatus} in this guild.`,
}],
};
};
export const generateApiSuccess = (args: string) => ({
embeds: [{
color: successColor,
title: `API rolls have successfully been ${args} for this guild.`,
}],
});
export const generateDMFailed = (user: string) => ({
embeds: [{
color: failColor,
title: `WARNING: ${user} could not be messaged.`,
description: 'If this issue persists, make sure direct messages are allowed from this server.',
}],
});
export const generateApiKeyEmail = (email: string, key: string) => ({
content: `<@${config.api.admin}> A USER HAS REQUESTED AN API KEY`,
embeds: [{
color: infoColor1,
fields: [
{
name: 'Send to:',
value: email,
},
{
name: 'Subject:',
value: 'Artificer API Key',
},
{
name: 'Body:',
value: `Hello Artificer API User,
Welcome aboard The Artificer's API. You can find full details about the API on the GitHub: https://github.com/Burn-E99/TheArtificer
Your API Key is: ${key}
Guard this well, as there is zero tolerance for API abuse.
Welcome aboard,
The Artificer Developer - Ean Milligan`,
},
],
}],
});
export const generateApiDeleteEmail = (email: string, deleteCode: string) => ({
content: `<@${config.api.admin}> A USER HAS REQUESTED A DELETE CODE`,
embeds: [{
color: infoColor1,
fields: [
{
name: 'Send to:',
value: email,
},
{
name: 'Subject:',
value: 'Artificer API Delete Code',
},
{
name: 'Body:',
value: `Hello Artificer API User,
I am sorry to see you go. If you would like, please respond to this email detailing what I could have done better.
As requested, here is your delete code: ${deleteCode}
Sorry to see you go,
The Artificer Developer - Ean Milligan`,
},
],
}],
});
export const generateRollError = (errorType: string, errorMsg: string) => ({
embeds: [{
color: failColor,
title: 'Roll command encountered the following error:',
fields: [{
name: errorType,
value: `${errorMsg}\n\nPlease try again. If the error is repeated, please report the issue using the \`${config.prefix}report\` command.`,
}],
}],
});
export const generateCountDetailsEmbed = (counts: CountDetails) => ({
color: infoColor1,
title: 'Roll Count Details:',
fields: [
{
name: 'Total Rolls:',
value: `${counts.total}`,
inline: true,
},
{
name: 'Successful Rolls:',
value: `${counts.successful}`,
inline: true,
},
{
name: 'Failed Rolls:',
value: `${counts.failed}`,
inline: true,
},
{
name: 'Rerolled Dice:',
value: `${counts.rerolled}`,
inline: true,
},
{
name: 'Dropped Dice:',
value: `${counts.dropped}`,
inline: true,
},
{
name: 'Exploded Dice:',
value: `${counts.exploded}`,
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}`,
footer: {
text: `Code: ${returnDetails.errorCode}`,
},
},
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 +=
'\n\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.';
return {
embed: {
color: infoColor2,
description: `${baseDesc}
${details}`,
},
hasAttachment: false,
attachment: {
'blob': await new Blob(['' as BlobPart], { 'type': 'text' }),
'name': 'rollDetails.txt',
},
};
} else {
details += '\n\nFull 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',
},
};
}
}
}
}
};

View File

@ -1,39 +1,35 @@
import { api } from 'commands/apiCmd.ts'; import { ping } from './ping.ts';
import { audit } from 'commands/audit.ts'; import { rip } from './rip.ts';
import { emoji } from 'commands/emoji.ts'; import { rollHelp } from './rollHelp.ts';
import { handleMentions } from 'commands/handleMentions.ts'; import { rollDecorators } from './rollDecorators.ts';
import { heatmap } from 'commands/heatmap.ts'; import { help } from './help.ts';
import { help } from 'commands/help.ts'; import { info } from './info.ts';
import { info } from 'commands/info.ts'; import { privacy } from './privacy.ts';
import { optIn } from 'commands/optIn.ts'; import { version } from './version.ts';
import { optOut } from 'commands/optOut.ts'; import { report } from './report.ts';
import { ping } from 'commands/ping.ts'; import { stats } from './stats.ts';
import { privacy } from 'commands/privacy.ts'; import { api } from './apiCmd.ts';
import { rip } from 'commands/rip.ts'; import { emoji } from './emoji.ts';
import { report } from 'commands/report.ts'; import { roll } from './roll.ts';
import { roll } from 'commands/roll.ts'; import { handleMentions } from './handleMentions.ts';
import { rollHelp } from 'commands/rollHelp.ts'; import { audit } from './audit.ts';
import { stats } from 'commands/stats.ts'; import { heatmap } from './heatmap.ts';
import { toggleInline } from 'commands/toggleInline.ts';
import { version } from 'commands/version.ts';
export default { export default {
api, ping,
audit, rip,
emoji, rollHelp,
handleMentions, rollDecorators,
heatmap, help,
help, info,
info, privacy,
optIn, version,
optOut, report,
ping, stats,
privacy, api,
rip, emoji,
report, roll,
roll, handleMentions,
rollHelp, audit,
stats, heatmap,
toggleInline,
version,
}; };

View File

@ -1,85 +1,74 @@
import { DiscordenoMessage, hasGuildPermissions } from '@discordeno'; import { dbClient, queries } from '../db.ts';
import {
import config from '~config'; // Discordeno deps
DiscordenoMessage,
import apiCommands from 'commands/apiCmd/_index.ts'; hasGuildPermissions,
} from '../../deps.ts';
import dbClient from 'db/client.ts'; import apiCommands from './apiCmd/_index.ts';
import { queries } from 'db/common.ts'; import { failColor } from '../commandUtils.ts';
import utils from '../utils.ts';
import { failColor } from 'embeds/colors.ts';
import utils from 'utils/utils.ts';
export const api = async (message: DiscordenoMessage, args: string[]) => { export const api = async (message: DiscordenoMessage, args: string[]) => {
// 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('api')).catch((e) => utils.commonLoggers.dbError('apiCmd.ts:16', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('api')).catch((e) => utils.commonLoggers.dbError('apiCmd.ts:16', 'call sproc INC_CNT on', e));
// Local apiArg in lowercase // Local apiArg in lowercase
const apiArg = (args[0] || 'help').toLowerCase(); const apiArg = (args[0] || 'help').toLowerCase();
// Alert users who DM the bot that this command is for guilds only // Alert users who DM the bot that this command is for guilds only
if (message.guildId === 0n) { if (message.guildId === 0n) {
message message.send({
.send({ embeds: [{
embeds: [ color: failColor,
{ title: 'API commands are only available in guilds.',
color: failColor, }],
title: 'API commands are only available in guilds.', }).catch((e: Error) => utils.commonLoggers.messageSendError('apiCmd.ts:30', message, e));
}, return;
], }
})
.catch((e: Error) => utils.commonLoggers.messageSendError('apiCmd.ts:30', message, e));
return;
}
// Makes sure the user is authenticated to run the API command // Makes sure the user is authenticated to run the API command
if (await hasGuildPermissions(message.authorId, message.guildId, ['ADMINISTRATOR'])) { if (await hasGuildPermissions(message.authorId, message.guildId, ['ADMINISTRATOR'])) {
switch (apiArg) { switch (apiArg) {
case 'help': case 'help':
case 'h': case 'h':
// [[api help // [[api help
// Shows API help details // Shows API help details
apiCommands.help(message); apiCommands.help(message);
break; break;
case 'allow': case 'allow':
case 'block': case 'block':
case 'enable': case 'enable':
case 'disable': case 'disable':
// [[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
apiCommands.allowBlock(message, apiArg); apiCommands.allowBlock(message, apiArg);
break; break;
case 'delete': case 'delete':
// [[api delete // [[api delete
// Lets a guild admin delete their server from the database // Lets a guild admin delete their server from the database
apiCommands.deleteGuild(message); apiCommands.deleteGuild(message);
break; break;
case 'status': case 'status':
// [[api status // [[api status
// Lets a guild admin check the status of API rolling in said guild // Lets a guild admin check the status of API rolling in said guild
apiCommands.status(message); apiCommands.status(message);
break; break;
case 'show-warn': case 'show-warn':
case 'hide-warn': case 'hide-warn':
// [[api show-warn/hide-warn // [[api show-warn/hide-warn
// Lets a guild admin decide if the API warning should be shown on messages from the API // Lets a guild admin decide if the API warning should be shown on messages from the API
apiCommands.showHideWarn(message, apiArg); apiCommands.showHideWarn(message, apiArg);
break; break;
default: default:
break; break;
} }
} else { } else {
message message.send({
.send({ embeds: [{
embeds: [ color: failColor,
{ title: 'API commands are powerful and can only be used by guild Owners and Admins.',
color: failColor, description: 'For information on how to use the API, please check the GitHub README for more information [here](https://github.com/Burn-E99/TheArtificer).',
title: 'API commands are powerful and can only be used by guild Owners and Admins.', }],
description: `For information on how to use the API, please check the GitHub README for more information [here](${config.links.sourceCode}).`, }).catch((e: Error) => utils.commonLoggers.messageSendError('apiCmd.ts:77', message, e));
}, }
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('apiCmd.ts:77', message, e));
}
}; };

View File

@ -1,13 +1,13 @@
import { allowBlock } from 'commands/apiCmd/allowBlock.ts'; import { help } from './apiHelp.ts';
import { help } from 'commands/apiCmd/apiHelp.ts'; import { allowBlock } from './allowBlock.ts';
import { deleteGuild } from 'commands/apiCmd/deleteGuild.ts'; import { deleteGuild } from './deleteGuild.ts';
import { showHideWarn } from 'commands/apiCmd/showHideWarn.ts'; import { status } from './status.ts';
import { status } from 'commands/apiCmd/status.ts'; import { showHideWarn } from './showHideWarn.ts';
export default { export default {
allowBlock, help,
help, allowBlock,
deleteGuild, deleteGuild,
showHideWarn, status,
status, showHideWarn,
}; };

View File

@ -1,52 +1,42 @@
import { DiscordenoMessage } from '@discordeno'; import { dbClient } from '../../db.ts';
import {
import dbClient from 'db/client.ts'; // Discordeno deps
DiscordenoMessage,
import { generateApiFailed, generateApiSuccess } from 'embeds/api.ts'; } from '../../../deps.ts';
import { generateApiFailed, generateApiSuccess } from '../../commandUtils.ts';
import utils from 'utils/utils.ts'; import utils from '../../utils.ts';
export const allowBlock = async (message: DiscordenoMessage, apiArg: string) => { export const allowBlock = async (message: DiscordenoMessage, apiArg: string) => {
let errorOutInitial = false; let errorOutInitial = false;
const guildQuery = await dbClient const guildQuery = await dbClient.query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => {
.query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]) utils.commonLoggers.dbError('allowBlock.ts:15', 'query', e0);
.catch((e0) => { message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:16', message, e));
utils.commonLoggers.dbError('allowBlock.ts:15', 'query', e0); errorOutInitial = true;
message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:16', message, e)); });
errorOutInitial = true; if (errorOutInitial) return;
});
if (errorOutInitial) return;
let errorOut = false; let errorOut = false;
if (guildQuery.length === 0) { if (guildQuery.length === 0) {
// Since guild is not in our DB, add it in // Since guild is not in our DB, add it in
await dbClient await dbClient.execute(`INSERT INTO allowed_guilds(guildid,channelid,active) values(?,?,?)`, [message.guildId, message.channelId, (apiArg === 'allow' || apiArg === 'enable') ? 1 : 0]).catch(
.execute(`INSERT INTO allowed_guilds(guildid,channelid,active) values(?,?,?)`, [ (e0) => {
message.guildId, utils.commonLoggers.dbError('allowBlock:26', 'insert into', e0);
message.channelId, message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:27', message, e));
apiArg === 'allow' || apiArg === 'enable' ? 1 : 0, errorOut = true;
]) },
.catch((e0) => { );
utils.commonLoggers.dbError('allowBlock:26', 'insert into', e0); } else {
message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:27', message, e)); // Since guild is in our DB, update it
errorOut = true; await dbClient.execute(`UPDATE allowed_guilds SET active = ? WHERE guildid = ? AND channelid = ?`, [(apiArg === 'allow' || apiArg === 'enable') ? 1 : 0, message.guildId, message.channelId]).catch(
}); (e0) => {
} else { utils.commonLoggers.dbError('allowBlock.ts:35', 'update', e0);
// Since guild is in our DB, update it message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:36', message, e));
await dbClient errorOut = true;
.execute(`UPDATE allowed_guilds SET active = ? WHERE guildid = ? AND channelid = ?`, [ },
apiArg === 'allow' || apiArg === 'enable' ? 1 : 0, );
message.guildId, }
message.channelId, if (errorOut) return;
])
.catch((e0) => {
utils.commonLoggers.dbError('allowBlock.ts:35', 'update', e0);
message.send(generateApiFailed(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:36', message, e));
errorOut = true;
});
}
if (errorOut) return;
// We won't get here if there's any errors, so we know it has bee successful, so report as such // We won't get here if there's any errors, so we know it has bee successful, so report as such
message.send(generateApiSuccess(`${apiArg}ed`)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:44', message, e)); message.send(generateApiSuccess(`${apiArg}ed`)).catch((e: Error) => utils.commonLoggers.messageSendError('allowBlock.ts:44', message, e));
}; };

View File

@ -1,67 +1,65 @@
import { DiscordenoMessage } from '@discordeno'; import config from '../../../config.ts';
import {
import config from '~config'; // Discordeno deps
DiscordenoMessage,
import { infoColor1, infoColor2 } from 'embeds/colors.ts'; } from '../../../deps.ts';
import { infoColor1, infoColor2 } from '../../commandUtils.ts';
import utils from 'utils/utils.ts'; import utils from '../../utils.ts';
export const help = (message: DiscordenoMessage) => { export const help = (message: DiscordenoMessage) => {
message message.send({
.send({ embeds: [
embeds: [ {
{ color: infoColor2,
color: infoColor2, title: 'The Artificer\'s API Details:',
title: `${config.name}'s API Details:`, description:
description: `The Artificer has a built in API that allows user to roll dice into Discord using third party programs. By default, API rolls are blocked from being sent in your guild. The API warning is also enabled by default. These commands may only be used by the Owner or Admins of your guild.
`${config.name} has a built in API that allows user to roll dice into Discord using third party programs. By default, API rolls are blocked from being sent in your guild. The API warning is also enabled by default. These commands may only be used by the Owner or Admins of your guild.
For information on how to use the API, please check the GitHub README for more information [here](${config.links.sourceCode}). For information on how to use the API, please check the GitHub README for more information [here](https://github.com/Burn-E99/TheArtificer).
You may enable and disable the API rolls for your guild as needed.`, You may enable and disable the API rolls for your guild as needed.`,
}, },
{ {
color: infoColor1, color: infoColor1,
title: 'Available API Commands:', title: 'Available API Commands:',
fields: [ fields: [
{ {
name: `\`${config.prefix}api help\``, name: `\`${config.prefix}api help\``,
value: 'This command', value: 'This command',
inline: true, inline: true,
}, },
{ {
name: `\`${config.prefix}api status\``, name: `\`${config.prefix}api status\``,
value: 'Shows the current status of the API for the channel this was run in', value: 'Shows the current status of the API for the channel this was run in',
inline: true, inline: true,
}, },
{ {
name: `\`${config.prefix}api allow/enable\``, name: `\`${config.prefix}api allow/enable\``,
value: 'Allows API Rolls to be sent to the channel this was run in', value: 'Allows API Rolls to be sent to the channel this was run in',
inline: true, inline: true,
}, },
{ {
name: `\`${config.prefix}api block/disable\``, name: `\`${config.prefix}api block/disable\``,
value: 'Blocks API Rolls from being sent to the channel this was run in', value: 'Blocks API Rolls from being sent to the channel this was run in',
inline: true, inline: true,
}, },
{ {
name: `\`${config.prefix}api delete\``, name: `\`${config.prefix}api delete\``,
value: `Deletes this channel's settings from ${config.name}'s database`, value: 'Deletes this channel\'s settings from The Artificer\'s database',
inline: true, inline: true,
}, },
{ {
name: `\`${config.prefix}api show-warn\``, name: `\`${config.prefix}api show-warn\``,
value: 'Shows the API warning on all rolls sent to the channel this was run in', value: 'Shows the API warning on all rolls sent to the channel this was run in',
inline: true, inline: true,
}, },
{ {
name: `\`${config.prefix}api hide-warn\``, name: `\`${config.prefix}api hide-warn\``,
value: 'Hides the API warning on all rolls sent to the channel this was run in', value: 'Hides the API warning on all rolls sent to the channel this was run in',
inline: true, inline: true,
}, },
], ],
}, },
], ],
}) }).catch((e: Error) => utils.commonLoggers.messageSendError('apiHelp.ts:67', message, e));
.catch((e: Error) => utils.commonLoggers.messageSendError('apiHelp.ts:67', message, e));
}; };

View File

@ -1,41 +1,31 @@
import { DiscordenoMessage } from '@discordeno'; import { dbClient } from '../../db.ts';
import {
import config from '~config'; // Discordeno deps
DiscordenoMessage,
import dbClient from 'db/client.ts'; } from '../../../deps.ts';
import { failColor, successColor } from '../../commandUtils.ts';
import { failColor, successColor } from 'embeds/colors.ts'; import utils from '../../utils.ts';
import utils from 'utils/utils.ts';
export const deleteGuild = async (message: DiscordenoMessage) => { export const deleteGuild = async (message: DiscordenoMessage) => {
let errorOut = false; let errorOut = false;
await dbClient.execute(`DELETE FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => { await dbClient.execute(`DELETE FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => {
utils.commonLoggers.dbError('deleteGuild.ts:15', 'query', e0); utils.commonLoggers.dbError('deleteGuild.ts:15', 'query', e0);
message message.send({
.send({ embeds: [{
embeds: [ color: failColor,
{ title: 'Failed to delete this guild from the database.',
color: failColor, description: 'If this issue persists, please report this to the developers.',
title: 'Failed to delete this guild from the database.', }],
description: 'If this issue persists, please report this to the developers.', }).catch((e: Error) => utils.commonLoggers.messageSendError('deleteGuild.ts:22', message, e));
}, errorOut = true;
], });
}) if (errorOut) return;
.catch((e: Error) => utils.commonLoggers.messageSendError('deleteGuild.ts:22', message, e));
errorOut = true;
});
if (errorOut) return;
// We won't get here if there's any errors, so we know it has bee successful, so report as such // We won't get here if there's any errors, so we know it has bee successful, so report as such
message message.send({
.send({ embeds: [{
embeds: [ color: successColor,
{ title: 'This guild\'s API setting has been removed from The Artifier\'s Database.',
color: successColor, }],
title: `This guild's API setting has been removed from ${config.name}'s Database.`, }).catch((e: Error) => utils.commonLoggers.messageSendError('deleteGuild.ts:33', message, e));
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('deleteGuild.ts:33', message, e));
}; };

View File

@ -1,48 +1,38 @@
import { DiscordenoMessage } from '@discordeno'; import { dbClient } from '../../db.ts';
import {
import dbClient from 'db/client.ts'; // Discordeno deps
DiscordenoMessage,
import { generateApiFailed, generateApiSuccess } from 'embeds/api.ts'; } from '../../../deps.ts';
import { generateApiFailed, generateApiSuccess } from '../../commandUtils.ts';
import utils from 'utils/utils.ts'; import utils from '../../utils.ts';
export const showHideWarn = async (message: DiscordenoMessage, apiArg: string) => { export const showHideWarn = async (message: DiscordenoMessage, apiArg: string) => {
let errorOutInitial = false; let errorOutInitial = false;
const guildQuery = await dbClient const guildQuery = await dbClient.query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => {
.query(`SELECT guildid, channelid FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]) utils.commonLoggers.dbError('showHideWarn.ts:15', 'query', e0);
.catch((e0) => { message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:16', message, e));
utils.commonLoggers.dbError('showHideWarn.ts:15', 'query', e0); errorOutInitial = true;
message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:16', message, e)); });
errorOutInitial = true; if (errorOutInitial) return;
});
if (errorOutInitial) return;
let errorOut = false; let errorOut = false;
if (guildQuery.length === 0) { if (guildQuery.length === 0) {
// Since guild is not in our DB, add it in // Since guild is not in our DB, add it in
await dbClient await dbClient.execute(`INSERT INTO allowed_guilds(guildid,channelid,hidewarn) values(?,?,?)`, [message.guildId, message.channelId, (apiArg === 'hide-warn') ? 1 : 0]).catch((e0) => {
.execute(`INSERT INTO allowed_guilds(guildid,channelid,hidewarn) values(?,?,?)`, [message.guildId, message.channelId, apiArg === 'hide-warn' ? 1 : 0]) utils.commonLoggers.dbError('showHideWarn.ts:25', 'insert inot', e0);
.catch((e0) => { message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:26', message, e));
utils.commonLoggers.dbError('showHideWarn.ts:25', 'insert into', e0); errorOut = true;
message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:26', message, e)); });
errorOut = true; } else {
}); // Since guild is in our DB, update it
} else { await dbClient.execute(`UPDATE allowed_guilds SET hidewarn = ? WHERE guildid = ? AND channelid = ?`, [(apiArg === 'hide-warn') ? 1 : 0, message.guildId, message.channelId]).catch((e0) => {
// Since guild is in our DB, update it utils.commonLoggers.dbError('showHideWarn.ts:32', 'update', e0);
await dbClient message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:33', message, e));
.execute(`UPDATE allowed_guilds SET hidewarn = ? WHERE guildid = ? AND channelid = ?`, [ errorOut = true;
apiArg === 'hide-warn' ? 1 : 0, });
message.guildId, }
message.channelId, if (errorOut) return;
])
.catch((e0) => {
utils.commonLoggers.dbError('showHideWarn.ts:32', 'update', e0);
message.send(generateApiFailed(`${apiArg} on`)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:33', message, e));
errorOut = true;
});
}
if (errorOut) return;
// We won't get here if there's any errors, so we know it has bee successful, so report as such // We won't get here if there's any errors, so we know it has bee successful, so report as such
message.send(generateApiSuccess(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:40', message, e)); message.send(generateApiSuccess(apiArg)).catch((e: Error) => utils.commonLoggers.messageSendError('showHideWarn.ts:40', message, e));
}; };

View File

@ -1,44 +1,37 @@
import { DiscordenoMessage } from '@discordeno'; import { dbClient } from '../../db.ts';
import {
import dbClient from 'db/client.ts'; // Discordeno deps
DiscordenoMessage,
import { generateApiStatus } from 'embeds/api.ts'; } from '../../../deps.ts';
import { failColor } from 'embeds/colors.ts'; import { failColor, generateApiStatus } from '../../commandUtils.ts';
import utils from '../../utils.ts';
import utils from 'utils/utils.ts';
export const status = async (message: DiscordenoMessage) => { export const status = async (message: DiscordenoMessage) => {
// Get status of guild from the db // Get status of guild from the db
let errorOut = false; let errorOut = false;
const guildQuery = await dbClient const guildQuery = await dbClient.query(`SELECT active, banned FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]).catch((e0) => {
.query(`SELECT active, banned FROM allowed_guilds WHERE guildid = ? AND channelid = ?`, [message.guildId, message.channelId]) utils.commonLoggers.dbError('status.ts:16', 'query', e0);
.catch((e0) => { message.send({
utils.commonLoggers.dbError('status.ts:16', 'query', e0); embeds: [{
message color: failColor,
.send({ title: 'Failed to check API rolls status for this guild.',
embeds: [ description: 'If this issue persists, please report this to the developers.',
{ }],
color: failColor, }).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:23', message, e));
title: 'Failed to check API rolls status for this guild.', errorOut = true;
description: 'If this issue persists, please report this to the developers.', });
}, if (errorOut) return;
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:23', message, e));
errorOut = true;
});
if (errorOut) return;
// Check if we got an item back or not // Check if we got an item back or not
if (guildQuery.length > 0) { if (guildQuery.length > 0) {
// Check if guild is banned from using API and return appropriate message // Check if guild is banned from using API and return appropriate message
if (guildQuery[0].banned) { if (guildQuery[0].banned) {
message.send(generateApiStatus(true, false)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:32', message, e)); message.send(generateApiStatus(true, false)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:32', message, e));
} else { } else {
message.send(generateApiStatus(false, guildQuery[0].active)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:34', message, e)); message.send(generateApiStatus(false, guildQuery[0].active)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:34', message, e));
} }
} else { } else {
// Guild is not in DB, therefore they are blocked // Guild is not in DB, therefore they are blocked
message.send(generateApiStatus(false, false)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:38', message, e)); message.send(generateApiStatus(false, false)).catch((e: Error) => utils.commonLoggers.messageSendError('status.ts:38', message, e));
} }
}; };

View File

@ -1,55 +1,48 @@
import { DiscordenoMessage } from '@discordeno'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts';
import {
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import auditCommands from './auditCmd/_index.ts';
import { failColor } from '../commandUtils.ts';
import utils from '../utils.ts';
import config from '~config'; export const audit = async (message: DiscordenoMessage, args: string[]) => {
// Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('audit')).catch((e) => utils.commonLoggers.dbError('audit.ts:16', 'call sproc INC_CNT on', e));
import auditCommands from 'commands/auditCmd/_index.ts'; // Local apiArg in lowercase
const auditArg = (args[0] || 'help').toLowerCase();
import dbClient from 'db/client.ts'; // Makes sure the user is authenticated to run the API command
import { queries } from 'db/common.ts'; if (message.authorId === config.api.admin) {
switch (auditArg) {
import { failColor } from 'embeds/colors.ts'; case 'help':
case 'h':
import utils from 'utils/utils.ts'; // [[audit help or [[audit h
// Shows API help details
export const audit = (message: DiscordenoMessage, args: string[]) => { auditCommands.auditHelp(message);
// Light telemetry to see how many times a command is being run break;
dbClient.execute(queries.callIncCnt('audit')).catch((e) => utils.commonLoggers.dbError('audit.ts:16', 'call sproc INC_CNT on', e)); case 'db':
// [[audit db
// Local apiArg in lowercase // Shows current DB table sizes
const auditArg = (args[0] || 'help').toLowerCase(); auditCommands.auditDB(message);
break;
// Makes sure the user is authenticated to run the API command case 'guilds':
if (message.authorId === config.api.admin) { // [[audit guilds
switch (auditArg) { // Shows breakdown of guilds and detials on them
case 'help': auditCommands.auditGuilds(message);
case 'h': break;
// [[audit help or [[audit h default:
// Shows API help details break;
auditCommands.auditHelp(message); }
break; } else {
case 'db': message.send({
// [[audit db embeds: [{
// Shows current DB table sizes color: failColor,
auditCommands.auditDB(message); title: `Audit commands are powerful and can only be used by ${config.name}'s owner.`,
break; }],
case 'guilds': }).catch((e: Error) => utils.commonLoggers.messageSendError('audit.ts:51', message, e));
// [[audit guilds }
// Shows breakdown of guilds and details on them
auditCommands.auditGuilds(message);
break;
default:
break;
}
} else {
message
.send({
embeds: [
{
color: failColor,
title: `Audit commands are powerful and can only be used by ${config.name}'s owner.`,
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('audit.ts:51', message, e));
}
}; };

View File

@ -1,9 +1,9 @@
import { auditDB } from 'commands/auditCmd/auditDB.ts'; import { auditHelp } from './auditHelp.ts';
import { auditGuilds } from 'commands/auditCmd/auditGuilds.ts'; import { auditDB } from './auditDB.ts';
import { auditHelp } from 'commands/auditCmd/auditHelp.ts'; import { auditGuilds } from './auditGuilds.ts';
export default { export default {
auditDB, auditHelp,
auditGuilds, auditDB,
auditHelp, auditGuilds,
}; };

View File

@ -1,49 +1,42 @@
import { DiscordenoMessage, EmbedField } from '@discordeno'; import { dbClient } from '../../db.ts';
import {
import dbClient from 'db/client.ts'; // Discordeno deps
DiscordenoMessage,
import { infoColor2 } from 'embeds/colors.ts'; EmbedField,
import { compilingStats } from 'embeds/common.ts'; } from '../../../deps.ts';
import { infoColor2 } from '../../commandUtils.ts';
import utils from 'utils/utils.ts'; import { compilingStats } from '../../commonEmbeds.ts';
import utils from '../../utils.ts';
interface DBSizeData {
table: string;
size: string;
rows: number;
}
export const auditDB = async (message: DiscordenoMessage) => { export const auditDB = async (message: DiscordenoMessage) => {
try { try {
const m = await message.send(compilingStats); const m = await message.send(compilingStats);
// Get DB statistics // Get DB statistics
const auditQuery = await dbClient.query(`SELECT * FROM db_size;`).catch((e) => utils.commonLoggers.dbError('auditDB.ts:19', 'query', e)); const auditQuery = await dbClient.query(`SELECT * FROM db_size;`).catch((e) => utils.commonLoggers.dbError('auditDB.ts:19', 'query', e));
// Turn all tables into embed fields, currently only properly will handle 25 tables, but we'll fix that when it gets 26 tables // Turn all tables into embed fields, currently only properly will handle 25 tables, but we'll fix that when artificer gets 26 tables
const embedFields: Array<EmbedField> = []; const embedFields: Array<EmbedField> = [];
auditQuery.forEach((row: DBSizeData) => { auditQuery.forEach((row: any) => {
embedFields.push({ embedFields.push({
name: `${row.table}`, name: `${row.table}`,
value: `**Size:** ${row.size} MB value: `**Size:** ${row.size} MB
**Rows:** ${row.rows}`, **Rows:** ${row.rows}`,
inline: true, inline: true,
}); });
}); });
// Send the results // Send the results
m.edit({ m.edit({
embeds: [ embeds: [{
{ color: infoColor2,
color: infoColor2, title: 'Database Audit',
title: 'Database Audit', description: 'Lists all tables with their current size and row count.',
description: 'Lists all tables with their current size and row count.', timestamp: new Date().toISOString(),
timestamp: new Date().toISOString(), fields: embedFields,
fields: embedFields, }],
}, }).catch((e: Error) => utils.commonLoggers.messageEditError('auditDB.ts:43', message, e));
], } catch (e) {
}).catch((e: Error) => utils.commonLoggers.messageEditError('auditDB.ts:43', message, e)); utils.commonLoggers.messageSendError('auditDB.ts:45', message, e);
} catch (e) { }
utils.commonLoggers.messageSendError('auditDB.ts:45', message, e as Error);
}
}; };

View File

@ -1,148 +1,94 @@
import { cache, cacheHandlers, DiscordenoGuild, DiscordenoMessage } from '@discordeno'; import config from '../../../config.ts';
import {
import config from '~config'; // Discordeno deps
cache,
import { infoColor2 } from 'embeds/colors.ts'; cacheHandlers,
DiscordenoMessage,
import utils from 'utils/utils.ts'; } from '../../../deps.ts';
import { infoColor2 } from '../../commandUtils.ts';
const sortGuildByMemberCount = (a: DiscordenoGuild, b: DiscordenoGuild) => { import utils from '../../utils.ts';
if (a.memberCount < b.memberCount) {
return 1;
}
if (a.memberCount > b.memberCount) {
return -1;
}
return 0;
};
export const auditGuilds = async (message: DiscordenoMessage) => { export const auditGuilds = async (message: DiscordenoMessage) => {
const cachedGuilds = await cacheHandlers.size('guilds'); const cachedGuilds = await cacheHandlers.size('guilds');
const guildOwnerCounts = new Map<bigint, number>(); let totalCount = 0;
const sizeCats = [10000, 5000, 1000, 500, 100, 50, 25, 10, 1]; let realCount = 0;
const guildSizeDist = new Map<number, number>(sizeCats.map((size) => [size, 0])); let botsCount = 0;
let totalCount = 0; let auditText = '';
let realCount = 0;
let botsCount = 0;
let auditText = ''; cache.guilds.forEach((guild) => {
totalCount += guild.memberCount;
let localBotCount = 0;
let localRealCount = 0;
guild.members.forEach((member) => {
if (member.bot) {
botsCount++;
localBotCount++;
} else {
realCount++;
localRealCount++;
}
});
cache.guilds auditText += `Guild: ${guild.name} (${guild.id})
.array()
.sort(sortGuildByMemberCount)
.forEach((guild) => {
totalCount += guild.memberCount;
let localBotCount = 0;
let localRealCount = 0;
guild.members.forEach((member) => {
if (member.bot) {
botsCount++;
localBotCount++;
} else {
realCount++;
localRealCount++;
}
});
sizeCats.some((size) => {
if (guild.memberCount >= size) {
guildSizeDist.set(size, (guildSizeDist.get(size) ?? 0) + 1);
return true;
}
});
// Track repeat guild owners
guildOwnerCounts.set(guild.ownerId, (guildOwnerCounts.get(guild.ownerId) ?? 0) + 1);
// Add guild to output text
auditText += `Guild: ${guild.name} (${guild.id})
Owner: ${guild.owner?.username}#${guild.owner?.discriminator} (${guild.ownerId}) Owner: ${guild.owner?.username}#${guild.owner?.discriminator} (${guild.ownerId})
Tot mem: ${guild.memberCount} | Real: ${localRealCount} | Bot: ${localBotCount} Tot mem: ${guild.memberCount} | Real: ${localRealCount} | Bot: ${localBotCount}
`; `;
}); });
const b = await new Blob([auditText as BlobPart], { type: 'text' }); const b = await new Blob([auditText as BlobPart], { 'type': 'text' });
const tooBig = await new Blob(['tooBig' as BlobPart], { type: 'text' }); const tooBig = await new Blob(['tooBig' as BlobPart], { 'type': 'text' });
// Condense repeat guild owners message.send({
const repeatCounts: number[] = []; embeds: [{
Array.from(guildOwnerCounts).map(([_owenId, cnt]) => { color: infoColor2,
repeatCounts[cnt - 1] = (repeatCounts[cnt - 1] ?? 0) + 1; title: 'Guilds Audit',
}); description: `Shows details of the guilds that ${config.name} serves.
message
.send({
embeds: [
{
color: infoColor2,
title: 'Guilds Audit',
description: `Shows details of the guilds that ${config.name} serves.
Please see attached file for audit details on cached guilds and members.`, Please see attached file for audit details on cached guilds and members.`,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
fields: [ fields: [
{ {
name: 'Total Guilds:', name: 'Total Guilds:',
value: `${cache.guilds.size}`, value: `${cache.guilds.size}`,
inline: true, inline: true,
}, },
{ {
name: 'Cached Guilds:', name: 'Cached Guilds:',
value: `${cachedGuilds}`, value: `${cachedGuilds}`,
inline: true, inline: true,
}, },
{ {
name: 'Uncached Guilds:', name: 'Uncached Guilds:',
value: `${cache.dispatchedGuildIds.size}`, value: `${cache.dispatchedGuildIds.size}`,
inline: true, inline: true,
}, },
{ {
name: 'Total Members\n(may be artificially higher if 1 user is in multiple guilds the bot is in):', name: 'Total Members\n(may be artificially higher if 1 user is in multiple guilds the bot is in):',
value: `${totalCount.toLocaleString()}`, value: `${totalCount}`,
inline: true, inline: true,
}, },
{ {
name: 'Cached Real People:', name: 'Cached Real People:',
value: `${realCount}`, value: `${realCount}`,
inline: true, inline: true,
}, },
{ {
name: 'Cached Bots:', name: 'Cached Bots:',
value: `${botsCount}`, value: `${botsCount}`,
inline: true, inline: true,
}, },
{ {
name: 'Average members per guild:', name: 'Average members per guild:',
value: `${(totalCount / cache.guilds.size).toFixed(2)}`, value: `${(totalCount / cache.guilds.size).toFixed(2)}`,
inline: true, inline: true,
}, },
{ ],
name: 'Repeat Guild Owners:', }],
value: repeatCounts file: {
.map((ownerCnt, serverIdx) => `${ownerCnt} ${ownerCnt === 1 ? 'person has' : 'people have'} me in ${serverIdx + 1} of their guilds`) 'blob': b.size > 8388290 ? tooBig : b,
.filter((str) => str) 'name': 'auditDetails.txt',
.join('\n') || 'No Repeat Guild Owners', },
}, }).catch((e: Error) => utils.commonLoggers.messageSendError('auditGuild.ts:19', message, e));
{
name: 'Guild Size Dist:',
value: Array.from(guildSizeDist)
.map(
([size, count], idx) =>
`${count} Guild${count === 1 ? ' has' : 's have'} ${
guildSizeDist.has(sizeCats[idx - 1]) ? `${size.toLocaleString()} - ${(sizeCats[idx - 1] - 1).toLocaleString()}` : `at least ${size.toLocaleString()}`
} Member${size === 1 ? '' : 's'}`,
)
.join('\n') || 'Not available',
},
],
},
],
file: {
blob: b.size > config.maxFileSize ? tooBig : b,
name: 'auditDetails.txt',
},
})
.catch((e: Error) => utils.commonLoggers.messageSendError('auditGuild.ts:19', message, e));
}; };

View File

@ -1,37 +1,33 @@
import { DiscordenoMessage } from '@discordeno'; import config from '../../../config.ts';
import {
import config from '~config'; // Discordeno deps
DiscordenoMessage,
import { infoColor1 } from 'embeds/colors.ts'; } from '../../../deps.ts';
import { infoColor1 } from '../../commandUtils.ts';
import utils from 'utils/utils.ts'; import utils from '../../utils.ts';
export const auditHelp = (message: DiscordenoMessage) => { export const auditHelp = (message: DiscordenoMessage) => {
message message.send({
.send({ embeds: [{
embeds: [ color: infoColor1,
{ title: 'Audit Help',
color: infoColor1, fields: [
title: 'Audit Help', {
fields: [ name: `\`${config.prefix}audit help\``,
{ value: 'This command',
name: `\`${config.prefix}audit help\``, inline: true,
value: 'This command', },
inline: true, {
}, name: `\`${config.prefix}audit db\``,
{ value: 'Shows current DB table sizes',
name: `\`${config.prefix}audit db\``, inline: true,
value: 'Shows current DB table sizes', },
inline: true, {
}, name: `\`${config.prefix}audit guilds\``,
{ value: 'Shows breakdown of guilds and detials on them',
name: `\`${config.prefix}audit guilds\``, inline: true,
value: 'Shows breakdown of guilds and details on them', },
inline: true, ],
}, }],
], }).catch((e: Error) => utils.commonLoggers.messageSendError('auditHelp.ts:35', message, e));
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('auditHelp.ts:35', message, e));
}; };

View File

@ -1,47 +1,40 @@
import { DiscordenoMessage } from '@discordeno'; import config from '../../config.ts';
import { log, LogTypes as LT } from '@Log4Deno'; import { dbClient, queries } from '../db.ts';
import {
import config from '~config'; // Discordeno deps
DiscordenoMessage,
import dbClient from 'db/client.ts'; // Log4Deno deps
import { queries } from 'db/common.ts'; log,
LT,
import utils from 'utils/utils.ts'; } from '../../deps.ts';
import { EmojiConf } from '../mod.d.ts';
interface EmojiConf { import utils from '../utils.ts';
name: string;
aliases: string[];
id: string;
animated: boolean;
deleteSender: boolean;
}
const allEmojiAliases: string[] = []; const allEmojiAliases: string[] = [];
config.emojis.forEach((curEmoji: EmojiConf) => { config.emojis.forEach((emji: EmojiConf) => {
allEmojiAliases.push(...curEmoji.aliases); allEmojiAliases.push(...emji.aliases);
}); });
export const emoji = (message: DiscordenoMessage, command: string) => { export const emoji = (message: DiscordenoMessage, command: string) => {
if (allEmojiAliases.includes(command)) { // shortcut
// Start looping thru the possible emojis if (allEmojiAliases.indexOf(command)) {
config.emojis.some((curEmoji: EmojiConf) => { // Start looping thru the possible emojis
log(LT.LOG, `Checking if command was emoji ${JSON.stringify(curEmoji)}`); config.emojis.some((emji: EmojiConf) => {
// If a match gets found log(LT.LOG, `Checking if command was emoji ${JSON.stringify(emji)}`);
if (curEmoji.aliases.includes(command || '')) { // If a match gets found
// Light telemetry to see how many times a command is being run if (emji.aliases.indexOf(command || '') > -1) {
dbClient.execute(queries.callIncCnt('emojis')).catch((e) => utils.commonLoggers.dbError('emojis.ts:28', 'call sproc INC_CNT on', e)); // Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('emojis')).catch((e) => utils.commonLoggers.dbError('emojis.ts:28', 'call sproc INC_CNT on', e));
// Send the needed emoji // Send the needed emoji
message message.send(`<${emji.animated ? 'a' : ''}:${emji.name}:${emji.id}>`).catch((e: Error) => utils.commonLoggers.messageSendError('emoji.ts:33', message, e));
.send(`<${curEmoji.animated ? 'a' : ''}:${curEmoji.name}:${curEmoji.id}>`) // And attempt to delete if needed
.catch((e: Error) => utils.commonLoggers.messageSendError('emoji.ts:33', message, e)); if (emji.deleteSender) {
// And attempt to delete if needed message.delete().catch((e: Error) => utils.commonLoggers.messageDeleteError('emoji.ts:36', message, e));
if (curEmoji.deleteSender) { }
message.delete().catch((e: Error) => utils.commonLoggers.messageDeleteError('emoji.ts:36', message, e)); return true;
} }
return true; });
} }
});
}
}; };

View File

@ -1,37 +1,29 @@
import { DiscordenoMessage } from '@discordeno'; import config from '../../config.ts';
import { log, LogTypes as LT } from '@Log4Deno'; import { dbClient, queries } from '../db.ts';
import {
import config from '~config'; // Discordeno deps
DiscordenoMessage,
import dbClient from 'db/client.ts'; // Log4Deno deps
import { queries } from 'db/common.ts'; log,
LT,
import { infoColor1 } from 'embeds/colors.ts'; } from '../../deps.ts';
import { infoColor1 } from '../commandUtils.ts';
import utils from 'utils/utils.ts'; import utils from '../utils.ts';
export const handleMentions = (message: DiscordenoMessage) => { export const handleMentions = (message: DiscordenoMessage) => {
log(LT.LOG, `Handling @mention message: ${JSON.stringify(message)}`); log(LT.LOG, `Handling @mention message: ${JSON.stringify(message)}`);
// 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('mention')).catch((e) => utils.commonLoggers.dbError('handleMentions.ts:17', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('mention')).catch((e) => utils.commonLoggers.dbError('handleMentions.ts:17', 'call sproc INC_CNT on', e));
message message.send({
.send({ embeds: [{
embeds: [ color: infoColor1,
{ title: `Hello! I am ${config.name}!`,
color: infoColor1, fields: [{
title: `Hello! I am ${config.name}!`, name: 'I am a bot that specializes in rolling dice and doing basic algebra',
fields: [ value: `To learn about my available commands, please run \`${config.prefix}help\``,
{ }],
name: 'I am a bot that specializes in rolling dice and doing basic algebra.', }],
value: `To learn about my available commands, please run \`${config.prefix}help\`. }).catch((e: Error) => utils.commonLoggers.messageSendError('handleMentions.ts:30', message, e));
Want me to ignore you? Simply run \`${config.prefix}opt-out\` and ${config.name} will no longer read your messages or respond to you.`,
},
],
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('handleMentions.ts:30', message, e));
}; };

View File

@ -1,51 +1,41 @@
import { DiscordenoMessage } from '@discordeno'; import { dbClient, queries } from '../db.ts';
import {
// Discordeno deps
DiscordenoMessage,
} from '../../deps.ts';
import config from '../../config.ts';
import { failColor, infoColor2 } from '../commandUtils.ts';
import utils from '../utils.ts';
import intervals from '../intervals.ts';
import config from '~config'; export const heatmap = async (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('heatmap')).catch((e) => utils.commonLoggers.dbError('heatmap.ts:14', 'call sproc INC_CNT on', e));
import dbClient from 'db/client.ts'; if (config.api.enable) {
import { queries } from 'db/common.ts'; message.send({
embeds: [{
import { failColor, infoColor2 } from 'embeds/colors.ts'; title: 'Roll Heatmap',
description: `Over time, this image will show a nice pattern of when rolls are requested the most.
import intervals from 'utils/intervals.ts';
import utils from 'utils/utils.ts';
export const heatmap = (message: DiscordenoMessage) => {
// Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('heatmap')).catch((e) => utils.commonLoggers.dbError('heatmap.ts:14', 'call sproc INC_CNT on', e));
if (config.api.enable) {
message
.send({
embeds: [
{
title: 'Roll Heatmap',
description: `Over time, this image will show a nice pattern of when rolls are requested the most.
Least Rolls: ${intervals.getMinRollCnt()} Least Rolls: ${intervals.getMinRollCnt()}
Most Rolls: ${intervals.getMaxRollCnt()}`, Most Rolls: ${intervals.getMaxRollCnt()}`,
footer: { footer: {
text: 'Data is shown in US Eastern Time. | This heatmap uses data starting 6/26/2022.', text: 'Data is shown in US Eastern Time. | This heatmap uses data starting 6/26/2022.',
}, },
color: infoColor2, color: infoColor2,
image: { image: {
url: `${config.api.publicDomain}api/heatmap.png?now=${new Date().getTime()}`, url: `${config.api.publicDomain}api/heatmap.png`,
}, },
}, }],
], }).catch((e) => utils.commonLoggers.messageSendError('heatmap.ts:21', message, e));
}) } else {
.catch((e) => utils.commonLoggers.messageSendError('heatmap.ts:21', message, e)); message.send({
} else { embeds: [{
message title: 'Roll Heatmap Disabled',
.send({ description: 'This command requires the bot\'s API to be enabled. If you are the host of this bot, check your `config.ts` file to enable it.',
embeds: [ color: failColor,
{ }],
title: 'Roll Heatmap Disabled', }).catch((e) => utils.commonLoggers.messageSendError('heatmap.ts:21', message, e));
description: "This command requires the bot's API to be enabled. If you are the host of this bot, check your `config.ts` file to enable it.", }
color: failColor,
},
],
})
.catch((e) => utils.commonLoggers.messageSendError('heatmap.ts:21', message, e));
}
}; };

View File

@ -1,104 +1,88 @@
import { DiscordenoMessage } from '@discordeno'; import config from '../../config.ts';
import { dbClient, queries } from '../db.ts';
import config from '~config'; import {
// Discordeno deps
import dbClient from 'db/client.ts'; DiscordenoMessage,
import { queries } from 'db/common.ts'; } from '../../deps.ts';
import { infoColor2 } from '../commandUtils.ts';
import { infoColor2 } from 'embeds/colors.ts'; import utils from '../utils.ts';
import utils from 'utils/utils.ts';
export const help = (message: DiscordenoMessage) => { export const help = (message: DiscordenoMessage) => {
// 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('help')).catch((e) => utils.commonLoggers.dbError('help.ts:15', 'call sproc INC_CNT on', e)); dbClient.execute(queries.callIncCnt('help')).catch((e) => utils.commonLoggers.dbError('htlp.ts:15', 'call sproc INC_CNT on', e));
message message.send({
.send({ embeds: [{
embeds: [ color: infoColor2,
{ title: 'The Artificer\'s Available Commands:',
color: infoColor2, fields: [
title: `${config.name}'s Available Commands:`, {
fields: [ name: `\`${config.prefix}?\``,
{ value: 'This command',
name: `\`${config.prefix}?\``, inline: true,
value: 'This command', },
inline: true, {
}, name: `\`${config.prefix}rollhelp\` or \`${config.prefix}??\``,
{ value: `Details on how to use the roll command, listed as \`${config.prefix}xdy...${config.postfix}\` below`,
name: `\`${config.prefix}rollhelp\` or \`${config.prefix}??\``, inline: true,
value: `Details on how to use the roll command, listed as \`${config.prefix}xdy...${config.postfix}\` below`, },
inline: true, {
}, name: `\`${config.prefix}rollDecorators\` or \`${config.prefix}???\``,
{ value: `Details on how to use decorators on the roll command`,
name: `\`${config.prefix}api [subcommand]\``, inline: true,
value: `Administrative tools for the bots's API, run \`${config.prefix}api help\` for more details`, },
inline: true, {
}, name: `\`${config.prefix}api [subcommand]\``,
{ value: `Administrative tools for the bots's API, run \`${config.prefix}api help\` for more details`,
name: `\`${config.prefix}ping\``, inline: true,
value: 'Pings the bot to check connectivity', },
inline: true, {
}, name: `\`${config.prefix}ping\``,
{ value: 'Pings the bot to check connectivity',
name: `\`${config.prefix}info\``, inline: true,
value: 'Prints some information and links relating to the bot', },
inline: true, {
}, name: `\`${config.prefix}info\``,
{ value: 'Prints some information and links relating to the bot',
name: `\`${config.prefix}privacy\``, inline: true,
value: 'Prints some information about the Privacy Policy', },
inline: true, {
}, name: `\`${config.prefix}privacy\``,
{ value: 'Prints some information about the Privacy Policy',
name: `\`${config.prefix}version\``, inline: true,
value: 'Prints the bots version', },
inline: true, {
}, name: `\`${config.prefix}version\``,
{ value: 'Prints the bots version',
name: `\`${config.prefix}popcat\``, inline: true,
value: 'Popcat', },
inline: true, {
}, name: `\`${config.prefix}popcat\``,
{ value: 'Popcat',
name: `\`${config.prefix}report [text]\``, inline: true,
value: 'Report a command that failed to run', },
inline: true, {
}, name: `\`${config.prefix}report [text]\``,
{ value: 'Report a command that failed to run',
name: `\`${config.prefix}stats\``, inline: true,
value: 'Statistics on the bot', },
inline: true, {
}, name: `\`${config.prefix}stats\``,
{ value: 'Statistics on the bot',
name: `\`${config.prefix}heatmap\``, inline: true,
value: 'Heatmap of when the roll command is run the most', },
inline: true, {
}, name: `\`${config.prefix}heatmap\``,
{ value: 'Heatmap of when the roll command is run the most',
name: `\`${config.prefix}opt-out\` or \`${config.prefix}ignore-me\``, inline: true,
value: 'Adds you to an ignore list so the bot will never respond to you', },
inline: true, {
}, name: `\`${config.prefix}xdydzracsq!${config.postfix}\` ...`,
{ value:
name: `\`${config.prefix}opt-in\` **Available via DM ONLY**`, `Rolls all configs requested, you may repeat the command multiple times in the same message (just ensure you close each roll with \`${config.postfix}\`), run \`${config.prefix}??\` for more details`,
value: 'Removes you from the ignore list', inline: true,
inline: true, },
}, ],
{ }],
name: `\`${config.prefix}inline [subcommand]\``, }).catch((e: Error) => utils.commonLoggers.messageSendError('help.ts:82', message, e));
value: `Controls whether or not inline rolls can be done in a guild, run \`${config.prefix}inline help\` for more details`,
inline: true,
},
{
name: `\`${config.prefix}xdydzracsq!${config.postfix}\` ...`,
value:
`Rolls all configs requested, you may repeat the command multiple times in the same message (just ensure you close each roll with \`${config.postfix}\`), run \`${config.prefix}??\` for more details`,
inline: true,
},
],
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('help.ts:82', message, e));
}; };

View File

@ -1,43 +0,0 @@
import config from '~config';
import { HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
import { DecoratorsHelpPages } from 'commands/helpLibrary/decorators.ts';
import { DiceOptionsHelpPages } from 'commands/helpLibrary/diceOptions.ts';
import { DiceTypesHelpPages } from 'commands/helpLibrary/diceTypes.ts';
import { DifferencesHelpPages } from 'commands/helpLibrary/differences.ts';
import { FormattingHelpPages } from 'commands/helpLibrary/formatting.ts';
import { LegalMathComplexFuncsHelpPages } from 'commands/helpLibrary/legalMathComplexFuncs.ts';
import { LegalMathConstsHelpPages } from 'commands/helpLibrary/legalMathConsts.ts';
import { LegalMathFuncsHelpPages } from 'commands/helpLibrary/legalMathFuncs.ts';
import { LegalMathOperators } from 'commands/helpLibrary/legalMathOperators.ts';
import { LegalMathTrigFuncsHelpPages } from 'commands/helpLibrary/legalMathTrigFuncs.ts';
import { MiscFeaturesHelpPages } from 'commands/helpLibrary/miscFeatures.ts';
const name = `${config.name}'s Roll Command Details`;
const description = `You can chain as many of these options as you want, as long as the option does not disallow it. This command also can fully solve math equations with parenthesis.
The help options in this group use the notation \`xdy\` to indicate the basic/required dice notation for die count and size as detailed in the \`Dice Options>Basic Dice Options\` page.
As this supports the [Roll20 formatting](${config.links.roll20Formatting}) syntax fully, more details and examples can be found [here](${config.links.roll20Formatting}).
Please use the dropdown/select menus to search through the provided documentation.`;
const dict = new Map<string, HelpPage>([
['differences', DifferencesHelpPages],
['dice-types', DiceTypesHelpPages],
['dice-options', DiceOptionsHelpPages],
['decorators', DecoratorsHelpPages],
['formatting', FormattingHelpPages],
['misc-features', MiscFeaturesHelpPages],
['legal-math-operators', LegalMathOperators],
['legal-math-consts', LegalMathConstsHelpPages],
['legal-math-funcs', LegalMathFuncsHelpPages],
['legal-math-trig-funcs', LegalMathTrigFuncsHelpPages],
['legal-math-complex-funcs', LegalMathComplexFuncsHelpPages],
]);
export const RootHelpPages: HelpPage = {
name,
description,
isPage: true,
dict,
};

View File

@ -1,204 +0,0 @@
import config from '~config';
import { reservedCharacters } from 'artigen/dice/getModifiers.ts';
import { HelpContents, HelpPage } from 'commands/helpLibrary/helpLibrary.d.ts';
const name = 'Roll Command Decorators';
const description = `This command also has some useful decorators that can used. These decorators simply need to be placed after all rolls in the message.
**Examples:** \`${config.prefix}d20${config.postfix} -nd\`, \`${config.prefix}d20${config.postfix} -nd -s\`, \`${config.prefix}d20${config.postfix} ${config.prefix}d20${config.postfix} ${config.prefix}d20${config.postfix} -o a\``;
const dict = new Map<string, HelpContents>([
[
'-nd',
{
name: 'No Details',
description: `**Usage:** \`-nd\`
Hides some of the details of the requested roll, but will still show the list of roll results along with the formatted results.`,
example: ['`[[2000d20]] -nd` => Limits the details shown to just be `[[2000d20]]` = **__20935__**'],
},
],
[
'-snd',
{
name: 'Super No Details',
description: `**Usage:** \`-snd\`
Suppresses all details of the requested roll.`,
example: ['`[[2000d20]] -nd` => Removes the details section entirely'],
},
],
[
'-s',
{
name: 'Spoiler',
description: `**Usage:** \`-s\`
Spoilers all details of the requested roll`,
example: ['`[[d20]] -s`'],
},
],
[
'-max',
{
name: 'Maximize Roll',
description: `**Usage:** \`-max\` or \`-m\`
Rolls the theoretical maximum roll.
**Notice:** Cannot be used with \`-n\`, \`-min\`, or \`-sn\``,
example: ['`[[d20]] -max` => **20**'],
},
],
[
'-min',
{
name: 'Minimize Roll',
description: `**Usage:** \`-min\`
Rolls the theoretical minimum roll.
**Notice:** Cannot be used with \`-m\`, \`-max\`, \`-n\`, or \`-sn\``,
example: ['`[[d20]] -min` => __1__'],
},
],
[
'-n',
{
name: 'Nominal Roll',
description: `**Usage:** \`-n\`
Rolls the theoretical nominal roll.
**Notice:** Cannot be used with \`-m\`, \`-max\`, \`-min\`, or \`-sn\``,
example: ['`[[d20]] -n` => 10.5'],
},
],
[
'-sn',
{
name: 'Simulated Nominal Roll',
description: `**Usage:** \`-sn\`
Rolls the requests roll many times to approximately simulate the nominal of complex rolls, can specify the amount or accept default amount by not specify the amount.
**Notice:** Cannot be used with \`-m\`, \`-max\`, \`-min\`, \`-n\`, or \`-cc\``,
example: ['`[[4d6d1]] -sn` => 12.274'],
},
],
[
'-o',
{
name: 'Order Roll',
description: `**Usage:** \`-o a\` or \`-o d\`
\`a\` - Ascending (least to greatest)
\`d\` - Descending (greatest to least)
Rolls the requested roll and orders the results in the requested direction.`,
example: [
'`[[4d6d1]] [[4d6d1]] [[4d6d1]] [[4d6d1]] -o a` => 9, __9__, 12, **15**',
'`[[4d6d1]] [[4d6d1]] [[4d6d1]] [[4d6d1]] -o a` => **17**, 14, **13**, 9',
],
},
],
[
'-c',
{
name: 'Count Rolls',
description: `**Usage:** \`-c\`
Shows the Count Embed, containing the count of successful rolls, failed rolls, rerolls, drops, and explosions.`,
example: ['`[[40d20]] -c`'],
},
],
[
'-cc',
{
name: 'Confirm Critical Hits',
description: `**Usage:** \`-cc\`
Automatically rerolls whenever a crit hits.
**Notice:** Cannot be used with \`-sn\``,
example: ['`[[d20]] -cc` => **20** Auto-Confirming Crit: **20** Auto-Confirming Crit: 7'],
},
],
[
'-ct',
{
name: 'Comma Totals',
description: `**Usage:** \`-ct\`
Adds commas to totals for readability.`,
example: ['`[[100d20]] -ct` => Shows **__1,110__** instead of **__1110__**'],
},
],
[
'-gm',
{
name: 'GM Roll',
description: `**Usage:** \`-gm @user1 @user2 ... @userN\`
Rolls the requested roll in GM mode, suppressing all publicly shown results/details and instead sending the results directly to the specified GMs.`,
example: ['[[d20]] -gm @$'],
},
],
[
'-hr',
{
name: 'Hide Raw',
description: `**Usage:** \`-hr\`
Hide the raw input, showing only the results/details of the roll.`,
example: ['`[[d20]] -hr`'],
},
],
[
'-rd',
{
name: 'Roll Distribution',
description: `**Usage:** \`-rd\`
Shows a raw roll distribution of all dice in roll.`,
example: ['`[[1000d20]] -rd`'],
},
],
[
'-nv',
{
name: 'Number Variables',
description: `**Usage:** \`-nv\` or \`-vn\`
Mainly a debug decorator, useful when creating complex rolls that will be reused. Will not number the final roll command in the list as it will not be available for use.`,
example: ['`[[d20]] [[d20]] [[d20]] -vn`'],
},
],
[
'-cd',
{
name: 'Custom Dice',
description: `**Usage:** \`-cd name:[side1,side2,...,sideN]\` or \`-cd nameA:[side1,side2,...,sideN];nameB:[side1,side2,...,sideN]\`
\`name\` - The name you want to use for the custom die. Cannot include any of the following characters: \`${reservedCharacters.join(', ')}\`
\`sideN\` - Any number. Decimals are allowed, but may not work with some dice options.
Allows a user specified die to be used, allowing weird numbers or combinations of numbers to be used as dice. If Crit Success/Fail are not specified, they will be determined automatically.
To use the user specified die in rolling, simply put the name of the custom die before the \`d\` in the dice config.
If multiple custom dice are needed, separate their configurations with a \`;\`. Do not include any spaces in the configuration.`,
example: [
'`[[10testd]] -cd test:[5,20,400,60]` => [**400** + 60 + **400** + __5__ + 60 + 20 + 60 + **400** + __5__ + 60] = **__1470__**',
'`[[5testd + 5named]] -cd test:[5,20,400,60];name:[4,5,6]` => [60 + __5__ + 20 + __5__ + **400**]+[__4__ + __4__ + 5 + 5 + **6**] = **__514__**',
'`[[5testdr20!]] -cd test:[5,20,400,60]` => [**400** + 60! + __5__ + __5__ + 60 + ~~20~~ + 60] = **__590__**',
],
},
],
]);
export const DecoratorsHelpPages: HelpPage = {
name,
description,
isPage: true,
dict,
};

Some files were not shown because too many files have changed in this diff Show More