Compare commits

..

440 Commits

Author SHA1 Message Date
Ean Milligan 83595482b7 V4.1.2 - increasing maxLoops to 2M to hopefully limit maxLoopsError in simnom 2025-08-06 15:18:51 -04:00
Ean Milligan cdc710385f add missing file 2025-08-06 15:12:31 -04:00
Ean Milligan 59d4435c32 improve logging 2025-08-06 15:04:30 -04:00
Ean Milligan 2cfa923093 -6 loops overall by only incrementing loop count when regex/str.replace is being called 2025-08-06 14:50:20 -04:00
Ean Milligan c58ebcc9f9 -1 loop per iteration by only asserting paren () balance when () exist in the mathConf 2025-08-06 14:47:32 -04:00
Ean Milligan e01097b1e3 improve loop log message 2025-08-06 14:45:07 -04:00
Ean Milligan 4efbed7424 deno fmt + fix spacing in log message 2025-08-06 14:43:26 -04:00
Ean Milligan 4a5e33c9a0 -3 loops per iteration by only incrementing loop count when math is actually being handled 2025-08-06 14:40:46 -04:00
Ean Milligan 6bf671f82d -1 loop per iteration by only counting when necessary 2025-08-06 14:35:33 -04:00
Ean Milligan 587d5aa19d -1 loop per iteration by only doing rollDist when needed 2025-08-06 14:30:41 -04:00
Ean Milligan 1bb8c1a308 fix loopCount debug to actually log 2025-08-05 17:59:03 -04:00
Ean Milligan e06abac9cf Add debug logging to loopCountCheck 2025-08-05 17:48:24 -04:00
Ean Milligan c80b7c2235 V4.1.1 ver bump 2025-08-05 16:53:02 -04:00
Ean Milligan 959fd6e120 fix simnom being able to be negative 2025-08-05 16:52:12 -04:00
Ean Milligan de02ebcc09 fix getModifiers errors being ignored 2025-08-05 16:51:59 -04:00
Ean Milligan f989be56db Make simnom iterations performed per roll more readable 2025-08-05 16:46:30 -04:00
Ean Milligan 255955d854 V4.1.0 - Add unrestricted repeat roll system. 2025-08-05 15:46:11 -04:00
Ean Milligan 2f088907ad V4.0.2 2025-08-04 16:59:34 -04:00
Ean Milligan f38096fafb update help docs for decorators 2025-08-04 16:55:58 -04:00
Ean Milligan 67d5704a76 fix nested rolls not working in certain scenarios 2025-08-04 16:48:22 -04:00
Ean Milligan 3f6162a1a4 add success/fail/matches to count details 2025-08-04 16:36:51 -04:00
Ean Milligan d715214c01 deno fmt 2025-08-04 16:01:28 -04:00
Ean Milligan 1eac531b41 add missing initial value 2025-08-04 16:01:08 -04:00
Ean Milligan b9c5af6c73 fix rollcount being a string/bigint in apistats 2025-08-04 13:37:13 -04:00
Ean Milligan b9d436ba92 ver bump readme 2025-07-30 00:42:32 -04:00
Ean Milligan e553042e88 Fixed !>5 not working correctly (was misidentified as a numberless option) and added some additional debug to help id issues with tsep parsing in the future 2025-07-30 00:42:06 -04:00
Ean Milligan 657765df00 version bump 2025-07-23 21:14:36 -04:00
Ean Milligan 719de69350 Privacy Policy update 2025-07-23 21:12:12 -04:00
Ean Milligan b0e2abb941 add slash commands to commands docs 2025-07-23 01:40:58 -04:00
Ean Milligan 799efd38f3 API docs update 2025-07-23 01:25:24 -04:00
Ean Milligan f704cd9fa0 Update self hosting docs 2025-07-23 01:23:00 -04:00
Ean Milligan 152b9ff153 deno fmt 2025-07-23 01:22:52 -04:00
Ean Milligan e957e181d5 Initial docs reformat 2025-07-23 01:18:53 -04:00
Ean Milligan a37f5eb7d5 improve api response for /roll 2025-07-23 00:16:33 -04:00
Ean Milligan d6c5dfee77 Add -yvariables decorator to allow repeat roll button to work consistently 2025-07-22 23:31:47 -04:00
Ean Milligan d2692f1805 make number more readable 2025-07-22 21:13:18 -04:00
Ean Milligan 509147d651 logging fix 2025-07-22 17:02:54 -04:00
Ean Milligan ef5709b6a6 deno fmt 2025-07-22 17:02:47 -04:00
Ean Milligan 0006e66b16 add api for getting statistics 2025-07-22 16:48:51 -04:00
Ean Milligan 57cae9c01f move api key to auth tab in bruno 2025-07-22 16:31:50 -04:00
Ean Milligan bc6ad87aff Add api Ping for status testing 2025-07-22 16:29:28 -04:00
Ean Milligan f1025ce129 Implement slash commands for most things, some bugfixes and god knows what else 2025-07-22 01:19:20 -04:00
Ean Milligan 633e786d95 prep for app commands 2025-07-17 17:53:31 -04:00
Ean Milligan 6d731951c7 deno fmt 2025-07-17 17:11:11 -04:00
Ean Milligan a4ddce0bc8 Add Repeat Roll button 2025-07-17 04:56:43 -04:00
Ean Milligan 50d9c78b45 Upgrade web view system with auto timeout 2025-07-17 04:29:20 -04:00
Ean Milligan 44a1200084 update webview to support dms 2025-07-17 03:12:50 -04:00
Ean Milligan 96cffd9c4c deno fmt 2025-07-17 03:01:28 -04:00
Ean Milligan 2d55c073ee apply the hotfix better 2025-07-17 03:01:05 -04:00
Ean Milligan 3146f2bc96 i hate this, fix attachment sending in dms by tricking dd into doing it right 2025-07-17 02:58:31 -04:00
Ean Milligan 65fc9e56de remove devmode flag 2025-07-17 02:51:48 -04:00
Ean Milligan 46757b31bc fix inline roll handling 2025-07-17 02:42:13 -04:00
Ean Milligan 1052e7d2e2 Add Roll Web View 2025-07-17 02:32:14 -04:00
Ean Milligan 5e94056fc5 that should be a word, but ok cspell 2025-07-15 15:43:47 -04:00
Ean Milligan 7744d3a72e Fix permission checks on api/inline/alias 2025-07-15 15:42:50 -04:00
Ean Milligan 6e71878d42 remove roll logging, never was used, update db notes in config.example 2025-07-15 05:12:34 -04:00
Ean Milligan b0401809c4 Reimplement the roll decorators help commands as a shortcut to the roll decorators help home page, migrate all old help pages to new help library 2025-07-15 04:58:08 -04:00
Ean Milligan 79d72654e2 bugfix, this check should only happen in guild mode 2025-07-15 04:10:07 -04:00
Ean Milligan 0767b66f33 1 more rename for reorg 2025-07-15 01:27:29 -04:00
Ean Milligan d41ce7fc34 help lib reorg 2025-07-15 01:25:43 -04:00
Ean Milligan 5233d029f3 alias import update 2025-07-15 01:24:28 -04:00
Ean Milligan f2ed8a4f16 add alias rename command 2025-07-14 22:47:47 -04:00
Ean Milligan d677fc6a0c add clone/copy alias command 2025-07-14 22:29:04 -04:00
Ean Milligan d386561855 add -ns decorator 2025-07-13 06:04:50 -04:00
Ean Milligan 3b3ce821bd add new tables to guild cleanup when bot is removed from a guild 2025-07-13 05:52:57 -04:00
Ean Milligan 60f996b0a9 add roll alias docs 2025-07-13 05:35:37 -04:00
Ean Milligan 660f9e9bc2 clean up typing 2025-07-13 05:10:57 -04:00
Ean Milligan 500579f2a3 upgrade charset on db to support emojis 2025-07-13 05:10:37 -04:00
Ean Milligan 7301df4d9d fix alias system 2025-07-13 04:42:47 -04:00
Ean Milligan b055e0174d deno fmt 2025-07-13 04:42:31 -04:00
Ean Milligan f19c4216c0 not sure how this did not come up with the api in the past, but found out a resolve func cannot be passed into a worker. devised a system to store these off while the worker works and pick them back up on the main thread when applicable 2025-07-13 04:25:55 -04:00
Ean Milligan fe70166e6a Change alias name length to be driven by config, increase to 200char 2025-07-13 03:09:36 -04:00
Ean Milligan 4bbdb59f3d Implement full roll alias system with support for yVars, currently untested. Additionally: made numbers in code more readable, change indentation to spaces in db init files, fix simulatedNominal system defaults to be config driven 2025-07-13 01:22:47 -04:00
Ean Milligan fedba62d52 fix import 2025-07-11 23:21:22 -04:00
Ean Milligan c8af87e407 add strict typing to api embeds 2025-07-11 20:42:07 -04:00
Ean Milligan 05174aecd9 fix typos 2025-07-11 18:08:52 -04:00
Ean Milligan 075fa1de71 idk why that wasn't bolded 2025-07-09 21:30:22 -04:00
Ean Milligan 6d987a6d83 update user formatting help page to mention inline rolling 2025-07-09 19:42:15 -04:00
Ean Milligan 6299cdc7ec Add labels help page 2025-07-09 19:34:43 -04:00
Ean Milligan 31e32a29c6 properly fix getmodifiers not eating or eating too much, now output format should properly match input format 2025-07-09 15:28:02 -04:00
Ean Milligan dd2fc5b8df rm shorthand report command from readme 2025-07-09 15:13:35 -04:00
Ean Milligan ff9168e493 add docs and delete option to inline command 2025-07-09 15:11:54 -04:00
Ean Milligan 66010047b5 Add support for inline rolls, toggled by new [[inline command 2025-07-09 15:02:25 -04:00
Ean Milligan 754ce054b5 add more debug 2025-07-09 14:49:16 -04:00
Ean Milligan cb5522345a add missing toJSON field to bigints inside artigen's worker 2025-07-09 14:42:10 -04:00
Ean Milligan 515021a295 only add prefix to rolls that need it 2025-07-09 14:41:43 -04:00
Ean Milligan e0fdb1eb31 remove old help commands 2025-07-09 13:16:09 -04:00
Ean Milligan d9c49c588c Add help page for custom dice 2025-07-09 13:13:25 -04:00
Ean Milligan c445364aa7 deno fmt 2025-07-09 13:13:17 -04:00
Ean Milligan 156f3d528f All decimals in custom dice 2025-07-09 13:13:04 -04:00
Ean Milligan 30f0314695 Implement Custom Dice Shapes 2025-07-09 12:53:10 -04:00
Ean Milligan b2d4d0c0a4 fix modifiers parsing 2025-07-08 17:57:07 -04:00
Ean Milligan 3d146e9c7b remove exclusions for nonexistant files 2025-07-08 02:44:15 -04:00
Ean Milligan 6aa4c73d5d remove command prompt, its never been used and its just legacy code that never got cleaned up 2025-07-08 02:36:59 -04:00
Ean Milligan 4b6683525c add group help pages 2025-07-08 02:26:10 -04:00
Ean Milligan 7a4a33f661 Fix success/fail gt/lt on groups to actually work correctly 2025-07-08 02:18:52 -04:00
Ean Milligan 6e88e96cda handle having an initial config correctly 2025-07-08 01:32:51 -04:00
Ean Milligan 02e2faf58f specify the initial value of all reducers to avoid weird bugs/edge cases 2025-07-08 01:25:30 -04:00
Ean Milligan 4e12e11fe2 change how pages are referenced 2025-07-07 20:57:13 -04:00
Ean Milligan bddf7c6fdb Add help pages to detail how artigen is different from roll20 2025-07-07 20:54:02 -04:00
Ean Milligan d989e9d473 FULLY SUPPORT GROUPS FOIASHFOIASHFOIASFDH :D 2025-07-07 20:17:32 -04:00
Ean Milligan ad0aef6c94 add missing previousGroupData 2025-07-07 11:48:26 -04:00
Ean Milligan 499c277fba add missing modifiers from group rolls 2025-07-07 11:45:12 -04:00
Ean Milligan 41214bd0d3 Add support for modifiers on multi-mode 2025-07-07 04:33:23 -04:00
Ean Milligan cbac134f79 Add initial group support, only support SUM mode (ie no modifiers allowed) 2025-07-07 02:12:53 -04:00
Ean Milligan cb3cb6777d add honse 2025-07-07 00:04:34 -04:00
Ean Milligan 69e9d6ca1d the x was for indeX retard. wip code here 2025-07-06 03:50:03 -04:00
Ean Milligan abe49d49c2 add group balance checker 2025-07-01 02:18:38 -04:00
Ean Milligan 139ef44556 fix import 2025-07-01 02:16:56 -04:00
Ean Milligan 2b9de4be81 fix naming on this, idk why there were x's on them 2025-07-01 02:15:46 -04:00
Ean Milligan 5ee02241a8 prepare for grouped rolls 2025-06-28 21:56:25 -04:00
Ean Milligan 2867e5f557 add a couple cspell words 2025-06-28 21:50:24 -04:00
Ean Milligan 3ea48838f4 fully fix fate dice 2025-06-28 21:47:09 -04:00
Ean Milligan b6b1f872d2 start fixing fate dice with other shit 2025-06-28 20:58:40 -04:00
Ean Milligan 4ed561a15b add success/failures to readme 2025-06-28 20:31:45 -04:00
Ean Milligan 15fd57ea18 Add target success/failures option 2025-06-28 20:26:44 -04:00
Ean Milligan 38bc021455 add sorting dice to readme 2025-06-28 05:48:33 -04:00
Ean Milligan ed5f7d9f1e fix wrong docs 2025-06-28 05:47:19 -04:00
Ean Milligan f0ea31edf3 Add ** as an option for x^y 2025-06-28 05:43:10 -04:00
Ean Milligan c9772d3ccf Add Dice Sorting option 2025-06-28 05:36:10 -04:00
Ean Milligan 5f58f9cae8 Properly handle mt! or !m (or other future no number mashups) 2025-06-28 05:16:48 -04:00
Ean Milligan 0e009441ca Implement dice matching option 2025-06-28 04:34:46 -04:00
Ean Milligan 791dd3a626 allow all roll20 options on ova and cwod dice 2025-06-28 03:18:32 -04:00
Ean Milligan 57ce6e1d1b how tf was that missing from the readme 2025-06-26 05:17:26 -04:00
Ean Milligan 7116851139 Add Number Variables flag 2025-06-26 04:32:39 -04:00
Ean Milligan 41fbb1bd50 fix newlines not being respected in roll command, fix some followup issues in the [[r or [[roll command variants 2025-06-26 04:15:12 -04:00
Ean Milligan fcae60cb69 deno fmt 2025-06-26 03:39:52 -04:00
Ean Milligan f4fcb097c3 improve the YouNeedAD error message 2025-06-26 03:39:48 -04:00
Ean Milligan ac63642d3a add extra newline between raw and results 2025-06-26 03:31:46 -04:00
Ean Milligan 4516f17949 deno fmt 2025-06-26 03:28:35 -04:00
Ean Milligan 414bab3a0d fix plurality of success and fail on cwod dice 2025-06-26 03:27:33 -04:00
Ean Milligan ae321885d6 Remove the rollDecorators command, implement roll command help library, god this was a pain to write 2025-06-26 03:17:26 -04:00
Ean Milligan 5b7ceb9f88 add internal flag of if a roll is complex to add warning message to nominal 2025-06-21 21:41:56 -04:00
Ean Milligan 6162339942 strip whitespace from initial config in output text 2025-06-21 21:23:19 -04:00
Ean Milligan 0066652590 deno fmt 2025-06-21 21:09:05 -04:00
Ean Milligan 9d6b389d71 Add simulated nominal flag 2025-06-21 21:08:53 -04:00
Ean Milligan 8793011350 fix bruno rd docs 2025-06-21 16:24:09 -04:00
Ean Milligan babc57497e Add Roll Distribution flag 2025-06-21 16:18:04 -04:00
Ean Milligan 308f897eb7 update countDetail reducer 2025-05-06 04:20:29 -04:00
Ean Milligan b1fce05149 eliminate some cpu cycles per roll by only reducing when count or confirmCrit is on 2025-05-06 03:10:40 -04:00
Ean Milligan 7a0b49dc0c Add loopCountCheck to more loops 2025-05-06 03:00:57 -04:00
Ean Milligan 5e1a509c96 add loopCountCheck to new loop 2025-05-06 02:53:15 -04:00
Ean Milligan 14e909c89f Move count reducer to artigen/utils/counter.ts, implement auto-confirm-crit flag 2025-05-06 02:50:56 -04:00
Ean Milligan b169ee8632 Change artigen to take rollRequest to use originalCommand as the raw text, then use remaining args after getModifiers eats mods 2025-05-05 18:19:39 -04:00
Ean Milligan 2fe2c5f296 add hideRaw flag 2025-05-05 16:08:18 -04:00
Ean Milligan 3bda7a1187 update CSP per mdn lighthouse 2025-05-05 15:09:05 -04:00
Ean Milligan 46026d6a08 change hide details to just hide the 1+2+3 part 2025-05-05 14:15:18 -04:00
Ean Milligan 5d1d39bebf move artigen embeds into artigen's utils folder 2025-05-05 14:08:54 -04:00
Ean Milligan 7ba6da1404 alphabetize populatedefaults 2025-05-05 14:03:01 -04:00
Ean Milligan 902dcb00ea this should also be lowercase 2025-05-05 14:02:49 -04:00
Ean Milligan acc3522c2a that should be lowercase 2025-05-05 13:58:36 -04:00
Ean Milligan 4f3a9bb797 update guild create 2025-05-05 13:56:56 -04:00
Ean Milligan 55b2367900 fix db logging for opt-in 2025-05-05 13:56:34 -04:00
Ean Milligan 5da94fafa0 add lemon 2025-05-04 01:40:59 -04:00
Ean Milligan 3aa8faf991 comment 2025-05-03 23:28:53 -04:00
Ean Milligan ede1562f33 doesnt on empty list 2025-05-03 23:22:59 -04:00
Ean Milligan 1ea2f64341 add batching system to guild create 2025-05-03 23:21:51 -04:00
Ean Milligan e5e77189ec update log msgs 2025-05-03 23:06:13 -04:00
Ean Milligan 426f0d7069 move event handlers to separate files 2025-05-03 22:53:53 -04:00
Ean Milligan 897fe80533 comments 2025-05-03 22:30:43 -04:00
Ean Milligan 0b864b0f21 removed [[r shorthand from report command, add [[r and [[roll as aliases to the roll command 2025-05-03 22:02:15 -04:00
Ean Milligan fafaee67b3 fix rollhelp chars 2025-05-03 21:47:40 -04:00
Ean Milligan 504df7c80f fix (5)-5 so it does +-5 instead of *-5 2025-05-03 21:41:23 -04:00
Ean Milligan 058695415e change export order to match import order 2025-05-03 21:07:06 -04:00
Ean Milligan 65a182cb50 deno fmt 2025-05-03 21:03:30 -04:00
Ean Milligan aa7814d1fe fix imports 2025-05-03 20:59:49 -04:00
Ean Milligan 9a5574f91e move utils around 2025-05-03 20:58:18 -04:00
Ean Milligan 3e6844ed10 remove mod.d.ts since all types have been relocated now 2025-05-03 20:53:17 -04:00
Ean Milligan f2797e6c33 update common embed location, move some back into their own files 2025-05-03 20:47:58 -04:00
Ean Milligan f6eb3b2b69 fix gm roll mentions, fix api gm roll, add link back to original roll message in gm message 2025-05-03 19:54:42 -04:00
Ean Milligan 4de1115fa9 add type 2025-05-03 19:11:18 -04:00
Ean Milligan 99c2d096f7 add default param 2025-05-03 19:06:43 -04:00
Ean Milligan b7e58f56a5 Add support for variables (pulling from previous commands in same message) 2025-05-03 18:15:51 -04:00
Ean Milligan d142b35522 fix -number check to identify +-1 and collapse the +-1 into "+" and "-1" instead of "+" "-" and "1" 2025-05-03 17:06:38 -04:00
Ean Milligan d094efb279 log error in artigen 2025-05-03 17:01:59 -04:00
Ean Milligan 7808093bc7 add initial value to reduce 2025-05-03 16:49:00 -04:00
Ean Milligan 9d7aed2773 remove redundant gmModifiers 2025-05-03 10:15:04 -04:00
Ean Milligan c31436e45d turn off debug mode 2025-05-03 10:03:14 -04:00
Ean Milligan 829ec0ecea move getModifiers into artigen, simplify its error handling a lot 2025-05-03 09:54:25 -04:00
Ean Milligan 9bd757741b remove logging from loopmanager 2025-05-03 09:25:59 -04:00
Ean Milligan bb9a2014ed Move getRollConf to new file, move loopCountChecker to a manager file, implement loopCountCheck on all loops inside artigen 2025-05-03 09:22:07 -04:00
Ean Milligan c6c1a8918e rename entry to artigen.runCmd instead of parser 2025-05-03 08:56:30 -04:00
Ean Milligan bd8f7c8a6f change all object types to interface 2025-05-03 08:52:21 -04:00
Ean Milligan 69f95bf701 organize artigen's types 2025-05-03 08:50:33 -04:00
Ean Milligan c9aff85452 Move RollModifiers to live in artigen 2025-05-03 08:31:07 -04:00
Ean Milligan 1b2851353e move some files around, organize imports 2025-05-03 08:25:00 -04:00
Ean Milligan 07e76733ec some renames 2025-05-03 08:21:11 -04:00
Ean Milligan 8befcc1ca1 typing cleanup 2025-05-03 08:12:05 -04:00
Ean Milligan 9582d91ac1 fix imports on db files 2025-05-03 08:02:07 -04:00
Ean Milligan 03a2acc386 BIIIIIG change here: NESTED ROLLS :D
Also, start the reorganization of artigen to support nesting better, make smaller more readable files, and better named functions
2025-05-03 07:51:12 -04:00
Ean Milligan bdec5e7850 fix bruno docs 2025-05-02 14:12:22 -04:00
Ean Milligan a7856bc3f2 minor formatting change 2025-05-01 23:43:19 -04:00
Ean Milligan 2b579eb4ac break rollUtils into multiple files 2025-05-01 23:41:23 -04:00
Ean Milligan 730c441645 another rename for clearer stuff 2025-05-01 23:24:45 -04:00
Ean Milligan 7053c25719 reorganize worker management and queue management to be better 2025-05-01 23:19:08 -04:00
Ean Milligan 6ab0923d71 how did i never catch this, if prefix is not 2 characters it breaks, this allows it to be anything 2025-05-01 18:20:23 -04:00
Ean Milligan 7230a7e6b5 Apparently that import map affects deno run as well, fixed so bot runs now 2025-05-01 18:19:57 -04:00
Ean Milligan c981fe5664 [untested] - apply import order to artigen 2025-05-01 18:09:18 -04:00
Ean Milligan cb00147f6b [untested] whoops wasn't supposed to add that... 2025-05-01 18:01:54 -04:00
Ean Milligan f50dec29c7 [untested] - deno fmt 2025-05-01 18:01:23 -04:00
Ean Milligan 6e1c6e8db3 [untested] - start reorganizing the solver folder (renamed to artigen here), organize imports better since deno has support for it now 2025-05-01 17:56:06 -04:00
Ean Milligan b9c7fac984 add comma totals decorator 2025-04-29 18:02:51 -04:00
Ean Milligan 861d1e00cd deno fmt 2025-04-29 18:02:00 -04:00
Ean Milligan ed60f10d04 add guild size dist to audit guilds 2025-04-29 17:42:41 -04:00
Ean Milligan 047d41a20e support all one arg Math functions 2025-04-29 04:11:06 -04:00
Ean Milligan 57bde22e1a remove erroneous console.log 2025-04-29 04:10:52 -04:00
Ean Milligan 0250135c5f improve audit guilds, show counts of duplicate guild owners 2025-04-29 03:16:41 -04:00
Ean Milligan 15ecb45e65 add additional paren balance check 2025-04-29 02:41:31 -04:00
Ean Milligan ba51bd471c update auditGuilds to sort guilds by largest to smallest 2025-04-28 23:26:14 -04:00
Ean Milligan 8cf55aacf5 add time param to heatmap to try to prevent caching of the image on discord 2025-04-28 23:07:30 -04:00
Ean Milligan 3864cb91fc Add support for d% dice 2025-04-28 22:57:49 -04:00
Ean Milligan f44014c22a add query timing to stats command 2025-04-28 22:57:23 -04:00
Ean Milligan 1e350d4e2a force space between all results all the time, means we don't need the regex fixers 2025-04-28 20:28:26 -04:00
Ean Milligan bba4217bd7 make result auto pluralize 2025-04-28 19:50:54 -04:00
Ean Milligan 6b198ecb47 add new error to catch [[floor1_1d3]] and error out correctly 2025-04-28 19:36:54 -04:00
Ean Milligan ac8602f598 Add support for floor/abs/other math operators 2025-04-28 19:17:01 -04:00
Ean Milligan fd7bc5f152 indicate these vars are holding indexes 2025-04-28 03:04:42 -04:00
Ean Milligan 5b0de24466 add docs 2025-04-28 03:04:28 -04:00
Ean Milligan dcd49e20dd deno fmt 2025-04-28 03:04:14 -04:00
Ean Milligan e3dc7bc8b4 add docs for -min 2025-04-27 17:02:32 -04:00
Ean Milligan 4207021fa9 Add Min flag and add -max as option for max flag 2025-04-27 16:58:51 -04:00
Ean Milligan 5bef3878cc remove unstable flag 2025-04-27 16:31:55 -04:00
Ean Milligan 06095e3bdc Add loopCountCheck to maximizeRoll reroll shortcut 2025-04-27 04:58:54 -04:00
Ean Milligan 2f2a8f67e0 further improve roll safety, no doubling up on drop/keep 2025-04-27 04:46:02 -04:00
Ean Milligan e69806c443 Add additional safeties on rerolling, cleanse other numlists 2025-04-27 04:34:01 -04:00
Ean Milligan 73e4ca94b7 Swap indexOf to includes when indexOf was just being used to check if an item was in an array 2025-04-27 04:15:04 -04:00
Ean Milligan 2e2e08f48a Fix maximize with reroll turned on 2025-04-27 04:09:41 -04:00
Ean Milligan 105bc14e71 improve MaxLoopsExceeded error throwing to be separate func, add it to all loops in the roller. 2025-04-27 03:28:25 -04:00
Ean Milligan 5c517fad67 ensure die count and size are whole numbers 2025-04-27 01:26:30 -04:00
Ean Milligan ed80e08755 add commas to stats, prevent negative /hour nums 2025-04-27 00:47:04 -04:00
Ean Milligan 951d15fcf9 add sex 2025-04-26 23:59:22 -04:00
Ean Milligan 6afaf882fa fix pie 2025-04-26 23:55:30 -04:00
Ean Milligan 1d31a4f059 disable logging 2025-04-26 23:44:03 -04:00
Ean Milligan a42b75aa51 deno fmt 2025-04-26 23:42:43 -04:00
Ean Milligan f3a6a36fb0 update links to be driven by config, update strings to use bot name from config 2025-04-26 23:24:26 -04:00
Ean Milligan 8d3c22a39f missed these two 2025-04-26 23:09:32 -04:00
Ean Milligan 864f281c60 Mass spelling fix 2025-04-26 23:08:57 -04:00
Ean Milligan 74c733308f linting/type cleanup 2025-04-26 22:44:39 -04:00
Ean Milligan 103ad8a8f5 deno fmt 2025-04-26 22:38:11 -04:00
Ean Milligan 09e97eabc1 Formatting updates, slight reorg on loop control, drop loop count to 1million, use new Log4Deno to get closeLog func, fix logging in workers 2025-04-26 22:34:07 -04:00
Ean Milligan 8f24a3bfae add flag.ts to allow list for worker 2025-04-26 22:31:10 -04:00
Ean Milligan 44d966971a Add BigInt override to allow JSON.stringify to function smoothly 2025-04-26 22:30:44 -04:00
Ean Milligan a017793997 update comments 2025-04-26 21:11:56 -04:00
Ean Milligan 2c991e44f8 update my username to remove the #num from it 2025-04-26 17:05:18 -04:00
Ean Milligan 957e84ea2b remove weird/useless comment 2025-04-26 17:03:18 -04:00
Ean Milligan 4fbcf15953 fix [[popcat not working, 0 is false lol 2025-04-26 17:02:54 -04:00
Ean Milligan 8483367cb5 3.0.0 release, support Deno 2.X 2025-04-26 16:47:55 -04:00
Ean Milligan dff1e97186 update api roll to use resolve instead of old respondWith 2025-04-26 16:42:44 -04:00
Ean Milligan 5e14b4207e rm api docs from readme 2025-04-26 15:49:46 -04:00
Ean Milligan 94f2073ce9 Add bruno api docs 2025-04-26 15:47:34 -04:00
Ean Milligan 1240bff99c update api to use cleaner method of verifying query params 2025-04-26 15:19:16 -04:00
Ean Milligan e0e013bf61 fix rollQueue to handle a queued api roll if it happens 2025-04-26 15:16:16 -04:00
Ean Milligan 1069b99091 start fixing apiRoll 2025-04-26 14:49:11 -04:00
Ean Milligan d2a7c25879 migrate to deno 2.0 serve 2025-04-26 14:27:36 -04:00
Ean Milligan 8d4e21056c fix start command 2025-04-26 13:45:50 -04:00
Ean Milligan 73461647f6 update start command 2025-04-26 13:28:31 -04:00
Ean Milligan c7c974c395 deno fmt 2025-04-26 13:23:37 -04:00
Ean Milligan 76e007e2e4 update db structure to let init work correctly 2025-04-26 13:22:45 -04:00
Ean Milligan ef08cd779a deno upgrade 2025-04-26 13:18:05 -04:00
Ean Milligan 65fbd115fb Deno apparently got more picky about launching workers, so added more files to the read permission 2024-08-27 08:41:15 -04:00
Ean Milligan (Bastion) 783395e4a5 V2.1.3 - Fix server count reporting 2023-05-01 15:45:03 -04:00
Ean Milligan (Bastion) 23ca3a2665 Fix error that was causing crash when posting statistics
Additionally, added system logging to rc.service
2022-07-31 16:09:14 -04:00
Ean Milligan (Bastion) 766dff179e Update wording around OPT-IN command 2022-07-10 02:50:18 -04:00
Ean Milligan (Bastion) 7bb6018bdf Made [[opt-in available via DM only 2022-07-10 02:17:41 -04:00
Ean Milligan (Bastion) f43cf0fa31 Add mention of opt-out to the @mention and [[privacy commands 2022-07-10 02:03:00 -04:00
Ean Milligan (Bastion) 38146a3c8f deno fmt 2022-07-10 01:59:55 -04:00
Ean Milligan (Bastion) 539616679b Add opt-out/opt-in commands
Allows users to be globally ignored by the bot.
2022-07-10 01:58:37 -04:00
Ean Milligan (Bastion) 7b03f3140e V2.0.3 - Fix multiline input 2022-07-09 17:02:22 -04:00
Ean Milligan (Bastion) 74b0e287fb Add autodeletion to Privacy Policy 2022-07-09 14:56:18 -04:00
Ean Milligan (Bastion) f4b0e04cec V2.0.2 - Fix faulty array.splice in solver 2022-07-09 02:23:42 -04:00
Ean Milligan (Bastion) c4c7098479 V2.0.1 - Fix parens, update guild audit command 2022-07-08 23:51:31 -04:00
Ean Milligan (Bastion) 9a8c718d57 actual final 2022-07-08 01:38:41 -04:00
Ean Milligan (Bastion) bcc75f5cde Final update to rc service 2022-07-08 01:36:24 -04:00
Ean Milligan (Bastion) 18c5d2a23c update heatmap.png 2022-07-08 01:31:13 -04:00
Ean Milligan (Bastion) 9b93f6c802 service update 2022-07-07 20:50:16 -04:00
Ean Milligan (Bastion) b1c81aff78 service update 2022-07-07 20:41:02 -04:00
Ean Milligan (Bastion) 2346a6d994 service update 2022-07-07 20:37:32 -04:00
Ean Milligan (Bastion) 28e697efa2 update rc.service 2022-07-07 20:26:26 -04:00
Ean Milligan (Bastion) b17c529ba7 update rc.service 2022-07-07 20:20:44 -04:00
Ean Milligan (Bastion) 6d5c457fe3 Add rc.d service 2022-07-07 20:07:28 -04:00
Ean Milligan (Bastion) 0055690360 update service 2022-07-07 19:46:15 -04:00
Ean Milligan (Bastion) 6aeb983242 localmode can be turned off now 2022-07-06 00:42:34 -04:00
Ean Milligan (Bastion) 5b197256ee Made embeds for join/leave/startup messages 2022-07-06 00:04:05 -04:00
Ean Milligan (Bastion) 01af03883b This should be the final commit for V2.0.0 2022-07-05 23:50:38 -04:00
Ean Milligan (Bastion) 5b7cb60382 Guild Audit command flushed out 2022-07-05 23:49:10 -04:00
Ean Milligan (Bastion) 36405421f6 Compounding explosions are now functional 2022-07-05 22:34:59 -04:00
Ean Milligan (Bastion) 7823227f6e Penetrating explosions are now functional, added formatting for explosions 2022-07-05 22:19:10 -04:00
Ean Milligan (Bastion) 54f081db74 suck my dick sonar, this is not duplicate code 2022-07-05 21:49:43 -04:00
Ean Milligan (Bastion) 5366206951 Begin adding support for penetrating and compounding explosions
Added rollDecorators help command to make [[?? small enough to fit in discord
Added documentation on penetrating and compounding
2022-07-05 21:47:37 -04:00
Ean Milligan (Bastion) 0e69231f20 Add fate dice documentation 2022-07-05 19:33:28 -04:00
Ean Milligan (Bastion) 094974df80 Add support for fate dice, fixed math with zeroes 2022-07-05 19:27:06 -04:00
Ean Milligan (Bastion) 876a97b9fd Attempt adding DB files back into sonar? 2022-07-04 22:42:58 -04:00
Ean Milligan (Bastion) 73a06a5112 attempting to add rollHelp.ts back into sonar 2022-07-04 22:41:42 -04:00
Ean Milligan (Bastion) f79091a2a8 Add artificer.service for proper prod running, attempting to add cmdUtils back into sonar 2022-07-04 22:39:25 -04:00
Ean Milligan (Bastion) edac1db702 deno fmt & sonar fix 2022-07-04 22:33:02 -04:00
Ean Milligan (Bastion) a06042e6d4 Add OVA Dice documentation
Additionally, simplified the ova dropper
2022-07-04 22:26:14 -04:00
Ean Milligan (Bastion) 169ed564ae OVA dice done 2022-07-04 22:19:09 -04:00
Ean Milligan (Bastion) 8e84be1656 Started work on OVA dice, adjusted parser to fall back to the roller 2022-07-04 20:37:38 -04:00
Ean Milligan (Bastion) ed0a12c3bc deno fmt + added docs on cwod dice 2022-06-29 03:28:15 -04:00
Ean Milligan (Bastion) f95d24375d sonar fix 2022-06-29 03:21:09 -04:00
Ean Milligan (Bastion) 31ad662212 sonar fix 2022-06-29 03:18:45 -04:00
Ean Milligan (Bastion) 229353c3fe screwed the boolean logic up lol 2022-06-29 03:16:53 -04:00
Ean Milligan (Bastion) 11e42c77fd rework, deduplicated code 2022-06-29 03:16:19 -04:00
Ean Milligan (Bastion) 6dd0bd0e8e Missed removing these 2022-06-29 03:02:37 -04:00
Ean Milligan (Bastion) 6ad0d2ca2b Basic support added for CWOD rolls 2022-06-29 03:01:20 -04:00
Ean Milligan (Bastion) 61607dc75d Fix a weird race condition where the worker wouldn't be ready when it needed to be 2022-06-28 23:44:14 -04:00
Ean Milligan (Bastion) 872f67d908 update heatmap embed 2022-06-27 20:32:03 -04:00
Ean Milligan (Bastion) f9355ae255 Sonar fix 2022-06-26 23:20:06 -04:00
Ean Milligan (Bastion) 6adf18008a Update heatmap embed to show min and max vals 2022-06-26 23:18:44 -04:00
Ean Milligan (Bastion) 51924b7f14 sonar fix 2022-06-26 23:11:58 -04:00
Ean Milligan (Bastion) c71f6f76e4 update deno fmt, update std version, sonar fix 2022-06-26 23:08:32 -04:00
Ean Milligan (Bastion) a7cdd91969 Add heatmap.png generator 2022-06-26 22:53:25 -04:00
Ean Milligan (Bastion) 42cb268a58 sonar fix 2022-06-26 20:23:42 -04:00
Ean Milligan (Bastion) 8e6467fd17 Continued work on heatmap 2022-06-26 20:18:41 -04:00
Ean Milligan (Bastion) e8464cf7bb Initial work on [[heatmap, fixed some loggers to be edit instead of send 2022-06-25 16:38:04 -04:00
Ean Milligan (Bastion) 2b22df032c Change dailyRate to hourlyRate, added math to calculate the rates 2022-06-25 16:26:48 -04:00
Ean Milligan (Bastion) b69cf2060f Create roll heatmap table and sproc, add dailyRate to command_cnt table 2022-06-25 02:09:41 -04:00
Ean Milligan (Bastion) 011120845e created callIncCnt function for simplicity 2022-06-24 21:32:18 -04:00
Ean Milligan (Bastion) b0266ad385 forgot to remove these 2022-06-24 21:11:27 -04:00
Ean Milligan (Bastion) a8bcdb346b deno fmt 2022-06-24 21:06:45 -04:00
Ean Milligan (Bastion) d769ac0e5a added db error message log, added edit message log 2022-06-24 20:52:56 -04:00
Ean Milligan (Bastion) 57518f75a9 deno fmt 2022-06-22 21:45:40 -04:00
Ean Milligan (Bastion) 021f33fc38 Started moving common log messages to one spot 2022-06-22 21:43:57 -04:00
Ean Milligan (Bastion) bff208d560 add catches to all messages 2022-06-22 02:06:50 -04:00
Ean Milligan (Bastion) dba1976a8e Add messages to all api calls 2022-06-22 01:57:38 -04:00
Ean Milligan (Bastion) cc45794497 Added messages to most api calls 2022-06-21 21:48:07 -04:00
Ean Milligan (Bastion) 93375585c1 sonar fixes 2022-06-21 21:36:21 -04:00
Ean Milligan (Bastion) d45cc89eec Cleaned up API responses to make them more standard and easier to customize 2022-06-21 21:33:24 -04:00
Ean Milligan (Bastion) 0cabfe0c99 sonar fixes 2022-06-20 21:03:01 -04:00
Ean Milligan (Bastion) 6e448907ee deno fmt 2022-06-20 20:55:40 -04:00
Ean Milligan (Bastion) df8c31d6d3 API updated to user rollWorker for rolling, fixed api not working at all lol 2022-06-20 20:55:12 -04:00
Ean Milligan (Bastion) a0dae3416f Preparing rollQueue to handle api Rolls 2022-06-20 19:18:23 -04:00
Ean Milligan (Bastion) 170c089fe9 deno fmt 2022-06-20 17:37:45 -04:00
Ean Milligan (Bastion) 71e1d42f68 rollQueue reorg 2022-06-20 17:35:00 -04:00
Ean Milligan (Bastion) 3931ee8394 Add roll error code to embed footer 2022-06-19 21:56:50 -04:00
Ean Milligan (Bastion) 137f6388f7 comment updated 2022-06-19 04:19:25 -04:00
Ean Milligan (Bastion) 66f08bf538 deno fmt 2022-06-19 03:56:04 -04:00
Ean Milligan (Bastion) 4a34596bee Restructured API code to be more readable (like commands) 2022-06-19 03:55:33 -04:00
Ean Milligan (Bastion) 60e13cbd51 Moar Sonar Cleanup 2022-06-19 01:57:10 -04:00
Ean Milligan (Bastion) 65d264d1c6 Sonar cleanup 2022-06-19 01:53:51 -04:00
Ean Milligan (Bastion) bb10d52506 Implemented queueing system for rollWorker to limit number of concurrent workers
Moved some consts to the config.ts file
Added flag to disable logging while rolling for a massive speed increase and to increase stability of the bot
2022-06-19 01:51:16 -04:00
Ean Milligan (Bastion) b58cf31edb Merge remote-tracking branch 'origin/master' 2022-06-19 00:04:18 -04:00
Ean Milligan (Bastion) 52d9258b09 Update start command for worker thread 2022-06-19 00:04:14 -04:00
Ean Milligan 544252b316
Delete Thumbs.db 2022-06-19 00:03:36 -04:00
Ean Milligan (Bastion) 92c97579c0 Fixed CSS for mobile devices 2022-05-28 04:25:17 -04:00
Ean Milligan (Bastion) 26085e8238 actually fixed negative numbers 2022-05-28 04:10:09 -04:00
Ean Milligan (Bastion) db1e55b415 Sonar Fix 2022-05-28 03:49:24 -04:00
Ean Milligan (Bastion) 6e5c525839 updated www image of artificer 2022-05-28 03:45:49 -04:00
Ean Milligan (Bastion) da00caaa74 deno fmt 2022-05-28 03:41:33 -04:00
Ean Milligan (Bastion) 1f4d1e3ef6 Added rollWorker so that multiple dice rolls can happen simultaneously 2022-05-28 03:37:11 -04:00
Ean Milligan (Bastion) 7391a0fd68 deno fmt 2022-05-28 02:07:23 -04:00
Ean Milligan (Bastion) a01b8b8fd2 Added Audit system, Guild auditing put on hold 2022-05-28 02:07:00 -04:00
Ean Milligan (Bastion) b7f9fc852a reorder deps so comments are correct 2022-05-28 00:48:34 -04:00
Ean Milligan (Bastion) 6cfe98e954 deno fmt + rework api subcommands to be a switch statement 2022-05-28 00:09:09 -04:00
Ean Milligan (Bastion) 7cf62d44aa Delete guild from DB when bot is removed from said guild 2022-05-27 23:07:44 -04:00
Ean Milligan (Bastion) c0ba9b7080 update versions in readme 2022-05-27 22:30:09 -04:00
Ean Milligan 1514f23f58
reformat readme 2022-05-27 22:24:03 -04:00
Ean Milligan (Bastion) dd10ef2616 Added a couple more badges 2022-05-27 22:21:34 -04:00
Ean Milligan (Bastion) e4e04004f9 Add Sonar badges to README 2022-05-27 22:19:02 -04:00
Ean Milligan (Bastion) d9cd3a5065 Bump Log4Deno version 2022-05-27 22:15:27 -04:00
Ean Milligan (Bastion) 63fa76c704 oops had wrong file path 2022-05-27 22:03:07 -04:00
Ean Milligan (Bastion) abcb4972bf sonar properties update 2022-05-27 22:01:37 -04:00
Ean Milligan (Bastion) 17c8e0c599 constantCmds rewrite, moved single-use text nodes to their individual command files 2022-05-27 21:59:23 -04:00
Ean Milligan (Bastion) 25cc171f3c removed unused import 2022-05-27 21:00:34 -04:00
Ean Milligan (Bastion) b887b93bb2 Roll command now utilizes embeds, count decorator (-c) complete, added [[api h shorthand 2022-05-27 20:59:09 -04:00
Ean Milligan (Bastion) bbba797dc3 Sonar Cleanup - Phase 8 2022-05-22 17:30:30 -04:00
Ean Milligan (Bastion) 764b8c103b Sonar Cleanup - Phase 7 2022-05-22 17:16:01 -04:00
Ean Milligan (Bastion) 45207f9cfc Sonar Cleanup - Phase 6 2022-05-22 17:08:02 -04:00
Ean Milligan (Bastion) aa97a1514c Sonar Cleanup - Phase 5 2022-05-22 16:59:40 -04:00
Ean Milligan (Bastion) 9eff1f0835 Sonar Cleanup - Phase 4 2022-05-22 16:12:05 -04:00
Ean Milligan (Bastion) 3c64e0cb06 Sonar Cleanup - Phase 3 2022-05-22 15:58:30 -04:00
Ean Milligan (Bastion) d6ec306792 Sonar Cleanup - Phase 2 2022-05-22 15:53:44 -04:00
Ean Milligan (Bastion) 891a36a9ba Sonar Cleanup - Phase 1 2022-05-22 15:29:59 -04:00
Ean Milligan (Bastion) 46d6014ed5 Trying advice from colin@sonar 2022-05-20 11:29:09 -04:00
Ean Milligan (Bastion) 1b01e93ef0 sonar fixed? 2022-05-20 10:19:32 -04:00
Ean Milligan (Bastion) 96300c31df add deno.json to sonar 2022-05-20 04:50:12 -04:00
Ean Milligan (Bastion) 5e84b1c0b4 added deno.json, ran deno fmt to standardize formatting 2022-05-20 04:43:22 -04:00
Ean Milligan (Bastion) 925eb08205 test 2022-05-20 04:24:21 -04:00
Ean Milligan (Bastion) 0277298c37 so it was reading this... 2022-05-20 04:21:46 -04:00
Ean Milligan (Bastion) c7b46b9cdc I'm confused 2022-05-20 04:19:08 -04:00
Ean Milligan (Bastion) dd725aa750 continue sonar 2022-05-20 04:14:37 -04:00
Ean Milligan (Bastion) c81fc11b2b continue 2022-05-20 04:12:34 -04:00
Ean Milligan (Bastion) 1e7addaed9 still tryna get sonar workin 2022-05-20 04:10:45 -04:00
Ean Milligan (Bastion) 23d1542104 messin with sonar setup 2022-05-20 04:00:34 -04:00
Ean Milligan (Bastion) e3c59673c5 put it back 2022-05-20 03:42:03 -04:00
Ean Milligan (Bastion) aaa04837a0 update projectkey 2022-05-20 03:38:43 -04:00
Ean Milligan 3f2b86c018
Create sonarcloud.yml 2022-05-20 03:35:34 -04:00
Ean Milligan (Bastion) 3ee26bdd86 nevermind on sonar I guess 2022-05-20 01:00:27 -04:00
Ean Milligan (Bastion) a994a81e89 nevermind on verbose I guess 2022-05-20 00:55:35 -04:00
Ean Milligan (Bastion) 551bfbbeaf ... 2022-05-20 00:51:59 -04:00
Ean Milligan (Bastion) 79da4e51da troubleshooting... 2022-05-20 00:49:28 -04:00
Ean Milligan (Bastion) 74337dcea4 actually fixed projectKey for sonar 2022-05-20 00:28:17 -04:00
Ean Milligan (Bastion) bf382d01ad Added embed generator for Counts, fixed projectKey for sonar 2022-05-20 00:27:27 -04:00
Ean Milligan 8454d3e189
Create sonarcloud.yml 2022-05-20 00:24:32 -04:00
Ean Milligan (Bastion) bf7a0aa5d2 Fix screwy embeds on mobile.
Why tf do embeds keep whitespace on mobile but not on desktop, no one will ever know.  Thanks for the consistency discord!
2022-05-19 23:14:41 -04:00
Ean Milligan (Bastion) a270a4b8f7 Started work on adding count decorator 2022-05-19 02:53:30 -04:00
Ean Milligan (Bastion) 04d7324769 deduplicated some code in the getModifiers func 2022-05-18 05:12:04 -04:00
Ean Milligan (Bastion) 394ae211df Set colors for all embeds 2022-05-18 05:03:24 -04:00
Ean Milligan (Bastion) 733908f2c0 Added !o !<>= and !o<>=, updated embeds to be more consistent, started coloring embeds 2022-05-18 04:48:52 -04:00
Ean Milligan (Bastion) 26b9309aa3 Added more loop protection, started on exploding once/on range options 2022-05-17 01:41:53 -04:00
Ean Milligan (Bastion) febd735c05 removed roll docs from roller.ts file
This was redundant info found in the README and [[?? command.  There is no reason to have three copies of this,
2022-05-17 01:11:38 -04:00
Ean Milligan (Bastion) 0cafda573f Added Reroll Once option, which cannot be used with the standard reroll option 2022-05-17 01:09:01 -04:00
Ean Milligan (Bastion) 54a6a900d3 Add roll formatting documentation to readme 2022-05-17 00:39:38 -04:00
Ean Milligan (Bastion) cd549eca81 small formatting fix 2022-05-17 00:36:46 -04:00
Ean Milligan (Bastion) 32815146e8 small formatting fix 2022-05-17 00:36:20 -04:00
Ean Milligan (Bastion) 781092a24f Added GEQ/LEQ documentation to readme 2022-05-17 00:35:42 -04:00
Ean Milligan (Bastion) ddd091d572 Improved rollhelp cmd 2022-05-17 00:28:26 -04:00
Ean Milligan (Bastion) b18ff7dc68 Updated rollhelp to inclue QEG/LEQ option on reroll 2022-05-13 19:25:23 -04:00
Ean Milligan (Bastion) 5b27abbba9 Added @mention command to learn about the bot 2022-05-13 18:57:45 -04:00
Ean Milligan (Bastion) 72b715b188 Added GEQ/LEQ option to reroll (similar to cs modifier) 2022-05-13 18:24:03 -04:00
Ean Milligan (Bastion) c8882bd500 added super no details api flag to docs 2022-05-10 00:26:00 -04:00
Ean Milligan (Bastion) 9b77912a85 Added super no details flag to the api 2022-05-09 19:52:15 -04:00
Ean Milligan (Bastion) eba7e028b2 Finalized hide-warn command 2022-05-09 19:18:49 -04:00
Ean Milligan (x1g5) 88faa27278 Added hideWarn cmd file with template code 2022-05-09 17:43:15 -04:00
Ean Milligan (x1g5) 0d1ef83f50 DB design update to support per channel configuration for the api 2022-05-09 17:25:59 -04:00
Ean Milligan (Bastion) fade84a87b starting addition of [[api hide-warn 2022-05-09 11:08:19 -04:00
Ean Milligan (Bastion) 337b266456 Continued reorg, broke solver down into parts for better readability 2022-05-06 23:20:04 -04:00
Ean Milligan (Bastion) 15e8f847c5 Add super no details decorator 2022-05-06 00:10:21 -04:00
Ean Milligan (Bastion) 06df068ac2 Added calculating stats message to stats command 2022-05-05 23:08:30 -04:00
Ean Milligan (Bastion) d422db9cc0 Readme format update 2022-05-05 03:49:47 -04:00
Ean Milligan (Bastion) 120532b630 Fixes issues around negative numbers 2022-05-05 03:39:22 -04:00
Ean Milligan (Bastion) 6a305d1479 added 𝜋 char support 2022-05-05 02:59:57 -04:00
Ean Milligan (Bastion) 6b2aae8dfb Update to use Log4Deno dep 2022-05-05 02:49:06 -04:00
Ean Milligan (Bastion) 50288534b6 roll flags renamed to decorators
Additional details also added to the privacy policy/terms of service to specify exactly what discord bot they apply to
2022-05-05 02:28:51 -04:00
Ean Milligan (Bastion) 23c16f7832 Optimized emoji command, made all command responsess more dynamic
Emoji command should be significantly more optimized by rejecting non-emojis much faster
Bot will now respond with the prefix/postfix set in the config instead of assuming the prefix/postfix are always [[/]]
2022-05-05 02:19:19 -04:00
Ean Milligan (Bastion) d2083bea53 converted all substr to substring 2022-05-05 01:51:05 -04:00
Ean Milligan (Bastion) 541747285f Minor adjustments
report command now requires a description
moved an insert query that was duplicated many times to new constant in the db.ts import
improved error reporting on roll modifiers, they now are fancy EMBEDS!
2022-05-05 01:46:47 -04:00
Ean Milligan (Bastion) ce77893d37 Use new dbClient import in the db init files 2022-05-05 01:03:28 -04:00
Ean Milligan (Bastion) 84768293c1 Code cleanup
Improved readability of command code by breaking each function into their own files, breaking down the api command, and starting to break down the roll command

Dep versions also updated, dbClient implemented as its own import, and constantCmds/README updated to improve readability
2022-05-05 00:57:34 -04:00
Ean Milligan (Bastion) b076751bf2 Converted 90% of bot messages into Embeds
Bot messages will now be pretty :3
2021-11-22 01:11:10 -05:00
Ean Milligan (Bastion) 60188ca5d8 Initial work on V2.0.0
Updated dependencies and corrected syntax errors caused by the newer deps.
2021-11-21 22:14:57 -05:00
Ean Milligan (Bastion) 73288073b1 typo fix 2021-05-18 10:47:39 -04:00
Ean Milligan (Bastion) 4442571dd0 Documentation Update
This update to documentation was done to prepare for verification with Discord.
Added a mention of the Privacy Policy and Terms in the README.
Small update to the privacy policy, fixing a placeholder text I forgot about and detailing why data may be kept in accordance with GDPR.
Added a Terms of Service/User Agreement.
2021-05-18 10:46:37 -04:00
Ean Milligan (Bastion) 15864e7f6d V1.4.3 - Minor Update + Bugfixes
All files have received a handful of debug messages, this is to aid in finding the source of some recent crashes and to help find future issues
config.example.ts, */index.html, README.md - version number bump
utils.ts - Console will only get spammed by DEBUG level messages if artificer is in DEBUG mode.  DEBUG statements are still logged to file
solver.ts - actually set the original index on the rolls to allow drop/keep to reset the order correctly, change return text to use localPrefix and remove code blocks
mod.ts - breaks args on \n now, -o arg now allows full words instead of just o or a, error checking on file size to make sure the results always send and details send when possible
api.ts - added error checking on file size to make sure the results always send and details send when possible
2021-03-28 00:29:06 -04:00
Ean Milligan (Bastion) d449d1d85d utils.log implemented
Log files will now be created locally for easy debugging
api.ts, mod.ts, intervals.ts, solver.ts - replaced console.log with utils.log
mod.d.ts - made object syntax consistent
utils.enums.ts - Created LogTypes enum for utils.log
utils.ts - Created initLog and log for logging to files
2021-03-14 23:27:53 -04:00
Ean Milligan (Bastion) 0b5f44d18c Shortlink created
Replaced roll20 direct links with a shorter link that I can change if roll20 moves/removes this page
2021-03-13 15:33:21 -05:00
Ean Milligan (Bastion) b4d2d71873 Implemented botlist statistic updates
config.example.ts - added botlist object(s), adjusted emoji default values since the ID won't be the same for anyone who spins this up themselves
deps.ts - added botID
mod.ts - Added calling of new updateListStatistics for non-local bots
README.md - Added support invite link, fixed some tab vs spaces issues, added mention of development plans on the milestones pge, linked Deno and Discordeno
intervals.ts - Added updateListStatistics function
2021-03-13 15:10:35 -05:00
Ean Milligan 2a22dc7b04 Update issue templates 2021-02-22 17:25:09 -05:00
Ean Milligan 28f93446bd Update issue templates 2021-02-22 17:18:37 -05:00
Ean Milligan (Bastion) ddb8f62eca V1.4.2 - Code Rework + minor bugfixes
.gitignore - added new logs folder for future feature
config.example.ts, README.md, *index.html - version bumped
deps.ts - moved all remote dependencies to this file to make version updating easier
db/* - changed deps to new format
longStrings.ts, README.md - fixed typo
mod.ts - changed deps to new format, implemented error catching for editBotStatus, moved api to separate file
api.ts - pulled api code to this file for better organization
intervals.ts - new utility file for functions that will be called after bot boots (and that continue to be called on a specified interval
mod.d.ts - documentation added
solver.ts - newline
utils.ts - updated deps to new format
2021-02-19 20:44:38 -05:00
196 changed files with 14199 additions and 3067 deletions

View File

@ -0,0 +1,17 @@
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

@ -0,0 +1,17 @@
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

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

View File

@ -0,0 +1,16 @@
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

@ -0,0 +1,16 @@
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

@ -0,0 +1,16 @@
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

@ -0,0 +1,16 @@
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

@ -0,0 +1,16 @@
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

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

View File

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

View File

@ -0,0 +1,16 @@
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

@ -0,0 +1,16 @@
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

@ -0,0 +1,16 @@
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

@ -0,0 +1,15 @@
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

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

View File

@ -0,0 +1,11 @@
meta {
name: Bot Statistics
type: http
seq: 2
}
get {
url: http://localhost:8166/api/stats
body: none
auth: inherit
}

View File

@ -0,0 +1,11 @@
meta {
name: Ping
type: http
seq: 1
}
get {
url: http://localhost:8166/api/ping
body: none
auth: inherit
}

View File

@ -0,0 +1,8 @@
meta {
name: General Utility
seq: 5
}
auth {
mode: inherit
}

View File

@ -0,0 +1,17 @@
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

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

View File

@ -0,0 +1,34 @@
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]&ns=[no-spaces, removes the default added space between rolls]
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]
ns: [no-spaces, removes the default added space between rolls]
}

View File

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

View File

@ -0,0 +1,14 @@
meta {
name: Authenticated
seq: 1
}
auth {
mode: apikey
}
auth:apikey {
key: X-Api-Key
value: [YOUR-API-KEY-HERE]
placement: header
}

View File

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

View File

@ -0,0 +1,16 @@
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

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

12
.bruno/bruno.json Normal file
View File

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

3
.bruno/collection.bru Normal file
View File

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

20
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,20 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.

View File

@ -0,0 +1,14 @@
---
name: Report API Abuse
about: Believe you have encountered API Abuse? Report it here
title: ''
labels: report api abuse
assignees: ''
---
**Describe the suspected API Abuse:**
If you believe you have encountered someone abusing the API, describe it here. For example, User X was sending x rolls per minute when it was not desired.
**Artificer Message ID:**
Copy and paste the Message ID of one (or more) of the messages sent by The Artificer that you believe were API Abuse. If you do not know what a Message ID is or how to copy it, please refer to [this Discord Help Article](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-) for details on how to find the Message ID.

4
.gitignore vendored
View File

@ -1,2 +1,4 @@
config.ts config.ts
emojis/Thumbs.db **/**/Thumbs.db
logs
src/endpoints/gets/heatmap.png

15
.sonarcloud.properties Normal file
View File

@ -0,0 +1,15 @@
# Path to sources
sonar.sources=.
sonar.exclusions=emojis
#sonar.inclusions=
# Path to tests
#sonar.tests=
#sonar.test.exclusions=
#sonar.test.inclusions=
# Source encoding
sonar.sourceEncoding=UTF-8
# Exclusions for copy-paste detection
#sonar.cpd.exclusions=

56
.vscode/settings.json vendored
View File

@ -1,12 +1,64 @@
{ {
"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",
"Empheral",
"Exponentials",
"funciton",
"guildid",
"hidewarn",
"imagescript",
"indev",
"Inno",
"LOCALMODE",
"localtoken",
"longtext",
"mtsf",
"Mult",
"nojs",
"noodp",
"noopener",
"noydir",
"oldcnt",
"oper",
"ovad",
"PEMDAS",
"ralias",
"Rehost",
"Rehosts",
"resultid",
"rolla",
"rollalias",
"rolldecorators",
"rollhelp",
"rollstr",
"rsfop",
"sproc",
"Tarantallegra",
"tinyint",
"tinytext",
"unauthorised",
"unban",
"xcwody",
"xdydz",
"xdydzracsq",
"xovady",
"yvar",
"yvariables"
]
} }

10
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,10 @@
## 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

@ -1,27 +1,59 @@
# The Artificer's Privacy Policy # The Artificer's Privacy Policy
## Information relating to Discord Interactions ## Information relating to Discord Interactions
### Public Bot Information ### Public Bot Information
Publicly available versions of `The Artificer#8166` (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#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. 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.
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 sent in a Guild with the Inline Roll System disabled and do not begin with _The Bot_'s command prefix are not scanned for commands. Data regarding these messages are not saved or stored anywhere.
* Messages that do begin with _The Bot_'s command prefix do not log user data, and most commands to not log any data. The commands that log data are the report command (in Discord, this command is known as `[[report` or `[[r`) and the API enable/disable commands (in Discord, these commands are known as `[[api enable`, `[[api allow`, `[[api disable`, and `[[api block`). * Messages sent in a Guild with the Inline Roll System enabled and do not begin with _The Bot_'s command prefix are only scanned for Inline Roll commands. If an Inline Roll command is found, it is sent through the Roll command handler. If an Inline Roll command is not found, the message is ignored and not scanned for other inline commands. Data regarding these messages are not saved or stored anywhere.
* The report command only stores the text placed within the message that is directly after the command (herein referred to as _The Report Text_). This command is entirely optional, meaning users never need to run this command under normal usage of _The Bot_. This command is only intended to be used to report roll commands that did not output what was expected. This command will accept any value for _The Report Text_, thus it is up to the user to remove any sensitive information before sending the command. _The Report Text_ is stored in a private Discord Guild in a channel that only _The Developer_ can see. _The Report Text_ is solely used to improve _The Bot_, either by providing a feature suggestions or alerting _The Developer_ to bugs that need patched. * Messages that do begin with _The Bot_'s command prefix are scanned for commands that _The Bot_ supports. Any commands that store any amount or kind of data are listed below. If the command or system is not listed, then it does not log or store any data.
* The API enable/disable commands only stores the Discord Guild ID upon usage. These commands are entirely optional, meaning users never need to run this command under normal usage of _The Bot_. These commands only need to be used when the user desires to utilize the optional API. Discord Guild IDs are internal IDs generated and provided by Discord. _The Bot_ only uses the stored Discord Guild IDs to ensure that API users cannot interact with Guilds that do not allow it or to check if an API user is a member of said Guild. The Guild IDs are only visible to _The Developer_ thru direct database administration. This direct database administration is only used when there are issues with _The Bot_'s database. * The report command (in Discord, this command is known as `/report`, `[[report`, or `[[r`):
* This command is entirely optional, meaning users never need to run this command under normal usage of _The Bot_. This command is only intended to be used to report roll commands that did not output what was expected.
* The report command only stores the text placed within the message that is directly after the command (herein referred to as _The Report Text_). This command will accept any value for _The Report Text_, thus it is up to the user to remove any sensitive information before sending the command.
* _The Report Text_ is solely used to improve _The Bot_, either by providing a feature suggestions or alerting _The Developer_ to bugs that need patched.
* _The Report Text_ is stored in a private Discord Guild in a channel that only _The Developer_ can see.
* The API Control System (in Discord, these commands are known as `[[api enable`, `[[api allow`, `[[api disable`, and `[[api block`):
* These commands are entirely optional, meaning users never need to run this command under normal usage of _The Bot_. These commands only need to be used when the user desires to utilize the optional API.
* The API enable/disable commands only stores the Discord Guild ID and Discord Channel ID upon usage. Discord Guild IDs and Channel IDs are internal IDs generated and provided by Discord.
* _The Bot_ only uses the stored Discord Guild IDs to ensure that API users cannot interact with Guilds that do not allow it or to check if an API user is a member of said Guild.
* The Discord Guild IDs and Discord Channel IDs are only visible to _The Developer_ thru direct database administration. This direct database administration is only used when there are issues with _The Bot_'s database.
* The Opt Out System (in Discord, this command is known as `[[opt-out` or `[[ignore-me`):
* This command is optional, meaning users normally do not need to run this command under normal usage of _The Bot_. This command is intended for privacy, allowing a user to block _The Bot_ from reading their messages.
* The Opt Out command only stores the Discord User ID of the user who ran the command. Discord User IDs are internal IDs generated and provided by Discord.
* _The Bot_ only uses the stored Discord User IDs to filter what messages it will scan for commands. If a Discord User ID is in the Opt Out list, messages from this user will be immediately ignored.
* The Discord User IDs are only visible to _The Developer_ thru direct database administration. This direct database administration is only used when there are issues with _The Bot_'s database.
* The Inline Roll System (in Discord, these commands are known as `/toggle-inline-rolls enable`, `[[inline enable`, `[[inline allow`, `/toggle-inline-rolls disable`, `[[inline block`, `[[inline disable`, and `[[inline delete`):
* This system is entirely optional, meaning users never need to run these commands under normal usage of _The Bot_. This system is only intended to be used when a user wants to utilize Inline Rolls in their Guild.
* The Inline Roll System only stores the Discord Guild ID upon usage. Discord Guild IDs are internal IDs generated and provided by Discord.
* _The Bot_ only uses the stored Discord Guild IDs to determine which Guilds it should do a preliminary scan for Inline Rolls for all messages sent.
* The Discord Guild IDs are only visible to _The Developer_ thru direct database administration. This direct database administration is only used when there are issues with _The Bot_'s database.
* The Unrestricted Repeat Roll System (in Discord, these commands are known as `/toggle-unrestricted-repeat enable`, `[[repeat enable`, `[[repeat allow`, `/toggle-unrestricted-repeat disable`, `[[repeat block`, `[[repeat disable`, and `[[repeat delete`):
* This system is entirely optional, meaning users never need to run these commands under normal usage of _The Bot_. This system is only intended to be used when a user wants to utilize Unrestricted Repeat Rolls in their Guild.
* The Unrestricted Repeat Roll System only stores the Discord Guild ID upon usage. Discord Guild IDs are internal IDs generated and provided by Discord.
* _The Bot_ only uses the stored Discord Guild IDs to determine which Guilds it should allow Unrestricted Repeat Rolls in.
* The Discord Guild IDs are only visible to _The Developer_ thru direct database administration. This direct database administration is only used when there are issues with _The Bot_'s database.
* The Roll Alias System (in Discord, this system contains all commands starting with `/alias`, `[[rollalias`, `[[ralias`, `[[alias`, `[[rolla`, and `[[ra`, and the subcommands that _The Bot_ will store data from are `add`, `create`, `set`, `update`, `replace`, `copy`, `clone`, and `rename`):
* This system is entirely optional, meaning users never need to run these commands under normal usage of _The Bot_. This system is only intended to be used when a user wants to save roll commands for later reuse.
* The Roll Alias System stores the user provided Alias Name, the user provided Roll String, and in Guild mode, the Discord Guild ID of the Guild the command was run in; and in personal mode, the Discord User ID of the user who ran the command. The Alias Name is string of up to 200 characters that the user provided to save the Roll String under. The Roll String is a string of up to 4,000 characters containing roll commands and formatting text the user provided. Discord Guild IDs and Discord User IDs are internal IDs generated and provided by Discord.
* _The Bot_ uses the Discord Guild IDs and Discord User IDs to determine who and where an alias can be used. The Alias Names are used to look up the requested Roll String. The Roll Strings are used to execute the requested alias.
* The Alias Names and Roll Strings may be shown in Discord by using the following subcommands: `list`, `list-all`, `preview`, and `view`. In Guild mode, these subcommands will only show the Alias Names and Roll Strings created in the Guild the command was run in. In Personal mode, these subcommands will only show the Alias Names and Roll Strings the user created. The Alias Names, Roll Strings, Discord Guild IDs and Discord User IDs are visible to _The Developer_ thru direct database administration. This direct database administration is only used when there are issues with _The Bot_'s database.
All commands contribute to a global counter to track the number of times a command is used. These counters do not keep track of where commands were run, only counting the number of times the command has been called. These counters have no way of being tracked back to the individual commands run by the users. All commands contribute to a global counter to track the number of times a command is used. These counters do not keep track of where commands were run, only counting the number of times the command has been called. These counters have no way of being tracked back to the individual commands run by the users.
If the Discord interaction is not explicitly mentioned above, it does not collect any information at all. If the Discord interaction is not explicitly mentioned above, it does not collect any information at all.
### Private Bot Information ### Private Bot Information
Privately hosted versions of The Artificer (in other words, bots running The Artificer's source code, but not running under the publicly available _Bot_, `The Artificer#8166`) (herein referred to as _Rehosts_ or _Rehost_) may be running in DEVMODE, a mode that allows the _Rehost_ to log every roll command used. This mode is intended for development use only, and only allows the roll command to function in the Guild specified in `config.ts` as `config.devServer`. _The Developer_ is not responsible for _Rehosts_, thus _Rehosts_ of _The Bot_ are not recommended to be used. Privately hosted versions of The Artificer (in other words, bots running The Artificer's source code, but not running under the publicly available _Bot_, `The Artificer#8166` (Discord ID: `789045930011656223`)) (herein referred to as _Rehosts_ or _Rehost_) may contain modifications that can log data not mentioned in this document.
All policies described in **Public Bot Information** apply to _Rehosts_.
Due to the nature of open source code, _Rehosts_ may not use the same codebase that is available in this repository. _The Developer_ does not moderate what other developers do to this codebase. This means that if you are not using the publicly available _Bot_ and instead using a _Rehost_, this _Rehost_ could collect any information it desires. Due to the nature of open source code, _Rehosts_ may not use the same codebase that is available in this repository. _The Developer_ does not moderate what other developers do to this codebase. This means that if you are not using the publicly available _Bot_ and instead using a _Rehost_, this _Rehost_ could collect any information it desires.
All policies described in **Public Bot Information** apply to _Rehosts_ running the official codebase. If the _Rehost_ has additional modifications, the developer(s) behind it should list any new instances data storage/logging.
# Direct Database Administration
_The Developer_ will only use direct database administration when there are issues with _The Bot_'s database. These issues are include, but are not limited to, the following: data corruption, data recovery, data migration, manual data deletion, database schema updates. While handling these issues, _The Developer_ will only view the required tables, and will not exfiltrate any data unless required by the issue (such as by data recovery). Any data retrieved from the database during an aforementioned issue will be stored securely.
# Information relating to the Optional API Interactions # Information relating to the Optional API Interactions
_The Bot_'s API (herein referred to as _The API_) does not automatically collect any information. Users utilizing _The API_ are required to provide a small amount of information before using _The API_. _The Bot_'s API (herein referred to as _The API_) does not automatically collect any information. Users utilizing _The API_ are required to provide a small amount of information before using _The API_.
@ -35,9 +67,41 @@ When using _The API_'s roll endpoint (herein referred to as _The Roll Endpoint_)
# Deleting Your Data # Deleting Your Data
## API Data Deletion ## API Data Deletion
If you would like to remove all of your submitted data, this can easily be done using the [[BUTTON NAME]] button on _The Bot_'s [API Tools](https://artificer.eanm.dev/). This will delete all Discord Channel ID/Discord User ID combos that you have submitted. This will also delete your API key entry, completely removing your email address and Discord User ID from _The Bot_'s database. If you would like to remove all of your submitted data, this can easily be done using the Delete API Key option on _The Bot_'s [API Tools](https://artificer.eanm.dev/). This will delete all Discord Channel ID/Discord User ID combos that you have submitted. This will also delete your API key entry, completely removing your email address and Discord User ID from _The Bot_'s database.
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 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.
## Discord Command Data Deletion 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 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.
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.
## Report 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.
## Opt Out System Data Deletion
If you would like to remove your Discord User ID from _The Bot_'s database, simply send `[[opt-in` as a Direct Message to _The Bot_. Please note the bot will no longer be ignoring your messages, and it will resume scanning our messages for commands.
## Inline Roll System Data Deletion
If you would like to remove your Discord Guild ID from _The Bot_'s database, simply send one of the following commands in the Guild: `/toggle-inline-rolls disable`, `[[inline disable`, `[[inline block`, or `[[inline delete`. All variants of this delete the Discord Guild ID from _The Bot_'s database.
Additionally, _The Bot_ will automatically delete the Discord Guild ID from _The Bot_'s database when _The Bot_ is removed from your guild.
## Unrestricted Repeat Roll System Data Deletion
If you would like to remove your Discord Guild ID from _The Bot_'s database, simply send one of the following commands in the Guild: `/toggle-unrestricted-repeat disable`, `[[repeat disable`, `[[repeat block`, or `[[repeat delete`. All variants of this delete the Discord Guild ID from _The Bot_'s database.
Additionally, _The Bot_ will automatically delete the Discord Guild ID from _The Bot_'s database when _The Bot_ is removed from your guild.
## Alias System Data Deletion
### Personal Mode
If you would like to remove all your Personal aliases from _The Bot_'s database, simply send one of the following commands in any channel you share with _The Bot_: `/alias personal delete-all`, `[[alias delete-all`, or `[[alias remove-all`. As deletion is irreversible, _The Bot_ requires a verification code to execute the deletion. _The Bot_ will create a verification code for you when you first send the command.
If you only need to delete one alias, the `delete-one` subcommand is available.
### Guild Mode
If you would like to remove all Guild aliases from _The Bot_'s database, simply send one of the following commands in the Guild: `/alias guild delete-all`, `[[alias guild delete-all`, or `[[alias guild remove-all`. As deletion is irreversible, _The Bot_ requires a verification code to execute the deletion. _The Bot_ will create a verification code for you when you first send the command.
If you only need to delete one alias, the `delete-one` subcommand is available.
Additionally, _The Bot_ will automatically delete all Guild aliases when _The Bot_ is removed from your guild.

186
README.md
View File

@ -1,182 +1,48 @@
# The Artificer - A Dice Rolling Discord Bot # The Artificer - A Dice Rolling Discord Bot | V4.1.2 - 2025/08/06
Version 1.4.1 - 2021/02/13 [![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)
The Artificer is a Discord bot that specializes in rolling dice. The bot utilizes the compact [Roll20 formatting](https://roll20.zendesk.com/hc/en-us/articles/360037773133-Dice-Reference) for ease of use and will correctly perform any needed math on the roll (limited to basic algebra). The Artificer is a Discord bot that specializes in rolling dice. The bot utilizes the compact [Roll20 formatting](https://artificer.eanm.dev/roll20) for ease of use and will correctly perform any needed math on the roll (limited to basic algebra).
This bot was developed to replace the Sidekick discord bot after it went offline many times for extended periods. This was also developed to fix some annoyances that were found with Sidekick, specifically its vague error messages (such as `"Tarantallegra!"`, what is that supposed to mean) and its inability to handle implicit multiplication (such as `4(12 + 20)`). This bot was developed to replace the Sidekick discord bot after it went offline many times for extended periods, and is now dead according to their GitHub. This was also developed to fix some annoyances that were found with Sidekick, specifically its vague error messages (such as `"Tarantallegra!"`, what is that supposed to mean) and its inability to handle implicit multiplication (such as `4(12 + 20)`).
## Using The Artificer ## Using The Artificer
I am hosting this bot for public use and you may find its invite link below. If you would like to host this bot yourself, details of how to do so are found at the end of this README, but I do not recommend this unless you are experienced with running Discord bots. I am hosting this bot for public use and you may find its invite link below. If you would like to host this bot yourself, details of how to do so are located [here](https://github.com/Burn-E99/TheArtificer/blob/master/docs/SELF_HOSTING.md), but I do not recommend this unless you are experienced with running Discord bots.
After inviting the bot, if you would like it to remove the message requesting the popcat emoji, you will need to give the `The Artificer` role the `Manage Messages` permission. All other permissions needed are handled by the invite link. After inviting the bot, if you would like it to remove the message requesting the popcat emoji, you will need to give the `The Artificer` role the `Manage Messages` permission. All other permissions needed are handled by the invite link.
[Bot Invite Link](https://discord.com/api/oauth2/authorize?client_id=789045930011656223&permissions=2048&scope=bot) [Bot Invite Link](https://discord.com/api/oauth2/authorize?client_id=789045930011656223&permissions=2048&scope=bot)
[Support Server Invite Link](https://discord.gg/peHASXMZYv)
--- ---
## Available Commands
The Artificer comes with a few supplemental commands to the main rolling command.
* `[[help or [[h or [[?`
* Provides a message similar to this available commands block.
* `[[rollhelp or [[??`
* Details on how to use the roll command, listed as `[[xdy...]]` below.
* `[[api [subcommand]`
* Administrative tools for the bots's API. These commands may only be used by the Owner or Admins of your guild.
* Available Subcommands:
* `[[api help`
* Provides a message similar to this subcommand description.
* `[[api status`
* Shows the current status of the API for this guild.
* `[[api allow or [[api enable`
* Allows API Rolls to be sent to this guild.
* `[[api block or [[api disable`
* Blocks API Rolls from being sent to this guild.
* `[[api delete`
* Deletes this guild from The Artificer's database.
* `[[ping`
* Tests the latency between you, Discord, and the bot.
* `[[info or [[i`
* Outputs some information and links relating to the bot.
* `[[privacy`
* Prints some information about the Privacy Policy, found in `PRIVACY.md`.
* `[[version or [[v`
* Prints out the current version of the bot.
* `[[popcat or [[pop or [[p`
* Sends the animated popcat emote for those who do not have Discord Nitro.
* If bot is given the permission `Manage Messages`, the bot will remove the message requesting the emote.
* `[[stats or [[s`
* Prints out how many users, channels, and servers the bot is currently serving.
* `[[report or [[r [command that failed]`
* 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.
* 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.
* `[[xdydzracsq!]]`
* 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://roll20.zendesk.com/hc/en-us/articles/360037773133-Dice-Reference), this will no different.
* 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.
* Parameters for rolling:
| Paramater | Required? | Repeatable? | Description |
|---------------|-------------|---------------|--------------------------------------------------------------------------------------------------|
| x | Optional | No | number of dice to roll, if omitted, 1 is used |
| 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 any other drop or keep options |
| kz or khz | Optional | No | keeps the highest z dice, cannot be used any other drop or keep options |
| dhz | Optional | No | drops the highest z dice, cannot be used any other drop or keep options |
| klz | Optional | No | keeps the lowest z dice, cannot be used any other drop or keep options |
| ra | Optional | Yes | rerolls any rolls that match a, r3 will reroll any dice that land on 3, throwing out old rolls |
| 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 greater than or equal to q |
| cfq or cs=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 greater than or equal to q |
| ! | Optional | No | exploding, rolls another dy for every crit roll |
* 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.
* Examples:
* `[[4d20]]` will roll 4 d20 dice and add them together.
* `[[4d20r1!]]` will roll 4 d20 dice, rerolling any dice that land on 1, and repeatedly rolling a new d20 for any critical success rolled.
* `[[d20/40]]` will roll a d20 die and divide it by 40.
* `[[((d20+20) - 10) / 5]]` will roll a d20, add 20 to that roll, subtract off 10, and finally divide by 5.
* This command also has some useful flags that can used. These flags simply need to be placed after all rolls in the message:
* `-nd` - No Details - Suppresses all details of the requested roll
* `-s` - Spoiler - Spoilers all details of the requested roll
* `-m` - Maximize Roll - Rolls the theoretical maximum roll, cannot be used with -n
* `-n` - Nominal Roll - Rolls the theoretical nominal roll, cannot be used with -m
* `-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
## 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.
Guilds Owners or Admins must run the `[[api allow` command for any users to be able to use the `/api/roll` endpoint.
Every API request **requires** the header `X-Api-Key` with the value set to the API key granted to you.
* If an API fails, these are the possible responses:
* `400` - Bad Request - Query parameters missing or malformed.
* `403` - Forbidden - API Key is not authenticated or user does not match the owner of the API Key.
* `404` - Not Found - Requested endpoint does not exist.
* `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.
API URL: `https://artificer.eanm.dev/api/`
Available Endpoints and Methods Required:
* `/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):
* `nd` - No Details - Suppresses all details of the requested roll.
* `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 [API Tools](https://artificer.eanm.dev/).
## 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.
--- ---
## Self Hosting The Artificer ## Available Commands
The Artificer was built on Deno `v1.7.0` using Discodeno `v10.0.0`. 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. Available commands are listed [here](https://github.com/Burn-E99/TheArtificer/blob/master/docs/COMMANDS.md).
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. ## API Details
API details are listed [here](https://github.com/Burn-E99/TheArtificer/blob/master/docs/API.md).
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`. ## Privacy Policy and Terms of Service
The Artificer has a Privacy Policy and Terms of Service to detail expectations of what user data is stored and how users should use The Artificer. The following Privacy Policy and Terms of Service only apply to the officially hosted version of The Artificer (`The Artificer#8166`, Discord ID: `789045930011656223`).
Privacy Policy TL;DR:
- If you use the report command, the text submitted will be stored for a short period of time.
- If you use the Roll Alias System, any data submitted to it will be stored until you delete it.
- If you use the API, submitted Discord Ids will be stored linked to your email.
- If you use the Inline Roll System, Discord Guild Ids will be stored.
- If you use the Opt Out System, Discord User Ids will be stored.
For more detailed information, please check out the full [PRIVACY POLICY](https://github.com/Burn-E99/TheArtificer/blob/master/PRIVACY.md).
Terms of Service TL;DR: Don't abuse or attempt to hack/damage The Artificer or its API. If you do, you may be banned from use. For more detailed information, please check out the full [TERMS OF SERVICE](https://github.com/Burn-E99/TheArtificer/blob/master/TERMS.md).
--- ---

14
TERMS.md Normal file
View File

@ -0,0 +1,14 @@
# The Artificer's Terms of Service/User Agreement
By using The Artificer and/or The Artificer's API, you agree to the following terms. Breaking these terms may result in your account being banned from using The Artificer's API, The Artificer (Discord Bot), or both.
1. **User Conduct**. You agree to obey all applicable laws in using the Service, and agree that you are responsible for the content and/or communications you send to or initiate via The Artificer. You agree that you are responsible for everything that you transmit to or in relation to The Artificer and you specifically agree (in relation to The Artificer) not to participate in any form of activity which is unlawful, harassing, libellous, defamatory, abusive, threatening, harmful, vulgar, obscene, profane, sexually-oriented, racially-offensive or otherwise includes objectionable material;
* not to collect personal data about other Users (for any purpose);
* not to register more than one account for yourself or anyone else;
* not to use The Artificer to engage in any commercial activities not approved in writing by the Developer, Ean Milligan;
* not to impose an unreasonable or disproportionately large load on our infrastructure; and
* not to attempt to gain unauthorised access to The Artificer's computer systems or engage in any activity that disrupts, diminishes the quality of, interferes with the performance of, or impairs the functionality of The Artificer or The Artificer's API.
2. **Hacking**. You agree and undertake not to attempt to damage, deny service to, hack, crack, or otherwise interfere (collectively, "Interfere") with The Artificer in any manner. If you in any way Interfere with these, you agree to pay all damages we incur as a result. We reserve the right to deny any or all access or service to any User for any reason, at any time, at our sole discretion. You agree that we may block your access, and at our sole discretion to disallow your continued use of The Artificer. We reserve the right to take any action we may deem appropriate in our sole discretion with respect to violations or enforcement of the terms of this Agreement, and we expressly reserve all rights and remedies available to us at law or in equity.
3. **Termination of this Agreement**. The Artificer may at any time terminate this legal Agreement, in our sole discretion without prior notice to you, if we believe that you may have breached (or acted in a manner indicating that you do not intend to or are unable to comply with) any term herein, or if we are legally required to do so by law, or if continuation is likely to be no longer commercially viable.
4. **Contacting Us**. If you have any questions, please contact us via email at <ean@milligan.dev>.

21
artificer.rc Normal file
View File

@ -0,0 +1,21 @@
#!/bin/sh
# PROVIDE: artificer
. /etc/rc.subr
name="artificer"
rcvar="artificer_enable"
pidfile="/var/dbots/TheArtificer/artificer.pid"
artificer_root="/var/dbots/TheArtificer"
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_log="/var/log/artificer.log"
artificer_chdir="${artificer_root}"
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"
load_rc_config artificer
run_rc_command "$1"

14
artificer.service Normal file
View File

@ -0,0 +1,14 @@
[Unit]
Description=The Artificer Discord Bot
Documentation=https://github.com/Burn-E99/TheArtificer
After=network.target
[Service]
Type=simple
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
RestartSec=60
Restart=on-failure
[Install]
WantedBy=multi-user.target

View File

@ -1,41 +1,93 @@
export const config = { export const config = {
"name": "The Artificer", // Name of the bot name: 'The Artificer', // Name of the bot
"version": "1.4.1", // Version of the bot maxFileSize: 8_388_290, // Max file size bot can send
"token": "the_bot_token", // Discord API Token for this bot version: '4.1.2', // Version of the bot
"localtoken": "local_testing_token", // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token" token: 'the_bot_token', // Discord API Token for this bot
"prefix": "[[", // Prefix for all commands localtoken: 'local_testing_token', // Discord API Token for a secondary OPTIONAL testing bot, THIS MUST BE DIFFERENT FROM "token"
"postfix": "]]", // Postfix for rolling command prefix: '[[', // Prefix for all commands
"api": { // Setting for the built-in API postfix: ']]', // Postfix for rolling command
"enable": false, // Leave this off if you have no intention of using this/supporting it limits: {
"port": 8080, // Port for the API to listen on // Limits for the bot functions
"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 alias: {
"rateLimitTime": 10000, // Time range for how often the API rate limits will be lifted (time in ms) // Roll Alias system
"rateLimitCnt": 10, // Amount of requests that can be made (successful or not) during above time range before getting rate limited maxNameLength: 200, // Max alias name length allowed in DB
"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 free: {
"adminKey": "your_25char_api_token", // API Key generated by nanoid that is 25 char long, this gets pre-populated into all_keys user: 100, // Allows users to have 100 aliased rolls for free
"email": "" // Temporary set up for email, this will be adjusted to an actual email using deno-smtp in the future. guild: 1_000, // Allows guilds to have 1000 aliased rolls for free
}, },
"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 },
"host": "", // IP address for the db, usually localhost maxLoops: 2_000_000, // Determines how long the bot will attempt a roll, number of loops before it kills a roll. Increase this at your own risk.
"localhost": "", // IP address for a secondary OPTIONAL local testing DB, usually also is localhost, but depends on your dev environment 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)
"port": 3306, // Port for the db workerTimeout: 300_000, // Maximum time before the bot kills a worker thread in ms
"username": "", // Username for the account that will access your DB, this account will need "DB Manager" admin rights and "REFERENCES" Global Privalages defaultSimulatedNominal: 10_000, // Default number of loops to run for simulating a nominal
"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 maxSimulatedNominal: 100_000, // Max number of loops a user can specify for simulating a nominal
"name": "" // Name of the database Schema to use for the bot },
}, api: {
"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 // Setting for the built-in API
"logChannel": "the_log_channel", // Discord channel ID where the bot should put startup messages and other error messages needed enable: false, // Leave this off if you have no intention of using this/supporting it
"reportChannel": "the_report_channel", // Discord channel ID where reports will be sent when using the built-in report command publicDomain: 'http://example.com/', // Public domain that the API is behind, should end with a /
"devServer": "the_dev_server", // Discord guild ID where testing of indev features/commands will be handled, used in conjuction with the DEVMODE bool in mod.ts port: 8080, // Port for the API to listen on
"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 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
{ // Emoji object, duplicate for each emoji rateLimitTime: 10_000, // Time range for how often the API rate limits will be lifted (time in ms)
"name": "popcat", // Name of emoji in discord rateLimitCnt: 10, // Amount of requests that can be made (successful or not) during above time range before getting rate limited
"aliases": ["popcat", "pop", "p"], // Commands that will activate this emoji 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
"id": "796340018377523221", // Discord emoji ID for this emoji adminKey: 'your_25char_api_token', // API Key generated by nanoid that is 25 char long, this gets pre-populated into all_keys
"animated": true, // Tells the bot this emoji is animated so it sends correctly email: 0n, // Temporary set up for email, this will be adjusted to an actual email using deno-smtp in the future.
"deleteSender": true // Tells the bot to attempt to delete the sender's message after sending the emoji },
} db: {
] // Settings for the MySQL database, this is required for use with the API, roll aliases, and inline rolling
host: '', // IP address for the db, usually localhost
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
username: '', // Username for the account that will access your DB, this account will need "DB Manager" admin rights and "REFERENCES" Global Privileges
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
name: '', // Name of the database Schema to use for the bot
},
links: {
// Links that are used in the bot
sourceCode: 'https://github.com/Burn-E99/TheArtificer', // Link to the repository
supportServer: '', // Invite link to the Discord support server
roll20Formatting: 'https://help.roll20.net/hc/en-us/articles/360037773133-Dice-Reference', // Link to Roll20 Dice Reference
mathDocs: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math', // Link to the MDN docs for Math
homePage: '', // Link to the bot's home/ad page
privacyPolicy: '', // Link to the current Privacy Policy
termsOfService: '', // Link to the current Terms of Service
},
logChannel: 0n, // Discord channel ID where the bot should put startup messages and other error messages needed
reportChannel: 0n, // Discord channel ID where reports will be sent when using the built-in report command
devServer: 0n, // Discord guild ID where testing of indev features/commands will be handled, used in conjunction 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
{
// 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
id: 'the_emoji_id', // Discord emoji ID for this emoji
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,118 +1,224 @@
// 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 { Client } from "https://deno.land/x/mysql/mod.ts"; import dbClient from 'db/client.ts';
import { LOCALMODE } from "../flags.ts"; console.log('Attempting to create DB');
import config from "../config.ts";
// Log into the MySQL DB
const dbClient = await new Client().connect({
hostname: LOCALMODE ? config.db.localhost : config.db.host,
port: config.db.port,
username: config.db.username,
password: config.db.password,
});
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};`);
await dbClient.execute(`USE ${config.db.name}`); await dbClient.execute(`USE ${config.db.name}`);
console.log("DB created"); console.log('DB created');
console.log("Attempt to drop all tables"); console.log('Attempt to drop all tables');
await dbClient.execute(`DROP VIEW IF EXISTS db_size;`);
await dbClient.execute(`DROP TABLE IF EXISTS allowed_channels;`); await dbClient.execute(`DROP TABLE IF EXISTS allowed_channels;`);
await dbClient.execute(`DROP TABLE IF EXISTS all_keys;`); await dbClient.execute(`DROP TABLE IF EXISTS all_keys;`);
await dbClient.execute(`DROP TABLE IF EXISTS allowed_guilds;`); await dbClient.execute(`DROP TABLE IF EXISTS allowed_guilds;`);
await dbClient.execute(`DROP TABLE IF EXISTS roll_log;`); await dbClient.execute(`DROP TABLE IF EXISTS roll_log;`);
await dbClient.execute(`DROP PROCEDURE IF EXISTS INC_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;`);
console.log("Tables dropped"); await dbClient.execute(`DROP TABLE IF EXISTS ignore_list;`);
await dbClient.execute(`DROP TABLE IF EXISTS allow_inline;`);
await dbClient.execute(`DROP TABLE IF EXISTS aliases;`);
await dbClient.execute(`DROP TABLE IF EXISTS allow_unrestricted_repeat;`);
console.log('Tables dropped');
console.log("Attempting to create table command_cnt"); // Holds guilds that have explicitly allowed anyone to repeat anyone's rolls
console.log('Attempting to create table allow_unrestricted_repeat');
await dbClient.execute(` await dbClient.execute(`
CREATE TABLE command_cnt ( CREATE TABLE allow_unrestricted_repeat (
command char(20) NOT NULL, guildid bigint unsigned NOT NULL,
count bigint unsigned NOT NULL DEFAULT 0, PRIMARY KEY (guildid),
PRIMARY KEY (command), UNIQUE KEY allow_unrestricted_repeat_guildid_UNIQUE (guildid)
UNIQUE KEY command_cnt_command_UNIQUE (command) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`); `);
console.log("Table created"); console.log('Table created');
console.log("Attempt creating increment Stored Procedure"); // Holds all aliases that have been created
console.log('Attempting to create table aliases');
await dbClient.execute(` await dbClient.execute(`
CREATE PROCEDURE INC_CNT( CREATE TABLE aliases (
IN cmd CHAR(20) guildid bigint unsigned NOT NULL,
) userid bigint unsigned NOT NULL,
BEGIN aliasName varchar(200) NOT NULL,
declare oldcnt bigint unsigned; rollStr varchar(4000) NOT NULL,
set oldcnt = (SELECT count FROM command_cnt WHERE command = cmd); yVarCnt tinyint unsigned NOT NULL,
UPDATE command_cnt SET count = oldcnt + 1 WHERE command = cmd; premium tinyint(1) NOT NULL,
END createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (guildid, userid, aliasName)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`); `);
console.log("Stored Procedure created"); console.log('Table created');
console.log("Attempting to create table roll_log"); // Holds guilds that have explicitly allowed inline rolls
console.log('Attempting to create table allow_inline');
await dbClient.execute(` await dbClient.execute(`
CREATE TABLE roll_log ( CREATE TABLE allow_inline (
id int unsigned NOT NULL AUTO_INCREMENT, guildid bigint unsigned NOT NULL,
input text NOT NULL, PRIMARY KEY (guildid),
resultid bigint NULL, UNIQUE KEY allow_inline_guildid_UNIQUE (guildid)
result longtext NOT NULL, ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
api tinyint(1) NOT NULL,
error tinyint(1) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY roll_log_id_UNIQUE (id),
UNIQUE KEY roll_log_resultid_UNIQUE (resultid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`); `);
console.log("Table created"); console.log('Table created');
console.log("Attempting to create table allowed_guilds"); // 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(` await dbClient.execute(`
CREATE TABLE allowed_guilds ( CREATE TABLE ignore_list (
guildid bigint unsigned NOT NULL, userid bigint unsigned NOT NULL,
createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (userid),
active tinyint(1) NOT NULL DEFAULT 0, UNIQUE KEY ignore_list_userid_UNIQUE (userid)
banned tinyint(1) NOT NULL DEFAULT 0, ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
PRIMARY KEY (guildid),
UNIQUE KEY allowed_guilds_guildid_UNIQUE (guildid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`); `);
console.log("Table created"); console.log('Table created');
console.log("Attempting to create table all_keys"); // Light telemetry on how many commands have been run
console.log('Attempting to create table command_cnt');
await dbClient.execute(` await dbClient.execute(`
CREATE TABLE all_keys ( CREATE TABLE command_cnt (
userid bigint unsigned NOT NULL, command char(20) NOT NULL,
apiKey char(25) NOT NULL, count bigint unsigned NOT NULL DEFAULT 0,
deleteCode char(10) NULL, hourlyRate float unsigned NOT NULL DEFAULT 0,
email char(255) NULL, PRIMARY KEY (command),
createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY command_cnt_command_UNIQUE (command)
active tinyint(1) NOT NULL DEFAULT 1, ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
banned tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (userid),
UNIQUE KEY all_keys_userid_UNIQUE (userid),
UNIQUE KEY all_keys_apiKey_UNIQUE (apiKey),
UNIQUE KEY all_keys_email_UNIQUE (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`); `);
console.log("Table created"); console.log('Table created');
console.log("Attempting to create table allowed_channels"); console.log('Attempt creating increment count Stored Procedure');
await dbClient.execute(` await dbClient.execute(`
CREATE TABLE allowed_channels ( CREATE PROCEDURE INC_CNT(
userid bigint unsigned NOT NULL, IN cmd CHAR(20)
channelid bigint unsigned NOT NULL, )
createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, BEGIN
active tinyint(1) NOT NULL DEFAULT 1, declare oldcnt bigint unsigned;
banned tinyint(1) NOT NULL DEFAULT 0, set oldcnt = (SELECT count FROM command_cnt WHERE command = cmd);
PRIMARY KEY (userid, channelid), UPDATE command_cnt SET count = oldcnt + 1 WHERE command = cmd;
CONSTRAINT allowed_channels_userid_FK FOREIGN KEY (userid) REFERENCES all_keys (userid) ON DELETE RESTRICT ON UPDATE RESTRICT END
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`); `);
console.log("Table created"); console.log('Stored Procedure created');
// Holds daily average of commands
console.log('Attempting to create table roll_time_heatmap');
await dbClient.execute(`
CREATE TABLE roll_time_heatmap (
hour tinyint(1) unsigned NOT NULL,
sunday bigint unsigned NOT NULL DEFAULT 0,
monday bigint unsigned NOT NULL DEFAULT 0,
tuesday bigint unsigned NOT NULL DEFAULT 0,
wednesday bigint unsigned NOT NULL DEFAULT 0,
thursday bigint unsigned NOT NULL DEFAULT 0,
friday bigint unsigned NOT NULL DEFAULT 0,
saturday bigint unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (hour),
UNIQUE KEY roll_time_heatmap_hour_UNIQUE (hour)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
console.log('Table created');
console.log('Attempt creating increment heatmap Stored Procedure');
await dbClient.execute(`
CREATE PROCEDURE INC_HEATMAP(
IN dy varchar(10),
IN hr tinyint(1)
)
BEGIN
SET @s1=CONCAT('SELECT ',dy,' FROM roll_time_heatmap WHERE hour = ',hr,' INTO @oldcnt');
PREPARE stmt1 FROM @s1;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
SET @s2=CONCAT('UPDATE roll_time_heatmap SET ',dy,' = @oldcnt + 1 WHERE hour = ',hr);
PREPARE stmt2 FROM @s2;
EXECUTE stmt2;
DEALLOCATE PREPARE stmt2;
END
`);
console.log('Stored Procedure created');
// Roll log, holds rolls when requests
console.log('Attempting to create table roll_log');
await dbClient.execute(`
CREATE TABLE roll_log (
id int unsigned NOT NULL AUTO_INCREMENT,
input text NOT NULL,
resultid bigint NULL,
result longtext NOT NULL,
createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
api tinyint(1) NOT NULL,
error tinyint(1) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY roll_log_id_UNIQUE (id),
UNIQUE KEY roll_log_resultid_UNIQUE (resultid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
console.log('Table created');
// Api guild settings
console.log('Attempting to create table allowed_guilds');
await dbClient.execute(`
CREATE TABLE allowed_guilds (
guildid bigint unsigned NOT NULL,
channelid bigint unsigned NOT NULL,
createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
active tinyint(1) NOT NULL DEFAULT 0,
banned tinyint(1) NOT NULL DEFAULT 0,
hidewarn tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (guildid, channelid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
console.log('Table created');
// Api keys
console.log('Attempting to create table all_keys');
await dbClient.execute(`
CREATE TABLE all_keys (
userid bigint unsigned NOT NULL,
apiKey char(25) NOT NULL,
deleteCode char(10) NULL,
email char(255) NULL,
createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
active tinyint(1) NOT NULL DEFAULT 1,
banned tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (userid),
UNIQUE KEY all_keys_userid_UNIQUE (userid),
UNIQUE KEY all_keys_apiKey_UNIQUE (apiKey),
UNIQUE KEY all_keys_email_UNIQUE (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
console.log('Table created');
// Api user settings
console.log('Attempting to create table allowed_channels');
await dbClient.execute(`
CREATE TABLE allowed_channels (
userid bigint unsigned NOT NULL,
channelid bigint unsigned NOT NULL,
createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
active tinyint(1) NOT NULL DEFAULT 1,
banned tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (userid, channelid),
CONSTRAINT allowed_channels_userid_FK FOREIGN KEY (userid) REFERENCES all_keys (userid) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
`);
console.log('Table created');
// Database sizes view
console.log('Attempting to create view db_size');
await dbClient.execute(`
CREATE VIEW db_size AS
SELECT
table_name AS "table",
ROUND(((data_length + index_length) / 1024 / 1024), 3) AS "size",
table_rows AS "rows"
FROM information_schema.TABLES
WHERE
table_schema = "${config.db.name}"
AND table_name <> "db_size";
`);
console.log('View Created');
await dbClient.close(); await dbClient.close();
console.log("Done!"); console.log('Done!');

View File

@ -1,34 +1,52 @@
// This file will populate the tables with default values // This file will populate the tables with default values
import config from '~config';
import { Client } from "https://deno.land/x/mysql/mod.ts"; import dbClient from 'db/client.ts';
console.log('Attempting to populate DB Admin API key');
import { LOCALMODE } from "../flags.ts"; await dbClient.execute('INSERT INTO all_keys(userid,apiKey) values(?,?)', [config.api.admin, config.api.adminKey]).catch((e) => {
import config from "../config.ts"; console.log('Failed to insert into database', e);
// Log into the MySQL DB
const dbClient = await new Client().connect({
hostname: LOCALMODE ? config.db.localhost : config.db.host,
port: config.db.port,
db: config.db.name,
username: config.db.username,
password: config.db.password,
}); });
console.log('Insertion done');
console.log("Attempting to populate DB Admin API key"); console.log('Attempting to insert default commands into command_cnt');
await dbClient.execute("INSERT INTO all_keys(userid,apiKey) values(?,?)", [config.api.admin, config.api.adminKey]).catch(e => { const commands = [
console.log("Failed to insert into database", e); 'alias',
}); 'api',
console.log("Inesrtion done"); 'audit',
'emojis',
console.log("Attempting to insert default commands into command_cnt"); 'heatmap',
const commands = ["ping", "rip", "rollhelp", "help", "info", "version", "report", "stats", "roll", "emojis", "api", "privacy"]; 'help',
for (let i = 0; i < commands.length; i++) { 'info',
await dbClient.execute("INSERT INTO command_cnt(command) values(?)", [commands[i]]).catch(e => { 'inline',
console.log(`Failed to insert into database`, e); 'mention',
}); 'opt-in',
'opt-out',
'ping',
'privacy',
'rip',
'repeat',
'report',
'roll',
'rolldecorators',
'rollhelp',
'stats',
'version',
];
for (const command of commands) {
await dbClient.execute('INSERT INTO command_cnt(command) values(?)', [command]).catch((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');
for (let i = 0; i <= 23; i++) {
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('Insertion done');
await dbClient.close(); await dbClient.close();
console.log("Done!"); console.log('Done!');

45
deno.json Normal file
View File

@ -0,0 +1,45 @@
{
"compilerOptions": {
"lib": ["deno.worker"],
"strict": true
},
"lint": {
"include": ["src/", "db/", "mod.ts", "deps.ts", "config.ts", "config.example.ts"],
"exclude": [],
"rules": {
"tags": ["recommended"],
"include": ["ban-untagged-todo"],
"exclude": []
}
},
"fmt": {
"include": ["src/", "db/", "mod.ts", "deps.ts", "config.ts", "config.example.ts"],
"exclude": [],
"lineWidth": 200,
"indentWidth": 2,
"singleQuote": true,
"proseWrap": "preserve"
},
"nodeModulesDir": "none",
"imports": {
"@discordeno": "https://deno.land/x/discordeno@12.0.1/mod.ts",
"@imagescript": "https://deno.land/x/imagescript@1.3.0/mod.ts",
"@Log4Deno": "https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.1/mod.ts",
"@mysql": "https://deno.land/x/mysql@v2.12.1/mod.ts",
"@nanoid": "https://deno.land/x/nanoid@v3.0.0/mod.ts",
"@showdown": "npm:showdown@2.1.0",
"@std/http": "jsr:@std/http@1.0.15",
"~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",
"src/mod.d.ts": "./src/mod.d.ts"
}
}

856
deno.lock Normal file
View File

@ -0,0 +1,856 @@
{
"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",
"npm:showdown@2.1.0": "2.1.0"
},
"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"
}
},
"npm": {
"commander@9.5.0": {
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="
},
"showdown@2.1.0": {
"integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==",
"dependencies": [
"commander"
]
}
},
"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",
"npm:showdown@2.1.0"
]
}
}

19
docs/API.md Normal file
View File

@ -0,0 +1,19 @@
## 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.
Guilds Owners or Admins must run the `[[api allow` command for any users to be able to use the `/api/roll` endpoint.
Every API request **requires** the header `X-Api-Key` with the value set to the API key granted to you.
* If an API fails, these are the possible responses:
* `400` - Bad Request - Query parameters missing or malformed.
* `403` - Forbidden - API Key is not authenticated or user does not match the owner of the API Key.
* `404` - Not Found - Requested endpoint does not exist.
* `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.
Official 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/). API requests listed in the `Authenticated/Admin Requests` are only available to the admin user defined in `config.ts`.
API Key management via a basic GUI is available on the [API Tools](https://artificer.eanm.dev/) website.

232
docs/COMMANDS.md Normal file
View File

@ -0,0 +1,232 @@
## Available Commands
The Artificer comes with a few supplemental commands to the main rolling command.
This document uses the default prefix (`[[`) on all commands listed. If a command starts with `/` (such as `/help`), this means the command is available as a Discord Slash Command.
* `/help` or `[[help` or `[[h` or `[[?`
* Provides a message similar to this available commands block.
* `[[rollhelp` or `[[??` or `[[rh` or `[[hr`
* Opens the new help library.
* `[[api [subcommand]`
* Administrative tools for the bots's API. These commands may only be used by the Owner or Admins of your guild.
* Available Subcommands:
* `[[api help`
* Provides a message similar to this subcommand description.
* `[[api status`
* Shows the current status of the API for this guild.
* `[[api allow` or `[[api enable`
* Allows API Rolls to be sent to this guild.
* `[[api block` or `[[api disable`
* Blocks API Rolls from being sent to this guild.
* `[[api delete`
* Deletes this guild from The Artificer's database.
* `[[ping`
* Tests the latency between you, Discord, and the bot.
* `/info` or `[[info` or `[[i`
* Outputs some information and links relating to the bot.
* `/privacy` or `[[privacy` or `[[tos`
* Prints some information about the Privacy Policy, found in `PRIVACY.md`.
* `/version` or `[[version` or `[[v`
* Prints out the current version of the bot.
* `[[popcat` or `[[pop` or `[[p`
* Sends the animated popcat emote for those who do not have Discord Nitro.
* If bot is given the permission `Manage Messages`, the bot will remove the message requesting the emote.
* `/stats` or `[[stats` or `[[s`
* Prints out how many users, channels, and servers the bot is currently serving.
* `/heatmap` or `[[heatmap` or `[[hm`
* Heatmap of when the roll command is run the most.
* `/report report-text:[issue-or-feature]` or `[[report [issue-or-feature]`
* 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.
* 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.
* `[[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
* `/toggle-inline-rolls [subcommand]` or `[[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:
* `/toggle-inline-rolls help`
* `[[inline help`
* Provides a message similar to this subcommand description.
* `/toggle-inline-rolls status`
* `[[inline status`
* Shows the current status of inline rolls for this guild.
* `/toggle-inline-rolls enable`
* `[[inline allow` or `[[inline enable`
* Allows inline rolls in the guild.
* `/toggle-inline-rolls disable`
* `[[inline block` or `[[inline disable` or `[[inline delete`
* Blocks inline rolls in the guild.
* `/toggle-unrestricted-repeat [subcommand]` or `[[repeat [subcommand]`
* Controls whether or not unrestricted repeat rolls can be done in a guild, defaults off. Unrestricted Repeat Rolls are whether or not anyone in a guild can use the `Repeat Roll` button on anyone's roll or only the original roller can use them. These commands may only be used by the Owner or Admins of your guild.
* An inline roll is a roll that does not immediately start with `[[`, such as `test [[d20]]`.
* Available subcommands:
* `/toggle-unrestricted-repeat help`
* `[[repeat help`
* Provides a message similar to this subcommand description.
* `/toggle-unrestricted-repeat status`
* `[[repeat status`
* Shows the current status of unrestricted repeat rolls for this guild.
* `/toggle-unrestricted-repeat enable`
* `[[repeat allow` or `[[repeat enable`
* Allows unrestricted repeat rolls in the guild.
* `/toggle-unrestricted-repeat disable`
* `[[repeat block` or `[[repeat disable` or `[[repeat delete`
* Blocks unrestricted repeat rolls in the guild.
* `/alias [subcommand]` or `[[rollalias [subcommand]` or `[[ralias [subcommand]` or `[[alias [subcommand]` or `[[rolla [subcommand]` or `[[ra [subcommand]`
* Custom Roll Alias System
* Allows anyone to store a roll string as a shortcut/alias for later use/reuse.
* Supports full roll syntax, plus y variables that are set every time the alias is called.
* Every command has a matching "Guild Mode" command that modifies aliases linked to a guild instead of linked to a user account.
* Available subcommands:
* `/alias personal help`
* `/alias guild help`
* `[[ra help`
* `[[ra guild help`
* Provides a message similar to this subcommand description.
* `/alias personal list-all`
* `/alias guild list-all`
* `[[ra list`
* `[[ra guild list`
* Lists all aliases currently set for your account or the guild you are in.
* `/alias personal create alias-name:[aliasName] roll-string:[rollString...]`
* `/alias guild create alias-name:[aliasName] roll-string:[rollString...]`
* `[[ra add [aliasName] [rollString...]`
* `[[ra guild add [aliasName] [rollString...]`
* Creates the desired alias, saving the roll string to your account or the guild you are in.
* `/alias personal replace alias-name:[aliasName] roll-string:[rollString...]`
* `/alias guild replace alias-name:[aliasName] roll-string:[rollString...]`
* `[[ra update [aliasName] [rollString...]`
* `[[ra guild update [aliasName] [rollString...]`
* Updates the desired alias, replacing the old roll string in your account or the guild you are in with the newly provided roll string.
* `/alias personal view alias-name:[aliasName]`
* `/alias guild view alias-name:[aliasName]`
* `[[ra view [aliasName]`
* `[[ra guild view [aliasName]`
* View the saved roll string and how many yVars are needed for it.
* `/alias personal delete-one alias-name:[aliasName] [verification-code:[verificationCode]?]`
* `/alias guild delete-one alias-name:[aliasName] [verification-code:[verificationCode]?]`
* `[[ra delete [aliasName] [verificationCode?]`
* `[[ra guild delete [aliasName] [verificationCode?]`
* Deletes the desired alias from your account or the guild you are in. Can be run without a verification code to get the needed code for deletion.
* `/alias personal delete-all alias-name:[aliasName] [verification-code:[verificationCode]?]`
* `/alias guild delete-all alias-name:[aliasName] [verification-code:[verificationCode]?]`
* `[[ra delete-all [aliasName] [verificationCode?]`
* `[[ra guild delete-all [aliasName] [verificationCode?]`
* Deletes all aliases from your account or the guild you are in. Can be run without a verification code to get the needed code for deletion.
* `/alias personal copy alias-name:[aliasName]`
* `[[ra clone [aliasName]`
* Copies the specified alias from your account to the guild you are in.
* `/alias guild copy alias-name:[aliasName]`
* `[[ra guild clone [aliasName]`
* Copies the specified alias from the guild you are in to your account.
* `/alias personal rename alias-name:[oldAliasName] alias-name-new:[newAliasName]`
* `/alias guild rename alias-name:[oldAliasName] alias-name-new:[newAliasName]`
* `[[ra rename [oldAliasName] [newAliasName]`
* `[[ra guild rename [oldAliasName] [newAliasName]`
* Renames the specified alias for your account or the guild you are in.
* `/alias personal run alias-name:[aliasName] [y-variables:[yVars...]?]`
* `[[ra [aliasName] [yVars?...]`
* `[[ra run [aliasName] [yVars?...]`
* Runs the desired personal alias with the specified yVars (if any are needed). If the alias is not found on your account, it will check the guild aliases and use a match from there if one exists.
* `/alias guild run alias-name:[aliasName] [y-variables:[yVars...]?]`
* `[[ra guild [aliasName] [yVars?...]`
* `[[ra guild run [aliasName] [yVars?...]`
* Runs the desired guild alias with the specified yVars (if any are needed).
* `/roll roll-string:[rollString...]` or `[[xdydzracsq!]]` AKA Roll Command
* 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.
* Any math (limited to exponential, 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.
* Parameters for rolling:
| Parameter | Required? | Repeatable? | Description |
|---------------|-------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| x | Optional | No | number of dice to roll, if omitted, 1 is used |
| dy | Required | No | size of dice to roll, d20 = 20 sided die, replace y with `F` to roll the dice as Fate dice |
| 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 |
| 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 |
| 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 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 |
| 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 |
| 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 greater than or equal 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 greater than or equal to q |
| ! | Optional | No | exploding, rolls another dy for every 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 |
| !! | 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 or greater |
| !<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 or greater |
| !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 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 |
| !!=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 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 Repeatable, it may occur multiple times in the roll configuration.
* Examples:
* `[[4d20]]` will roll 4 d20 dice and add them together.
* `[[4d20r1!]]` will roll 4 d20 dice, rerolling any dice that land on 1, and repeatedly rolling a new d20 for any critical success rolled.
* `[[d20/40]]` will roll a d20 die and divide it by 40.
* `[[((d20+20) - 10) / 5]]` will roll a d20, add 20 to that roll, subtract off 10, and finally divide by 5.
* This command can also handle some custom format dice:
* CWOD Dice - `[[xcwody]]`
* `x` - Number of CWOD dice to roll
* `y` - Difficulty to roll at
* OVA Dice - `[[xovady]]`
* `x` - Number of OVA dice to roll
* `y` - Size of the die to roll (defaults to 6 if omitted)
* This command also has some useful decorators that can used. These decorators simply need to be placed after all rolls in the message:
* `-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` or `-max` - Maximize Roll - Rolls the theoretical maximum roll, cannot be used with `-n`, `-min`, or `-sn`
* `-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`, `-max`, `-min`, or `-sn`
* `-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
* `-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
* `-ns` - No Spaces - Removes the default padding added space between rolls (`[[d4]][[d4]]` will output `22` instead of `2 2`)
* `-yvariables y0,y1,...,yN` - Y Variables - Intended for internal use only, but is mentioned here since it is available externally. Takes a comma separated list of numbers. Unlike other decorators, this one will not be shown in the raw output.
* The results have some formatting applied on them to provide details on what happened during this roll.
* Critical successes will be **bolded**
* Critical fails will be <ins>underlined</ins>
* Rolls that were dropped or rerolled ~~crossed out~~
* Rolls that exploded have an `!` added after them

10
docs/SELF_HOSTING.md Normal file
View File

@ -0,0 +1,10 @@
## Self Hosting The Artificer
The Artificer is built on [Deno](https://deno.land/) `v2.2.9` 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.
Once everything is set up, starting the bot can simply be done with the command in `start.command`.
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 disable the API, please note some features like the roll heatmap and roll webview will not be available.

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 // DEBUG is used to toggle the cmdPrompt and show debug log messages
export const DEBUG = false; export const DEBUG = false;
// LOCALMODE is used to run a differnt bot token for local testing // LOCALMODE is used to run a different bot token for local testing
export const LOCALMODE = false; export const LOCALMODE = false;

View File

@ -1,87 +0,0 @@
export const longStrs = {
"help": [ // Array of strings that makes up the help command, placed here to keep source code cleaner
"```fix",
"The Artificer Help",
"```",
"__**Commands:**__",
"```",
"[[? - This command",
"[[rollhelp or [[?? - Details on how to use the roll command, listed as [[xdy...]] below",
"[[api [subcommand] - Administrative tools for the bots's API, run [[api help for more details",
"[[ping - Pings the bot to check connectivity",
"[[info - Prints some information and links relating to the bot",
"[[privacy - Prints some information about the Privacy Policy",
"[[version - Prints the bots version",
"[[popcat - Popcat",
"[[report [text] - Report a command that failed to run",
"[[stats - Statistics on the bot",
"[[xdydzracsq!]] ... - Rolls all configs requested, you may repeat the command multiple times in the same message (just ensure you close each roll with ]]), run [[?? for more details",
"```"
],
"rollhelp": [ // Array of strings that makes up the rollhelp command, placed here to keep source code cleaner
"```fix",
"The Artificer Roll Command Details",
"```",
"```",
"[[xdydzracsq!]] ... - Rolls all configs requested, you may repeat the command multiple times in the same message (just ensure you close each roll with ]])",
"* x [OPT] - number of dice to roll, if omitted, 1 is used",
"* dy [REQ] - size of dice to roll, d20 = 20 sided die",
"* dz or dlz [OPT] - drops the lowest z dice, cannot be used with kz",
"* kz or khz [OPT] - keeps the highest z dice, cannot be used with dz",
"* dhz [OPT] - drops the highest z dice, cannot be used with kz",
"* klz [OPT] - keeps the lowest z dice, cannot be used with dz",
"* ra [OPT] - rerolls any rolls that match a, r3 will reroll any dice that land on 3, throwing out old rolls",
"* csq or cs=q [OPT] - changes crit score to q",
"* cs<q [OPT] - changes crit score to be less than or equal to q",
"* cs>q [OPT] - changes crit score to be greater than or equal to q ",
"* cfq or cs=q [OPT] - changes crit fail to q",
"* cf<q [OPT] - changes crit fail to be less than or equal to q",
"* cf>q [OPT] - changes crit fail to be greater than or equal to q",
"* ! [OPT] - exploding, rolls another dy for every crit roll",
"*",
"* This command also can fully solve math equations with parenthesis",
"*",
"* This command also has some useful flags that can used. These flags simply need to be placed after all rolls in the message:",
" * -nd No Details - Suppresses all details of the requested roll",
" * -s Spoiler - Spoilers all details of the requested roll",
" * -m Maximize Roll - Rolls the theoretical maximum roll, cannot be used with -n",
" * -n Nominal Roll - Rolls the theoretical nominal roll, cannot be used with -m",
" * -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",
"```"
],
"apihelp": [ // Array of strings making up the api help command, placed here to keep source code cleaner
"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. 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: <https://github.com/Burn-E99/TheArtificer>",
"",
"__**Available Subcommands:**__",
"```",
"[[api help - This command",
"[[api status - Shows the current status of the API for this guild",
"[[api allow/enable - Allows API Rolls to be sent to this guild",
"[[api block/disable - Blocks API Rolls from being sent to this guild",
"[[api delete - Deletes this guild from The Artificer's database",
"```",
"You may enable and disable the API rolls for your guild as needed."
],
"info": [ // Array of strings making up the info command, placed here to keep source code cleaner
"The Artificer is a Discord bot that specializes in rolling dice and calculating math.",
"",
"The Artificer is developed by Ean AKA Burn_E99.",
"",
"Additional information can be found on my website: <https://discord.burne99.com/TheArtificer/>",
"Want to check out my source code? Check it out here: <https://github.com/Burn-E99/TheArtificer>",
"Need help with this bot? Join my support server here: https://discord.gg/peHASXMZYv"
],
"privacy": [ // Array of strings making up the privacy command, placed here to keep source code cleaner
"The Artificer does not track or collect user information via Discord.",
"The only user submitted information that is stored is submitted via the `[[report` command. This information is only stored for a short period of time in a location that only the Developer of The Artificer can see.",
"",
"For more details, please check out the Privacy Policy on the GitHub: <https://github.com/Burn-E99/TheArtificer/blob/master/PRIVACY.md>"
]
};
export default longStrs;

1300
mod.ts

File diff suppressed because it is too large Load Diff

207
src/api.ts Normal file
View File

@ -0,0 +1,207 @@
/* The Artificer was built in memory of Babka
* With love, Ean
*
* December 21, 2020
*/
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
import dbClient from 'db/client.ts';
import endpoints from 'endpoints/_index.ts';
import stdResp from 'endpoints/stdResponses.ts';
// start() returns nothing
// start initializes and runs the entire API for the bot
const start = () => {
log(LT.INFO, `HTTP api running at: http://localhost:${config.api.port}/`);
// rateLimitTime holds all users with the last time they started a rate limit timer
const rateLimitTime = new Map<string, number>();
// rateLimitCnt holds the number of times the user has called the api in the current rate limit timer
const rateLimitCnt = new Map<string, number>();
// Catching every request made to the server
Deno.serve({ port: config.api.port }, async (request) => {
log(LT.LOG, `Handling request: ${JSON.stringify(request.headers)} | ${JSON.stringify(request.method)} | ${JSON.stringify(request.url)}`);
// Check if user is authenticated to be using this API
let authenticated = false;
let rateLimited = false;
let updateRateLimitTime = false;
let apiUserid = 0n;
let apiUseridStr = '';
let apiUserEmail = '';
let apiUserDelCode = '';
// Check the requests API key
if (request.headers.has('X-Api-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', [
request.headers.get('X-Api-Key'),
]);
// If only one user returned, is not banned, and is currently active, mark as authenticated
if (dbApiQuery.length === 1) {
apiUserid = BigInt(dbApiQuery[0].userid);
apiUserEmail = dbApiQuery[0].email;
apiUserDelCode = dbApiQuery[0].deleteCode;
authenticated = true;
// Rate limiting inits
apiUseridStr = apiUserid.toString();
const apiTimeNow = new Date().getTime();
// Check if user has sent a request recently
if (rateLimitTime.has(apiUseridStr) && (rateLimitTime.get(apiUseridStr) || 0) + config.api.rateLimitTime > apiTimeNow) {
// Get current count
const currentCnt = rateLimitCnt.get(apiUseridStr) || 0;
if (currentCnt < config.api.rateLimitCnt) {
// Limit not yet exceeded, update count
rateLimitCnt.set(apiUseridStr, currentCnt + 1);
} else {
// Limit exceeded, prevent API use
rateLimited = true;
}
} else {
// Update the maps
updateRateLimitTime = true;
rateLimitCnt.set(apiUseridStr, 1);
}
}
}
if (!rateLimited) {
// Get path and query as a string
const [urlPath, tempQ] = request.url.split('?');
const path = urlPath.split('api')[1];
// Turn the query into a map (if it exists)
const query = new Map<string, string>();
if (tempQ !== undefined) {
tempQ.split('&').forEach((e: string) => {
log(LT.LOG, `Parsing request query ${JSON.stringify(request)} ${e}`);
const [option, params] = e.split('=');
query.set(option.toLowerCase(), params);
});
}
if (path) {
const lowerCasePath = path.toLowerCase().trim();
if (authenticated) {
// Update rate limit details
if (updateRateLimitTime) {
const apiTimeNow = new Date().getTime();
rateLimitTime.set(apiUseridStr, apiTimeNow);
}
// Handle the authenticated request
switch (request.method) {
case 'GET':
switch (lowerCasePath) {
case '/ping':
case '/ping/':
return endpoints.get.apiPing();
case '/stats':
case '/stats/':
return endpoints.get.apiStats();
case '/key':
case '/key/':
return endpoints.get.apiKeyAdmin(query, apiUserid);
case '/channel':
case '/channel/':
return endpoints.get.apiChannel(query, apiUserid);
case '/roll':
case '/roll/':
return endpoints.get.apiRoll(query, apiUserid);
default:
// Alert API user that they messed up
return stdResp.NotFound('Auth Get');
}
break;
case 'POST':
switch (lowerCasePath) {
case '/channel/add':
case '/channel/add/':
return endpoints.post.apiChannelAdd(query, apiUserid);
default:
// Alert API user that they messed up
return stdResp.NotFound('Auth Post');
}
break;
case 'PUT':
switch (lowerCasePath) {
case '/key/ban':
case '/key/ban/':
case '/key/unban':
case '/key/unban/':
case '/key/activate':
case '/key/activate/':
case '/key/deactivate':
case '/key/deactivate/':
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 (lowerCasePath) {
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 (lowerCasePath) {
case '/key':
case '/key/':
return endpoints.get.apiKey(query);
case '/heatmap.png':
return endpoints.get.heatmapPng();
case '/webview':
return endpoints.get.generateWebView(query);
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 };

View File

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

43
src/artigen/artigen.d.ts vendored Normal file
View File

@ -0,0 +1,43 @@
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;
footer: 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;
}

185
src/artigen/artigen.ts Normal file
View File

@ -0,0 +1,185 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { SolvedRoll } from 'artigen/artigen.d.ts';
import { tokenizeCmd } from 'artigen/cmdTokenizer.ts';
import { Modifiers } from 'artigen/dice/getModifiers.ts';
import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { reduceCountDetails } from 'artigen/utils/counter.ts';
import { cmdSplitRegex, escapeCharacters, withYVarsDash } from 'artigen/utils/escape.ts';
import { loggingEnabled, showLoopCountDebug } from 'artigen/utils/logFlag.ts';
import { assertPrePostBalance } from 'artigen/utils/parenBalance.ts';
import { reduceRollDistMaps } from 'artigen/utils/rollDist.ts';
import { compareTotalRolls, compareTotalRollsReverse, sortYVars } 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: '',
footer: '',
counts: {
total: 0,
successful: 0,
failed: 0,
rerolled: 0,
dropped: 0,
exploded: 0,
success: 0,
fail: 0,
matches: new Map<string, number>(),
},
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
let rawCmd = escapeCharacters(rollRequest.originalCommand.trim(), '|').replace(/`/g, '');
// Remove yvariables from the rawCmd since this is intended for internal use only
if (rawCmd.includes(Modifiers.YVars)) {
rawCmd = rawCmd.replaceAll(new RegExp(`( ${Modifiers.YVars} (\\d+,)+\\d+)`, '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}\``;
}
if (rollRequest.modifiers.yVars.size) {
line1 += `\n${withYVarsDash} With yVars: ${
rollRequest.modifiers.yVars
.entries()
.toArray()
.sort((a, b) => sortYVars(a[0], b[0]))
.map((yVar) => `\`${yVar[0]}=${yVar[1]}\``)
.join(' ')
}`;
}
// List number of iterations on simulated nominals
if (rollRequest.modifiers.simulatedNominal) line2 += `Iterations performed per roll: \`${rollRequest.modifiers.simulatedNominal.toLocaleString()}\`\n`;
// Reduce counts to a single object
if (rollRequest.modifiers.count) 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";
}
const line2Space = rollRequest.modifiers.noSpaces ? '' : ' ';
// Fill out all of the details and results now
tempReturnData.forEach((e, i) => {
loopCountCheck('artigen.ts - tempReturnData');
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, '|*_~`') : line2Space}${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
if (rollRequest.modifiers.rollDist) 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);
}
if (showLoopCountDebug) returnMsg.footer = `Loop Count: ${getLoopCount()}`;
return returnMsg;
};

235
src/artigen/cmdTokenizer.ts Normal file
View File

@ -0,0 +1,235 @@
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('cmdTokenizer.ts - while cmd includes prefix');
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(`cmdTokenizer.ts - simulate nominal loop #${i}`);
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('cmdTokenizer.ts - confirming crit');
// 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('cmdTokenizer.ts - handling internal group result merging');
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('cmdTokenizer.ts - handling internal nested roll result merging');
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];
}
};

170
src/artigen/dice/dice.d.ts vendored Normal file
View File

@ -0,0 +1,170 @@
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;
success: number;
fail: number;
matches: Map<string, 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;
noSpaces: boolean;
yVars: Map<string, number>;
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

@ -0,0 +1,355 @@
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('executeRoll.ts - handling initial rolling');
// 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('executeRoll.ts - handling rerolling and exploding');
// This big boolean statement first checks if reroll is on, if the roll is within the reroll range, and finally if ro is ON, make sure we haven't already rerolled the roll
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('executeRoll.ts - maximizeRoll');
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('executeRoll.ts - minimizeRoll');
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('executeRoll.ts - penetrating explosion');
// 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('executeRoll.ts - compounding explosion');
// 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++) {
loopCountCheck('executeRoll.ts - count rerolls');
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) {
loopCountCheck('executeRoll.ts - dropping/keeping');
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('executeRoll.ts - OVA');
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('executeRoll.ts - matching');
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('executeRoll.ts - labeling matches');
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

@ -0,0 +1,102 @@
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('generateFormattedRoll.ts - formatting executed roll');
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

@ -0,0 +1,73 @@
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('getGroupConf.ts - finding biggest number for die size');
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('getGroupConf.ts - parsing groupConf');
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

@ -0,0 +1,257 @@
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',
NoSpaces: '-ns',
YVars: '-yvariables',
});
// 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[]>(),
noSpaces: false,
yVars: 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 = config.limits.defaultSimulatedNominal;
}
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;
}
case Modifiers.NoSpaces:
modifiers.noSpaces = true;
break;
case Modifiers.YVars: {
// Shift the -yvariables out of the array so the next item is the first yVar
args.splice(i, 2);
const yVars = args[i].split(',');
yVars.forEach((yVar, idx) => {
modifiers.yVars.set(`y${idx}`, parseFloat(yVar));
});
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.maxSimulatedNominal) {
modifiers.error.name = 'SimNominalTooBig';
modifiers.error.message = `Number of iterations for \`simulatedNominal\` cannot be greater than \`${config.limits.maxSimulatedNominal}\``;
modifiers.valid = false;
}
if (modifiers.simulatedNominal < 0) {
modifiers.error.name = 'NegativeSimNominal';
modifiers.error.message = 'Number of iterations for `simulatedNominal` must be at least 1';
modifiers.valid = false;
}
return [modifiers, args];
};

View File

@ -0,0 +1,548 @@
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('getRollConf.ts - setting cwod difficulty');
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('getRollConf.ts - parsing rollConf');
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);
loggingEnabled && log(LT.LOG, `tempSep: ${tempSep}`);
let noNumberAfter = false;
if (!(Object.values(DiceOptions) as string[]).includes(tempSep)) {
NumberlessDiceOptions.some((opt) => {
loopCountCheck('getRollConf.ts - parsing numberlessDiceOptions');
loggingEnabled && log(LT.LOG, `In NumberlessDiceOptions ${opt} ${tempSep.startsWith(opt) && tempSep !== opt}`);
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);
loggingEnabled && log(LT.LOG, `tSep: ${tSep}, remains: ${remains}`);
// 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

@ -0,0 +1,284 @@
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 'artigen/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('groupHandler.ts - handling nested groups');
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('groupHandler.ts - solving commaParts');
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('groupHandler.ts - handling group drop/keep');
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('groupHandler.ts - handling group success/fail');
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('groupHandler.ts - handling merging nested groups up');
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

@ -0,0 +1,41 @@
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

@ -0,0 +1,69 @@
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

@ -0,0 +1,50 @@
import { closeLog, initLog } from '@Log4Deno';
import { runCmd } from 'artigen/artigen.ts';
import { SolvedRoll } from 'artigen/artigen.d.ts';
import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { loggingEnabled, loopLoggingEnabled } from 'artigen/utils/logFlag.ts';
if (loggingEnabled || loopLoggingEnabled) initLog('logs/worker', loggingEnabled || loopLoggingEnabled);
// 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: SolvedRoll = runCmd(payload) || {
error: true,
errorCode: 'EmptyMessage',
errorMsg: 'Error: Empty message',
line1: '',
line2: '',
line3: '',
footer: '',
counts: {
total: 0,
successful: 0,
failed: 0,
rerolled: 0,
dropped: 0,
exploded: 0,
success: 0,
fail: 0,
matches: new Map<string, number>(),
},
};
self.postMessage(returnMsg);
if (loggingEnabled || loopLoggingEnabled) await closeLog();
self.close();
};

View File

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

View File

@ -0,0 +1,322 @@
import { botId, ButtonStyles, DiscordenoMessage, Embed, FileContent, MessageComponentTypes, sendDirectMessage, sendMessage } from '@discordeno';
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
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 { ApiResolveMap, TestResolveMap } from 'artigen/managers/resolveManager.ts';
import { generateCountDetailsEmbed, generateDMFailed, generateRollDistsEmbed, generateRollEmbed, toggleWebView } from 'artigen/utils/embeds.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { basicReducer } from 'artigen/utils/reducers.ts';
import dbClient from 'db/client.ts';
import { queries } from 'db/common.ts';
import { infoColor1 } from 'embeds/colors.ts';
import stdResp from 'endpoints/stdResponses.ts';
import { InteractionValueSeparator } from 'events/interactionCreate.ts';
import utils from 'utils/utils.ts';
import { STATUS_CODE, STATUS_TEXT } from '@std/http/status';
const getUserIdForEmbed = (rollRequest: QueuedRoll): bigint => {
if (rollRequest.apiRoll) return rollRequest.api.userId;
if (rollRequest.ddRoll) {
if (rollRequest.dd.overrideAuthorId === 0n) return rollRequest.dd.authorId;
return rollRequest.dd.overrideAuthorId;
}
return 0n;
};
const getAuthorIdForButton = (rollRequest: QueuedRoll): bigint => {
if (rollRequest.apiRoll) return rollRequest.api.userId;
if (rollRequest.ddRoll) return rollRequest.dd.authorId;
return 0n;
};
export const repeatRollCustomId = 'repeatRoll';
export const onWorkerComplete = async (workerMessage: MessageEvent<SolvedRoll>, workerTimeout: number, rollRequest: QueuedRoll) => {
const apiResolve = rollRequest.apiRoll ? ApiResolveMap.get(rollRequest.resolve as string) : undefined;
const testResolve = rollRequest.testRoll ? TestResolveMap.get(rollRequest.resolve as string) : undefined;
rollRequest.apiRoll && ApiResolveMap.delete(rollRequest.resolve as string);
rollRequest.testRoll && TestResolveMap.delete(rollRequest.resolve as string);
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(getUserIdForEmbed(rollRequest), returnMsg, rollRequest.modifiers);
const gmEmbedDetails = generateRollEmbed(getUserIdForEmbed(rollRequest), 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] : [];
let countEmbed, rollDistEmbed;
// Handle adding count embed to correct list
if (rollRequest.modifiers.count) {
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) {
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) {
apiResolve && apiResolve(stdResp.InternalServerError(returnMsg.errorMsg));
} else if (rollRequest.ddRoll) {
rollRequest.dd.myResponse.edit({ embeds: pubEmbeds });
} else if (rollRequest.testRoll) {
testResolve &&
testResolve({
error: true,
errorMsg: returnMsg.errorMsg,
errorCode: returnMsg.errorCode,
});
}
if (rollRequest.apiRoll) {
// If enabled, log rolls so we can see what went wrong
dbClient
.execute(queries.insertRollLogCmd(rollRequest.apiRoll ? 1 : 0, 1), [rollRequest.originalCommand, returnMsg.errorCode, null])
.catch((e) => utils.commonLoggers.dbError('rollQueue.ts:82', 'insert into', e));
}
return;
}
// Test roll will assume that messages send successfully
if (rollRequest.testRoll) {
testResolve &&
testResolve({
error: false,
});
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;
apiResolve && apiResolve(stdResp.InternalServerError('Message failed to send - location 0.'));
});
} else {
// Send the public embed to correct channel
rollRequest.dd.myResponse.edit({ embeds: pubEmbeds });
}
// HOTFIX: makes discordeno actually be able to reply to any message (user or bot) while in dms
if (newMsg && !newMsg.guildId) newMsg.guildId = -1n;
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;
apiResolve && apiResolve(stdResp.InternalServerError('Message failed to send - location 1.'));
});
} else {
newMsg = await rollRequest.dd.myResponse.edit({
content: rollRequest.dd.overrideAuthorId === 0n ? '' : `<@${rollRequest.dd.overrideAuthorId}> used the \`Repeat Roll\` button for the referenced message:`,
embeds: pubEmbeds,
components: [
{
type: MessageComponentTypes.ActionRow,
components: [
{
type: MessageComponentTypes.Button,
label: 'Repeat Roll',
customId: `${repeatRollCustomId}${InteractionValueSeparator}${getAuthorIdForButton(rollRequest).toString()}`,
style: ButtonStyles.Secondary,
emoji: '🎲',
},
],
},
],
});
}
// HOTFIX: makes discordeno actually be able to reply to any message (user or bot) while in dms
if (newMsg && !newMsg.guildId) newMsg.guildId = -1n;
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.**
Please 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 &&
newMsg
.reply({
embeds: respMessage,
file: pubAttachments,
})
.then((attachmentMsg) => toggleWebView(attachmentMsg, getUserIdForEmbed(rollRequest).toString(), false))
.catch((e) => utils.commonLoggers.messageSendError('workerComplete.ts:230', newMsg as DiscordenoMessage, e));
} else {
pubAttachments.forEach((file) => {
newMsg &&
newMsg
.reply({
embeds: respMessage,
file,
})
.then((attachmentMsg) => toggleWebView(attachmentMsg, getUserIdForEmbed(rollRequest).toString(), false))
.catch((e) => utils.commonLoggers.messageSendError('workerComplete.ts:240', newMsg as DiscordenoMessage, e));
});
}
}
}
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));
const headers = new Headers();
headers.append('Content-Type', 'text/json');
apiResolve &&
apiResolve(
new Response(
JSON.stringify({
discordEmbeds: {
rollResponse: pubEmbedDetails,
countsResponse: countEmbed ?? null,
rollDistResponse: rollDistEmbed ?? null,
},
rawData: {
roll: {
raw: returnMsg.line1,
results: returnMsg.line2,
details: returnMsg.line3,
},
counts: rollRequest.modifiers.count ? returnMsg.counts : null,
rollDistributions: rollRequest.modifiers.rollDist ? returnMsg.rollDistributions.entries().toArray() : null,
},
}),
{
status: STATUS_CODE.OK,
statusText: STATUS_TEXT[STATUS_CODE.OK],
headers,
},
),
);
}
} catch (e) {
log(LT.ERROR, `Unhandled rollRequest Error: ${JSON.stringify(e)}`);
if (rollRequest.ddRoll) {
rollRequest.dd.myResponse.edit({
embeds: [
(
await generateRollEmbed(
0n,
<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,
],
});
} else if (rollRequest.apiRoll && !apiErroredOut) {
apiResolve && apiResolve(stdResp.InternalServerError(JSON.stringify(e)));
} else if (rollRequest.testRoll) {
testResolve &&
testResolve({
error: true,
errorMsg: 'Something weird went wrong.',
errorCode: 'UnhandledWorkerComplete',
});
}
}
};

View File

@ -0,0 +1,18 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { nanoid } from '@nanoid';
import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { ApiResolveMap, TestResolveMap } from 'artigen/managers/resolveManager.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
export const onWorkerReady = (rollWorker: Worker, rollRequest: QueuedRoll) => {
if ((rollRequest.apiRoll || rollRequest.testRoll) && typeof rollRequest.resolve !== 'string') {
const resolveId = nanoid();
rollRequest.apiRoll && ApiResolveMap.set(resolveId, rollRequest.resolve);
rollRequest.testRoll && TestResolveMap.set(resolveId, rollRequest.resolve);
rollRequest.resolve = resolveId;
}
loggingEnabled && log(LT.LOG, `Sending roll to worker: ${rollRequest.rollCmd}, ${JSON.stringify(rollRequest.modifiers)}`);
rollWorker.postMessage(rollRequest);
};

View File

@ -0,0 +1,51 @@
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 { ApiResolveMap, TestResolveMap } from 'artigen/managers/resolveManager.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();
const apiResolve = rollRequest.apiRoll ? ApiResolveMap.get(rollRequest.resolve as string) : undefined;
const testResolve = rollRequest.testRoll ? TestResolveMap.get(rollRequest.resolve as string) : undefined;
rollRequest.apiRoll && ApiResolveMap.delete(rollRequest.resolve as string);
rollRequest.testRoll && TestResolveMap.delete(rollRequest.resolve as string);
if (rollRequest.apiRoll) {
apiResolve && apiResolve(stdResp.RequestTimeout('Roll took too long to process, try breaking roll down into simpler parts'));
} else if (rollRequest.ddRoll) {
rollRequest.dd.myResponse
.edit({
embeds: [
(
await generateRollEmbed(
0n,
<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));
} else if (rollRequest.testRoll) {
testResolve &&
testResolve({
error: true,
errorCode: 'TooComplex',
errorMsg: 'Error: Roll took too long to process, try breaking roll down into simpler parts',
});
}
};

View File

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

49
src/artigen/managers/manager.d.ts vendored Normal file
View File

@ -0,0 +1,49 @@
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 type ApiResolve = (value: Response | PromiseLike<Response>) => void;
interface ApiQueuedRoll extends BaseQueuedRoll {
apiRoll: true;
ddRoll: false;
testRoll: false;
resolve: string | ApiResolve;
api: {
channelId: bigint;
userId: bigint;
};
}
interface DDQueuedRoll extends BaseQueuedRoll {
apiRoll: false;
ddRoll: true;
testRoll: false;
dd: {
myResponse: DiscordenoMessage;
originalMessage: DiscordenoMessage;
overrideAuthorId: bigint;
authorId: bigint;
};
}
interface TestResultFail {
error: true;
errorMsg: string;
errorCode: string;
}
interface TestResultSuccess {
error: false;
}
export type TestResults = TestResultFail | TestResultSuccess;
export type TestResolve = (value: TestResults) => void;
interface TestQueuedRoll extends BaseQueuedRoll {
apiRoll: false;
ddRoll: false;
testRoll: true;
resolve: string | TestResolve;
}
export type QueuedRoll = ApiQueuedRoll | DDQueuedRoll | TestQueuedRoll;

View File

@ -0,0 +1,56 @@
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 (!rollQueue.length && getWorkerCnt() < config.limits.maxWorkers) {
handleRollRequest(rollRequest);
} else {
rollQueue.push(rollRequest);
rollRequest.ddRoll &&
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.ddRoll &&
rollRequest.dd.myResponse
.edit(rollingEmbed)
.catch((e: Error) => utils.commonLoggers.messageEditError('rollQueue.ts:208', rollRequest.dd.myResponse, e));
handleRollRequest(rollRequest);
}
}
}, 1_000);

View File

@ -0,0 +1,4 @@
import { ApiResolve, TestResolve } from 'artigen/managers/manager.d.ts';
export const ApiResolveMap = new Map<string, ApiResolve>();
export const TestResolveMap = new Map<string, TestResolve>();

View File

@ -0,0 +1,23 @@
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);
});
};

11
src/artigen/math/math.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
// 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

@ -0,0 +1,220 @@
/* 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('mathSolver.ts - evaluating parens');
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('mathSolver.ts - checking for implicit multiplication');
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) => {
// No loopCountCheck here since its finite/will always be 3 loops
loggingEnabled && log(LT.LOG, `Cur Ops ${JSON.stringify(curOps)}`);
// Iterate thru all operators/operands in the conf
for (let i = 0; i < conf.length; i++) {
loggingEnabled && log(LT.LOG, `Checking ${JSON.stringify(conf[i])}`);
// Check if the current index is in the active tier of operators
if (curOps.includes(conf[i].toString())) {
loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} as ${JSON.stringify(conf[i])} is in curOps`);
loopCountCheck('mathSolver.ts - evaluating roll');
// Grab the operands from before and after the operator
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

@ -0,0 +1,347 @@
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
if (mathConf.includes('(') || mathConf.includes(')')) assertParenBalance(mathConf);
// Evaluate all rolls into stepSolve format and all numbers into floats
for (let i = 0; i < mathConf.length; i++) {
loopCountCheck('mathTokenizer.ts - parsing all tokens into MathConf');
loggingEnabled && log(LT.LOG, `Parsing roll ${JSON.stringify(cmd)} | Evaluating rolls into math-able items ${JSON.stringify(mathConf[i])}`);
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 (/(y\d+(\.\d*)?)/.test(curMathConfStr)) {
// Identify when someone is using a variable from alias input
if (curMathConfStr.includes('.')) {
// Verify someone did not enter y1.1 as a variable
throw new Error(`IllegalVariable_${curMathConfStr}`);
}
const yValue = modifiers.yVars.get(curMathConfStr);
if (typeof yValue === 'number') {
mathConf[i] = yValue;
} else {
throw new Error(`VariableMissingValue_${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);
if (modifiers.rollDist) 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('mathTokenizer.ts - handling group dropping');
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('mathTokenizer.ts - handling group success/fails');
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);
if (modifiers.rollDist) 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

@ -0,0 +1,65 @@
import { CountDetails, RollSet } from 'artigen/dice/dice.d.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
export const rollCounter = (rollSet: RollSet[]): CountDetails => {
const countDetails: CountDetails = {
total: 0,
successful: 0,
failed: 0,
rerolled: 0,
dropped: 0,
exploded: 0,
success: 0,
fail: 0,
matches: new Map<string, number>(),
};
rollSet.forEach((roll) => {
loopCountCheck('counter.ts - summing RollSet into CountDetails');
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++;
if (roll.success) countDetails.success++;
if (roll.fail) countDetails.fail++;
if (roll.matchLabel) countDetails.matches.set(roll.matchLabel, (countDetails.matches.get(roll.matchLabel) ?? 0) + 1);
});
return countDetails;
};
export const reduceCountDetails = (counts: CountDetails[]): CountDetails =>
counts.reduce(
(acc, cur) => {
loopCountCheck('counter.ts - merging array of CountDetails down to single CountDetail');
cur.matches.forEach((cnt, label) => {
loopCountCheck('counter.ts - merging matches');
acc.matches.set(label, (acc.matches.get(label) ?? 0) + cnt);
});
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,
success: acc.success + cur.success,
fail: acc.fail + cur.fail,
matches: acc.matches,
};
},
{
total: 0,
successful: 0,
failed: 0,
rerolled: 0,
dropped: 0,
exploded: 0,
success: 0,
fail: 0,
matches: new Map<string, number>(),
},
);

View File

@ -0,0 +1,35 @@
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';
}
};

356
src/artigen/utils/embeds.ts Normal file
View File

@ -0,0 +1,356 @@
import { ButtonStyles, CreateMessage, DiscordenoMessage, EmbedField, MessageComponentTypes } 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 { basicReducer } from 'artigen/utils/reducers.ts';
import { failColor, infoColor1, infoColor2 } from 'embeds/colors.ts';
import { InteractionValueSeparator } from 'events/interactionCreate.ts';
import utils from 'utils/utils.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: 'Critically Successful Rolls:',
value: `${counts.successful}`,
inline: true,
},
{
name: 'Critically 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,
},
{
name: 'Successful Rolls:',
value: `${counts.success}`,
inline: true,
},
{
name: 'Failed Rolls:',
value: `${counts.fail}`,
inline: true,
},
{
name: 'Matched Roll Labels:',
value: `${
counts.matches
.entries()
.toArray()
.sort()
.map(([label, count]) => `${label}: ${count}`)
.join(', ')
}`,
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 > 4_000 || 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' });
let rollDistErrDesc = 'The roll distribution was omitted from this message as it was over 4,000 characters, ';
if (rollDistBlob.size > config.maxFileSize) {
rollDistErrDesc +=
'and was too large to be attached as the file would be too large for Discord to handle. If you would like to see the roll distribution details, please simplify or send the rolls in multiple messages.';
return {
charCount: rollDistTitle.length + rollDistErrDesc.length,
embed: {
color: failColor,
title: rollDistTitle,
description: rollDistErrDesc,
},
hasAttachment: false,
};
} else {
rollDistErrDesc += 'and has been attached to a followup message as a formatted `.md` file.';
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(': ')}`;
const fullDesc = `${baseDesc}\n\n${details}`;
const formattingCount = (fullDesc.match(/(\*\*)|(__)|(~~)|(`)/g) ?? []).length / 2 + (fullDesc.match(/(<@)|(<#)/g) ?? []).length;
// Embed desc limit is 4096
// Discord only formats 200 items per message
const fullSize = fullDesc.length + returnDetails.footer.length;
if (fullSize < 4_000 && formattingCount <= 200) {
// Response is valid size
return {
charCount: fullSize,
embed: {
color: infoColor2,
description: fullDesc,
footer: {
text: returnDetails.footer,
},
},
hasAttachment: false,
};
}
// Response is too big, collapse it into a .md file and send that instead.
const b = new Blob([fullDesc as BlobPart], { type: 'text' });
details = `${baseDesc}\n\nDetails have been omitted from this message for ${fullDesc.length < 4_000 ? 'being over 4,000 characters' : 'having over 200 formatted items'}.`;
if (b.size > config.maxFileSize) {
// blob is too big, don't attach it
details +=
'\n\nFull details could not be attached as the file would be too large for Discord to handle. If you would like to see the details of rolls, please simplify or 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 a followup message as a formatted `.md` file for verification purposes.';
return {
charCount: details.length,
embed: {
color: infoColor2,
description: details,
},
hasAttachment: true,
attachment: {
blob: b,
name: 'rollDetails.md',
},
};
};
export const webViewCustomId = 'webview';
export const disabledStr = 'disabled';
export const toggleWebView = (attachmentMessage: DiscordenoMessage, ownerId: string, enableWebView: boolean) => {
attachmentMessage
.edit({
embeds: [
{
...attachmentMessage.embeds[0],
fields: [
{
name: 'Web View Link:',
value: enableWebView
? `[Open Web View](${config.api.publicDomain}api/webview?c=${attachmentMessage.channelId}&m=${attachmentMessage.id}#${new Date().getTime()}#${ownerId})`
: `Web View is ${disabledStr}.\n- Click the button below to enable Web View and generate a link for this roll.`,
inline: true,
},
{
name: enableWebView ? 'Web View Status:' : 'What is Web View?',
value: enableWebView
? `For privacy, Web View will automatically time out on this roll <t:${
Math.floor(
(new Date().getTime() + 1_000 * 60 * 60) / 1000,
)
}:R>. The link will still show on this message after it has timed out, so clicking on the link after it has been timed out will show an error and remove the link.`
: '- Web View is a system for viewing extremely large or complex rolls with full formatting.\n- As anyone with the Web View link can view the roll, Web View is disabled by default for privacy.',
inline: true,
},
],
},
],
components: [
{
type: MessageComponentTypes.ActionRow,
components: [
{
type: MessageComponentTypes.Button,
label: enableWebView ? 'Disable Web View' : 'Enable Web View',
customId: `${webViewCustomId}${InteractionValueSeparator}${ownerId}${InteractionValueSeparator}${enableWebView ? 'disable' : 'enable'}`,
style: ButtonStyles.Secondary,
},
],
},
],
})
.catch((e) => utils.commonLoggers.messageEditError('embeds.ts:304', attachmentMessage, e));
};

View File

@ -0,0 +1,48 @@
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) {
loggingEnabled && log(LT.LOG, `Escaping character ${e} | ${str}, ${esc}`);
if (str.includes(e)) {
loopCountCheck(`escape.ts - escaping character ${e}`);
// Create a new regex to look for that char that needs replaced and escape it
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!=<>])-)|([xy]\d+(\.\d*)?)/g;
// breaks the string on spaces and newlines, but keeps them in the array to allow for recreating the input correctly
export const argSpacesSplitRegex = /([ \n]+)/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');
// Marker to look for when repeating a roll AND it was from a SLASH COMMAND alias run command
export const withYVarsDash = '\u2043';

View File

@ -0,0 +1,13 @@
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

@ -0,0 +1,11 @@
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

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

View File

@ -0,0 +1,74 @@
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,
countLoops = true,
): 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++) {
countLoops &&
loopCountCheck(`parenBalance.ts - ${getMatching ? 'Looking for matching' : 'Checking'} ${openStr}/${closeStr}${getMatching ? '' : ' balance'}`);
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[], countLoops = true) => checkBalance(conf, config.prefix, config.postfix, 'PrefixPostfix', false, 0, countLoops);
// 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, countLoops = true): number => checkBalance(conf, config.prefix, config.postfix, 'PrefixPostfix', true, openIdx, countLoops);

View File

@ -0,0 +1,26 @@
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(`rangeAdder.ts - ${tSep} range adder`);
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

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

View File

@ -0,0 +1,42 @@
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('rollDist.ts - convert RollSet into RollDist');
const tempArr: number[] = rollDistMap.get(rollDistKey(roll.type, roll.size)) ?? new Array<number>(roll.type === 'fate' ? roll.size + 2 : roll.size).fill(0);
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('rollDist.ts - merge array of RollDists into single RollDist');
cur
.entries()
.toArray()
.forEach(([key, value]) => {
loopCountCheck('rollDist.ts - doing the merge on each item of current');
const tempArr = acc.get(key) ?? new Array<number>(value.length).fill(0);
for (let i = 0; i < tempArr.length; i++) {
loopCountCheck('rollDist.ts - doing the merge');
tempArr[i] += value[i];
}
acc.set(key, tempArr);
});
return acc;
}, new Map<string, number[]>());

View File

@ -0,0 +1,25 @@
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('rollValCounter.ts - counting roll vals');
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

@ -0,0 +1,60 @@
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;
};
// Sort yVars by their name
export const sortYVars = (a: string, b: string) => {
if (a.length < b.length) return -1;
if (a.length > b.length) return 1;
if (a < b) return -1;
if (a > b) return 1;
return 0;
};

View File

@ -0,0 +1,139 @@
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 'VariableMissingValue':
errorMsg = `Error: \`${errorDetails}\` is missing a valid value`;
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];
};

64
src/commands/_index.ts Normal file
View File

@ -0,0 +1,64 @@
import { upsertSlashCommands } from '@discordeno';
import { alias, aliasSC } from 'commands/aliasCmd.ts';
import { api } from 'commands/apiCmd.ts';
import { audit } from 'commands/audit.ts';
import { emoji } from 'commands/emoji.ts';
import { handleMentions } from 'commands/handleMentions.ts';
import { heatmap, heatmapSC } from 'commands/heatmap.ts';
import { help, helpSC } from 'commands/help.ts';
import { info, infoSC } from 'commands/info.ts';
import { optIn } from 'commands/optIn.ts';
import { optOut } from 'commands/optOut.ts';
import { ping } from 'commands/ping.ts';
import { privacy, privacySC } from 'commands/privacy.ts';
import { report, reportSC } from 'commands/report.ts';
import { rip, ripSC } from 'commands/rip.ts';
import { roll, rollSC } from 'commands/roll.ts';
import { rollHelp } from 'commands/rollHelp.ts';
import { stats, statsSC } from 'commands/stats.ts';
import { toggleInline, toggleInlineSC } from 'commands/toggleInline.ts';
import { toggleRepeat, toggleRepeatSC } from 'commands/toggleUnrestrictedRepeat.ts';
import { version, versionSC } from 'commands/version.ts';
export const announceSlashCommands = () => {
upsertSlashCommands([aliasSC, heatmapSC, helpSC, infoSC, privacySC, reportSC, ripSC, rollSC, statsSC, toggleInlineSC, toggleRepeatSC, versionSC]);
};
export const commands = {
alias,
api,
audit,
emoji,
handleMentions,
heatmap,
help,
info,
optIn,
optOut,
ping,
privacy,
report,
rip,
roll,
rollHelp,
stats,
toggleInline,
toggleRepeat,
version,
};
export const slashCommandDetails = {
aliasSC,
heatmapSC,
helpSC,
infoSC,
privacySC,
ripSC,
reportSC,
rollSC,
statsSC,
toggleInlineSC,
toggleRepeatSC,
versionSC,
};

199
src/commands/aliasCmd.ts Normal file
View File

@ -0,0 +1,199 @@
import { ApplicationCommandOption, CreateGlobalApplicationCommand, DiscordApplicationCommandOptionTypes, DiscordenoMessage } from '@discordeno';
import aliasCommands from 'commands/aliasCmd/_index.ts';
import { generateHelpMessage } from 'commands/helpLibrary/generateHelpMessage.ts';
import dbClient from 'db/client.ts';
import { queries } from 'db/common.ts';
import { failColor } from 'embeds/colors.ts';
import { SlashCommandInteractionWithGuildId } from 'src/mod.d.ts';
import utils from 'utils/utils.ts';
const aliasNameOption = (action: string, rename = false): ApplicationCommandOption => ({
type: DiscordApplicationCommandOptionTypes.String,
name: `alias-name${rename ? '-new' : ''}`,
description: `The ${rename ? 'new ' : ''}name of the alias${rename ? '' : `you wish to ${action}`}.`,
required: true,
});
const rollStringOption = (action: string): ApplicationCommandOption => ({
type: DiscordApplicationCommandOptionTypes.String,
name: 'roll-string',
description: `The the full roll string to ${action}.`,
required: true,
});
const verificationCodeOption: ApplicationCommandOption = {
type: DiscordApplicationCommandOptionTypes.String,
name: 'verification-code',
description: 'The four digit confirmation code for deletion. Can be left blank to generate one.',
required: false,
};
const aliasOptions = (guildMode: boolean): ApplicationCommandOption[] => [
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'help',
description: `Opens the help library to the ${guildMode ? 'Guild' : 'Personal'} Mode Alias System section.`,
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'list-all',
description: `List all available aliases ${guildMode ? 'in this Guild' : 'on your account'}.`,
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'view',
description: `Preview the roll string behind an alias ${guildMode ? 'in this Guild' : 'on your account'}.`,
options: [aliasNameOption('view')],
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'create',
description: `Create a new alias ${guildMode ? 'in this Guild' : 'on your account'}.`,
options: [aliasNameOption('create'), rollStringOption('create')],
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'replace',
description: `Update an alias ${guildMode ? 'in this Guild' : 'on your account'} with a new roll string.`,
options: [aliasNameOption('replace'), rollStringOption('replace the old one with')],
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'rename',
description: `Rename an alias ${guildMode ? 'in this Guild' : 'on your account'}.`,
options: [aliasNameOption('rename'), aliasNameOption('rename', true)],
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'delete-one',
description: `Delete an alias from ${guildMode ? 'this Guild' : 'your account'}.`,
options: [aliasNameOption('delete'), verificationCodeOption],
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'delete-all',
description: `Delete all aliases from ${guildMode ? 'this Guild' : 'your account'}.`,
options: [verificationCodeOption],
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'copy',
description: `Copy an alias from ${guildMode ? 'this Guild' : 'your account'} to ${guildMode ? 'your account' : 'this Guild'}.`,
options: [aliasNameOption(`copy to ${guildMode ? 'your personal account' : 'this guild'}`)],
},
{
type: DiscordApplicationCommandOptionTypes.SubCommand,
name: 'run',
description: `Runs the specified ${guildMode ? 'Guild' : 'Personal'} alias.`,
options: [
aliasNameOption('run'),
{
type: DiscordApplicationCommandOptionTypes.String,
name: 'y-variables',
description: 'A space separated list of numbers. Can be left blank if an alias does not require any.',
required: false,
},
],
},
];
export const aliasSC: CreateGlobalApplicationCommand = {
name: 'alias',
description: 'Custom Roll Alias system, create and use Roll Aliases for easily reusing the same rolls.',
options: [
{
type: DiscordApplicationCommandOptionTypes.SubCommandGroup,
name: 'personal',
description: 'Manage and run Personal aliases.',
options: aliasOptions(false),
},
{
type: DiscordApplicationCommandOptionTypes.SubCommandGroup,
name: 'guild',
description: 'Manage and run Guild aliases.',
options: aliasOptions(true),
},
],
};
export const alias = (msgOrInt: DiscordenoMessage | SlashCommandInteractionWithGuildId, argSpaces: string[]) => {
// Light telemetry to see how many times a command is being run
dbClient.execute(queries.callIncCnt('alias')).catch((e) => utils.commonLoggers.dbError('aliasCmd.ts:125', 'call sproc INC_CNT on', e));
// argSpaces will come in with a space or \n before every real arg, so extra shifts exist to remove them
argSpaces.shift();
let aliasArg = (argSpaces.shift() || '').toLowerCase().trim();
argSpaces.shift();
let guildMode = false;
if (aliasArg === 'guild') {
guildMode = true;
aliasArg = (argSpaces.shift() || '').toLowerCase().trim();
argSpaces.shift();
}
if (guildMode && BigInt(msgOrInt.guildId) === 0n) {
utils.sendOrInteract(msgOrInt, 'aliasCmd.ts:140', {
embeds: [
{
color: failColor,
title: 'Guild Aliases can only be modified from within the desired guild.',
},
],
});
return;
}
switch (aliasArg) {
case 'help':
case 'h':
case '?':
case '':
utils.sendOrInteract(msgOrInt, 'aliasCmd.ts:156', generateHelpMessage('alias'));
break;
case 'list':
case 'list-all':
aliasCommands.list(msgOrInt, guildMode);
break;
case 'add':
case 'create':
case 'set':
aliasCommands.add(msgOrInt, guildMode, argSpaces);
break;
case 'update':
case 'replace':
aliasCommands.update(msgOrInt, guildMode, argSpaces);
break;
case 'preview':
case 'view':
aliasCommands.view(msgOrInt, guildMode, argSpaces);
break;
case 'delete':
case 'remove':
case 'delete-one':
aliasCommands.deleteOne(msgOrInt, guildMode, argSpaces);
break;
case 'delete-all':
case 'remove-all':
aliasCommands.deleteAll(msgOrInt, guildMode, argSpaces);
break;
case 'clone':
case 'copy':
aliasCommands.clone(msgOrInt, guildMode, argSpaces);
break;
case 'rename':
aliasCommands.rename(msgOrInt, guildMode, argSpaces);
break;
case 'run':
case 'execute':
default:
aliasCommands.run(msgOrInt, guildMode, aliasArg, argSpaces);
break;
}
};

View File

@ -0,0 +1,19 @@
import { add, update } from 'commands/aliasCmd/aliasAddUpdate.ts';
import { clone } from 'commands/aliasCmd/clone.ts';
import { deleteAll, deleteOne } from 'commands/aliasCmd/aliasDelete.ts';
import { list } from 'commands/aliasCmd/list.ts';
import { rename } from 'commands/aliasCmd/rename.ts';
import { run } from 'commands/aliasCmd/run.ts';
import { view } from 'commands/aliasCmd/view.ts';
export default {
add,
clone,
deleteAll,
deleteOne,
list,
rename,
run,
update,
view,
};

View File

@ -0,0 +1,411 @@
import { DiscordenoMessage, EmbedField, hasGuildPermissions } from '@discordeno';
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
import { getModifiers } from 'artigen/dice/getModifiers.ts';
import { TestResults } from 'artigen/managers/manager.d.ts';
import { sendRollRequest } from 'artigen/managers/queueManager.ts';
import { cmdSplitRegex } from 'artigen/utils/escape.ts';
import { assertPrePostBalance, getMatchingPostfixIdx } from 'artigen/utils/parenBalance.ts';
import { sortYVars } from 'artigen/utils/sortFuncs.ts';
import { ReservedWords } from 'commands/aliasCmd/reservedWords.ts';
import dbClient from 'db/client.ts';
import { generateAliasError } from 'embeds/alias.ts';
import { failColor, infoColor1, successColor } from 'embeds/colors.ts';
import { SlashCommandInteractionWithGuildId } from 'src/mod.d.ts';
import utils from 'utils/utils.ts';
interface QueryShape {
aliasName: string;
}
const handleAddUpdate = async (
msgOrInt: DiscordenoMessage | SlashCommandInteractionWithGuildId,
guildMode: boolean,
argSpaces: string[],
replaceAlias: boolean,
) => {
if (guildMode && !(await hasGuildPermissions(BigInt(msgOrInt.guildId), utils.getAuthorIdFromMessageOrInteraction(msgOrInt), ['ADMINISTRATOR']))) {
utils.sendOrInteract(msgOrInt, 'aliasAddUpdate.ts:43', {
embeds: [
{
color: failColor,
title: `Error: Only Guild Owners and Admins can add/update guild aliases`,
},
],
});
return;
}
const aliasName = (argSpaces.shift() || '').trim();
argSpaces.shift();
if (aliasName.length > config.limits.alias.maxNameLength) {
utils.sendOrInteract(msgOrInt, 'aliasAddUpdate.ts:59', {
embeds: [
{
color: failColor,
title: 'Error: Alias Name is too long',
description:
`\`${aliasName}\` (\`${aliasName.length}\` characters) is longer than the allowed max length of \`${config.limits.alias.maxNameLength}\` characters. Please choose a shorter alias name.`,
},
],
});
return;
}
if (ReservedWords.includes(aliasName?.toLowerCase())) {
utils.sendOrInteract(msgOrInt, 'aliasAddUpdate.ts:74', {
embeds: [
{
color: failColor,
title: `Error: \`${aliasName}\` is a reserved word`,
description: `Please choose a different name for this alias.
You cannot use any of the following reserved words: \`${ReservedWords.join('`, `')}\`.`,
},
],
});
return;
}
let errorOut = false;
const query: QueryShape[] = await dbClient
.query(
`SELECT aliasName FROM aliases WHERE guildid = ? AND userid = ? AND aliasName = ?`,
guildMode ? [BigInt(msgOrInt.guildId), 0n, aliasName.toLowerCase()] : [0n, utils.getAuthorIdFromMessageOrInteraction(msgOrInt), aliasName.toLowerCase()],
)
.catch((e0) => {
utils.commonLoggers.dbError('add.ts:44', 'query', e0);
utils.sendOrInteract(
msgOrInt,
'aliasAddUpdate.ts:97',
generateAliasError(
'DB Query Failed.',
`add-q0-${guildMode ? 't' : 'f'}-${aliasName}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
);
errorOut = true;
});
if (errorOut) return;
if (!replaceAlias && query.length) {
utils.sendOrInteract(msgOrInt, 'aliasAddUpdate.ts:109', {
embeds: [
{
color: failColor,
title: `Error: \`${aliasName}\` already exists as a ${guildMode ? 'guild' : 'personal'} alias`,
description: 'Please choose a different name for this alias.',
},
],
});
return;
} else if (replaceAlias && !query.length) {
utils.sendOrInteract(msgOrInt, 'aliasAddUpdate.ts:121', {
embeds: [
{
color: failColor,
title: `Error: \`${aliasName}\` does not exist as a ${guildMode ? 'guild' : 'personal'} alias`,
description: `If you are trying to create a new ${guildMode ? 'guild' : 'personal'} alias, please run the following command:
\`${config.prefix}ra ${guildMode ? 'guild ' : ''}add\` followed by the desired alias name and roll string.
If you are trying to update an existing alias, but forgot the name, please run the following command to view all your ${guildMode ? 'guild ' : ''}aliases:
\`${config.prefix}ra ${guildMode ? 'guild ' : ''}list\``,
},
],
});
return;
}
const rawRollStr = argSpaces.join('').trim();
const newMsg: DiscordenoMessage | void = await utils.sendOrInteract(
msgOrInt,
'aliasAddUpdate.ts:139',
{
embeds: [
{
color: infoColor1,
title: 'Please wait, testing your roll string . . .',
description: `The following roll string is being tested. Once the verdict of your roll has been determined, this message will be updated.
\`${rawRollStr}\``,
},
],
},
true,
);
if (!newMsg) {
log(LT.ERROR, `My message didn't send! ${msgOrInt}`);
return;
}
const [modifiers, remainingArgs] = getModifiers(argSpaces);
const failedRollMsg = `The provided roll string (listed below) encountered an error. Please try this roll outside the roll alias system and resolve the error before trying again.
\`${rawRollStr}\`${rawRollStr.length > 1_700 ? ' (trimmed to 2,000 characters to fit in the error message)' : ''}`.slice(0, 2_000);
if (!modifiers.valid) {
newMsg
.edit({
embeds: [
{
color: failColor,
title: 'Roll failed',
description: failedRollMsg,
fields: [
{
name: 'Error Details:',
value: modifiers.error.message,
},
],
footer: {
text: modifiers.error.name,
},
},
],
})
.catch((e: Error) => utils.commonLoggers.messageEditError('add.ts:116', newMsg, e));
return;
}
const rollCmd = remainingArgs.join('');
const testCmdConf = rollCmd
.toLowerCase()
.split(cmdSplitRegex)
.filter((x) => x);
try {
assertPrePostBalance(testCmdConf, false);
let openIdx = testCmdConf.indexOf(config.prefix);
while (openIdx !== -1) {
const closeIdx = getMatchingPostfixIdx(testCmdConf, openIdx, false);
const possibleYVars = testCmdConf
.slice(openIdx + 1, closeIdx)
.join('')
.split(/(y\d+(\.\d*)?)/g)
.filter((y) => y && y.startsWith('y'));
for (const yVar of possibleYVars) {
if (yVar.includes('.')) {
newMsg
.edit({
embeds: [
{
color: failColor,
title: 'Roll failed',
description: failedRollMsg,
fields: [
{
name: 'Error Details:',
value: `yVars cannot have decimals`,
},
],
footer: {
text: 'yVarDecimal',
},
},
],
})
.catch((e: Error) => utils.commonLoggers.messageEditError('add.ts:163', newMsg, e));
return;
}
if (!modifiers.yVars.has(yVar)) {
modifiers.yVars.set(yVar, Math.ceil(Math.random() * 20));
}
}
openIdx = testCmdConf.indexOf(config.prefix, closeIdx);
}
} catch (e) {
const err = e as Error;
newMsg
.edit({
embeds: [
{
color: failColor,
title: 'Roll failed',
description: failedRollMsg,
fields: [
{
name: 'Error Details:',
value: `Failed to find yVars, requested rollStr likely has unbalanced \`${config.prefix}\`/\`${config.postfix}\``,
},
{
name: 'Raw Error:',
value: `${err.name}: ${err.message}`,
},
],
footer: {
text: 'caughtErrYVarUnbalanced',
},
},
],
})
.catch((e: Error) => utils.commonLoggers.messageEditError('add.ts:191', newMsg, e));
return;
}
let i = 0;
while (i < modifiers.yVars.size) {
if (!modifiers.yVars.has(`y${i}`)) {
modifiers.yVars.set(`y${i}`, 0);
}
i++;
}
const rollStrVerdict = await new Promise<TestResults>((resolve) => {
sendRollRequest({
apiRoll: false,
ddRoll: false,
testRoll: true,
resolve,
rollCmd,
modifiers,
originalCommand: rawRollStr,
});
});
if (rollStrVerdict.error) {
const errorFields: EmbedField[] = [
{
name: 'Error Details:',
value: rollStrVerdict.errorMsg,
},
];
if (modifiers.yVars.size) {
errorFields.push({
name: 'The following YVars were used in testing:',
value: modifiers.yVars
.entries()
.toArray()
.sort((a, b) => sortYVars(a[0], b[0]))
.map(([yVar, value]) => `\`${yVar}\`: \`${value}\``)
.join('\n'),
});
}
newMsg
.edit({
embeds: [
{
color: failColor,
title: 'Roll failed',
description: failedRollMsg,
fields: errorFields,
footer: {
text: rollStrVerdict.errorCode,
},
},
],
})
.catch((e: Error) => utils.commonLoggers.messageEditError('add.ts:153', newMsg, e));
return;
}
if (replaceAlias) {
await dbClient
.execute('UPDATE aliases SET rollStr = ?, yVarCnt = ? WHERE guildid = ? AND userid = ? AND aliasName = ?', [
rawRollStr,
modifiers.yVars.size,
guildMode ? BigInt(msgOrInt.guildId) : 0n,
guildMode ? 0n : utils.getAuthorIdFromMessageOrInteraction(msgOrInt),
aliasName.toLowerCase(),
])
.catch((e0) => {
utils.commonLoggers.dbError('add.ts:169', 'update', e0);
newMsg
.edit(
generateAliasError(
'DB Update Failed.',
`add-q1-${guildMode ? 't' : 'f'}-${aliasName}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
)
.catch((e: Error) => utils.commonLoggers.messageSendError('add.ts:170', msgOrInt, e));
errorOut = true;
});
} else {
const currentAliases: QueryShape[] = await dbClient
.query(
'SELECT aliasName FROM aliases WHERE guildid = ? AND userid = ?',
guildMode ? [BigInt(msgOrInt.guildId), 0n] : [0n, utils.getAuthorIdFromMessageOrInteraction(msgOrInt)],
)
.catch((e0) => {
utils.commonLoggers.dbError('add.ts:266', 'get count', e0);
newMsg
.edit(
generateAliasError(
'DB Query Failed.',
`add-q2-${guildMode ? 't' : 'f'}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
)
.catch((e: Error) => utils.commonLoggers.messageSendError('add.ts:269', msgOrInt, e));
errorOut = true;
});
if (errorOut) return;
if (currentAliases.length < (guildMode ? config.limits.alias.free.guild : config.limits.alias.free.user)) {
await dbClient
.execute('INSERT INTO aliases(guildid,userid,aliasName,rollStr,yVarCnt,premium) values(?,?,?,?,?,?)', [
guildMode ? BigInt(msgOrInt.guildId) : 0n,
guildMode ? 0n : utils.getAuthorIdFromMessageOrInteraction(msgOrInt),
aliasName.toLowerCase(),
rawRollStr,
modifiers.yVars.size,
0,
])
.catch((e0) => {
utils.commonLoggers.dbError('add.ts:169', 'insert into', e0);
newMsg
.edit(
generateAliasError(
'DB Insert Failed.',
`add-q3-${guildMode ? 't' : 'f'}-${aliasName}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
)
.catch((e: Error) => utils.commonLoggers.messageSendError('add.ts:187', msgOrInt, e));
errorOut = true;
});
} else {
newMsg
.edit({
embeds: [
{
color: failColor,
title: `Over ${guildMode ? 'guild' : 'personal'} Alias Limit`,
description: `Cannot add another alias as this account already has \`${currentAliases.length}\` aliases saved.
The current limits imposed on the Alias System are \`${config.limits.alias.free.guild}\` guild aliases and \`${config.limits.alias.free.user}\` account aliases.
If you need this limit raised, please join the [support server](${config.links.supportServer})`,
},
],
})
.catch((e: Error) => utils.commonLoggers.messageEditError('add.ts:302', newMsg, e));
return;
}
}
if (errorOut) return;
const yVarString = ' ' + modifiers.yVars.keys().toArray().sort(sortYVars).join(' ');
newMsg
.edit({
embeds: [
{
color: successColor,
title: `Successfully ${replaceAlias ? 'replaced' : 'added'} the ${guildMode ? 'guild' : 'personal'} alias \`${aliasName}\`!`,
description: `You can try it out now using the following command:
\`${config.prefix}ra ${guildMode ? 'guild ' : ''}${aliasName}${modifiers.yVars.size ? yVarString : ''}\``,
},
],
})
.catch((e: Error) => utils.commonLoggers.messageSendError('add.ts:321', msgOrInt, e));
};
// Using wrappers to limit "magic" booleans
export const add = (msgOrInt: DiscordenoMessage | SlashCommandInteractionWithGuildId, guildMode: boolean, argSpaces: string[]) => handleAddUpdate(msgOrInt, guildMode, argSpaces, false);
export const update = (msgOrInt: DiscordenoMessage | SlashCommandInteractionWithGuildId, guildMode: boolean, argSpaces: string[]) => handleAddUpdate(msgOrInt, guildMode, argSpaces, true);

View File

@ -0,0 +1,202 @@
import { DiscordenoMessage, hasGuildPermissions } from '@discordeno';
import config from '~config';
import dbClient from 'db/client.ts';
import { generateAliasError } from 'embeds/alias.ts';
import { failColor, successColor, warnColor } from 'embeds/colors.ts';
import { SlashCommandInteractionWithGuildId } from 'src/mod.d.ts';
import utils from 'utils/utils.ts';
const handleDelete = async (msgOrInt: DiscordenoMessage | SlashCommandInteractionWithGuildId, guildMode: boolean, argSpaces: string[], deleteAll: boolean) => {
if (guildMode && !(await hasGuildPermissions(BigInt(msgOrInt.guildId), utils.getAuthorIdFromMessageOrInteraction(msgOrInt), ['ADMINISTRATOR']))) {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:16', {
embeds: [
{
color: failColor,
title: 'Error: Only Guild Owners and Admins can delete guild aliases',
},
],
});
return;
}
const verificationCode = (guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)).toString().slice(-4);
const aliasName = (argSpaces.shift() || '').trim();
argSpaces.shift();
const userEnteredVCode = (argSpaces.shift() || '').trim();
let errorOut = false;
if (!deleteAll) {
if (!aliasName) {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:36', {
embeds: [
{
color: failColor,
title: 'Error: Please specify one alias to delete',
},
],
});
return;
} else if (!userEnteredVCode) {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:47', {
embeds: [
{
color: warnColor,
title: `Deletion is permanent, please confirm you want to delete \`${aliasName}\``,
description: `Are you sure you want to delete the ${guildMode ? 'guild' : 'personal'} alias \`${aliasName}\`?
If you are certain you want to delete \`${aliasName}\` from ${guildMode ? 'this guild' : 'your account'}, please run the following command:
\`${config.prefix}ra ${guildMode ? 'guild ' : ''}delete ${aliasName} ${verificationCode}\``,
},
],
});
return;
} else if (userEnteredVCode !== verificationCode) {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:62', {
embeds: [
{
color: failColor,
title: 'Error: Incorrect verification code',
description: `If you are certain you want to delete \`${aliasName}\` from ${guildMode ? 'this guild' : 'your account'}, please run the following command:
\`${config.prefix}ra ${guildMode ? 'guild ' : ''}delete ${aliasName} ${verificationCode}\``,
},
],
});
return;
} else if (userEnteredVCode === verificationCode) {
const deleteResults = await dbClient
.execute('DELETE FROM aliases WHERE guildid = ? AND userid = ? AND aliasName = ?', [
guildMode ? BigInt(msgOrInt.guildId) : 0n,
guildMode ? 0n : utils.getAuthorIdFromMessageOrInteraction(msgOrInt),
aliasName,
])
.catch((e) => {
utils.commonLoggers.dbError('aliasDelete.ts:76', 'delete from aliases', e);
errorOut = true;
});
if (errorOut || !deleteResults) {
utils.sendOrInteract(
msgOrInt,
'aliasDelete.ts:86',
generateAliasError(
'Delete failed.',
`delete-q0-${guildMode ? 't' : 'f'}-${aliasName}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
);
return;
} else if (deleteResults.affectedRows) {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:95', {
embeds: [
{
color: successColor,
title: 'Alias Deleted Successfully',
description: `The ${guildMode ? 'guild' : 'personal'} alias named \`${aliasName}\` was successfully deleted.`,
},
],
});
} else {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:105', {
embeds: [
{
color: warnColor,
title: 'Nothing deleted',
description: `Looks like you${guildMode ? "r guild doesn't" : " don't"} have an alias named \`${aliasName}\`.
Please run \`${config.prefix}ra ${guildMode ? 'guild ' : ''}list\` to view the current aliases for ${guildMode ? 'this guild' : 'your account'}.`,
},
],
});
}
return;
} else {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:119', generateAliasError('How are you here?', 'deleteOne-how'));
return;
}
} else {
// We're in deleteAll mode, so aliasName will carry the user verification code.
// Since one wasn't provided, prompt for confirmation
if (!aliasName) {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:119', {
embeds: [
{
color: warnColor,
title: 'Deletion is permanent, please confirm you want to delete all aliases',
description: `Are you sure you want to delete all aliases for ${guildMode ? 'this guild' : 'your account'}?
If you are certain you want to delete all aliases for ${guildMode ? 'this guild' : 'your account'}, please run the following command:
\`${config.prefix}ra ${guildMode ? 'guild ' : ''}delete-all ${verificationCode}\``,
},
],
});
return;
} else if (aliasName !== verificationCode) {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:142', {
embeds: [
{
color: failColor,
title: 'Error: Incorrect verification code',
description: `If you are certain you want to delete all aliases for ${guildMode ? 'this guild' : 'your account'}, please run the following command:
\`${config.prefix}ra ${guildMode ? 'guild ' : ''}delete-all ${verificationCode}\``,
},
],
});
return;
} else if (aliasName === verificationCode) {
const deleteResults = await dbClient
.execute('DELETE FROM aliases WHERE guildid = ? AND userid = ?', [
guildMode ? BigInt(msgOrInt.guildId) : 0n,
guildMode ? 0n : utils.getAuthorIdFromMessageOrInteraction(msgOrInt),
])
.catch((e) => {
utils.commonLoggers.dbError('aliasDelete.ts:159', 'delete from aliases', e);
errorOut = true;
});
if (errorOut || !deleteResults) {
utils.sendOrInteract(
msgOrInt,
'aliasDelete.ts:165',
generateAliasError(
'Delete failed.',
`delete-q1-${guildMode ? 't' : 'f'}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
);
return;
} else if (deleteResults.affectedRows) {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:174', {
embeds: [
{
color: successColor,
title: 'All Aliases Deleted Successfully',
description: `All ${guildMode ? 'guild' : 'personal'} aliases for ${guildMode ? 'this guild' : 'your account'} were successfully deleted.`,
},
],
});
} else {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:184', {
embeds: [
{
color: warnColor,
title: 'Nothing deleted',
description: `Looks like you${guildMode ? "r guild doesn't" : " don't"} have any aliases to delete.
Please run \`${config.prefix}ra ${guildMode ? 'guild ' : ''}list\` to view the current aliases for ${guildMode ? 'this guild' : 'your account'}.
If anything shows up there after running this command, please \`${config.prefix}report\` this to the developer.`,
},
],
});
}
return;
} else {
utils.sendOrInteract(msgOrInt, 'aliasDelete.ts:199', generateAliasError('How are you here?', 'deleteAll-how'));
return;
}
}
};
// Using wrappers to limit "magic" booleans
export const deleteOne = (msgOrInt: DiscordenoMessage | SlashCommandInteractionWithGuildId, guildMode: boolean, argSpaces: string[]) => handleDelete(msgOrInt, guildMode, argSpaces, false);
export const deleteAll = (msgOrInt: DiscordenoMessage | SlashCommandInteractionWithGuildId, guildMode: boolean, argSpaces: string[]) => handleDelete(msgOrInt, guildMode, argSpaces, true);

View File

@ -0,0 +1,145 @@
import { DiscordenoMessage, hasGuildPermissions } from '@discordeno';
import config from '~config';
import dbClient from 'db/client.ts';
import { generateAliasError } from 'embeds/alias.ts';
import { failColor, successColor } from 'embeds/colors.ts';
import { SlashCommandInteractionWithGuildId } from 'src/mod.d.ts';
import utils from 'utils/utils.ts';
interface QueryShape {
aliasName: string;
yVarCnt: number;
rollStr: string;
}
export const clone = async (msgOrInt: DiscordenoMessage | SlashCommandInteractionWithGuildId, guildMode: boolean, argSpaces: string[]) => {
if (!guildMode && !(await hasGuildPermissions(BigInt(msgOrInt.guildId), utils.getAuthorIdFromMessageOrInteraction(msgOrInt), ['ADMINISTRATOR']))) {
utils.sendOrInteract(msgOrInt, 'clone.ts:22', {
embeds: [
{
color: failColor,
title: `Error: Only Guild Owners and Admins can copy a personal alias to a guild aliases`,
},
],
});
return;
}
const aliasName = (argSpaces.shift() || '').trim().toLowerCase();
if (!aliasName) {
utils.sendOrInteract(msgOrInt, 'clone.ts:37', {
embeds: [
{
color: failColor,
title: `Error: Please specify an alias to copy to ${guildMode ? 'your account' : 'this guild'}`,
},
],
});
return;
}
let errorOut = false;
const query: QueryShape[] = await dbClient
.query(
`SELECT aliasName, yVarCnt, rollStr FROM aliases WHERE guildid = ? AND userid = ? AND aliasName = ?`,
guildMode ? [BigInt(msgOrInt.guildId), 0n, aliasName] : [0n, utils.getAuthorIdFromMessageOrInteraction(msgOrInt), aliasName],
)
.catch((e0) => {
utils.commonLoggers.dbError('clone.ts:51', 'query', e0);
utils.sendOrInteract(
msgOrInt,
'clone.ts:57',
generateAliasError(
'DB Query Failed.',
`clone-q0-${guildMode ? 't' : 'f'}-${aliasName}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
);
errorOut = true;
});
if (errorOut) return;
const details = query[0];
if (!details) {
utils.sendOrInteract(msgOrInt, 'clone.ts:71', {
embeds: [
{
color: failColor,
title: `\`${aliasName}\` does not exist as a${guildMode ? ' guild alias' : 'n alias on your account'}.`,
description: `Did you mean to run \`${config.prefix}ra ${guildMode ? '' : 'guild '}clone ${aliasName}\`?`,
},
],
});
}
const targetQuery: QueryShape[] = await dbClient
.query(
`SELECT aliasName, yVarCnt, rollStr FROM aliases WHERE guildid = ? AND userid = ? AND aliasName = ?`,
guildMode ? [0n, utils.getAuthorIdFromMessageOrInteraction(msgOrInt), aliasName] : [BigInt(msgOrInt.guildId), 0n, aliasName],
)
.catch((e0) => {
utils.commonLoggers.dbError('clone.ts:82', 'query', e0);
utils.sendOrInteract(
msgOrInt,
'clone.ts:90',
generateAliasError(
'DB Query Failed.',
`clone-q1-${guildMode ? 't' : 'f'}-${aliasName}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
);
errorOut = true;
});
if (errorOut) return;
if (targetQuery.length) {
utils.sendOrInteract(msgOrInt, 'clone.ts:102', {
embeds: [
{
color: failColor,
title: `\`${aliasName}\` already exists as an alias ${guildMode ? 'on your account' : 'in this guild'}.`,
description: `Please delete or rename the ${guildMode ? 'personal' : 'guild'} alias \`${aliasName}\` and try again.`,
},
],
});
return;
}
await dbClient
.execute(`INSERT INTO aliases(guildid,userid,aliasName,rollStr,yVarCnt,premium) values(?,?,?,?,?,?)`, [
guildMode ? 0n : BigInt(msgOrInt.guildId),
guildMode ? utils.getAuthorIdFromMessageOrInteraction(msgOrInt) : 0n,
aliasName,
details.rollStr,
details.yVarCnt,
0,
])
.catch((e0) => {
utils.commonLoggers.dbError('clone.ts:110', 'query', e0);
utils.sendOrInteract(
msgOrInt,
'clone.ts:126',
generateAliasError(
'DB Insert Failed.',
`clone-q2-${guildMode ? 't' : 'f'}-${aliasName}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
);
errorOut = true;
});
if (errorOut) return;
utils.sendOrInteract(msgOrInt, 'clone.ts:137', {
embeds: [
{
color: successColor,
title: `Successfully copied the ${guildMode ? 'guild' : 'personal'} alias \`${aliasName}\`!`,
description: `\`${aliasName}\` is now available as an alias ${guildMode ? 'on your account' : 'in this guild'}.`,
},
],
});
};

View File

@ -0,0 +1,51 @@
import { DiscordenoMessage } from '@discordeno';
import dbClient from 'db/client.ts';
import { generateAliasError } from 'embeds/alias.ts';
import { successColor } from 'embeds/colors.ts';
import { SlashCommandInteractionWithGuildId } from 'src/mod.d.ts';
import utils from 'utils/utils.ts';
interface QueryShape {
aliasName: string;
yVarCnt: number;
}
export const list = async (msgOrInt: DiscordenoMessage | SlashCommandInteractionWithGuildId, guildMode: boolean) => {
let errorOut = false;
const query: QueryShape[] = await dbClient
.query(
`SELECT aliasName, yVarCnt FROM aliases WHERE guildid = ? AND userid = ? ORDER BY createdAt ASC`,
guildMode ? [BigInt(msgOrInt.guildId), 0n] : [0n, utils.getAuthorIdFromMessageOrInteraction(msgOrInt)],
)
.catch((e0) => {
utils.commonLoggers.dbError('list.ts:10', 'query', e0);
utils.sendOrInteract(
msgOrInt,
'list.ts:26',
generateAliasError(
'DB Query Failed.',
`list-q0-${guildMode ? 't' : 'f'}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
);
errorOut = true;
});
if (errorOut) return;
utils.sendOrInteract(msgOrInt, 'list.ts:33', {
embeds: [
{
color: successColor,
title: `Found ${query.length} alias${query.length === 1 ? '' : 'es'} for ${guildMode ? 'this guild' : 'your account'}:`,
description: query.length
? `Format shown is \`alias-name\` followed by the number of yVars required for the alias in parenthesis, if there are any required.
${query.map((a) => `\`${a.aliasName}\`${a.yVarCnt ? ` (${a.yVarCnt})` : ''}`).join(', ')}`
: '',
},
],
});
};

View File

@ -0,0 +1,144 @@
import { DiscordenoMessage, hasGuildPermissions } from '@discordeno';
import config from '~config';
import dbClient from 'db/client.ts';
import { generateAliasError } from 'embeds/alias.ts';
import { failColor, successColor } from 'embeds/colors.ts';
import { SlashCommandInteractionWithGuildId } from 'src/mod.d.ts';
import utils from 'utils/utils.ts';
interface QueryShape {
aliasName: string;
}
export const rename = async (msgOrInt: DiscordenoMessage | SlashCommandInteractionWithGuildId, guildMode: boolean, argSpaces: string[]) => {
if (guildMode && !(await hasGuildPermissions(BigInt(msgOrInt.guildId), utils.getAuthorIdFromMessageOrInteraction(msgOrInt), ['ADMINISTRATOR']))) {
utils.sendOrInteract(msgOrInt, 'rename.ts:20', {
embeds: [
{
color: failColor,
title: `Error: Only Guild Owners and Admins can rename a guild aliases`,
},
],
});
return;
}
const oldAliasName = (argSpaces.shift() || '').trim().toLowerCase();
argSpaces.shift();
const newAliasName = (argSpaces.shift() || '').trim().toLowerCase();
if (!oldAliasName || !newAliasName) {
utils.sendOrInteract(msgOrInt, 'rename.ts:37', {
embeds: [
{
color: failColor,
title: `Error: Please specify both an alias to rename, and the new name to set it to.`,
},
],
});
return;
}
// make sure old alias exists, and new doesn't exist first
let errorOut = false;
const queryOld: QueryShape[] = await dbClient
.query(
`SELECT aliasName FROM aliases WHERE guildid = ? AND userid = ? AND aliasName = ?`,
guildMode ? [BigInt(msgOrInt.guildId), 0n, oldAliasName] : [0n, utils.getAuthorIdFromMessageOrInteraction(msgOrInt), oldAliasName],
)
.catch((e0) => {
utils.commonLoggers.dbError('rename.ts:44', 'query', e0);
utils.sendOrInteract(
msgOrInt,
'rename.ts:58',
generateAliasError(
'DB Query Failed.',
`rename-q0-${guildMode ? 't' : 'f'}-${oldAliasName}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
);
errorOut = true;
});
if (errorOut) return;
if (!queryOld.length) {
utils.sendOrInteract(msgOrInt, 'rename.ts:70', {
embeds: [
{
color: failColor,
title: `Error: \`${oldAliasName}\` does not exist as a ${guildMode ? 'guild' : 'personal'} alias.`,
description: `If you are trying to update an existing alias, but forgot the name, please run the following command to view all your ${guildMode ? 'guild ' : ''}aliases:
\`${config.prefix}ra ${guildMode ? 'guild ' : ''}list\``,
},
],
});
return;
}
const queryNew: QueryShape[] = await dbClient
.query(
`SELECT aliasName FROM aliases WHERE guildid = ? AND userid = ? AND aliasName = ?`,
guildMode ? [BigInt(msgOrInt.guildId), 0n, newAliasName] : [0n, utils.getAuthorIdFromMessageOrInteraction(msgOrInt), newAliasName],
)
.catch((e0) => {
utils.commonLoggers.dbError('rename.ts:44', 'query', e0);
utils.sendOrInteract(
msgOrInt,
'rename.ts:91',
generateAliasError(
'DB Query Failed.',
`rename-q1-${guildMode ? 't' : 'f'}-${newAliasName}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
);
errorOut = true;
});
if (errorOut) return;
if (queryNew.length) {
utils.sendOrInteract(msgOrInt, 'rename.ts:103', {
embeds: [
{
color: failColor,
title: `Error: \`${newAliasName}\` already exists as a ${guildMode ? 'guild' : 'personal'} alias.`,
description: 'Please choose a different name for this alias.',
},
],
});
return;
}
// do the rename
await dbClient
.execute('UPDATE aliases SET aliasName = ? WHERE guildid = ? AND userid = ? AND aliasName = ?', [
newAliasName,
guildMode ? BigInt(msgOrInt.guildId) : 0n,
guildMode ? 0n : utils.getAuthorIdFromMessageOrInteraction(msgOrInt),
oldAliasName,
])
.catch((e0) => {
utils.commonLoggers.dbError('rename.ts:169', 'update', e0);
utils.sendOrInteract(
msgOrInt,
'rename.ts:126',
generateAliasError(
'DB Update Failed.',
`rename-q2-${guildMode ? 't' : 'f'}-${oldAliasName}-${newAliasName}-${guildMode ? BigInt(msgOrInt.guildId) : utils.getAuthorIdFromMessageOrInteraction(msgOrInt)}`,
),
);
errorOut = true;
});
utils.sendOrInteract(msgOrInt, 'rename.ts:136', {
embeds: [
{
color: successColor,
title: `Successfully renamed the ${guildMode ? 'guild' : 'personal'} alias \`${oldAliasName}\` to \`${newAliasName}\`!`,
description: `\`${newAliasName}\` is now available as an alias ${guildMode ? 'in this guild' : 'on your account'}.`,
},
],
});
};

View File

@ -0,0 +1,25 @@
export const ReservedWords = Object.freeze([
'guild',
'help',
'h',
'?',
'list',
'list-all',
'add',
'create',
'set',
'update',
'replace',
'preview',
'view',
'delete',
'remove',
'delete-one',
'delete-all',
'remove-all',
'run',
'execute',
'clone',
'copy',
'rename',
]);

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