Warning
This project only supports Node.js 20.x and 21.x.
Node.js 21.7.3 is the final supported version.
Node.js 22 and newer are not supported.
Use Node.js 20.19.6 if you plan on enabling the inspector features.
Install the bot with:
npm install --omit=optionalOptional runtime support:
vm2support depends on the optionalvm2package being available- external language eval support depends on the separate external VM setup being installed on the machine
This section is for bot setup. It covers credentials, feature toggles, interface settings, and the main config files.
You can provide bot credentials in any of these ways:
config/auth.jsonconfig/auth.env- existing environment variables
Supported environment variable names:
LEVERET_TOKENLEVERET_OWNER
If config/auth.json exists, the bot uses that file for token and owner.
If there is no config/auth.json, the bot looks for config/auth.env and then for the matching environment variables.
config/auth.json example:
{
"token": "bot token",
"owner": "your discord id"
}config/auth.env example:
LEVERET_TOKEN=your bot token
LEVERET_OWNER=your discord idconfig/config.json controls the main bot behavior, including command prefixes, limits, enabled features, logging, and paths.
Important groups of settings:
- Discord text interface:
cmdPrefix - CLI interface:
enableCliCommands,cliCmdPrefix - WebSocket interface:
enableWebsocket,websocketPort - Eval modes and script network access:
enableEval,enableVM2,enableOtherLangs,allowLocalhostRequests,allowFilesystemRequests - Inspector and debugging:
enableInspector,enableUserInspector, timeout and port settings - Tags and quotas:
maxTagNameLength,tagNameRegex,maxTagSize(per-tag limits fortext,script, andbinary),maxQuota,maxTagCount - Reply and output limits:
outCharLimit,outLineLimit,embedCharLimit,embedLineLimit - Major features:
enablePreviews,enableSed,enablePermissions,enableReminders - Logging and file paths: log files, log level, database path, command paths, Discord log settings
config/reactions.json controls automatic emoji reactions.
At minimum, it can simply disable the feature:
{
"enableReacts": false
}If reactions are enabled, the file defines the bracket reactions and the word-triggered reactions:
{
"enableReacts": true,
"parens": {
"left": ["left parenthesis emoji ids"],
"right": ["right parenthesis emoji ids"]
},
"funnyWords": [
{
"word(s)": "word or [word1, word2]",
"react(s)": "emoji or [emoji1, emoji2]"
}
]
}- If
enableCliCommandsis off, the local CLI section in this README does not apply. - If
enableWebsocketis off, the WebSocket API is not available. - If
enableEvalis off, eval commands are not available. - If
enableVM2is off, VM2 eval and VM2 tag execution are not available. - If
enableOtherLangsis off,c,cpp, andpyeval modes are not available. - If
allowLocalhostRequestsis off (the default), script HTTP requests (request/fetch) cannot connect to localhost, loopback, or private network IP addresses. - If
allowFilesystemRequestsis off (the default), script HTTP requests cannot access local files throughfile://URLs. - If inspector support is off, debug flows described in this README are not available.
- Discord text commands with a configurable prefix
- Tags with pipelines and streaming, aliases, quotas, search, raw dump, ownership, and script support
- Snapshot-based tag and permission audit history with revision reversion
- JavaScript eval, optional
vm2, optional other languages, and inspector debugging - Permission groups with configurable levels
- Reminders delivered by DM
- Message link previews, sed-style replacements, and reaction triggers
- CLI commands for local testing and tag/script emulation
- WebSocket command API for external tools and editor integration
Run the following commands to update the bot with the latest code:
git fetch origin
git reset --soft origin/main- Extract responses into a lang file
- Rewrite vm2 runner
- Add cabal (banning)
- Add the option to save tag attachments locally
- Add support for slash commands (perhaps)
- Add support for other databases
- Add caching
- Add different behaviour when replying to messages
All Discord commands use the bot's text-command prefix. By default that prefix is %.
Argument notation used below:
(value)means required[value]means optional"quoted text"means keep the text together as one argument
%help
Shows the commands you can currently use.
If a command has its own help text, you can usually ask for it with:
%(command) -help
%ping
Checks whether the bot is responsive and shows latency information.
%version
Shows the version of the bot that is currently running.
%uptime
Shows how long the bot has been running and when it started.
Tags are saved snippets you can call by name. They are useful for canned replies, reusable snippets, aliases, and script-based responses.
Who uses this:
- everyone for normal tag use
- tag owners for creating and maintaining their own tags
- moderators for moderator-only actions such as
infoandset_type
Common usage:
%tag (name) [args]
Runs the tag named (name). Any extra text is passed to the tag as its arguments.
Main actions:
add (name) [body]creates a tagedit (name) [new_body]replaces a tag's bodydelete (name)removes a tagrename (name) (new_name)renames a taghide (name)hides a tag from search, suggestions, and fullsearchunhide (name)unhides a hidden tag (binary tags cannot be unhidden)raw (name)shows the stored body exactly as savedinfo (name) [raw]shows stored tag details for moderatorsowner (name)shows the ownerlist [user]lists your tags or another user's tagssearch (text) [max_results / all]searches tag namesfullsearch (query) [max_results / all]searches inside tag bodiesrandom (prefix)picks a random matching tagcount [all/new/script/me/user]shows counts by scope or typeleaderboard (count/size/usage) [limit]shows tag leaderboardsquotashows how much tag space you are usingdump [inline/full] [spaces]exports tags.fullsends a JSON filealias (name) (target) [args]creates an alias-style tagchown (name) (new_owner)transfers ownershipset_type (name) (type|version)changes script metadata for moderatorsaudit [tag_name] [revision_id] [--options]shows recent tag changes or a full revision diff for moderatorsaudit_clear [tag_name] [revision_id] [end_revision_id] [--options]clears tag audit history and resets the revision sequence when fully cleared (bot owner only)revert <name> [revision_id]restores a previous tag state. Tag owners can revert their own latest actions within one hour; moderators can restore arbitrary revisions.stream <pipeline>(orpipe) executes a pipeline of tags and stream operators
Examples:
%tag auditshows the full audit log across all tags%tag audit 42shows the before/after diff for revision42directly%tag audit examplelists recent revisions forexample%tag audit example 42shows the before/after diff for revision42%tag audit --operation update --limit 20lists recent tag updates%tag audit_clearclears all tag audit history and resets the revision sequence back to 0 (bot owner only)%tag audit_clear exampleclears history for one tag%tag audit_clear example 42 45clears an inclusive revision-ID range for one tag%tag audit_clear example --from "last week" --to "yesterday"clears a date range for one tag%tag stream source_tag > upperrunssource_tagand converts the result to uppercase%tag stream echo hello world | upperpipes echoed text through theupperoperator%tag stream source_tag | echo prefix $ suffixuses$to interpolate piped input%tag alias shout stream echo_tag $ | uppercreates an alias tag using a pipeline
Tag audit options:
--useror--actorfilters by the user who made the change--operationor--opfilters byimport,create,update,delete, orrevert--fromand--tofilter by a date or time range--pageand--limitcontrol pagination
Audit results are shown in a compact paginated embed. Providing a revision ID shows the tracked fields before and after that change. --from and --to accept natural-language dates and times.
Tag stream / pipeline:
%tag stream (step1) > (step2) > ... > (stepN)
%tag pipe (step1) | (step2) | ... | (stepN)
Executes a sequence of steps chained together, passing the output of each step into the next. Both > and | can be used as step delimiters. Up to 30 steps are supported in a single pipeline.
Each step can be the name of an existing tag or a built-in stream operator.
Argument handling and the $ placeholder:
- If a step's arguments contain
$,$is replaced by the piped output from the previous step (for example,source_tag | echo prefix $ suffix). - If a step does not contain
$, the piped output is prepended to the step's arguments. - Quotes (
"..."or'...') keep delimiters like>and|within arguments from splitting the pipeline. - Tags can alias to
streampipelines (for example,%tag alias shout stream echo_tag $ | upper), which allows calling%t shout custom_input.
Stream operators:
echo [text]: Outputs the specified text or forwards the piped input. Whentextcontains$, it replaces$with the piped input.unembed [url]: Extracts and stringifies text from embeds. Accepts JSON embed objects/arrays as input, a Discord message URL in arguments or input, or extracts embeds from the current or referenced message.unescape: Removes backslash escape characters (for example,\*to*or\_to_).trim: Trims leading and trailing whitespace from the input.lower: Converts text to lowercase.upper: Converts text to uppercase.head [count / -n count]: Returns the firstnlines of input (defaults to 10 lines).tail [count / -n count]: Returns the lastnlines of input (defaults to 10 lines).
What users should know:
- Tag names are normalized to lowercase.
- Tag names and bodies are validated before the tag is saved.
- You can usually edit, rename, delete, or transfer only your own tags unless you have elevated permissions.
- If a tag name is wrong, the bot may suggest similar tags.
Tag body input:
- Plain text creates a normal text tag.
- A code block creates a script tag.
- Starting the body with
vm2creates avm2script tag. - An attached image is stored as an image URL tag.
- An attached text file is stored as script content.
- An attached binary file (MIME
application/octet-streamor extension.bin) is stored as a binary tag. Binary tags cannot be executed directly, are hidden by default, and can be retrieved viaraw,fetch, ordump. - For
addandedit, admins can also give a local file path instead of inline body text. Absolute paths andfile://URLs must exist. Relative paths are only treated as files when the file exists. - If a saved tag uses a Discord-hosted attachment URL, the bot warns that the media can disappear if the original source message is deleted.
Eval commands run code through one of the bot's script environments.
Who uses this:
- people allowed to use eval features on the server
- admins when file-path input is used
Base usage:
%eval (script)
Runs JavaScript in the default eval environment. You can pass inline code, a code block, or an attached text file.
Modes:
langslists the available eval languagesvm2 (script)runs the script in the VM2 environmentc (script),cpp (script),py (script)run code in those languages when that support is available
What users should know:
- simple text is sent back as text
- objects and arrays are formatted for you
- empty output is rejected
- some modes may be unavailable on a given bot depending on how that bot is set up
Path input:
- Admins can give a local file path instead of inline script text.
- Absolute paths and
file://URLs must point to a real file. - Relative paths are only treated as file input when the file exists.
Debugging:
%eval debug (script)starts a debug session when user-facing inspector support is enabled on the bot.
Permission groups decide who can use higher-privilege bot features.
Who uses this:
- moderators and admins
- the bot owner for the highest-level changes
Base usage:
%perm (subcommand) ...
Main actions:
listshows groups and levelscheck (user)shows a user's permissionsadd (group_name) (user)adds a user to a groupremove (group_name) (user)removes a user from a groupremove_all (user)removes all groups from a useradd_group (group_name) (level)creates a groupremove_group (group_name)deletes a groupupdate_group (group_name) [new_name/unchanged] [new_level/unchanged]updates a groupaudit [subject] [revision_id] [--options]shows permission group and membership changes or a full revision diff for permission adminsaudit_clear [subject] [revision_id] [end_revision_id] [--options]clears permission audit history for the bot ownerrevert group (group_name) [revision_id]restores a group revision for permission adminsrevert membership (user_id/group_name) [revision_id]restores a membership revision for permission admins
Examples:
%perm audit moderatorslists revisions for themoderatorsgroup%perm audit moderators 12345shows the before/after diff for revision12345%perm audit user-id/moderatorslists revisions for one membership%perm audit --target permission_group --operation updatelists group updates%perm audit_clearclears all permission audit history (bot owner only)%perm audit_clear moderatorsclears history for one group or membership%perm audit_clear moderators 42 45clears an inclusive revision-ID range for one subject%perm audit_clear --from "last week" --to "yesterday"clears a date range
Permission audit options:
--useror--actorfilters by the administrator who made the change--operationor--opfilters byimport,create,update,delete, orrevert--targetfilters bypermission_grouporpermission_user--fromand--tofilter by a date or time range--pageand--limitcontrol pagination
Audit results show who changed a group or membership, when it happened, and which fields changed.
What users should know:
- The special
ownergroup cannot be edited or removed. - You cannot add yourself to a group above your own level.
- User arguments may be accepted as a mention, ID, username, or tag depending on the command.
Reminders let users schedule a DM for a future time.
Who uses this:
- everyone who can use reminder features on the server
Base usage:
%reminder (subcommand) ...
Main actions:
add (date) "message"creates a reminderlistshows your remindersremove (index)removes one reminderremove_allremoves all reminders
What users should know:
- The date is parsed in natural language, so inputs like
tomorrow 18:00orin 2 hoursusually work. - The message should be quoted when it contains spaces.
- Dates in the past are rejected.
removeuses a 1-based index from your list.- Reminders are sent to you by DM.
These are general-purpose helper commands for calculations and lookups.
Calculates GregTech recipe overclocking, voltage requirements, duration, and parallel throughput.
Syntax:
%oc [mode] <EU> <duration> [base_chance] [chance_bonus] [parallel] [amperage] [--options]
%oc ebf <EU> <duration> <recipe_heat> <coil_heat> [parallel] [amperage] [--options]
Note: <> denotes required arguments, [] denotes optional arguments. Use - to skip an optional positional argument.
Modes:
standard: Normal overclocking (2x speed, 4x power). Default mode.ebf: Electric Blast Furnace mode; calculates heat difference overclocking (each 900K above required heat gives an extra 0.95x duration multiplier).lcr: Large Chemical Reactor / perfect overclocking (4x speed, 4x power).ce: Emulates GTCE overclocking (2.8x speed, chance doubling per overclock).macerator: GTCE macerator mode with special chanced output handling.
Arguments:
[mode]: Machine processing mode (standard,ebf,lcr,ce,macerator).<EU>: Base EU/t cost of the recipe.<duration>: Base recipe duration in seconds or ticks (e.g.20,30s,1200t).[base_chance]: Base chanced output percentage (1-100).[chance_bonus]: Chance increase percentage per voltage tier.[parallel]: Number of items or batch operations run in parallel.[amperage]: Available amperage (default: 1).<recipe_heat>: Minimum heat capacity required by the recipe in Kelvin (EBF mode only).<coil_heat>: Total coil heat available in Kelvin (EBF mode only).
Options:
--tape: Applies maintenance/config hatch speed boost (0.9x duration).--subtick: Enables subtick overclocking (doubles parallel processing once duration reaches 1t).--extra: Displays extra high-tier voltages (UHV through MAX).--rf: Treats input EU as RF cost (divides by 4).--time <multiplier>: Multiplies recipe duration by a factor.--eu <multiplier>: Multiplies recipe EU/t cost by a factor.--voltage <tier>or--<tier>: Filters output table to only the specified voltage tier (e.g.--iv).--rates: Displays item production rates (recipes/s) in the table.--input <multiplier>,--output <multiplier>: Multiplies input consumption and output production rates.--count <number>: Sets recipe batch count.--tick: Displays durations in ticks only.--text: Outputs plain text table instead of Discord embed.--auto: Automatically selects the optimal voltage tier.--bulk: Calculates multiple recipes at once, one per line.
Examples:
%oc 120 20(overclock 120 EU/t for 20s)%oc 480 30s 100 10 4 2 --iv(480 EU/t, 30s, 4 parallel, 2A, filter to IV)%oc ebf 1920 60.3s 3600 5200 4 1(EBF mode at 1920 EU/t, 3600K recipe heat, 5200K coil heat)%oc lcr 2000 15s --rates --subtick(LCR perfect overclocking with rates and subtick)
convert <value> <from_unit> <to_unit...>: Converts between supported units (metric, imperial, digital, temp) and displays the conversion chain.cleanroomcalc/crc <width>x<height>x<length>: Calculates required filter casings, plascrete, and construction blocks for a GregTech cleanroom of given dimensions (e.g.5x5x5,9x7x9).stoik <reactants> -> <products>: Balances chemical equations and verifies reaction stoichiometry.
These are higher-privilege operational commands for moderators, admins, or the bot owner:
reload_commandsrestartstopadmin_eval
The bot can respond to message links with an embed of the message and/or the first attachment when present.
If the link's sender cannot read the original message, the bot does not preview it, which prevents leaking private channels.
Previews can also be generated for message links inside tag output.
Example:
The following syntax:
sed/regex/replace/flags
can be used to replace a pattern in a previous message with another pattern.
When replying, only the referenced message is considered. Otherwise, the bot searches backward for the first matching message.
Match groups can be referenced in the output using $1, $2, and so on.
Example:
The bot can react to certain words in a message with configured emojis.
It can also react to mismatched brackets if bracket reaction emojis are configured.
Example:
The CLI is the local command interface for the bot process. When it is enabled, the default prefix is ..
What it is for:
- quick local checks without Discord
- testing tag execution with a fake message
- testing script execution with fake author, guild, channel, or tag data
Available commands:
.help.version.uptime.clear.eval (expression).vm_eval [--debug/-d] (script).execute_tag (name) [args].reload_commands.restart.stop
General behavior:
.evalruns a plain JavaScript expression in the local REPL context.vm_evalruns a full script through the main script environment.execute_tagruns a stored tag, or an emulated tag if you provide emulation values.clearclears the terminal.stopstops the process
File path input:
.evaland.vm_evalcan take a local file path instead of inline code..execute_tagcan also take a file path for an emulated tag body.- Absolute paths and
file://URLs must exist. - Relative paths are only treated as files when the target exists. Otherwise the text is treated as normal input.
Common emulation options:
- Message emulation lets you supply author, guild, channel, attachment, and embed data.
- Tag emulation lets you supply tag body, type, language, owner, and tag arguments.
- If you provide tag emulation fields,
.execute_taguses the emulated tag instead of loading one from storage.
Examples:
.eval 1 + 2
.eval ./scripts/test.js
.vm_eval --authorId 123 --authorUsername Alex "return msg.author.username;"
.execute_tag my_tag some args
.execute_tag --tagBody ./tags/example.js --tagType script --tagLanguage jsThe WebSocket API is a command interface for external tools.
Request format:
{
"id": "optional-request-id",
"op": "command_name",
"data": {}
}Response format:
{
"id": "same-request-id",
"op": "command_name",
"status": "success",
"data": {}
}status is either success or error.
Available commands:
helplists available websocket commandsversionreturns the bot versionuptimereturns uptime informationevalruns JavaScript fromdata.codeand returns the outputvm_evalruns a script fromdata.code, with optional debug and emulated message dataexecute_tagruns a stored or emulated tagreload_commands,restart, andstopreturn a success flag and a short message
Validation and rules:
eval.codeandvm_eval.codemust be non-empty strings.execute_tag.nameis required unless you provide atagobject for emulation.- Group objects such as
msgandtagreject unknown properties. - Wrong types are rejected before the command runs.
- Raw websocket commands do not guess file paths. If you want to run code from a file, read the file yourself and send its contents.
Emulated objects:
msgcan provide fake message, author, guild, and channel datatagcan providename,aliasName,body,owner,args,type, andlanguage
Inspector events:
vm_evalwith"debug": truesends aninspector_readyevent before the final response.- The event payload contains the connection details for the debugger.
Helper scripts:
node ./scripts/websocket-client.jsstarts an interactive client for the websocket APInode ./scripts/websocket-client.js helpsends one command and prints the JSON replynode ./scripts/vscode-debug.js ./path/to/script.jsreads a local file and sends it tovm_evalwith debugging enabled
The interactive websocket client accepts either full JSON or a lightweight command syntax. Examples:
help
version
eval 1 + 2
vm_eval debug return "hello";
execute_tag my_tag some args
execute_tag my_tag arg1 arg2 authorId="123" guildName="Test Server"Mirrors the API of Leveret; see Neeve's API documentation.
Main differences:
util.dumpTags(true)returns a full dump instead of only namesmsg.replyexits the script right awayutil.fetchMessage(ch_id | null, msg_id)allows fetching a single messageutil.fetchMessagesaccepts message fetch optionsutil.findUserByIdallows fetching a user that is not necessarily in the same server as the bot- Script HTTP requests (
request/fetch) block localhost/private IP addresses andfile://URLs by default unlessallowLocalhostRequestsorallowFilesystemRequestsis enabled inconfig/config.json
Output rules:
- Strings are sent as-is.
- Objects and arrays are formatted automatically.
- Empty output becomes
Cannot send an empty message.
Example:
Inspector behavior:
- When inspector support is enabled without user-facing sessions, the bot exposes one console-driven inspector session at a time.
- When user-facing sessions are enabled, users can start isolated sessions with
%eval debug (script)and%t debug (tag_name) [tag_args]. - In user-inspector mode, the bot reply includes the DevTools and VS Code connection details.
This backend allows more advanced scripts than the pure JS API, including async flows and importing from the allowed module list.
Tags can use VM2 scripts with:
%t add (name) vm2 (script)
Result rules:
- If you use
reply, the script can send directly. - If you do not use
reply, return the final value.
- assert
- buffer
- crypto
- events
- path
- querystring
- url
- util
- zlib
tagis available when the script is running from a tagmsgexposes the emulated or real messagereply,request,fetchTag,dumpTags, andfindUsersare async helpers
%eval vm2 ```js
return "Amogus";
```
First-time setup:
- Install a supported Node.js version.
- Run
npm install. - Provide bot credentials with either
config/auth.json,config/auth.env, or theLEVERET_TOKENandLEVERET_OWNERenvironment variables. - Adjust
config/config.jsonandconfig/reactions.jsonif needed.
Starting the bot:
npm startWhat to expect:
- logs are printed to the console
- logs are also written to the configured log files in
logs/ - the CLI starts only if it is enabled in configuration
- the WebSocket server starts only if it is enabled in configuration
To export the current tag database from the bot, use:
%tag dump full [spaces]
This sends a tags.json file that can be fed to the importer.
Importer usage:
npm run importer -- --help
npm run importer -- --path "path-to-tags.json"
npm run importer -- --path "path-to-tags.db" --owners owner1 owner2
npm run importer -- --path "path-to-tags.json" --amend
npm run importer -- --fix
npm run importer -- --purge-oldWhat each mode does:
-p, --pathimports a tags file (.jsonor.db/ SQLite database)-j, --json-pathlegacy path alias for importing a JSON tag dump-o, --ownersfilters imported tags to only specified owner IDs (--owners owner1 owner2)--amendupdates existing tags instead of treating them as conflicts--fixchecks and repairs database issues--purge-oldremoves old imported tags
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣤⣤⣤⣤⣤⣤⣤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⡿⠛⠉⠙⠛⠛⠛⠛⠻⢿⣿⣷⣤⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⠋⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⠈⢻⣿⣿⡄⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣸⣿⡏⠀⠀⠀⣠⣶⣾⣿⣿⣿⠿⠿⠿⢿⣿⣿⣿⣄⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣿⣿⠁⠀⠀⢰⣿⣿⣯⠁⠀⠀⠀⠀⠀⠀⠀⠈⠙⢿⣷⡄⠀
⠀⠀⣀⣤⣴⣶⣶⣿⡟⠀⠀⠀⢸⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣷⠀
⠀⢰⣿⡟⠋⠉⣹⣿⡇⠀⠀⠀⠘⣿⣿⣿⣿⣷⣦⣤⣤⣤⣶⣶⣶⣶⣿⣿⠀
⠀⢸⣿⡇⠀⠀⣿⣿⡇⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿
⠀⣸⣿⡇⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠉⠻⠿⣿⣿⣿⣿⡿⠿⠿⠛⢻⣿⡇⠀⠀
⠀⣿⣿⠁⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣧⠀⠀
⠀⣿⣿⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⠀⠀
⠀⣿⣿⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⠀⠀
⠀⢿⣿⡆⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⡇⠀⠀
⠀⠸⣿⣧⡀⠀⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠃⠀⠀
⠀⠀⠛⢿⣿⣿⣿⣿⣇⠀⠀⠀⠀⣰⣿⣿⣷⣶⣶⣶⣶⠶⠀⠀⢠⣿⣿⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⣿⣿⡇⠀⣽⣿⡏⠁⠀⠀⠀⢸⣿⡇⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⣿⣿⡇⠀⢹⣿⡆⠀⠀⠀⠀⣸⣿⠇⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢿⣿⣦⣄⣀⣠⣴⣿⣿⠁⠀⠈⠻⣿⣿⣿⣿⡿⠏⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⠛⠻⠿⠿⠿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀





