diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 59c6ac0..c7cc36e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Contributing Guidelines Open Ticket Logo -[![discord](https://img.shields.io/badge/discord-support%20server-5865F2.svg?style=flat-square&logo=discord)](https://discord.com/invite/26vT9wt3n3) [![version](https://img.shields.io/badge/version-4.1.3-brightgreen.svg?style=flat-square)](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.1.3) [![Sponsor DJj123dj](https://img.shields.io/badge/sponsor-DJj123dj-ea4aaa?style=flat-square&logo=githubsponsors)](https://github.com/sponsors/DJj123dj) +[![discord](https://img.shields.io/badge/discord-support%20server-5865F2.svg?style=flat-square&logo=discord)](https://discord.com/invite/26vT9wt3n3) [![version](https://img.shields.io/badge/version-4.2.0-brightgreen.svg?style=flat-square)](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.2.0) [![Sponsor DJj123dj](https://img.shields.io/badge/sponsor-DJj123dj-ea4aaa?style=flat-square&logo=githubsponsors)](https://github.com/sponsors/DJj123dj) These are the Contributing Guidelines of Open Ticket!
Here you can find everything you need to know about contributing to Open Ticket.
diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 3f704b6..a381a61 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -19,18 +19,15 @@ This list will be updated on every release. | Version | Supported | Notes | |------------|-----------|---------------------------------------------------------------| -| 4.2.0 | ๐ŸŸฆ | In Development | -| 4.1.x | ๐ŸŸฆ | In Development | -| 4.1.3 | โœ… | | -| 4.1.2 | โœ… | | -| 4.1.1 | โœ… | Supported Until April 2026 (LTS) | +| 4.2.x | ๐ŸŸฆ | In Development | +| 4.2.1 | ๐ŸŸฆ | In Development | +| 4.2.0 | โœ… | | +| 4.1.3 | โœ… | (LTS) Long-Term-Support, Until September 2026 | +| 4.1.2 | ๐Ÿšง | | +| 4.1.1 | ๐Ÿšง | | | 4.1.0 | ๐Ÿšง | | -| 4.0.7 | ๐Ÿšง | | -| 4.0.6 | ๐Ÿšง | | -| 4.0.5 | ๐ŸŸง | Deprecated | -| 4.0.4 | ๐ŸŸง | Deprecated | -| < 4.0.4 | ๐ŸŸง | Deprecated, Transcripts v2.0, Documentation Only | -| < 4.0.0 | โŒ | | +| 4.0.7 | ๐ŸŸง | Deprecated | +| < 4.0.7 | โŒ | | ### ๐Ÿ•ท๏ธ Reporting Vulnerabilities You can report vulnerabilities, errors & bugs using one of the following methods: diff --git a/.gitignore b/.gitignore index 243efae..193d71e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,11 @@ otdebug.txt */*/*/.DS_Store **/.DS_Store -.docs/* -!.docs/createDocs.js -!.docs/mergeTranslations.js -!.docs/typedoc-config.json \ No newline at end of file +.backup/* +.tools/* +!.tools/createDocs.js +!.tools/mergeTranslations.js +!.tools/typedoc-config.json +!.tools/pterodactyl-eggs/ +!.tools/docker-compose.yml +!.tools/dockerfile \ No newline at end of file diff --git a/.docs/createDocs.js b/.tools/createDocs.js similarity index 100% rename from .docs/createDocs.js rename to .tools/createDocs.js diff --git a/docker-compose.yml b/.tools/docker-compose.yml similarity index 100% rename from docker-compose.yml rename to .tools/docker-compose.yml diff --git a/dockerfile b/.tools/dockerfile similarity index 83% rename from dockerfile rename to .tools/dockerfile index 4a0acad..514203f 100644 --- a/dockerfile +++ b/.tools/dockerfile @@ -1,6 +1,6 @@ # Docker File for Open Ticket v4 -# Use the official Node.js 20 image from Docker Hub -FROM node:20-alpine +# Use the official Node.js 22 image from Docker Hub +FROM node:22-alpine # Set pterodactyl working directory inside the container WORKDIR /home/container diff --git a/.docs/mergeTranslations.js b/.tools/mergeTranslations.js similarity index 100% rename from .docs/mergeTranslations.js rename to .tools/mergeTranslations.js diff --git a/.eggs/README.md b/.tools/pterodactyl-eggs/README.md similarity index 91% rename from .eggs/README.md rename to .tools/pterodactyl-eggs/README.md index 4585e89..c22f4a4 100644 --- a/.eggs/README.md +++ b/.tools/pterodactyl-eggs/README.md @@ -2,7 +2,7 @@ Open Ticket Logo [![discord](https://img.shields.io/badge/discord-support%20server-5865F2.svg?style=flat-square&logo=discord)](https://discord.com/invite/26vT9wt3n3) -[![version](https://img.shields.io/badge/version-4.1.3-brightgreen.svg?style=flat-square)](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.1.3) +[![version](https://img.shields.io/badge/version-4.2.0-brightgreen.svg?style=flat-square)](https://github.com/open-discord-bots/open-ticket/releases/tag/v4.2.0) [![Sponsor DJj123dj](https://img.shields.io/badge/sponsor-DJj123dj-ea4aaa?style=flat-square&logo=githubsponsors)](https://github.com/sponsors/DJj123dj) [![Open Ticket supports Pterodactyl Eggs!](https://img.shields.io/badge/pterodactyl-supported-10539F?style=flat-square&logo=pterodactyl)](.eggs/README.md) @@ -20,6 +20,9 @@ It's recommended to provide at least `1GB` of **Memory/RAM** and `5GB` of **disk [**`openticket-egg-main.json` (Recommended)**](openticket-egg-main.json) - This egg will use the `main` branch of Open Ticket. +[**`openticket-egg-v4.2.0.json`**](openticket-egg-v4.2.0.json) +- This egg will always use Open Ticket `v4.2.0`. Open Ticket updates will not have an effect on this egg. + [**`openticket-egg-v4.1.3.json`**](openticket-egg-v4.1.3.json) - This egg will always use Open Ticket `v4.1.3`. Open Ticket updates will not have an effect on this egg. diff --git a/.eggs/openticket-egg-dev.json b/.tools/pterodactyl-eggs/openticket-egg-dev.json similarity index 100% rename from .eggs/openticket-egg-dev.json rename to .tools/pterodactyl-eggs/openticket-egg-dev.json diff --git a/.eggs/openticket-egg-main.json b/.tools/pterodactyl-eggs/openticket-egg-main.json similarity index 100% rename from .eggs/openticket-egg-main.json rename to .tools/pterodactyl-eggs/openticket-egg-main.json diff --git a/.eggs/openticket-egg-v3.5.9.json b/.tools/pterodactyl-eggs/openticket-egg-v3.5.9.json similarity index 100% rename from .eggs/openticket-egg-v3.5.9.json rename to .tools/pterodactyl-eggs/openticket-egg-v3.5.9.json diff --git a/.eggs/openticket-egg-v4.0.7.json b/.tools/pterodactyl-eggs/openticket-egg-v4.0.7.json similarity index 100% rename from .eggs/openticket-egg-v4.0.7.json rename to .tools/pterodactyl-eggs/openticket-egg-v4.0.7.json diff --git a/.eggs/openticket-egg-v4.1.0.json b/.tools/pterodactyl-eggs/openticket-egg-v4.1.0.json similarity index 100% rename from .eggs/openticket-egg-v4.1.0.json rename to .tools/pterodactyl-eggs/openticket-egg-v4.1.0.json diff --git a/.eggs/openticket-egg-v4.1.1.json b/.tools/pterodactyl-eggs/openticket-egg-v4.1.1.json similarity index 100% rename from .eggs/openticket-egg-v4.1.1.json rename to .tools/pterodactyl-eggs/openticket-egg-v4.1.1.json diff --git a/.eggs/openticket-egg-v4.1.2.json b/.tools/pterodactyl-eggs/openticket-egg-v4.1.2.json similarity index 100% rename from .eggs/openticket-egg-v4.1.2.json rename to .tools/pterodactyl-eggs/openticket-egg-v4.1.2.json diff --git a/.eggs/openticket-egg-v4.1.3.json b/.tools/pterodactyl-eggs/openticket-egg-v4.1.3.json similarity index 100% rename from .eggs/openticket-egg-v4.1.3.json rename to .tools/pterodactyl-eggs/openticket-egg-v4.1.3.json diff --git a/.tools/pterodactyl-eggs/openticket-egg-v4.2.0.json b/.tools/pterodactyl-eggs/openticket-egg-v4.2.0.json new file mode 100644 index 0000000..88530ca --- /dev/null +++ b/.tools/pterodactyl-eggs/openticket-egg-v4.2.0.json @@ -0,0 +1,62 @@ +{ + "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", + "meta": { + "version": "PTDL_v2", + "update_url": null + }, + "exported_at": "2025-03-16T18:10:18+01:00", + "name": "Open Ticket (v4.2.0)", + "author": "support@dj-dj.be", + "description": "This is the official Pterodactyl egg for Open Ticket v4.2.0, the most advanced & customisable discord ticket bot that you will ever find! You can customise up to 300+ variables! This includes Html Transcripts, Advanced Plugins, Custom Embeds, Questions\/Modals, Stats & more!", + "features": null, + "docker_images": { + "ghcr.io\/parkervcp\/yolks:nodejs_20": "ghcr.io\/parkervcp\/yolks:nodejs_20" + }, + "file_denylist": [], + "startup": "if [[ ! -z ${NODE_PACKAGES} ]]; then npm install ${NODE_PACKAGES}; fi; if [[ ! -z ${UNNODE_PACKAGES} ]]; then npm uninstall ${UNNODE_PACKAGES}; fi; if [ -f \/home\/container\/package.json ]; then npm install; fi; node \"\/home\/container\/index.js\" ${NODE_FLAGS};", + "config": { + "files": "{}", + "startup": "{\r\n \"done\": \"STARTUP INFO:\"\r\n}", + "logs": "{}", + "stop": "^C" + }, + "scripts": { + "installation": { + "script": "#!\/bin\/bash\r\n# Open Ticket Installation Script (v4.2.0)\r\n# Inspired by: Node.js Installation Script\r\n# \u00a9 DJdj Development\r\n\r\necho -e \"[OT INSTALLER] installing dependencies. please wait...\"\r\napt update\r\napt install -y git curl\r\n\r\necho -e \"[OT INSTALLER] updating npm. please wait...\"\r\nnpm install npm@latest --location=global\r\n\r\nmkdir -p \/mnt\/server\r\ncd \/mnt\/server\r\n\r\nBRANCH=\"v4.2.0\"\r\n\r\nif [ \"$(ls -A \/mnt\/server)\" ]; then\r\n echo -e \"[OT INSTALLER] \/mnt\/server directory is not empty.\"\r\n if [ -d .git ]; then\r\n echo -e \"[OT INSTALLER] .git directory exists\"\r\n if [ -f .git\/config ]; then\r\n echo -e \"[OT INSTALLER] loading info from git config\"\r\n ORIGIN=$(git config --get remote.origin.url)\r\n else\r\n echo -e \"[OT INSTALLER] files found with no git config\"\r\n echo -e \"[OT INSTALLER] closing out without touching things to not break anything\"\r\n exit 10\r\n fi\r\n fi\r\n\r\n if [ \"${ORIGIN}\" == \"https:\/\/github.com\/open-discord-bots\/open-ticket.git\" ]; then\r\n echo \"pulling latest from github\"\r\n git pull\r\n fi\r\nelse\r\n echo -e \"[OT INSTALLER] \/mnt\/server is empty.\"\r\n echo -e \"[OT INSTALLER] cloning files into repo.\"\r\n if [ -z ${BRANCH} ]; then\r\n echo -e \"[OT INSTALLER] cloning default branch\"\r\n git clone https:\/\/github.com\/open-discord-bots\/open-ticket.git .\r\n else\r\n echo -e \"[OT INSTALLER] cloning ${BRANCH}'\"\r\n git clone --single-branch --branch ${BRANCH} https:\/\/github.com\/open-discord-bots\/open-ticket.git .\r\n fi\r\nfi\r\n\r\necho -e \"[OT INSTALLER] installing Open Ticket node.js packages.\"\r\nif [ -f \/mnt\/server\/package.json ]; then\r\n \/usr\/local\/bin\/npm install --omit=dev\r\nfi\r\n\r\necho -e \"[OT INSTALLER] installing custom node.js packages.\"\r\nif [[ ! -z ${NODE_PACKAGES} ]]; then\r\n \/usr\/local\/bin\/npm install ${NODE_PACKAGES}\r\nfi\r\n\r\necho -e \"[OT INSTALLER] install complete!\"\r\nexit 0", + "container": "node:latest", + "entrypoint": "bash" + } + }, + "variables": [ + { + "name": "Additional Npm Packages", + "description": "Specify additional npm packages used by Open Ticket plugins. Use spaces to separate.", + "env_variable": "NODE_PACKAGES", + "default_value": "", + "user_viewable": false, + "user_editable": true, + "rules": "string|nullable", + "field_type": "text" + }, + { + "name": "Startup Flags", + "description": "Start the bot with additional Open Ticket flags. Separate using spaces.\r\nA full reference list can be found in the documentation.", + "env_variable": "NODE_FLAGS", + "default_value": "", + "user_viewable": false, + "user_editable": true, + "rules": "string|nullable", + "field_type": "text" + }, + { + "name": "Uninstall Npm Packages", + "description": "A list of npm packages to uninstall. Separate by spaces.", + "env_variable": "UNNODE_PACKAGES", + "default_value": "", + "user_viewable": false, + "user_editable": true, + "rules": "string|nullable", + "field_type": "text" + } + ] +} \ No newline at end of file diff --git a/.docs/typedoc-config.json b/.tools/typedoc-config.json similarity index 100% rename from .docs/typedoc-config.json rename to .tools/typedoc-config.json diff --git a/README.md b/README.md index 48f8a8f..16c90c3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Related Projects:
Open Discord

Discord Invite Link -Open Ticket Version +Open Ticket Version Open Ticket Documentation Open Ticket License Open Ticket Stars @@ -17,7 +17,7 @@

Open Ticket is the most advanced and customizable Discord ticket bot available. With 350+ configurable settings, you have full control over every aspect of your ticket system! From HTML transcripts and Advanced Plugins to Claiming & Pinning, Questions & Modals, Detailed Statistics, and much more.

-The bot is fully translated into 36+ languages and has been battle-tested in large Discord servers.
+The bot is fully translated into 37+ languages and has been battle-tested in large Discord servers.
Need help or want to get involved? Feel free to join our Discord server.

@@ -25,7 +25,7 @@ Need help or want to get involved? Feel free to join our โค๏ธ Love Open Ticket? Sponsorships help fuel our HTML transcript servers and future features! โค๏ธ

--- -> **[-> Navigate to (โฑ๏ธ Quick Setup)](#๏ธ-quick-start-using-cli)** +> **[-> Navigate to (โฑ๏ธ Quick Setup)](#๏ธ-quick-start-using-interactive-cli-tool)** > **[-> Navigate to (๐Ÿ“š Documentation)](https://otdocs.dj-dj.be)** > **[-> Navigate to (๐Ÿ“ž Support Server)](https://discord.dj-dj.be)** @@ -37,7 +37,7 @@ Need help or want to get involved? Feel free to join our > "none" -> Command disabled + * >> "everyone" -> Allowed for everyone + * >> "admin" -> Global & ticket admins only + * >> "DISCORD_ROLE_ID" -> Custom role only + */ + "permissions":{ + "help":"everyone", + "panel":"admin", + "ticket":"none", + "close":"everyone", + "delete":"admin", + "reopen":"everyone", + "claim":"admin", + "unclaim":"admin", + "pin":"admin", + "unpin":"admin", + "move":"admin", + "rename":"admin", + "add":"admin", + "remove":"admin", + "blacklist":"admin", + "stats":"everyone", + "clear":"admin", + "autoclose":"admin", + "autodelete":"admin", + "transfer":"admin", + "topic":"admin", + "priority":"admin", + "transcripts":"admin" + } +} \ No newline at end of file diff --git a/config/options.json b/config/options.json deleted file mode 100644 index 981a1bc..0000000 --- a/config/options.json +++ /dev/null @@ -1,126 +0,0 @@ -[ - { - "id":"example-ticket", - "name":"Question", - "description":"Create this ticket if you have a question! (or leave empty)", - "type":"ticket", - - "button":{ - "emoji":"๐ŸŽซ (or leave empty)", - "label":"question (or leave empty)", - "color":"gray OR red OR green OR blue" - }, - - "ticketAdmins":["discord role id"], - "readonlyAdmins":["discord role id"], - "allowCreationByBlacklistedUsers":false, - "questions":["example-question-1","example-question-2"], - - "channel":{ - "prefix":"question-", - "suffix":"user-name OR user-id OR random-number OR random-hex OR counter-dynamic OR counter-fixed", - "category":"category id (or leave empty)", - "closedCategory":"category id (or leave empty)", - "backupCategory":"category id (or leave empty)", - "claimedCategory":[ - {"user":"user id","category":"category id"} - ], - "topic":"This is the topic of this ticket channel and is visible to everyone! (or leave empty)" - }, - - "dmMessage":{ - "enabled":false, - "text":"Thank you for creating a ticket in our server! (or leave empty)", - "embed":{ - "enabled":false, - "title":"Embed Title! (or leave empty)", - "description":"Description (or leave empty)", - "customColor":"#f8ab00 (or leave empty)", - - "image":"https://www.example.com/image.png (or leave empty)", - "thumbnail":"https://www.example.com/image.png (or leave empty)", - "fields":[ - {"name":"field name","value":"field value","inline":false} - ], - "timestamp":false - } - }, - "ticketMessage":{ - "enabled":true, - "text":"", - "embed":{ - "enabled":true, - "title":"Question Ticket", - "description":"Thank you for creating a 'Question' ticket in our server!\nOur support team will help you as soon as possible!", - "customColor":"#f8ab00 (or leave empty)", - - "image":"https://www.example.com/image.png (or leave empty)", - "thumbnail":"https://www.example.com/image.png (or leave empty)", - "fields":[ - {"name":"field name","value":"field value","inline":false} - ], - "timestamp":false - }, - "ping":{ - "@here":true, - "@everyone":false, - "custom":["discord role id"] - } - }, - "autoclose":{ - "enableInactiveHours":false, - "inactiveHours":24, - "enableUserLeave":false, - "disableOnClaim":false - }, - "autodelete":{ - "enableInactiveDays":false, - "inactiveDays":7, - "enableUserLeave":false, - "disableOnClaim":false - }, - "cooldown":{ - "enabled":false, - "cooldownMinutes":10 - }, - "limits":{ - "enabled":false, - "globalMaximum":20, - "userMaximum":3 - }, - "slowMode":{ - "enabled":false, - "slowModeSeconds":20 - } - }, - { - "id":"example-website", - "name":"Website", - "description":"Go to our website.", - "type":"website", - - "button":{ - "emoji":"๐Ÿ˜ƒ", - "label":"Visit our website" - }, - - "url":"https://www.dj-dj.be" - }, - { - "id":"example-role", - "name":"Update Ping", - "description":"Click here to get notified on updates!", - "type":"role", - - "button":{ - "emoji":"๐Ÿ“ข", - "label":"Update Ping", - "color":"gray OR red OR green OR blue" - }, - - "roles":["discord role id"], - "mode":"add&remove OR remove OR add", - "removeRolesOnAdd":["discord role id"], - "addOnMemberJoin":false - } -] \ No newline at end of file diff --git a/config/options.jsonc b/config/options.jsonc new file mode 100644 index 0000000..1948f6e --- /dev/null +++ b/config/options.jsonc @@ -0,0 +1,165 @@ +/* + * OPEN TICKET BUTTON OPTIONS + * ---------------------------------------------- + * Create customizable ticket, website, reaction-role or sub-panel button options. + * Up to 25 options can be added to each panel in (config/panels.jsonc) + * There are 4 types of options available: ticket, website, role, sub-panel + * + * TIP: Create new options by copying everything between and including the {...} brackets of an option. Paste it after the last option and make sure that they are seperated by a comma. + */ +[ + { + /* A ticket option creates a button to open a ticket. */ + "id":"example-ticket", + "name":"Question", + "description":"Want to tell us something? Create this ticket for general questions for our support team.", //Leave empty to disable + "type":"ticket", + + "button":{ + /* Configure the button style of this option. At least one of "emoji" or "label" must be provided. */ + "emoji":"๐ŸŽซ", + "label":"Question", + "color":"gray" //Choices: gray, red, green, blue + }, + + /* Add up to 5 modal questions IDs from (config/questions.jsonc). */ + "questions":["example-question-1","example-question-2"], + + "ticketAdmins":["DISCORD_ROLE_ID"], + "readonlyAdmins":["DISCORD_ROLE_ID"], + "allowCreationByBlacklistedUsers":false, + + "channel":{ + /* Configure the name, topic and category of the ticket option. */ + "prefix":"question-", + "suffix":"user-name", //Choices: user-name, user-id, random-number, random-hex, counter-dynamic, counter-fixed + "category":"DISCORD_CATEGORY_ID", //Leave empty to disable + "topic":"The ticket topic shown in the channel." //Leave empty to disable + }, + + "dmMessage":{ + /* Send a customisable message in DM when creating a ticket. */ + "enabled":false, + "text":"", //Leave empty to disable + "embed":{ + "enabled":false, + "title":"Question Ticket", //Leave empty to disable + "description":"Thank you for creating a ticket in our server. We will try to help you as soon as possible.", //Leave empty to disable + "customColor":"#f8ab00", //Leave empty to use default color + + "image":"", //Image URL. Leave empty to disable + "thumbnail":"", //Image URL. Leave empty to disable + /* Embed fields. Set to empty list [] to disable. */ + "fields":[ + {"name":"Field name","value":"Field value","inline":false} + ], + "timestamp":false + } + }, + "ticketMessage":{ + /* Send a customisable message in the ticket with close, claim, delete, ... buttons. */ + "enabled":true, + "text":"", //Leave empty to disable + "embed":{ + "enabled":true, + "title":"Question Ticket", //Leave empty to disable + "description":"Thank you for creating a ticket in our server.\nOur support team will assist you as soon as possible. (leave empty to disable)", //Leave empty to disable + "customColor":"#f8ab00 (leave empty to disable)", //Leave empty to use default color + + "image":"https://www.example.com/image.png (leave empty to disable)", //Image URL. Leave empty to disable + "thumbnail":"https://www.example.com/image.png (leave empty to disable)", //Image URL. Leave empty to disable + /* Embed fields. Set to empty list [] to disable. */ + "fields":[ + {"name":"Field name","value":"Field value","inline":false} + ], + "timestamp":false + }, + "ping":{ + /* Customise the user & role mentions of this ticket message. */ + "@here":true, + "@everyone":false, + "custom":["DISCORD_ROLE_ID"] + } + }, + "autoclose":{ + /* Autoclose this ticket after a period of inactivity or when the creator leaves the server. */ + "enableInactiveHours":false, + "inactiveHours":24, + "enableUserLeave":false, + "disableOnClaim":false + }, + "autodelete":{ + /* Autodelete this ticket after a period of inactivity or when the creator leaves the server. */ + "enableInactiveDays":false, + "inactiveDays":7, + "enableUserLeave":false, + "disableOnClaim":false + }, + "cooldown":{ + /* Users must wait a certain period before being able to create another ticket of this type. */ + "enabled":false, + "cooldownMinutes":10 + }, + "limits":{ + /* Set the maximum amount of simultaneous tickets of this option. */ + "enabled":false, + "globalMaximum":20, + "userMaximum":3 + }, + "slowMode":{ + /* Enable slow-mode in the ticket channel. */ + "enabled":false, + "slowModeSeconds":20 + } + }, + { + /* A website option creates a button with a URL to an external website. */ + "id":"example-website", + "name":"Website", + "description":"Take a look at our amazing website.", //Leave empty to disable + "type":"website", + + "button":{ + /* Configure the button style of this option. At least one of "emoji" or "label" must be provided. */ + "emoji":"๐Ÿ˜ƒ", + "label":"Visit Website" + }, + + "url":"https://www.dj-dj.be" + }, + { + /* A reaction-role option creates a button for members to choose roles. */ + "id":"example-role", + "name":"Update Ping", + "description":"Receive notifications about updates in our server.", //Leave empty to disable + "type":"role", + + "button":{ + /* Configure the button style of this option. At least one of "emoji" or "label" must be provided. */ + "emoji":"๐Ÿ“ข", + "label":"Update Ping", + "color":"gray" //Choices: gray, red, green, blue + }, + + "roles":["DISCORD_ROLE_ID"], + "mode":"add&remove OR remove OR add", //What to do with the roles. Choices: add&remove, add, remove + "removeRolesOnAdd":["DISCORD_ROLE_ID"], //Remove these old roles when new roles are added. + "addOnMemberJoin":false //Add these roles automatically when joining the server. + }, + { + /* A sub-panel option creates a button which sends another panel for additional options. */ + "id":"example-sub-panel", + "name":"Example Sub-Panel", + "description":"This is an example of how to implement a sub-panel in Open Ticket.", //Leave empty to disable + "type":"sub-panel", + + "button":{ + /* Configure the button style of this option. At least one of "emoji" or "label" must be provided. */ + "emoji":"๐Ÿ“‹", + "label":"Sub-Panel Example", + "color":"gray" //Choices: gray, red, green, blue + }, + + "subPanelId":"example-panel" //Choose a panel ID from (config/panels.jsonc) + } +] \ No newline at end of file diff --git a/config/panels.json b/config/panels.json deleted file mode 100644 index acc1093..0000000 --- a/config/panels.json +++ /dev/null @@ -1,39 +0,0 @@ -[ - { - "id":"example-embed", - "name":"Example Embed", - "dropdown":false, - "options":["example-ticket","example-website","example-role"], - - "text":"", - "embed":{ - "enabled":true, - "title":"Tickets:", - "description":"Create a ticket by clicking one of the buttons below!", - - "customColor":"#f8ab00 (or leave empty)", - "url":"https://openticket.dj-dj.be (or leave empty)", - - "image":"https://www.example.com/image.png (or leave empty)", - "thumbnail":"https://www.example.com/image.png (or leave empty)", - - "footer":"Open Ticket v4.1.3 (or leave empty)", - "fields":[ - {"name":"field name","value":"field value","inline":false} - ], - "timestamp":false - }, - "settings":{ - "dropdownPlaceholder":"Create a ticket!", - - "enableMaxTicketsWarningInText":false, - "enableMaxTicketsWarningInEmbed":true, - - "describeOptionsLayout":"simple OR normal OR detailed", - "describeOptionsCustomTitle":"", - "describeOptionsInText":false, - "describeOptionsInEmbedFields":true, - "describeOptionsInEmbedDescription":false - } - } -] \ No newline at end of file diff --git a/config/panels.jsonc b/config/panels.jsonc new file mode 100644 index 0000000..cf5bca8 --- /dev/null +++ b/config/panels.jsonc @@ -0,0 +1,57 @@ +/* + * OPEN TICKET PANELS + * ---------------------------------------------- + * Create customizable panel messages with buttons or a dropdown. + * Add up to 25 options to this panel from (config/options.jsonc) + * Panels can be customised with text, images, colors and more. + * + * Spawn the panel in Discord using: /panel + * + * TIP: Create new panels by copying everything between and including the {...} brackets of an panel. Paste it after the last panel and make sure that they are seperated by a comma. + */ +[ + { + /* A panel is creates a message with up to 25 options as buttons or dropdown. */ + "id":"example-panel", + "name":"Example Panel", + "dropdown":false, + + /* Add up to 5 option IDs from (config/options.jsonc). */ + "options":["example-ticket","example-website","example-role"], + + "text":"", //Leave empty to disable + "embed":{ + "enabled":true, + "title":"Tickets:", //Leave empty to disable + "description":"Create a ticket by selecting one of the options below. Once selected, a private ticket will be created for you and our support team will be able to assist you directly.", //Leave empty to disable + + "customColor":"#f8ab00", //Leave empty to use default color + "url":"", //URL. Leave empty to disable + + "image":"", //Image URL. Leave empty to disable + "thumbnail":"", //Image URL. Leave empty to disable + + "footer":"Open Ticket v4.2.0", //Leave empty to disable + /* Embed fields. Set to empty list [] to disable. */ + "fields":[ + {"name":"Field name","value":"Field value","inline":false} + ], + "timestamp":false + }, + "settings":{ + "dropdownPlaceholder":"Create a ticket...", //Leave empty to use default. + "maximumButtonsPerRow":5, + + /* Display the maximum amount of tickets per user. */ + "enableMaxTicketsWarningInText":false, + "enableMaxTicketsWarningInEmbed":true, + + /* Automatically generate option descriptions from (config/options.jsonc). */ + "describeOptionsLayout":"normal", //Choices: simple, normal, detailed + "describeOptionsCustomTitle":"", + "describeOptionsInText":false, + "describeOptionsInEmbedFields":true, + "describeOptionsInEmbedDescription":false + } + } +] \ No newline at end of file diff --git a/config/questions.json b/config/questions.json deleted file mode 100644 index 0d2556e..0000000 --- a/config/questions.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "id":"example-question-1", - "name":"Example Question 1", - "type":"short", - - "required":true, - "placeholder":"Insert your short answer here!", - "length":{ - "enabled":false, - "min":0, - "max":1000 - } - }, - { - "id":"example-question-2", - "name":"Example Question 2", - "type":"paragraph", - - "required":false, - "placeholder":"Insert your long answer here!", - "length":{ - "enabled":false, - "min":0, - "max":1000 - } - } -] \ No newline at end of file diff --git a/config/questions.jsonc b/config/questions.jsonc new file mode 100644 index 0000000..8f9062e --- /dev/null +++ b/config/questions.jsonc @@ -0,0 +1,102 @@ +/* + * OPEN TICKET MODAL QUESTIONS + * ---------------------------------------------- + * Create customizable modal questions that will be shown before creating a ticket. + * Each ticket option (config/options.jsonc) can contain a maximum of 5 questions. + * There are 6 types of questions available: short, paragraph, dropdown, radio-select, checkbox-select, text-display + * + * TIP: Create new questions by copying everything between and including the {...} brackets of a question. Paste it after the last question and make sure that they are seperated by a comma. + */ +[ + { + /* A short text input modal question. */ + "id":"example-question-1", + "name":"Example Question 1", + "description":"", //Leave empty to disable + "type":"short", + "required":true, + + "placeholder":"Insert answer...", + "length":{ + /* Configure length limits for the answer. */ + "enabled":false, + "min":0, + "max":1000 + } + }, + { + /* A paragraph text input modal question. */ + "id":"example-question-2", + "name":"Example Question 2", + "description":"", //Leave empty to disable + "type":"paragraph", + "required":false, + + "placeholder":"Insert answer...", + "length":{ + /* Configure length limits for the answer. */ + "enabled":false, + "min":0, + "max":1000 + } + }, + { + /* A dropdown menu input modal question with up to 25 choices. "emoji" & "description" fields are optional. */ + "id":"example-question-3", + "name":"Example Question 3", + "description":"This is a dropdown question.", //Leave empty to disable + "type":"dropdown", + "required":false, + + "placeholder":"Choose your answer...", + "choices":[ + {"title":"Choice A","description":"Apple","emoji":"๐ŸŽ"}, + {"title":"Choice B","description":"Banana","emoji":"๐ŸŒ"}, + {"title":"Choice C","description":"Orange","emoji":"๐ŸŠ"}, + {"title":"Choice D","description":"Kiwi","emoji":"๐Ÿฅ"} + ] + }, + { + /* A radio select input modal question with up to 10 choices. */ + "id":"example-question-4", + "name":"Example Question 4", + "description":"This is a radio select question.", //Leave empty to disable + "type":"radio-select", + "required":true, + + "choices":[ + {"title":"Choice A","description":"Up","selectedByDefault":false}, + {"title":"Choice B","description":"Down","selectedByDefault":false}, + {"title":"Choice C","description":"Left","selectedByDefault":false}, + {"title":"Choice D","description":"Right","selectedByDefault":false} + ] + }, + { + /* A checkbox select input modal question with up to 10 choices. */ + "id":"example-question-5", + "name":"Example Question 5", + "description":"This is a checkbox select question.", //Leave empty to disable + "type":"checkbox-select", + "required":true, + + "limits":{ + /* Configure checkbox amount limits for the answer. */ + "enabled":false, + "min":0, + "max":10 + }, + "choices":[ + {"title":"Choice A","description":"Happiness","selectedByDefault":false}, + {"title":"Choice B","description":"Anger","selectedByDefault":false}, + {"title":"Choice C","description":"Sadness","selectedByDefault":false}, + {"title":"Choice D","description":"Fear","selectedByDefault":false} + ] + }, + { + /* Show text in a modal to provide extra details or explain questions. */ + "id":"example-text-display", + "type":"text-display", + + "textContents":"This is a text display. It isn't a question, but allows you to display additional details." + } +] \ No newline at end of file diff --git a/config/transcripts.json b/config/transcripts.json deleted file mode 100644 index 37651ae..0000000 --- a/config/transcripts.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "general":{ - "enabled":false, - - "enableChannel":false, - "enableCreatorDM":false, - "enableParticipantDM":false, - "enableActiveAdminDM":false, - "enableEveryAdminDM":false, - - "channel":"transcript channel id (or leave empty)", - "mode":"html OR text" - }, - "embedSettings":{ - "customColor":"#f8ab00 (or leave empty)", - "listAllParticipants":false, - "includeTicketStats":false - }, - "textTranscriptStyle":{ - "layout":"simple OR normal OR detailed", - "includeStats":true, - "includeIds":false, - "includeEmbeds":true, - "includeFiles":true, - "includeBotMessages":true, - - "fileMode":"custom OR channel-name OR channel-id OR user-name OR user-id", - "customFileName":"this-is-a-transcript (or leave empty)" - }, - "htmlTranscriptStyle":{ - "background":{ - "enableCustomBackground":false, - "backgroundColor":"#f8ba00 (or leave empty)", - "backgroundImage":"https://www.example.com/image.png (or leave empty)" - }, - "header":{ - "enableCustomHeader":false, - "backgroundColor":"#202225", - "decoColor":"#f8ba00", - "textColor":"#ffffff" - }, - "stats":{ - "enableCustomStats":false, - "backgroundColor":"#202225", - "keyTextColor":"#737373", - "valueTextColor":"#ffffff", - "hideBackgroundColor":"#40444a", - "hideTextColor":"#ffffff" - }, - "favicon":{ - "enableCustomFavicon":false, - "imageUrl":"https://t.dj-dj.be/favicon.png" - } - } -} \ No newline at end of file diff --git a/config/transcripts.jsonc b/config/transcripts.jsonc new file mode 100644 index 0000000..2a441c5 --- /dev/null +++ b/config/transcripts.jsonc @@ -0,0 +1,70 @@ +/* + * OPEN TICKET TRANSCRIPTS + * ---------------------------------------------- + * Enable transcript creation when tickets are deleted. There are 2 available transcript types: HTML & Text + * + * HTML Transcripts (recommended): + * Generate transcripts as HTML files to view in the browser. No server or domain required. HTML Transcripts use an external service to process and host the transcripts. + * + * Text Transcripts: + * Generate transcripts as simple .txt files with limited details. Processing happens fully local. + */ +{ + "general":{ + "enabled":false, + + /* Choose which users and channel get the generated transcript. */ + "enableChannel":false, + "enableCreatorDM":false, + "enableParticipantDM":false, + "enableActiveAdminDM":false, + "enableEveryAdminDM":false, + + "channel":"DISCORD_CHANNEL_ID", //Where to send transcripts. Leave empty when disabled. + "mode":"html" //The type of transcript to use. Choices: html, text + }, + "embedSettings":{ + /* Customise the embed which contains the generated transcript file or URL. */ + "customColor":"#f8ab00", //Leave empty to use default color + "listAllParticipants":false, + "includeTicketStats":false + }, + "textTranscriptStyle":{ + /* Customise layout of the text transcripts. */ + "layout":"normal", //Choices: simple, normal, detailed + "includeStats":true, + "includeIds":false, + "includeEmbeds":true, + "includeFiles":true, + "includeBotMessages":true, + + "fileMode":"custom", //How to name the transcript file? Choices: custom, channel-name, channel-id, user-name, user-id + "customFileName":"transcript" //Custom filename without extension + }, + "htmlTranscriptStyle":{ + /* Customise layout of the HTML transcripts. */ + "background":{ + "enableCustomBackground":false, + "backgroundColor":"#f8ba00", //Leave empty to use Open Ticket color (#f8ba00) + "backgroundImage":"https://www.example.com/image.png" //Image URL to fill entire background. Leave empty to disable + }, + "header":{ + "enableCustomHeader":false, + "backgroundColor":"#202225", + "decoColor":"#f8ba00", + "textColor":"#ffffff" + }, + "stats":{ + "enableCustomStats":false, + "backgroundColor":"#202225", + "keyTextColor":"#737373", + "valueTextColor":"#ffffff", + "hideBackgroundColor":"#40444a", + "hideTextColor":"#ffffff" + }, + "favicon":{ + "enableCustomFavicon":false, + "imageUrl":"https://t.dj-dj.be/favicon.png" + } + } +} \ No newline at end of file diff --git a/database/states.json b/database/states.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/database/states.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/index.js b/index.js index 6cead33..2935154 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,6 @@ const flags = [ //PTERODACTYL PANEL //add startup flags here (e.g. "--no-compile") when running via the panel ] -process.argv.push(...flags) /////////////// STARTUP FLAGS /////////////// /* @@ -14,170 +13,19 @@ process.argv.push(...flags) โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• - v4.1.3 - Made by DJj123dj & Contributors + v4.2.0 - Made by DJj123dj & Contributors Discord: https://discord.dj-dj.be Docs: https://otdocs.dj-dj.be Support Us: https://github.com/sponsors/DJj123dj/ */ + /////////////////////////////////////////// ////////// COMPILATION + STARTUP ////////// /////////////////////////////////////////// -const fs = require("fs") -const ts = require("typescript") -const {createHash,Hash} = require("crypto") -const nodepath = require('path') -const ansis = require("ansis") -/** ## What is this? - * This is a function which compares `./src/` with a hash stored in `./dist/hash.txt`. - * The hash is based on the modified date & file metadata of all files in `./src/`. - * - * If the hash is different, the bot will automatically re-compile. - * This will help you save CPU resources because the bot shouldn't re-compile when nothing has been changed :) - * - * @param {string} dir - * @param {Hash|null} upperHash - */ -function computeSourceHash(dir,upperHash){ - const hash = upperHash ? upperHash : createHash("sha256") - const info = fs.readdirSync(dir,{withFileTypes:true}) - - for (const file of info) { - const fullPath = nodepath.join(dir,file.name) - if (file.isFile() && [".js",".ts",".jsx",".tsx"].some((ext) => file.name.endsWith(ext))){ - const statInfo = fs.statSync(fullPath) - //compute hash using file metadata - const fileInfo = `${fullPath}:${statInfo.size}:${statInfo.mtimeMs}` - hash.update(fileInfo) - - }else if (file.isDirectory()){ - //recursively compute all folders - computeSourceHash(fullPath,hash) - } - } - //return when not being called recursively - if (!upperHash) { - return hash.digest("hex") - } -} -function requiresCompilation(){ - //check hashes when not using "--compile-only" flag - if (process.argv.includes("--compile-only")) return true - - console.log("OT: Comparing prebuilds with source...") - const sourceHash = computeSourceHash("./src/") - const pluginHash = computeSourceHash("./plugins/") - const hash = sourceHash+":"+pluginHash - - if (fs.existsSync("./dist/hash.txt")){ - const distHash = fs.readFileSync("./dist/hash.txt").toString() - if (distHash === hash) return false - else return true - }else return true -} -function saveNewCompilationHash(){ - const sourceHash = computeSourceHash("./src/") - const pluginHash = computeSourceHash("./plugins/") - const hash = sourceHash+":"+pluginHash - fs.writeFileSync("./dist/hash.txt",hash) -} - -if (!process.argv.includes("--no-compile")){ - const requiredDependencies = new Set() - if (fs.existsSync("./plugins")){ - console.log("OT: Reading plugin.json files...") - for (const pluginDir of fs.readdirSync("./plugins")){ - if (pluginDir === ".DS_Store") continue - const pluginPath = nodepath.join("./plugins", pluginDir) - if (!fs.statSync(pluginPath).isDirectory()) continue - - const pluginJsonPath = nodepath.join(pluginPath, "plugin.json") - if (fs.existsSync(pluginJsonPath)){ - try{ - const pluginData = JSON.parse(fs.readFileSync(pluginJsonPath).toString()) - if (pluginData.npmDependencies && Array.isArray(pluginData.npmDependencies)){ - pluginData.npmDependencies.forEach((dep) => { - if (typeof dep === "string" && dep.trim()){ - requiredDependencies.add(dep.trim()) - } - }) - } - }catch(err){ - // skip invalid plugin.json files, will be caught later - } - } - } - - if (requiredDependencies.size > 0){ - console.log("OT: Checking plugin npm dependencies...") - /**@type {string[]} */ - const missingDeps = [] - for (const dep of requiredDependencies){ - try{ - require.resolve(dep) - }catch(err){ - missingDeps.push(dep) - } - } - - if (missingDeps.length > 0){ - console.log(ansis.red("OT: โŒ Fatal Error --> Missing npm dependencies required by plugins:\n\n")+ansis.cyan(missingDeps.map((dep) => " - "+dep).join("\n")+"\n")) - console.log("OT: Please install missing dependencies using the following command:\n> "+ansis.bold.green("npm install " + missingDeps.join(" "))+"\n") - process.exit(1) - } - } - } - - if (requiresCompilation()){ - console.log("OT: Compilation Required...") - - //REMOVE EXISTING BUILDS - console.log("OT: Removing Prebuilds...") - fs.rmSync("./dist",{recursive:true,force:true}) - - //COMPILE TYPESCRIPT - console.log("OT: Compiling Typescript...") - const configPath = nodepath.resolve('./tsconfig.json') - const configFile = ts.readConfigFile(configPath,ts.sys.readFile) - - //check for tsconfig errors - if (configFile.error){ - const message = ts.formatDiagnosticsWithColorAndContext([configFile.error],ts.createCompilerHost({})) - console.error(message) - process.exit(1) - } - - //parse tsconfig file - const parsedConfig = ts.parseJsonConfigFileContent(configFile.config,ts.sys,nodepath.dirname(configPath)) - - //create program/compiler - const program = ts.createProgram({ - rootNames:parsedConfig.fileNames, - options:parsedConfig.options - }) - - //emit all compiled files - const emitResult = program.emit() - - //print emit errors/warnings (type errors) - const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics) - const formattedDiagnostics = ts.formatDiagnosticsWithColorAndContext(allDiagnostics, ts.createCompilerHost(parsedConfig.options)) - console.log(formattedDiagnostics) - - if (emitResult.emitSkipped || allDiagnostics.find((d) => d.category == ts.DiagnosticCategory.Error || d.category == ts.DiagnosticCategory.Warning)){ - console.log("OT: Compilation Failed!") - process.exit(1) - } - }else console.log("OT: No Compilation Required...") - - //save new compilation hash - saveNewCompilationHash() -} - -//START BOT -console.log("OT: Compilation Succeeded!") -if (process.argv.includes("--compile-only")) process.exit(0) //exit when only compile is required! -console.log("OT: Starting Bot!") -require("./dist/src/index.js") \ No newline at end of file +import { frameworkStartup } from "@open-discord-bots/framework" +frameworkStartup(flags,"openticket",async () => { + await import("./dist/src/index.js") +}) \ No newline at end of file diff --git a/languages/arabic.json b/languages/arabic.json index 694bf33..40c7cff 100644 --- a/languages/arabic.json +++ b/languages/arabic.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["palestinian"], "lastedited":"16/02/2026", "language":"Arabic", diff --git a/languages/bengali.json b/languages/bengali.json index fe5894f..317f3d5 100644 --- a/languages/bengali.json +++ b/languages/bengali.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta"], "lastedited":"16/02/2026", "language":"Bengali", diff --git a/languages/catalan.json b/languages/catalan.json index 1336d34..fed41c0 100644 --- a/languages/catalan.json +++ b/languages/catalan.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["guillee3"], "lastedited":"16/02/2026", "language":"Catalan", diff --git a/languages/custom.json b/languages/custom.json index 7b9888d..f22936f 100644 --- a/languages/custom.json +++ b/languages/custom.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["DJj123dj"], "lastedited":"16/02/2026", "language":"Custom", @@ -13,94 +13,94 @@ "typeWarning":"[WARNING]", "typeInfo":"[INFO]", "headerConfigChecker":"CONFIG CHECKER", - "headerDescription":"check for errors in your config files!", - "footerError":"the bot won't start until all {0}'s are fixed!", - "footerWarning":"it's recommended to fix all {0}'s before starting!", + "headerDescription":"Validating config files...", + "footerError":"The bot will not start until all {0}'s are resolved.", + "footerWarning":"The bot may behave unexpectedly until all {0}'s are resolved.", "footerSupport":"SUPPORT: {0} - DOCS: {1}", - "compactInformation":"use {0} for more information!", + "compactInformation":"Use {0} for a detailed config report.", "dataPath":"path", "dataDocs":"docs", "dataMessages":"message" }, "messages":{ - "stringTooShort":"This string can't be shorter than {0} characters!", - "stringTooLong":"This string can't be longer than {0} characters!", - "stringLengthInvalid":"This string needs to be {0} characters long!", - "stringStartsWith":"This string needs to start with {0}!", - "stringEndsWith":"This string needs to end with {0}!", - "stringContains":"This string needs to contain {0}!", - "stringChoices":"This string can only be one of the following values: {0}!", - "stringRegex":"This string is invalid!", - "stringInvertedContains":"This string is not allowed to contain {0}!", - "stringLowercase":"This string must be written in lowercase only!", - "stringUppercase":"This string must be written in uppercase only!", - "stringSpecialCharacters":"This string is not allowed to contain any special characters! (a-z, 0-9 & space only)", - "stringNoSpaces":"This string is not allowed to contain spaces!", - "stringCapitalWord":"It's recommended that each word in this string starts with a capital letter!", - "stringCapitalSentence":"It looks like some sentences in this string don't start with a capital letter!", - "stringPunctuation":"It looks like the sentence in this string doesn't end with a punctuation mark!", + "stringTooShort":"Text must be at least {0} characters long", + "stringTooLong":"Text must be no longer than {0} characters", + "stringLengthInvalid":"Text must be exactly {0} characters long", + "stringStartsWith":"Text must start with {0}", + "stringEndsWith":"Text must end with {0}", + "stringContains":"Text must contain {0}", + "stringChoices":"Text must be one of the following: {0}", + "stringRegex":"Text does not match the required format", + "stringInvertedContains":"Text must not contain {0}", + "stringLowercase":"Text must be entirely lowercase", + "stringUppercase":"Text must be entirely uppercase", + "stringSpecialCharacters":"Text must only contain letters (aโ€“z), numbers (0โ€“9), and spaces", + "stringNoSpaces":"Text must not contain spaces", + "stringCapitalWord":"Each word in this value should start with a capital letter", + "stringCapitalSentence":"One or more sentences in this value do not start with a capital letter", + "stringPunctuation":"The sentence in this value does not end with a punctuation mark", - "numberTooShort":"This number can't be shorter than {0} characters!", - "numberTooLong":"This number can't be longer than {0} characters!", - "numberLengthInvalid":"This number needs to be {0} characters long!", - "numberTooSmall":"This number needs to be at least {0}!", - "numberTooLarge":"This number needs to be at most {0}!", - "numberNotEqual":"This number needs to be {0}!", - "numberStep":"This number needs to be a multiple of {0}!", - "numberStepOffset":"This number needs to be a multiple of {0} starting with {1}!", - "numberStartsWith":"This number needs to start with {0}!", - "numberEndsWith":"This number needs to end with {0}!", - "numberContains":"This number needs to contain {0}!", - "numberChoices":"This number can only be one of the following values: {0}!", - "numberFloat":"This number can't be a decimal!", - "numberNegative":"This number can't be negative!", - "numberPositive":"This number can't be positive!", - "numberZero":"This number can't be zero!", - "numberNan":"This number can't be NaN (Not A Number)!", - "numberInvertedContains":"This number is not allowed to contain {0}!", + "numberTooShort":"Number must be at least {0} digits long", + "numberTooLong":"Number must be no longer than {0} digits", + "numberLengthInvalid":"Number must be exactly {0} digits long", + "numberTooSmall":"Number must be at least {0}", + "numberTooLarge":"Number must be at most {0}", + "numberNotEqual":"Number must be exactly {0}", + "numberStep":"Number must be a multiple of {0}", + "numberStepOffset":"Number must be a multiple of {0}, starting from {1}", + "numberStartsWith":"Number must start with {0}", + "numberEndsWith":"Number must end with {0}", + "numberContains":"Number must contain {0}", + "numberChoices":"Number must be one of the following: {0}", + "numberFloat":"Number must be a whole number", + "numberNegative":"Number must be a positive number", + "numberPositive":"Number must be a negative number", + "numberZero":"Number must not be zero", + "numberNan":"Number must be a valid number", + "numberInvertedContains":"Number must not contain {0}", - "booleanTrue":"This boolean can't be true!", - "booleanFalse":"This boolean can't be false!", + "booleanTrue":"Boolean must be false", + "booleanFalse":"Boolean must be true", + + "arrayEmptyDisabled":"List must not be empty", + "arrayEmptyRequired":"List must be empty", + "arrayTooShort":"List must have at least {0} items", + "arrayTooLong":"List must have at most {0} items", + "arrayLengthInvalid":"List must have exactly {0} items", + "arrayInvalidTypes":"List may only contain the following types: {0}", + "arrayDouble":"List must not contain duplicate values", + + "discordInvalidId":"Invalid Discord {0} ID", + "discordInvalidIdOptions":"Invalid Discord {0} ID. Alternatively, use one of the following: {1}", + "discordInvalidToken":"Invalid Discord token", + "colorInvalid":"Invalid hex color", + "emojiTooShort":"Value must contain at least {0} emoji", + "emojiTooLong":"Value must contain at most {0} emoji", + "emojiCustom":"Custom Discord emojis are not allowed here", + "emojiInvalid":"Invalid emoji", + "urlInvalid":"Invalid URL", + "urlInvalidHttp":"URL must use the https:// protocol", + "urlInvalidProtocol":"URL must use the http:// or https:// protocol", + "urlInvalidHostname":"URL hostname is not allowed", + "urlInvalidExtension":"Invalid URL extension. Allowed extensions: {0}", + "urlInvalidPath":"Invalid URL path", + "idNotUnique":"This ID is already in use. Please choose a unique ID", + "idNonExistent":"ID {0} does not exist", + + "invalidType":"Property must be of type: {0}", + "propertyMissing":"Required property {0} is missing from the object", + "propertyOptional":"Property {0} is optional in the object", + "objectDisabled":"This object is disabled. Enable it using {0}", + "nullInvalid":"Property must not be null", + "switchInvalidType":"Value must be one of the following types: {0}", + "objectSwitchInvalid":"Object must be one of the following types: {0}", - "arrayEmptyDisabled":"This array isn't allowed to be empty!", - "arrayEmptyRequired":"This array is required to be empty!", - "arrayTooShort":"This array needs to have a length of at least {0}!", - "arrayTooLong":"This array needs to have a length of at most {0}!", - "arrayLengthInvalid":"This array needs to have a length of {0}!", - "arrayInvalidTypes":"This array can only contain the following types: {0}!", - "arrayDouble":"This array doesn't allow the same value twice!", - - "discordInvalidId":"This is an invalid discord {0} id!", - "discordInvalidIdOptions":"This is an invalid discord {0} id! You can also use one of these: {1}!", - "discordInvalidToken":"This is an invalid discord token (syntactically)!", - "colorInvalid":"This is an invalid hex color!", - "emojiTooShort":"This string needs to have at least {0} emoji's!", - "emojiTooLong":"This string needs to have at most {0} emoji's!", - "emojiCustom":"This emoji can't be a custom discord emoji!", - "emojiInvalid":"This is an invalid emoji!", - "urlInvalid":"This url is invalid!", - "urlInvalidHttp":"This url can only use the https:// protocol!", - "urlInvalidProtocol":"This url can only use the http:// & https:// protocols!", - "urlInvalidHostname":"This url has a disallowed hostname!", - "urlInvalidExtension":"This url has an invalid extension! Choose between: {0}!", - "urlInvalidPath":"This url has an invalid path!", - "idNotUnique":"This id isn't unique, use another id instead!", - "idNonExistent":"The id {0} doesn't exist!", - - "invalidType":"This property needs to be the type: {0}!", - "propertyMissing":"The property {0} is missing from this object!", - "propertyOptional":"The property {0} is optional in this object!", - "objectDisabled":"This object is disabled, enable it using {0}!", - "nullInvalid":"This property can't be null!", - "switchInvalidType":"This needs to be one of the following types: {0}!", - "objectSwitchInvalid":"This object needs to be one of the following types: {0}!", - - "invalidLanguage":"This is an invalid language!", - "invalidButton":"This button needs to have at least an {0} or {1}!", - "unusedOption":"The option {0} isn't used anywhere!", - "unusedQuestion":"The question {0} isn't used anywhere!", - "dropdownOption":"A panel with dropdown enabled can only contain options of the 'ticket' type!", - "customInvalidVersion":"The version specified in your config does not match! Make sure you have updated the config to the latest version!" + "invalidLanguage":"Invalid language", + "invalidButton":"Button must have at least an {0} or {1}", + "unusedOption":"Option {0} is not used anywhere", + "unusedQuestion":"Question {0} is not used anywhere", + "dropdownOption":"Panels with dropdown enabled may only contain options of the 'ticket' type", + "customInvalidVersion":"Config version mismatch. Make sure to update your config to the latest version" } }, "actions":{ @@ -159,118 +159,118 @@ "transfer":"Ticket Transferred" }, "descriptions":{ - "create":"Your ticket has been created. Click the button below to access it!", - "close":"The ticket has been closed successfully!", - "delete":"The ticket has been deleted successfully!", - "reopen":"The ticket has been reopened successfully!", - "claim":"The ticket has been claimed successfully!", - "unclaim":"The ticket has been unclaimed successfully!", - "pin":"The ticket has been pinned successfully!", - "unpin":"The ticket has been unpinned successfully!", - "rename":"The ticket has been renamed to {0} successfully!", - "move":"The ticket has been moved to {0} successfully!", - "add":"{0} has been added to the ticket successfully!", - "remove":"{0} has been removed from the ticket successfully!", + "create":"Your ticket is ready. Click the button below to view and continue.", + "close":"The ticket has been closed.", + "delete":"The ticket has been deleted.", + "reopen":"The ticket has been reopened.", + "claim":"The ticket has been claimed.", + "unclaim":"The ticket has been unclaimed.", + "pin":"The ticket has been pinned.", + "unpin":"The ticket has been unpinned.", + "rename":"The ticket has been renamed to {0}.", + "move":"The ticket has been moved to {0}.", + "add":"{0} has been added to the ticket.", + "remove":"{0} has been removed from the ticket.", "helpExplanation":"`` => required parameter\n`[name]` => optional parameter", - "statsReset":"The bot stats have been reset successfully!", - "statsError":"Unable to view ticket stats!\n{0} is not a ticket!", - "blacklistAdd":"{0} has been blacklisted successfully!", - "blacklistRemove":"{0} has been released successfully!", - "blacklistGetSuccess":"{0} is currently blacklisted!", - "blacklistGetEmpty":"{0} is currently not blacklisted!", - "blacklistViewEmpty":"No-one has been blacklisted yet!", - "blacklistViewTip":"Use \"/blacklist add\" to blacklist a user!", - "clearVerify":"Are you sure you want to delete multiple tickets?\nThis action can't be undone!", - "clearReady":"{0} tickets have been deleted successfully!", - "rolesEmpty":"No roles have been updated!", + "statsReset":"The bot statistics have been reset.", + "statsError":"Unable to retrieve ticket statistics.\n{0} is not a valid ticket.", + "blacklistAdd":"{0} has been blacklisted.", + "blacklistRemove":"{0} has been released.", + "blacklistGetSuccess":"{0} is blacklisted!", + "blacklistGetEmpty":"{0} is not blacklisted!", + "blacklistViewEmpty":"No users have been blacklisted yet.", + "blacklistViewTip":"Use \"/blacklist add\" to add a user to the blacklist.", + "clearVerify":"Are you sure you want to delete multiple tickets?\nThis action cannot be undone.", + "clearReady":"{0} ticket(s) have been deleted.", + "rolesEmpty":"No roles were modified.", - "autocloseLeave":"This ticket has been autoclosed because the creator left the server!", - "autocloseTimeout":"This ticket has been autoclosed because it has been inactive for more than `{0}h`!", - "autodeleteLeave":"This ticket has been autodeleted because the creator left the server!", - "autodeleteTimeout":"This ticket has been autodeleted because it has been inactive for more than `{0} days`!", - "autocloseEnabled":"Autoclose has been enabled in this ticket!\nIt will be closed when it is inactive for more than `{0}h`!", - "autocloseDisabled":"Autoclose has been disabled in this ticket!\nIt won't be closed automatically anymore!", - "autodeleteEnabled":"Autodelete has been enabled in this ticket!\nIt will be deleted when it is inactive for more than `{0} days`!", - "autodeleteDisabled":"Autodelete has been disabled in this ticket!\nIt won't be deleted automatically anymore!", + "autocloseLeave":"This ticket was automatically closed because its creator left the server.", + "autocloseTimeout":"This ticket was automatically closed due to inactivity exceeding `{0}h`.", + "autodeleteLeave":"This ticket was automatically deleted because its creator left the server.", + "autodeleteTimeout":"This ticket was automatically deleted due to inactivity exceeding `{0} days`.", + "autocloseEnabled":"Autoclose has been enabled for this ticket.\nIt will close after `{0}h` of inactivity.", + "autocloseDisabled":"Autoclose has been disabled for this ticket.\nThis ticket will no longer close automatically.", + "autodeleteEnabled":"Autodelete has been enabled for this ticket.\nIt will be deleted after `{0} days` of inactivity.", + "autodeleteDisabled":"Autodelete has been disabled for this ticket.\nThis ticket will no longer be deleted automatically.", - "ticketMessageLimit":"You can only create {0} ticket(s) at the same time!", - "ticketMessageAutoclose":"This ticket will be autoclosed when inactive for {0}h!", - "ticketMessageAutodelete":"This ticket will be autodeleted when inactive for {0} days!", - "panelReady":"The panel is available in the followup message!\nThis message can now be deleted!", + "ticketMessageLimit":"You can only have {0} active ticket(s) at a time.", + "ticketMessageAutoclose":"This ticket will automatically close after `{0}h` of inactivity.", + "ticketMessageAutodelete":"This ticket will automatically be deleted after `{0} days` of inactivity.", + "panelReady":"The panel has been sent in the follow-up message.\nYou may now delete this message.", - "topicSet":"The channel topic has been changed by {0} successfully!", - "prioritySet":"The ticket priority has been changed to {0} by {1} successfully!", - "priorityGet":"The current priority of this ticket is {0}.", - "transfer":"The ticket ownership has been transferred from {0} to {1} by {2} successfully!" + "topicSet":"The channel topic has been changed by {0}.", + "prioritySet":"The ticket priority has been changed to {0} by {1}.", + "priorityGet":"The priority of this ticket is {0}.", + "transfer":"The ticket ownership has been transferred from {0} to {1} by {2}." }, "modal":{ - "closePlaceholder":"Why did you close this ticket?", - "deletePlaceholder":"Why did you delete this ticket?", - "reopenPlaceholder":"Why did you reopen this ticket?", - "claimPlaceholder":"Why did you claim this ticket?", - "unclaimPlaceholder":"Why did you unclaim this ticket?", - "pinPlaceholder":"Why did you pin this ticket?", - "unpinPlaceholder":"Why did you unpin this ticket?" + "closePlaceholder":"Why would you like to close this ticket?", + "deletePlaceholder":"Why would you like to delete this ticket?", + "reopenPlaceholder":"Why would you like to reopen this ticket?", + "claimPlaceholder":"Why would you like to claim this ticket?", + "unclaimPlaceholder":"Why would you like to unclaim this ticket?", + "pinPlaceholder":"Why would you like to pin this ticket?", + "unpinPlaceholder":"Why would you like to unpin this ticket?" }, "logs":{ - "createLog":"A new ticket got created by {0}!", - "closeLog":"This ticket has been closed by {0}!", - "closeDm":"Your ticket has been closed in our server!", - "deleteLog":"This ticket has been deleted by {0}!", - "deleteDm":"Your ticket has been deleted in our server!", - "reopenLog":"This ticket has been reopened by {0}!", - "reopenDm":"Your ticket has been reopened in our server!", - "claimLog":"This ticket has been claimed by {0}!", - "claimDm":"Your ticket has been claimed in our server!", - "unclaimLog":"This ticket has been unclaimed by {0}!", - "unclaimDm":"Your ticket has been unclaimed in our server!", - "pinLog":"This ticket has been pinned by {0}!", - "pinDm":"Your ticket has been pinned in our server!", - "unpinLog":"This ticket has been unpinned by {0}!", - "unpinDm":"Your ticket has been unpinned in our server!", - "renameLog":"This ticket has been renamed to {0} by {1}!", - "renameDm":"Your ticket has been renamed to {0} in our server!", - "moveLog":"This ticket has been moved to {0} by {1}!", - "moveDm":"Your ticket has been moved to {0} in our server!", - "addLog":"{0} has been added to this ticket by {1}!", - "addDm":"{0} has been added to your ticket in our server!", - "removeLog":"{0} has been removed from this ticket by {1}!", - "removeDm":"{0} has been removed from your ticket in our server!", + "createLog":"A new ticket got created by {0}.", + "closeLog":"This ticket has been closed by {0}.", + "closeDm":"Your ticket has been closed.", + "deleteLog":"This ticket has been deleted by {0}.", + "deleteDm":"Your ticket has been deleted.", + "reopenLog":"This ticket has been reopened by {0}.", + "reopenDm":"Your ticket has been reopened.", + "claimLog":"This ticket has been claimed by {0}.", + "claimDm":"Your ticket has been claimed.", + "unclaimLog":"This ticket has been unclaimed by {0}.", + "unclaimDm":"Your ticket has been unclaimed.", + "pinLog":"This ticket has been pinned by {0}.", + "pinDm":"Your ticket has been pinned.", + "unpinLog":"This ticket has been unpinned by {0}.", + "unpinDm":"Your ticket has been unpinned.", + "renameLog":"This ticket has been renamed to {0} by {1}.", + "renameDm":"Your ticket has been renamed to {0}.", + "moveLog":"This ticket has been moved to {0} by {1}.", + "moveDm":"Your ticket has been moved to {0}.", + "addLog":"{0} has been added to this ticket by {1}.", + "addDm":"{0} has been added to your ticket.", + "removeLog":"{0} has been removed from this ticket by {1}.", + "removeDm":"{0} has been removed from your ticket.", - "blacklistAddLog":"{0} was blacklisted by {1}!", - "blacklistRemoveLog":"{0} was removed from the blacklist by {1}!", - "blacklistAddDm":"You have been blacklisted in our server!\nFrom now on, you are unable to create a ticket!", - "blacklistRemoveDm":"You have been removed from the blacklist in our server!\nNow you can create tickets again!", - "clearLog":"{0} tickets have been deleted by {1}!", + "blacklistAddLog":"{0} has been blacklisted by {1}.", + "blacklistRemoveLog":"{0} has been removed from the blacklist by {1}.", + "blacklistAddDm":"You have been blacklisted from this server.\nYou are no longer able to create tickets.", + "blacklistRemoveDm":"You have been removed from the blacklist.\nYou can now create tickets again.", + "clearLog":"{0} ticket(s) have been deleted by {1}.", - "transferLog":"The ownership of this ticket has been transferred from {0} to {1} by {2}!", - "transferDm":"The ownership of your ticket has been transferred from {0} to {1} in our server!", - "prioritySetLog":"The priority of this ticket has been changed to {0} by {1}!", - "prioritySetDm":"The priority of your ticket has been changed to {0} in our server!", - "roleUpdateLog":"{0} has updated their roles!", - "roleUpdateDm":"Your roles in our server have been updated!" + "transferLog":"Ticket ownership has been transferred from {0} to {1} by {2}.", + "transferDm":"Ownership of your ticket has been transferred from {0} to {1}.", + "prioritySetLog":"The priority of this ticket has been set to {0} by {1}.", + "prioritySetDm":"The priority of your ticket has been set to {0}.", + "roleUpdateLog":"{0} has modified their roles.", + "roleUpdateDm":"Your roles have been modified." } }, "transcripts":{ "success":{ "visit":"Visit Transcript", "ready":"Transcript Created", - "textFileDescription":"This is the text transcript of a deleted ticket!", - "htmlProgress":"Please wait while this html transcript is getting processed...", - - "createdChannel":"A new {0} transcript has been created in the server!", - "createdCreator":"A new {0} transcript has been created for one of your tickets!", - "createdParticipant":"A new {0} transcript has been created in one of the tickets you participated in!", - "createdActiveAdmin":"A new {0} transcript has been created in one of the tickets you participated as admin!", - "createdEveryAdmin":"A new {0} transcript has been created in one of the tickets you were admin in!", - "createdOther":"A new {0} transcript has been created!" + "textFileDescription":"This is a text transcript of a deleted ticket.", + "htmlProgress":"Please wait while the HTML transcript is being generated...", + + "createdChannel":"A new {0} transcript has been created in the server.", + "createdCreator":"A new {0} transcript has been created for one of your tickets.", + "createdParticipant":"A new {0} transcript has been created for a ticket you participated in.", + "createdActiveAdmin":"A new {0} transcript has been created for a ticket you participated in as an admin.", + "createdEveryAdmin":"A new {0} transcript has been created for a ticket you managed as an admin.", + "createdOther":"A new {0} transcript has been created." }, "errors":{ "retry":"Retry", "continue":"Delete Without Transcript", "backup":"Create Backup Transcript", - "error":"Something went wrong while trying to create the transcript.\nWhat would you like to do?\n\nThis ticket won't be deleted until you click one of these buttons.", + "error":"Something went wrong while creating the transcript.\nWhat would you like to do?\n\nThis ticket will not be deleted until you select one of the options below.", "title":"Transcript Error" }, "text":{ @@ -279,7 +279,7 @@ "fileTitle":"FILE", "fieldsTitle":"FIELDS", "reactionsTitle":"REACTIONS", - "statsTitle":"STATS", + "statsTitle":"STATISTICS", "emptyContent":"", "noTitle":"", "noDesc":"" @@ -302,68 +302,68 @@ "permissionError":"Permission Error" }, "descriptions":{ - "askForInfo":"Contact the owner of this bot for more info!", - "askForInfoResolve":"Contact the bot owner of this bot if this issue doesn't resolve after a few tries.", - "internalError":"Failed to respond to this {0} due to an internal error!", - "optionMissing":"A required parameter is missing in this command!", - "optionInvalid":"A parameter in this command is invalid!", - "optionInvalidChoose":"Choose between", - "unknownCommand":"Try visiting the help menu for more info!", - "noPermissions":"You are not allowed to use this {0}!", - "noPermissionsList":"Required Permissions: (one of them)", - "noPermissionsCooldown":"You are not allowed to use this {0} because you have a cooldown!", - "noPermissionsBlacklist":"You are not allowed to use this {0} because you have been blacklisted!", - "noPermissionsLimitGlobal":"You are not allowed to create a ticket because the server reached the max tickets limit!", - "noPermissionsLimitGlobalUser":"You are not allowed to create a ticket because you reached the max tickets limit!", - "noPermissionsLimitOption":"You are not allowed to create a ticket because the server reached the max tickets limit for this option!", - "noPermissionsLimitOptionUser":"You are not allowed to create a ticket because you reached the max tickets limit for this option!", - "unknownTicket":"Try this command again in a valid ticket!", - "deprecatedTicket":"The current channel is not a valid ticket! It might have been a ticket from an old Open Ticket version!", - "notInGuild":"This {0} doesn't work in DM! Please try it again in a server!", - "channelRename":"Due to discord ratelimits, it's currently impossible for the bot to rename the channel. The channel will automatically be renamed over 10 minutes if the bot isn't rebooted.", - "channelRenameSource":"The source of this error is: {0}", - "busy":"Unable to use this {0}!\nThe ticket is currently being processed by the bot.\n\nPlease try again in a few seconds!", - "closeBeforeMessage":"This ticket cannot be closed/deleted before a message has been sent by a user.", - "closeBeforeAdminMessage":"This ticket cannot be closed/deleted before a message has been sent by a ticket admin or support member.", - "unableToCreateTicket":"You are unable to create a ticket." + "askForInfo":"Please contact the bot owner for more information.", + "askForInfoResolve":"If the issue persists after a few attempts, please contact the bot owner.", + "internalError":"An internal error occurred while processing this {0}.", + "optionMissing":"A required parameter is missing for this command.", + "optionInvalid":"One or more parameters provided for this command are invalid.", + "optionInvalidChoose":"Please choose between", + "unknownCommand":"Please use the help menu for more information.", + "noPermissions":"You are not permitted to use this {0}.", + "noPermissionsList":"Required permissions (one of the following):", + "noPermissionsCooldown":"You cannot use this {0} while on cooldown.", + "noPermissionsBlacklist":"You cannot use this {0} because you are blacklisted.", + "noPermissionsLimitGlobal":"You cannot create a ticket because the server has reached its maximum ticket limit.", + "noPermissionsLimitGlobalUser":"You cannot create a ticket because you have reached your maximum ticket limit.", + "noPermissionsLimitOption":"You cannot create a ticket because the server has reached the maximum ticket limit for this option.", + "noPermissionsLimitOptionUser":"You cannot create a ticket because you have reached the maximum ticket limit for this option.", + "unknownTicket":"Please run this command inside a valid ticket channel.", + "deprecatedTicket":"This channel is not recognized as a valid ticket. It may have been created with an older version of the bot.", + "notInGuild":"This {0} cannot be used in direct messages. Please use it in a server.", + "channelRename":"Due to Discord rate limits, the channel could not be renamed immediately. It will be renamed automatically within 10 minutes if the bot remains online.", + "channelRenameSource":"Error source: {0}", + "busy":"This {0} is currently unavailable.\nThe ticket is being processed by the bot.\n\nPlease try again in a few seconds.", + "closeBeforeMessage":"This ticket cannot be closed or deleted before a user has sent a message.", + "closeBeforeAdminMessage":"This ticket cannot be closed or deleted before a support member or admin has sent a message.", + "unableToCreateTicket":"You are currently unable to create a ticket." }, "optionInvalidReasons":{ - "stringRegex":"Value doesn't match pattern!", - "stringMinLength":"Value needs to be at least {0} characters!", - "stringMaxLength":"Value needs to be at most {0} characters!", - "numberInvalid":"Invalid number!", - "numberMin":"Number needs to be at least {0}!", - "numberMax":"Number needs to be at most {0}!", - "numberDecimal":"Number is not allowed to be a decimal!", - "numberNegative":"Number is not allowed to be negative!", - "numberPositive":"Number is not allowed to be positive!", - "numberZero":"Number is not allowed to be zero!", - "channelNotFound":"Unable to find channel!", - "userNotFound":"Unable to find user!", - "roleNotFound":"Unable to find role!", - "memberNotFound":"Unable to find user!", - "mentionableNotFound":"Unable to find user or role!", - "channelType":"Invalid channel type!", - "notInGuild":"This option requires you to be in a server!" + "stringRegex":"The value does not match the required pattern.", + "stringMinLength":"The value must be at least {0} characters long.", + "stringMaxLength":"The value must be at most {0} characters long.", + "numberInvalid":"Invalid number provided.", + "numberMin":"The number must be at least {0}.", + "numberMax":"The number must be at most {0}.", + "numberDecimal":"Decimals are not allowed.", + "numberNegative":"Negative numbers are not allowed.", + "numberPositive":"Positive numbers are not allowed.", + "numberZero":"Zero is not allowed.", + "channelNotFound":"Channel not found.", + "userNotFound":"User not found.", + "roleNotFound":"Role not found.", + "memberNotFound":"Member not found.", + "mentionableNotFound":"User or role not found.", + "channelType":"Invalid channel type.", + "notInGuild":"This option can only be used in a server." }, "permissions":{ - "developer":"You need to be the developer of the bot.", - "owner":"You need to be the server owner.", - "admin":"You need to be a server admin.", - "moderator":"You need to be a moderator.", - "support":"You need to be in the support team.", - "member":"You need to be a member.", - "discord-administrator":"You need to have the `ADMINISTRATOR` permission." + "developer":"This action is restricted to the bot developer.", + "owner":"You must be the server owner to use this.", + "admin":"You must have administrator permissions to use this.", + "moderator":"You must be a moderator to use this.", + "support":"You must be part of the support team to use this.", + "member":"You must be a server member to use this.", + "discord-administrator":"You must have the `ADMINISTRATOR` permission." }, "actionInvalid":{ - "close":"Ticket is already closed!", - "reopen":"Ticket is not closed yet!", - "claim":"Ticket is already claimed!", - "unclaim":"Ticket is not claimed yet!", - "pin":"Ticket is already pinned!", - "unpin":"Ticket is not pinned yet!", - "add":"This user is already able to access the ticket!", - "remove":"Unable to remove this user from the ticket!" + "close":"This ticket is already closed.", + "reopen":"This ticket is not closed.", + "claim":"This ticket is already claimed.", + "unclaim":"This ticket is not claimed.", + "pin":"This ticket is already pinned.", + "unpin":"This ticket is not pinned.", + "add":"This user already has access to this ticket.", + "remove":"This user does not have access to this ticket." } }, "params":{ @@ -432,107 +432,107 @@ } }, "commands":{ - "reason":"Specify an optional reason that will be visible in logs.", - "help":"Get a list of all the available commands.", - "panel":"Spawn a message with a dropdown or buttons (for ticket creation).", - "panelId":"The identifier of the panel that you want to spawn.", - "panelAutoUpdate":"Do you want this panel to automatically update when edited?", - "ticket":"Instantly create a ticket.", - "ticketId":"The identifier of the ticket that you want to create.", - "close":"Close a ticket.", - "delete":"Delete a ticket.", - "deleteNoTranscript":"Delete this ticket without creating a transcript.", - "reopen":"Reopen a ticket.", - "claim":"Claim a ticket.", - "claimUser":"Claim this ticket to someone else instead of yourself.", - "unclaim":"Unclaim a ticket.", - "pin":"Pin a ticket.", - "unpin":"Unpin a ticket.", + "reason":"Optional reason shown in logs.", + "help":"Display all available commands.", + "panel":"Create a ticket panel (buttons or dropdown).", + "panelId":"ID of the panel to spawn.", + "panelAutoUpdate":"Automatically update this panel when edited.", + "ticket":"Create a ticket instantly.", + "ticketId":"ID of the ticket to create.", + "close":"Close this ticket.", + "delete":"Delete this ticket.", + "deleteNoTranscript":"Delete ticket without saving a transcript.", + "reopen":"Reopen a closed ticket.", + "claim":"Claim this ticket.", + "claimUser":"Claim the ticket for another user.", + "unclaim":"Unclaim this ticket.", + "pin":"Pin this ticket.", + "unpin":"Unpin this ticket.", - "move":"Move a ticket.", - "moveId":"The identifier of the option that you want to move to.", - "rename":"Rename a ticket.", - "renameName":"The new name for this ticket.", - "add":"Add a user to a ticket.", - "addUser":"The user to add.", - "remove":"Remove a user from a ticket.", - "removeUser":"The user to remove.", + "move":"Move ticket to another option.", + "moveId":"Target option ID.", + "rename":"Rename this ticket.", + "renameName":"New name for the ticket.", + "add":"Add a user to this ticket.", + "addUser":"User to add.", + "remove":"Remove a user from this ticket.", + "removeUser":"User to remove.", - "blacklist":"Manage the ticket blacklist.", - "blacklistView":"View a list of the current blacklist.", + "blacklist":"Manage the blacklist.", + "blacklistView":"View all blacklisted users.", "blacklistAdd":"Add a user to the blacklist.", "blacklistRemove":"Remove a user from the blacklist.", - "blacklistGet":"Get the details from a blacklisted user.", - "blacklistGetUser":"The user to get details from.", - "stats":"View statistics from the bot, a member or a ticket.", - "statsReset":"Reset all the stats of the bot (and start counting from zero).", - "statsGlobal":"View the global stats.", - "statsUser":"View the stats from a user in the server.", - "statsUserUser":"The user to view.", - "statsTicket":"View the stats of a ticket in the server.", - "statsTicketTicket":"The ticket to view.", + "blacklistGet":"View blacklist entry details.", + "blacklistGetUser":"User to look up.", + "stats":"View bot, user or ticket statistics.", + "statsReset":"Reset all bot statistics.", + "statsGlobal":"View global bot statistics.", + "statsUser":"View a user's statistics.", + "statsUserUser":"User to view.", + "statsTicket":"View ticket statistics.", + "statsTicketTicket":"Ticket to view.", - "clear":"Delete multiple tickets at the same time.", - "clearFilter":"The filter for clearing tickets.", + "clear":"Delete multiple tickets at once.", + "clearFilter":"Filter used when clearing tickets.", "clearFilters":{ - "all":"All", - "open":"Open", - "close":"Closed", - "claim":"Claimed", - "unclaim":"Unclaimed", - "pin":"Pinned", - "unpin":"Unpinned", - "autoclose":"Autoclosed" + "all":"All tickets", + "open":"Open tickets", + "close":"Closed tickets", + "claim":"Claimed tickets", + "unclaim":"Unclaimed tickets", + "pin":"Pinned tickets", + "unpin":"Unpinned tickets", + "autoclose":"Autoclosed tickets" }, - "autoclose":"Manage autoclose in a ticket.", - "autocloseDisable":"Disable autoclose in this ticket.", - "autocloseEnable":"Enable autoclose in this ticket.", - "autocloseEnableTime":"The amount of hours this ticket needs to be inactive to close it.", - "autodelete":"Manage autodelete in a ticket.", - "autodeleteDisable":"Disable autodelete in this ticket.", - "autodeleteEnable":"Enable autodelete in this ticket.", - "autodeleteEnableTime":"The amount of days this ticket needs to be inactive to delete it.", + "autoclose":"Manage ticket autoclose.", + "autocloseDisable":"Disable autoclose.", + "autocloseEnable":"Enable autoclose.", + "autocloseEnableTime":"Hours of inactivity before closing.", + "autodelete":"Manage ticket autodelete.", + "autodeleteDisable":"Disable autodelete.", + "autodeleteEnable":"Enable autodelete.", + "autodeleteEnableTime":"Days of inactivity before deletion.", - "topic":"Manage the topic of the ticket channel.", - "topicSet":"Set the topic of the ticket channel.", - "topicValue":"The new topic of the channel.", - "topicList":"Get a list of all tickets with their topic and stats.", - "priority":"Manage the priority of the ticket.", - "prioritySet":"Set the priority of the ticket.", - "priorityValue":"The priority of the channel.", - "priorityGet":"Get the priority of the ticket.", - "priorityList":"Get a list of all tickets with their priority status.", - "transfer":"Transfer the ticket ownership from one user to another.", - "transferUser":"The user to transfer to." + "topic":"Manage ticket channel topic.", + "topicSet":"Set the ticket channel topic.", + "topicValue":"New channel topic text.", + "topicList":"List all ticket channel topics.", + "priority":"Manage ticket priority.", + "prioritySet":"Set ticket priority.", + "priorityValue":"Priority level.", + "priorityGet":"View ticket priority.", + "priorityList":"List all ticket priorities.", + "transfer":"Transfer ticket ownership.", + "transferUser":"User to transfer to." }, "helpMenu":{ - "help":"Get a list of all the available commands.", - "ticket":"Instantly create a ticket.", - "close":"Close a ticket, this disables writing in this channel.", - "delete":"Delete a ticket, this creates a transcript when enabled.", - "reopen":"Reopen a ticket, this enables writing in this channel again.", - "pin":"Pin a ticket. This will move the ticket to the top and will add a '๐Ÿ“Œ' emoij to the name.", - "unpin":"Unpin a ticket. The ticket will stay on it's position but will lose the '๐Ÿ“Œ' emoij.", - "move":"Move a ticket. This will change the type of this ticket.", - "rename":"Rename a ticket. This will change the channel name of this ticket.", - "claim":"Claim a ticket. With this, you can let your team know you are handling this ticket.", - "unclaim":"Unclaim a ticket. With this, you can let your team know that this ticket is free again.", - "add":"Add a user to a ticket. This will allow the user to read & write in this ticket.", - "remove":"Remove a user from a ticket. This will remove the ability to read & write for a user in this ticket.", - "panel":"Spawn a message with a dropdown or buttons (for ticket creation).", - "blacklistView":"View a list of the current blacklist.", + "help":"View all available commands.", + "ticket":"Create a ticket instantly.", + "close":"Close this ticket and disable messaging in the channel.", + "delete":"Delete this ticket (creates a transcript if enabled).", + "reopen":"Reopen a closed ticket and restore messaging.", + "pin":"Pin this ticket and move it to the top with '๐Ÿ“Œ'.", + "unpin":"Unpin this ticket and remove the '๐Ÿ“Œ' emoji.", + "move":"Move this ticket to a different option or ticket type.", + "rename":"Rename this ticket channel.", + "claim":"Claim this ticket to show you are handling it.", + "unclaim":"Unclaim this ticket to mark it as available.", + "add":"Add a user to this ticket.", + "remove":"Remove a user's access from this ticket.", + "panel":"Create a ticket panel with buttons or a dropdown.", + "blacklistView":"View all blacklisted users.", "blacklistAdd":"Add a user to the blacklist.", "blacklistRemove":"Remove a user from the blacklist.", - "blacklistGet":"Get the details from a blacklisted user.", - "statsGlobal":"View the global stats.", - "statsTicket":"View the stats of a ticket in the server.", - "statsUser":"View the stats from a user in the server.", - "statsReset":"Reset all the stats of the bot (and start counting from zero).", - "autocloseDisable":"Disable autoclose in this ticket.", - "autocloseEnable":"Enable autoclose in this ticket.", - "autodeleteDisable":"Disable autodelete in this ticket.", - "autodeleteEnable":"Enable autodelete in this ticket.", + "blacklistGet":"View details of a blacklisted user.", + "statsGlobal":"View global bot statistics.", + "statsTicket":"View statistics for a ticket.", + "statsUser":"View statistics for a user.", + "statsReset":"Reset all bot statistics.", + "autocloseDisable":"Disable autoclose for this ticket.", + "autocloseEnable":"Enable autoclose for this ticket.", + "autodeleteDisable":"Disable autodelete for this ticket.", + "autodeleteEnable":"Enable autodelete for this ticket.", "categories":{ "general":"General Commands", "basicTicket":"Basic Ticket Commands", @@ -545,10 +545,10 @@ }, "stats":{ "scopes":{ - "global":"Global Stats", - "system":"System Stats", - "user":"User Stats", - "ticket":"Ticket Stats", + "global":"Global Statistics", + "system":"System Statistics", + "user":"User Statistics", + "ticket":"Ticket Statistics", "participants":"Participants", "messages":"Messages" }, @@ -592,9 +592,9 @@ } }, "panel":{ - "selectTicket":"Select your ticket", - "selectRole":"Select your role", - "selectOption":"Select your option" + "selectTicket":"Select a ticket", + "selectRole":"Select a role", + "selectOption":"Select an option" }, "priorities":{ "urgent":"Urgent", diff --git a/languages/czech.json b/languages/czech.json index ef4a93a..03b8bc3 100644 --- a/languages/czech.json +++ b/languages/czech.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["spyeye_"], "lastedited":"16/02/2026", "language":"Czech", diff --git a/languages/danish.json b/languages/danish.json index 4f93fed..ab42fd0 100644 --- a/languages/danish.json +++ b/languages/danish.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["the_gamer"], "lastedited":"16/02/2026", "language":"Danish", diff --git a/languages/dutch.json b/languages/dutch.json index f13ddfc..9c19866 100644 --- a/languages/dutch.json +++ b/languages/dutch.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["DJj123dj"], "lastedited":"16/02/2026", "language":"Dutch", diff --git a/languages/english.json b/languages/english.json index a833cf8..7244893 100644 --- a/languages/english.json +++ b/languages/english.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["DJj123dj"], "lastedited":"16/02/2026", "language":"English", @@ -13,94 +13,94 @@ "typeWarning":"[WARNING]", "typeInfo":"[INFO]", "headerConfigChecker":"CONFIG CHECKER", - "headerDescription":"check for errors in your config files!", - "footerError":"the bot won't start until all {0}'s are fixed!", - "footerWarning":"it's recommended to fix all {0}'s before starting!", + "headerDescription":"Validating config files...", + "footerError":"The bot will not start until all {0}'s are resolved.", + "footerWarning":"The bot may behave unexpectedly until all {0}'s are resolved.", "footerSupport":"SUPPORT: {0} - DOCS: {1}", - "compactInformation":"use {0} for more information!", + "compactInformation":"Use {0} for a detailed config report.", "dataPath":"path", "dataDocs":"docs", "dataMessages":"message" }, "messages":{ - "stringTooShort":"This string can't be shorter than {0} characters!", - "stringTooLong":"This string can't be longer than {0} characters!", - "stringLengthInvalid":"This string needs to be {0} characters long!", - "stringStartsWith":"This string needs to start with {0}!", - "stringEndsWith":"This string needs to end with {0}!", - "stringContains":"This string needs to contain {0}!", - "stringChoices":"This string can only be one of the following values: {0}!", - "stringRegex":"This string is invalid!", - "stringInvertedContains":"This string is not allowed to contain {0}!", - "stringLowercase":"This string must be written in lowercase only!", - "stringUppercase":"This string must be written in uppercase only!", - "stringSpecialCharacters":"This string is not allowed to contain any special characters! (a-z, 0-9 & space only)", - "stringNoSpaces":"This string is not allowed to contain spaces!", - "stringCapitalWord":"It's recommended that each word in this string starts with a capital letter!", - "stringCapitalSentence":"It looks like some sentences in this string don't start with a capital letter!", - "stringPunctuation":"It looks like the sentence in this string doesn't end with a punctuation mark!", + "stringTooShort":"Text must be at least {0} characters long", + "stringTooLong":"Text must be no longer than {0} characters", + "stringLengthInvalid":"Text must be exactly {0} characters long", + "stringStartsWith":"Text must start with {0}", + "stringEndsWith":"Text must end with {0}", + "stringContains":"Text must contain {0}", + "stringChoices":"Text must be one of the following: {0}", + "stringRegex":"Text does not match the required format", + "stringInvertedContains":"Text must not contain {0}", + "stringLowercase":"Text must be entirely lowercase", + "stringUppercase":"Text must be entirely uppercase", + "stringSpecialCharacters":"Text must only contain letters (aโ€“z), numbers (0โ€“9), and spaces", + "stringNoSpaces":"Text must not contain spaces", + "stringCapitalWord":"Each word in this value should start with a capital letter", + "stringCapitalSentence":"One or more sentences in this value do not start with a capital letter", + "stringPunctuation":"The sentence in this value does not end with a punctuation mark", - "numberTooShort":"This number can't be shorter than {0} characters!", - "numberTooLong":"This number can't be longer than {0} characters!", - "numberLengthInvalid":"This number needs to be {0} characters long!", - "numberTooSmall":"This number needs to be at least {0}!", - "numberTooLarge":"This number needs to be at most {0}!", - "numberNotEqual":"This number needs to be {0}!", - "numberStep":"This number needs to be a multiple of {0}!", - "numberStepOffset":"This number needs to be a multiple of {0} starting with {1}!", - "numberStartsWith":"This number needs to start with {0}!", - "numberEndsWith":"This number needs to end with {0}!", - "numberContains":"This number needs to contain {0}!", - "numberChoices":"This number can only be one of the following values: {0}!", - "numberFloat":"This number can't be a decimal!", - "numberNegative":"This number can't be negative!", - "numberPositive":"This number can't be positive!", - "numberZero":"This number can't be zero!", - "numberNan":"This number can't be NaN (Not A Number)!", - "numberInvertedContains":"This number is not allowed to contain {0}!", + "numberTooShort":"Number must be at least {0} digits long", + "numberTooLong":"Number must be no longer than {0} digits", + "numberLengthInvalid":"Number must be exactly {0} digits long", + "numberTooSmall":"Number must be at least {0}", + "numberTooLarge":"Number must be at most {0}", + "numberNotEqual":"Number must be exactly {0}", + "numberStep":"Number must be a multiple of {0}", + "numberStepOffset":"Number must be a multiple of {0}, starting from {1}", + "numberStartsWith":"Number must start with {0}", + "numberEndsWith":"Number must end with {0}", + "numberContains":"Number must contain {0}", + "numberChoices":"Number must be one of the following: {0}", + "numberFloat":"Number must be a whole number", + "numberNegative":"Number must be a positive number", + "numberPositive":"Number must be a negative number", + "numberZero":"Number must not be zero", + "numberNan":"Number must be a valid number", + "numberInvertedContains":"Number must not contain {0}", - "booleanTrue":"This boolean can't be true!", - "booleanFalse":"This boolean can't be false!", + "booleanTrue":"Boolean must be false", + "booleanFalse":"Boolean must be true", + + "arrayEmptyDisabled":"List must not be empty", + "arrayEmptyRequired":"List must be empty", + "arrayTooShort":"List must have at least {0} items", + "arrayTooLong":"List must have at most {0} items", + "arrayLengthInvalid":"List must have exactly {0} items", + "arrayInvalidTypes":"List may only contain the following types: {0}", + "arrayDouble":"List must not contain duplicate values", + + "discordInvalidId":"Invalid Discord {0} ID", + "discordInvalidIdOptions":"Invalid Discord {0} ID. Alternatively, use one of the following: {1}", + "discordInvalidToken":"Invalid Discord token", + "colorInvalid":"Invalid hex color", + "emojiTooShort":"Value must contain at least {0} emoji", + "emojiTooLong":"Value must contain at most {0} emoji", + "emojiCustom":"Custom Discord emojis are not allowed here", + "emojiInvalid":"Invalid emoji", + "urlInvalid":"Invalid URL", + "urlInvalidHttp":"URL must use the https:// protocol", + "urlInvalidProtocol":"URL must use the http:// or https:// protocol", + "urlInvalidHostname":"URL hostname is not allowed", + "urlInvalidExtension":"Invalid URL extension. Allowed extensions: {0}", + "urlInvalidPath":"Invalid URL path", + "idNotUnique":"This ID is already in use. Please choose a unique ID", + "idNonExistent":"ID {0} does not exist", + + "invalidType":"Property must be of type: {0}", + "propertyMissing":"Required property {0} is missing from the object", + "propertyOptional":"Property {0} is optional in the object", + "objectDisabled":"This object is disabled. Enable it using {0}", + "nullInvalid":"Property must not be null", + "switchInvalidType":"Value must be one of the following types: {0}", + "objectSwitchInvalid":"Object must be one of the following types: {0}", - "arrayEmptyDisabled":"This array isn't allowed to be empty!", - "arrayEmptyRequired":"This array is required to be empty!", - "arrayTooShort":"This array needs to have a length of at least {0}!", - "arrayTooLong":"This array needs to have a length of at most {0}!", - "arrayLengthInvalid":"This array needs to have a length of {0}!", - "arrayInvalidTypes":"This array can only contain the following types: {0}!", - "arrayDouble":"This array doesn't allow the same value twice!", - - "discordInvalidId":"This is an invalid discord {0} id!", - "discordInvalidIdOptions":"This is an invalid discord {0} id! You can also use one of these: {1}!", - "discordInvalidToken":"This is an invalid discord token (syntactically)!", - "colorInvalid":"This is an invalid hex color!", - "emojiTooShort":"This string needs to have at least {0} emoji's!", - "emojiTooLong":"This string needs to have at most {0} emoji's!", - "emojiCustom":"This emoji can't be a custom discord emoji!", - "emojiInvalid":"This is an invalid emoji!", - "urlInvalid":"This url is invalid!", - "urlInvalidHttp":"This url can only use the https:// protocol!", - "urlInvalidProtocol":"This url can only use the http:// & https:// protocols!", - "urlInvalidHostname":"This url has a disallowed hostname!", - "urlInvalidExtension":"This url has an invalid extension! Choose between: {0}!", - "urlInvalidPath":"This url has an invalid path!", - "idNotUnique":"This id isn't unique, use another id instead!", - "idNonExistent":"The id {0} doesn't exist!", - - "invalidType":"This property needs to be the type: {0}!", - "propertyMissing":"The property {0} is missing from this object!", - "propertyOptional":"The property {0} is optional in this object!", - "objectDisabled":"This object is disabled, enable it using {0}!", - "nullInvalid":"This property can't be null!", - "switchInvalidType":"This needs to be one of the following types: {0}!", - "objectSwitchInvalid":"This object needs to be one of the following types: {0}!", - - "invalidLanguage":"This is an invalid language!", - "invalidButton":"This button needs to have at least an {0} or {1}!", - "unusedOption":"The option {0} isn't used anywhere!", - "unusedQuestion":"The question {0} isn't used anywhere!", - "dropdownOption":"A panel with dropdown enabled can only contain options of the 'ticket' type!", - "customInvalidVersion":"The version specified in your config does not match! Make sure you have updated the config to the latest version!" + "invalidLanguage":"Invalid language", + "invalidButton":"Button must have at least an {0} or {1}", + "unusedOption":"Option {0} is not used anywhere", + "unusedQuestion":"Question {0} is not used anywhere", + "dropdownOption":"Panels with dropdown enabled may only contain options of the 'ticket' type", + "customInvalidVersion":"Config version mismatch. Make sure to update your config to the latest version" } }, "actions":{ @@ -159,118 +159,118 @@ "transfer":"Ticket Transferred" }, "descriptions":{ - "create":"Your ticket has been created. Click the button below to access it!", - "close":"The ticket has been closed successfully!", - "delete":"The ticket has been deleted successfully!", - "reopen":"The ticket has been reopened successfully!", - "claim":"The ticket has been claimed successfully!", - "unclaim":"The ticket has been unclaimed successfully!", - "pin":"The ticket has been pinned successfully!", - "unpin":"The ticket has been unpinned successfully!", - "rename":"The ticket has been renamed to {0} successfully!", - "move":"The ticket has been moved to {0} successfully!", - "add":"{0} has been added to the ticket successfully!", - "remove":"{0} has been removed from the ticket successfully!", + "create":"Your ticket is ready. Click the button below to view and continue.", + "close":"The ticket has been closed.", + "delete":"The ticket has been deleted.", + "reopen":"The ticket has been reopened.", + "claim":"The ticket has been claimed.", + "unclaim":"The ticket has been unclaimed.", + "pin":"The ticket has been pinned.", + "unpin":"The ticket has been unpinned.", + "rename":"The ticket has been renamed to {0}.", + "move":"The ticket has been moved to {0}.", + "add":"{0} has been added to the ticket.", + "remove":"{0} has been removed from the ticket.", "helpExplanation":"`` => required parameter\n`[name]` => optional parameter", - "statsReset":"The bot stats have been reset successfully!", - "statsError":"Unable to view ticket stats!\n{0} is not a ticket!", - "blacklistAdd":"{0} has been blacklisted successfully!", - "blacklistRemove":"{0} has been released successfully!", - "blacklistGetSuccess":"{0} is currently blacklisted!", - "blacklistGetEmpty":"{0} is currently not blacklisted!", - "blacklistViewEmpty":"No-one has been blacklisted yet!", - "blacklistViewTip":"Use \"/blacklist add\" to blacklist a user!", - "clearVerify":"Are you sure you want to delete multiple tickets?\nThis action can't be undone!", - "clearReady":"{0} tickets have been deleted successfully!", - "rolesEmpty":"No roles have been updated!", + "statsReset":"The bot statistics have been reset.", + "statsError":"Unable to retrieve ticket statistics.\n{0} is not a valid ticket.", + "blacklistAdd":"{0} has been blacklisted.", + "blacklistRemove":"{0} has been released.", + "blacklistGetSuccess":"{0} is blacklisted!", + "blacklistGetEmpty":"{0} is not blacklisted!", + "blacklistViewEmpty":"No users have been blacklisted yet.", + "blacklistViewTip":"Use \"/blacklist add\" to add a user to the blacklist.", + "clearVerify":"Are you sure you want to delete multiple tickets?\nThis action cannot be undone.", + "clearReady":"{0} ticket(s) have been deleted.", + "rolesEmpty":"No roles were modified.", - "autocloseLeave":"This ticket has been autoclosed because the creator left the server!", - "autocloseTimeout":"This ticket has been autoclosed because it has been inactive for more than `{0}h`!", - "autodeleteLeave":"This ticket has been autodeleted because the creator left the server!", - "autodeleteTimeout":"This ticket has been autodeleted because it has been inactive for more than `{0} days`!", - "autocloseEnabled":"Autoclose has been enabled in this ticket!\nIt will be closed when it is inactive for more than `{0}h`!", - "autocloseDisabled":"Autoclose has been disabled in this ticket!\nIt won't be closed automatically anymore!", - "autodeleteEnabled":"Autodelete has been enabled in this ticket!\nIt will be deleted when it is inactive for more than `{0} days`!", - "autodeleteDisabled":"Autodelete has been disabled in this ticket!\nIt won't be deleted automatically anymore!", + "autocloseLeave":"This ticket was automatically closed because its creator left the server.", + "autocloseTimeout":"This ticket was automatically closed due to inactivity exceeding `{0}h`.", + "autodeleteLeave":"This ticket was automatically deleted because its creator left the server.", + "autodeleteTimeout":"This ticket was automatically deleted due to inactivity exceeding `{0} days`.", + "autocloseEnabled":"Autoclose has been enabled for this ticket.\nIt will close after `{0}h` of inactivity.", + "autocloseDisabled":"Autoclose has been disabled for this ticket.\nThis ticket will no longer close automatically.", + "autodeleteEnabled":"Autodelete has been enabled for this ticket.\nIt will be deleted after `{0} days` of inactivity.", + "autodeleteDisabled":"Autodelete has been disabled for this ticket.\nThis ticket will no longer be deleted automatically.", - "ticketMessageLimit":"You can only create {0} ticket(s) at the same time!", - "ticketMessageAutoclose":"This ticket will be autoclosed when inactive for {0}h!", - "ticketMessageAutodelete":"This ticket will be autodeleted when inactive for {0} days!", - "panelReady":"The panel is available in the followup message!\nThis message can now be deleted!", + "ticketMessageLimit":"You can only have {0} active ticket(s) at a time.", + "ticketMessageAutoclose":"This ticket will automatically close after `{0}h` of inactivity.", + "ticketMessageAutodelete":"This ticket will automatically be deleted after `{0} days` of inactivity.", + "panelReady":"The panel has been sent in the follow-up message.\nYou may now delete this message.", - "topicSet":"The channel topic has been changed by {0} successfully!", - "prioritySet":"The ticket priority has been changed to {0} by {1} successfully!", - "priorityGet":"The current priority of this ticket is {0}.", - "transfer":"The ticket ownership has been transferred from {0} to {1} by {2} successfully!" + "topicSet":"The channel topic has been changed by {0}.", + "prioritySet":"The ticket priority has been changed to {0} by {1}.", + "priorityGet":"The priority of this ticket is {0}.", + "transfer":"The ticket ownership has been transferred from {0} to {1} by {2}." }, "modal":{ - "closePlaceholder":"Why did you close this ticket?", - "deletePlaceholder":"Why did you delete this ticket?", - "reopenPlaceholder":"Why did you reopen this ticket?", - "claimPlaceholder":"Why did you claim this ticket?", - "unclaimPlaceholder":"Why did you unclaim this ticket?", - "pinPlaceholder":"Why did you pin this ticket?", - "unpinPlaceholder":"Why did you unpin this ticket?" + "closePlaceholder":"Why would you like to close this ticket?", + "deletePlaceholder":"Why would you like to delete this ticket?", + "reopenPlaceholder":"Why would you like to reopen this ticket?", + "claimPlaceholder":"Why would you like to claim this ticket?", + "unclaimPlaceholder":"Why would you like to unclaim this ticket?", + "pinPlaceholder":"Why would you like to pin this ticket?", + "unpinPlaceholder":"Why would you like to unpin this ticket?" }, "logs":{ - "createLog":"A new ticket got created by {0}!", - "closeLog":"This ticket has been closed by {0}!", - "closeDm":"Your ticket has been closed in our server!", - "deleteLog":"This ticket has been deleted by {0}!", - "deleteDm":"Your ticket has been deleted in our server!", - "reopenLog":"This ticket has been reopened by {0}!", - "reopenDm":"Your ticket has been reopened in our server!", - "claimLog":"This ticket has been claimed by {0}!", - "claimDm":"Your ticket has been claimed in our server!", - "unclaimLog":"This ticket has been unclaimed by {0}!", - "unclaimDm":"Your ticket has been unclaimed in our server!", - "pinLog":"This ticket has been pinned by {0}!", - "pinDm":"Your ticket has been pinned in our server!", - "unpinLog":"This ticket has been unpinned by {0}!", - "unpinDm":"Your ticket has been unpinned in our server!", - "renameLog":"This ticket has been renamed to {0} by {1}!", - "renameDm":"Your ticket has been renamed to {0} in our server!", - "moveLog":"This ticket has been moved to {0} by {1}!", - "moveDm":"Your ticket has been moved to {0} in our server!", - "addLog":"{0} has been added to this ticket by {1}!", - "addDm":"{0} has been added to your ticket in our server!", - "removeLog":"{0} has been removed from this ticket by {1}!", - "removeDm":"{0} has been removed from your ticket in our server!", + "createLog":"A new ticket got created by {0}.", + "closeLog":"This ticket has been closed by {0}.", + "closeDm":"Your ticket has been closed.", + "deleteLog":"This ticket has been deleted by {0}.", + "deleteDm":"Your ticket has been deleted.", + "reopenLog":"This ticket has been reopened by {0}.", + "reopenDm":"Your ticket has been reopened.", + "claimLog":"This ticket has been claimed by {0}.", + "claimDm":"Your ticket has been claimed.", + "unclaimLog":"This ticket has been unclaimed by {0}.", + "unclaimDm":"Your ticket has been unclaimed.", + "pinLog":"This ticket has been pinned by {0}.", + "pinDm":"Your ticket has been pinned.", + "unpinLog":"This ticket has been unpinned by {0}.", + "unpinDm":"Your ticket has been unpinned.", + "renameLog":"This ticket has been renamed to {0} by {1}.", + "renameDm":"Your ticket has been renamed to {0}.", + "moveLog":"This ticket has been moved to {0} by {1}.", + "moveDm":"Your ticket has been moved to {0}.", + "addLog":"{0} has been added to this ticket by {1}.", + "addDm":"{0} has been added to your ticket.", + "removeLog":"{0} has been removed from this ticket by {1}.", + "removeDm":"{0} has been removed from your ticket.", - "blacklistAddLog":"{0} was blacklisted by {1}!", - "blacklistRemoveLog":"{0} was removed from the blacklist by {1}!", - "blacklistAddDm":"You have been blacklisted in our server!\nFrom now on, you are unable to create a ticket!", - "blacklistRemoveDm":"You have been removed from the blacklist in our server!\nNow you can create tickets again!", - "clearLog":"{0} tickets have been deleted by {1}!", + "blacklistAddLog":"{0} has been blacklisted by {1}.", + "blacklistRemoveLog":"{0} has been removed from the blacklist by {1}.", + "blacklistAddDm":"You have been blacklisted from this server.\nYou are no longer able to create tickets.", + "blacklistRemoveDm":"You have been removed from the blacklist.\nYou can now create tickets again.", + "clearLog":"{0} ticket(s) have been deleted by {1}.", - "transferLog":"The ownership of this ticket has been transferred from {0} to {1} by {2}!", - "transferDm":"The ownership of your ticket has been transferred from {0} to {1} in our server!", - "prioritySetLog":"The priority of this ticket has been changed to {0} by {1}!", - "prioritySetDm":"The priority of your ticket has been changed to {0} in our server!", - "roleUpdateLog":"{0} has updated their roles!", - "roleUpdateDm":"Your roles in our server have been updated!" + "transferLog":"Ticket ownership has been transferred from {0} to {1} by {2}.", + "transferDm":"Ownership of your ticket has been transferred from {0} to {1}.", + "prioritySetLog":"The priority of this ticket has been set to {0} by {1}.", + "prioritySetDm":"The priority of your ticket has been set to {0}.", + "roleUpdateLog":"{0} has modified their roles.", + "roleUpdateDm":"Your roles have been modified." } }, "transcripts":{ "success":{ "visit":"Visit Transcript", "ready":"Transcript Created", - "textFileDescription":"This is the text transcript of a deleted ticket!", - "htmlProgress":"Please wait while this html transcript is getting processed...", - - "createdChannel":"A new {0} transcript has been created in the server!", - "createdCreator":"A new {0} transcript has been created for one of your tickets!", - "createdParticipant":"A new {0} transcript has been created in one of the tickets you participated in!", - "createdActiveAdmin":"A new {0} transcript has been created in one of the tickets you participated as admin!", - "createdEveryAdmin":"A new {0} transcript has been created in one of the tickets you were admin in!", - "createdOther":"A new {0} transcript has been created!" + "textFileDescription":"This is a text transcript of a deleted ticket.", + "htmlProgress":"Please wait while the HTML transcript is being generated...", + + "createdChannel":"A new {0} transcript has been created in the server.", + "createdCreator":"A new {0} transcript has been created for one of your tickets.", + "createdParticipant":"A new {0} transcript has been created for a ticket you participated in.", + "createdActiveAdmin":"A new {0} transcript has been created for a ticket you participated in as an admin.", + "createdEveryAdmin":"A new {0} transcript has been created for a ticket you managed as an admin.", + "createdOther":"A new {0} transcript has been created." }, "errors":{ "retry":"Retry", "continue":"Delete Without Transcript", "backup":"Create Backup Transcript", - "error":"Something went wrong while trying to create the transcript.\nWhat would you like to do?\n\nThis ticket won't be deleted until you click one of these buttons.", + "error":"Something went wrong while creating the transcript.\nWhat would you like to do?\n\nThis ticket will not be deleted until you select one of the options below.", "title":"Transcript Error" }, "text":{ @@ -279,7 +279,7 @@ "fileTitle":"FILE", "fieldsTitle":"FIELDS", "reactionsTitle":"REACTIONS", - "statsTitle":"STATS", + "statsTitle":"STATISTICS", "emptyContent":"", "noTitle":"", "noDesc":"" @@ -302,68 +302,68 @@ "permissionError":"Permission Error" }, "descriptions":{ - "askForInfo":"Contact the owner of this bot for more info!", - "askForInfoResolve":"Contact the bot owner of this bot if this issue doesn't resolve after a few tries.", - "internalError":"Failed to respond to this {0} due to an internal error!", - "optionMissing":"A required parameter is missing in this command!", - "optionInvalid":"A parameter in this command is invalid!", - "optionInvalidChoose":"Choose between", - "unknownCommand":"Try visiting the help menu for more info!", - "noPermissions":"You are not allowed to use this {0}!", - "noPermissionsList":"Required Permissions: (one of them)", - "noPermissionsCooldown":"You are not allowed to use this {0} because you have a cooldown!", - "noPermissionsBlacklist":"You are not allowed to use this {0} because you have been blacklisted!", - "noPermissionsLimitGlobal":"You are not allowed to create a ticket because the server reached the max tickets limit!", - "noPermissionsLimitGlobalUser":"You are not allowed to create a ticket because you reached the max tickets limit!", - "noPermissionsLimitOption":"You are not allowed to create a ticket because the server reached the max tickets limit for this option!", - "noPermissionsLimitOptionUser":"You are not allowed to create a ticket because you reached the max tickets limit for this option!", - "unknownTicket":"Try this command again in a valid ticket!", - "deprecatedTicket":"The current channel is not a valid ticket! It might have been a ticket from an old Open Ticket version!", - "notInGuild":"This {0} doesn't work in DM! Please try it again in a server!", - "channelRename":"Due to discord ratelimits, it's currently impossible for the bot to rename the channel. The channel will automatically be renamed over 10 minutes if the bot isn't rebooted.", - "channelRenameSource":"The source of this error is: {0}", - "busy":"Unable to use this {0}!\nThe ticket is currently being processed by the bot.\n\nPlease try again in a few seconds!", - "closeBeforeMessage":"This ticket cannot be closed/deleted before a message has been sent by a user.", - "closeBeforeAdminMessage":"This ticket cannot be closed/deleted before a message has been sent by a ticket admin or support member.", - "unableToCreateTicket":"You are unable to create a ticket." + "askForInfo":"Please contact the bot owner for more information.", + "askForInfoResolve":"If the issue persists after a few attempts, please contact the bot owner.", + "internalError":"An internal error occurred while processing this {0}.", + "optionMissing":"A required parameter is missing for this command.", + "optionInvalid":"One or more parameters provided for this command are invalid.", + "optionInvalidChoose":"Please choose between", + "unknownCommand":"Please use the help menu for more information.", + "noPermissions":"You are not permitted to use this {0}.", + "noPermissionsList":"Required permissions (one of the following):", + "noPermissionsCooldown":"You cannot use this {0} while on cooldown.", + "noPermissionsBlacklist":"You cannot use this {0} because you are blacklisted.", + "noPermissionsLimitGlobal":"You cannot create a ticket because the server has reached its maximum ticket limit.", + "noPermissionsLimitGlobalUser":"You cannot create a ticket because you have reached your maximum ticket limit.", + "noPermissionsLimitOption":"You cannot create a ticket because the server has reached the maximum ticket limit for this option.", + "noPermissionsLimitOptionUser":"You cannot create a ticket because you have reached the maximum ticket limit for this option.", + "unknownTicket":"Please run this command inside a valid ticket channel.", + "deprecatedTicket":"This channel is not recognized as a valid ticket. It may have been created with an older version of the bot.", + "notInGuild":"This {0} cannot be used in direct messages. Please use it in a server.", + "channelRename":"Due to Discord rate limits, the channel could not be renamed immediately. It will be renamed automatically within 10 minutes if the bot remains online.", + "channelRenameSource":"Error source: {0}", + "busy":"This {0} is currently unavailable.\nThe ticket is being processed by the bot.\n\nPlease try again in a few seconds.", + "closeBeforeMessage":"This ticket cannot be closed or deleted before a user has sent a message.", + "closeBeforeAdminMessage":"This ticket cannot be closed or deleted before a support member or admin has sent a message.", + "unableToCreateTicket":"You are currently unable to create a ticket." }, "optionInvalidReasons":{ - "stringRegex":"Value doesn't match pattern!", - "stringMinLength":"Value needs to be at least {0} characters!", - "stringMaxLength":"Value needs to be at most {0} characters!", - "numberInvalid":"Invalid number!", - "numberMin":"Number needs to be at least {0}!", - "numberMax":"Number needs to be at most {0}!", - "numberDecimal":"Number is not allowed to be a decimal!", - "numberNegative":"Number is not allowed to be negative!", - "numberPositive":"Number is not allowed to be positive!", - "numberZero":"Number is not allowed to be zero!", - "channelNotFound":"Unable to find channel!", - "userNotFound":"Unable to find user!", - "roleNotFound":"Unable to find role!", - "memberNotFound":"Unable to find user!", - "mentionableNotFound":"Unable to find user or role!", - "channelType":"Invalid channel type!", - "notInGuild":"This option requires you to be in a server!" + "stringRegex":"The value does not match the required pattern.", + "stringMinLength":"The value must be at least {0} characters long.", + "stringMaxLength":"The value must be at most {0} characters long.", + "numberInvalid":"Invalid number provided.", + "numberMin":"The number must be at least {0}.", + "numberMax":"The number must be at most {0}.", + "numberDecimal":"Decimals are not allowed.", + "numberNegative":"Negative numbers are not allowed.", + "numberPositive":"Positive numbers are not allowed.", + "numberZero":"Zero is not allowed.", + "channelNotFound":"Channel not found.", + "userNotFound":"User not found.", + "roleNotFound":"Role not found.", + "memberNotFound":"Member not found.", + "mentionableNotFound":"User or role not found.", + "channelType":"Invalid channel type.", + "notInGuild":"This option can only be used in a server." }, "permissions":{ - "developer":"You need to be the developer of the bot.", - "owner":"You need to be the server owner.", - "admin":"You need to be a server admin.", - "moderator":"You need to be a moderator.", - "support":"You need to be in the support team.", - "member":"You need to be a member.", - "discord-administrator":"You need to have the `ADMINISTRATOR` permission." + "developer":"This action is restricted to the bot developer.", + "owner":"You must be the server owner to use this.", + "admin":"You must have administrator permissions to use this.", + "moderator":"You must be a moderator to use this.", + "support":"You must be part of the support team to use this.", + "member":"You must be a server member to use this.", + "discord-administrator":"You must have the `ADMINISTRATOR` permission." }, "actionInvalid":{ - "close":"Ticket is already closed!", - "reopen":"Ticket is not closed yet!", - "claim":"Ticket is already claimed!", - "unclaim":"Ticket is not claimed yet!", - "pin":"Ticket is already pinned!", - "unpin":"Ticket is not pinned yet!", - "add":"This user is already able to access the ticket!", - "remove":"Unable to remove this user from the ticket!" + "close":"This ticket is already closed.", + "reopen":"This ticket is not closed.", + "claim":"This ticket is already claimed.", + "unclaim":"This ticket is not claimed.", + "pin":"This ticket is already pinned.", + "unpin":"This ticket is not pinned.", + "add":"This user already has access to this ticket.", + "remove":"This user does not have access to this ticket." } }, "params":{ @@ -432,107 +432,107 @@ } }, "commands":{ - "reason":"Specify an optional reason that will be visible in logs.", - "help":"Get a list of all the available commands.", - "panel":"Spawn a message with a dropdown or buttons (for ticket creation).", - "panelId":"The identifier of the panel that you want to spawn.", - "panelAutoUpdate":"Do you want this panel to automatically update when edited?", - "ticket":"Instantly create a ticket.", - "ticketId":"The identifier of the ticket that you want to create.", - "close":"Close a ticket.", - "delete":"Delete a ticket.", - "deleteNoTranscript":"Delete this ticket without creating a transcript.", - "reopen":"Reopen a ticket.", - "claim":"Claim a ticket.", - "claimUser":"Claim this ticket to someone else instead of yourself.", - "unclaim":"Unclaim a ticket.", - "pin":"Pin a ticket.", - "unpin":"Unpin a ticket.", + "reason":"Optional reason shown in logs.", + "help":"Display all available commands.", + "panel":"Create a ticket panel (buttons or dropdown).", + "panelId":"ID of the panel to spawn.", + "panelAutoUpdate":"Automatically update this panel when edited.", + "ticket":"Create a ticket instantly.", + "ticketId":"ID of the ticket to create.", + "close":"Close this ticket.", + "delete":"Delete this ticket.", + "deleteNoTranscript":"Delete ticket without saving a transcript.", + "reopen":"Reopen a closed ticket.", + "claim":"Claim this ticket.", + "claimUser":"Claim the ticket for another user.", + "unclaim":"Unclaim this ticket.", + "pin":"Pin this ticket.", + "unpin":"Unpin this ticket.", - "move":"Move a ticket.", - "moveId":"The identifier of the option that you want to move to.", - "rename":"Rename a ticket.", - "renameName":"The new name for this ticket.", - "add":"Add a user to a ticket.", - "addUser":"The user to add.", - "remove":"Remove a user from a ticket.", - "removeUser":"The user to remove.", + "move":"Move ticket to another option.", + "moveId":"Target option ID.", + "rename":"Rename this ticket.", + "renameName":"New name for the ticket.", + "add":"Add a user to this ticket.", + "addUser":"User to add.", + "remove":"Remove a user from this ticket.", + "removeUser":"User to remove.", - "blacklist":"Manage the ticket blacklist.", - "blacklistView":"View a list of the current blacklist.", + "blacklist":"Manage the blacklist.", + "blacklistView":"View all blacklisted users.", "blacklistAdd":"Add a user to the blacklist.", "blacklistRemove":"Remove a user from the blacklist.", - "blacklistGet":"Get the details from a blacklisted user.", - "blacklistGetUser":"The user to get details from.", - "stats":"View statistics from the bot, a member or a ticket.", - "statsReset":"Reset all the stats of the bot (and start counting from zero).", - "statsGlobal":"View the global stats.", - "statsUser":"View the stats from a user in the server.", - "statsUserUser":"The user to view.", - "statsTicket":"View the stats of a ticket in the server.", - "statsTicketTicket":"The ticket to view.", + "blacklistGet":"View blacklist entry details.", + "blacklistGetUser":"User to look up.", + "stats":"View bot, user or ticket statistics.", + "statsReset":"Reset all bot statistics.", + "statsGlobal":"View global bot statistics.", + "statsUser":"View a user's statistics.", + "statsUserUser":"User to view.", + "statsTicket":"View ticket statistics.", + "statsTicketTicket":"Ticket to view.", - "clear":"Delete multiple tickets at the same time.", - "clearFilter":"The filter for clearing tickets.", + "clear":"Delete multiple tickets at once.", + "clearFilter":"Filter used when clearing tickets.", "clearFilters":{ - "all":"All", - "open":"Open", - "close":"Closed", - "claim":"Claimed", - "unclaim":"Unclaimed", - "pin":"Pinned", - "unpin":"Unpinned", - "autoclose":"Autoclosed" + "all":"All tickets", + "open":"Open tickets", + "close":"Closed tickets", + "claim":"Claimed tickets", + "unclaim":"Unclaimed tickets", + "pin":"Pinned tickets", + "unpin":"Unpinned tickets", + "autoclose":"Autoclosed tickets" }, - "autoclose":"Manage autoclose in a ticket.", - "autocloseDisable":"Disable autoclose in this ticket.", - "autocloseEnable":"Enable autoclose in this ticket.", - "autocloseEnableTime":"The amount of hours this ticket needs to be inactive to close it.", - "autodelete":"Manage autodelete in a ticket.", - "autodeleteDisable":"Disable autodelete in this ticket.", - "autodeleteEnable":"Enable autodelete in this ticket.", - "autodeleteEnableTime":"The amount of days this ticket needs to be inactive to delete it.", + "autoclose":"Manage ticket autoclose.", + "autocloseDisable":"Disable autoclose.", + "autocloseEnable":"Enable autoclose.", + "autocloseEnableTime":"Hours of inactivity before closing.", + "autodelete":"Manage ticket autodelete.", + "autodeleteDisable":"Disable autodelete.", + "autodeleteEnable":"Enable autodelete.", + "autodeleteEnableTime":"Days of inactivity before deletion.", - "topic":"Manage the topic of the ticket channel.", - "topicSet":"Set the topic of the ticket channel.", - "topicValue":"The new topic of the channel.", - "topicList":"Get a list of all tickets with their topic and stats.", - "priority":"Manage the priority of the ticket.", - "prioritySet":"Set the priority of the ticket.", - "priorityValue":"The priority of the channel.", - "priorityGet":"Get the priority of the ticket.", - "priorityList":"Get a list of all tickets with their priority status.", - "transfer":"Transfer the ticket ownership from one user to another.", - "transferUser":"The user to transfer to." + "topic":"Manage ticket channel topic.", + "topicSet":"Set the ticket channel topic.", + "topicValue":"New channel topic text.", + "topicList":"List all ticket channel topics.", + "priority":"Manage ticket priority.", + "prioritySet":"Set ticket priority.", + "priorityValue":"Priority level.", + "priorityGet":"View ticket priority.", + "priorityList":"List all ticket priorities.", + "transfer":"Transfer ticket ownership.", + "transferUser":"User to transfer to." }, "helpMenu":{ - "help":"Get a list of all the available commands.", - "ticket":"Instantly create a ticket.", - "close":"Close a ticket, this disables writing in this channel.", - "delete":"Delete a ticket, this creates a transcript when enabled.", - "reopen":"Reopen a ticket, this enables writing in this channel again.", - "pin":"Pin a ticket. This will move the ticket to the top and will add a '๐Ÿ“Œ' emoij to the name.", - "unpin":"Unpin a ticket. The ticket will stay on it's position but will lose the '๐Ÿ“Œ' emoij.", - "move":"Move a ticket. This will change the type of this ticket.", - "rename":"Rename a ticket. This will change the channel name of this ticket.", - "claim":"Claim a ticket. With this, you can let your team know you are handling this ticket.", - "unclaim":"Unclaim a ticket. With this, you can let your team know that this ticket is free again.", - "add":"Add a user to a ticket. This will allow the user to read & write in this ticket.", - "remove":"Remove a user from a ticket. This will remove the ability to read & write for a user in this ticket.", - "panel":"Spawn a message with a dropdown or buttons (for ticket creation).", - "blacklistView":"View a list of the current blacklist.", + "help":"View all available commands.", + "ticket":"Create a ticket instantly.", + "close":"Close this ticket and disable messaging in the channel.", + "delete":"Delete this ticket (creates a transcript if enabled).", + "reopen":"Reopen a closed ticket and restore messaging.", + "pin":"Pin this ticket and move it to the top with '๐Ÿ“Œ'.", + "unpin":"Unpin this ticket and remove the '๐Ÿ“Œ' emoji.", + "move":"Move this ticket to a different option or ticket type.", + "rename":"Rename this ticket channel.", + "claim":"Claim this ticket to show you are handling it.", + "unclaim":"Unclaim this ticket to mark it as available.", + "add":"Add a user to this ticket.", + "remove":"Remove a user's access from this ticket.", + "panel":"Create a ticket panel with buttons or a dropdown.", + "blacklistView":"View all blacklisted users.", "blacklistAdd":"Add a user to the blacklist.", "blacklistRemove":"Remove a user from the blacklist.", - "blacklistGet":"Get the details from a blacklisted user.", - "statsGlobal":"View the global stats.", - "statsTicket":"View the stats of a ticket in the server.", - "statsUser":"View the stats from a user in the server.", - "statsReset":"Reset all the stats of the bot (and start counting from zero).", - "autocloseDisable":"Disable autoclose in this ticket.", - "autocloseEnable":"Enable autoclose in this ticket.", - "autodeleteDisable":"Disable autodelete in this ticket.", - "autodeleteEnable":"Enable autodelete in this ticket.", + "blacklistGet":"View details of a blacklisted user.", + "statsGlobal":"View global bot statistics.", + "statsTicket":"View statistics for a ticket.", + "statsUser":"View statistics for a user.", + "statsReset":"Reset all bot statistics.", + "autocloseDisable":"Disable autoclose for this ticket.", + "autocloseEnable":"Enable autoclose for this ticket.", + "autodeleteDisable":"Disable autodelete for this ticket.", + "autodeleteEnable":"Enable autodelete for this ticket.", "categories":{ "general":"General Commands", "basicTicket":"Basic Ticket Commands", @@ -545,10 +545,10 @@ }, "stats":{ "scopes":{ - "global":"Global Stats", - "system":"System Stats", - "user":"User Stats", - "ticket":"Ticket Stats", + "global":"Global Statistics", + "system":"System Statistics", + "user":"User Statistics", + "ticket":"Ticket Statistics", "participants":"Participants", "messages":"Messages" }, @@ -592,9 +592,9 @@ } }, "panel":{ - "selectTicket":"Select your ticket", - "selectRole":"Select your role", - "selectOption":"Select your option" + "selectTicket":"Select a ticket", + "selectRole":"Select a role", + "selectOption":"Select an option" }, "priorities":{ "urgent":"Urgent", diff --git a/languages/estonian.json b/languages/estonian.json index 241bfec..1225d79 100644 --- a/languages/estonian.json +++ b/languages/estonian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["iamnotmega","ChatGPT"], "lastedited":"16/02/2026", "language":"Estonian", diff --git a/languages/finnish.json b/languages/finnish.json index d922108..895a1bf 100644 --- a/languages/finnish.json +++ b/languages/finnish.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["iamnotmega","ChatGPT"], "lastedited":"16/02/2026", "language":"Finnish", diff --git a/languages/french.json b/languages/french.json index d50f971..38b582f 100644 --- a/languages/french.json +++ b/languages/french.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["guillee3"], "lastedited":"16/02/2026", "language":"French", diff --git a/languages/german.json b/languages/german.json index 5ab33e5..0c8e1c4 100644 --- a/languages/german.json +++ b/languages/german.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["benzorich"], "lastedited":"16/02/2026", "language":"German", @@ -14,8 +14,8 @@ "typeInfo":"[INFO]", "headerConfigChecker":"CONFIG CHECKER", "headerDescription":"Prรผfen Sie auf Fehler in Ihrer Konfigurationsdateien!", - "footerError":"Der Bot wird nicht starten, bis alle {0}'s repariert sind!", - "footerWarning":"Es wird empfohlen, vor dem Start alle {0}'s zu korrigieren!", + "footerError":"Der Bot wird nicht starten, bis alle {0}en repariert sind!", + "footerWarning":"Es wird empfohlen, vor dem Start alle {0}en zu korrigieren!", "footerSupport":"SUPPORT: {0} - DOCS: {1}", "compactInformation":"Verwenden Sie {0} fรผr weitere Informationen!", "dataPath":"Pfad", diff --git a/languages/greek.json b/languages/greek.json index 998e304..bc11230 100644 --- a/languages/greek.json +++ b/languages/greek.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], "lastedited":"16/02/2026", "language":"Greek", diff --git a/languages/hindi.json b/languages/hindi.json index 30dd621..0addf7e 100644 --- a/languages/hindi.json +++ b/languages/hindi.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["challenger_nova"], "lastedited":"16/02/2026", "language":"Hindi", diff --git a/languages/hungarian.json b/languages/hungarian.json index bf989b3..7908d10 100644 --- a/languages/hungarian.json +++ b/languages/hungarian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["Kornel0706"], "lastedited":"16/02/2026", "language":"Hungarian", diff --git a/languages/indonesian.json b/languages/indonesian.json index 66c1483..7ea791d 100644 --- a/languages/indonesian.json +++ b/languages/indonesian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["erxg"], "lastedited":"16/02/2026", "language":"Indonesian", diff --git a/languages/italian.json b/languages/italian.json index 28a6723..e7be54b 100644 --- a/languages/italian.json +++ b/languages/italian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["fraden1mvp.","imperatorix_17"], "lastedited":"16/02/2026", "language":"Italian", diff --git a/languages/japanese.json b/languages/japanese.json index f528a9a..ad91014 100644 --- a/languages/japanese.json +++ b/languages/japanese.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], "lastedited":"16/02/2026", "language":"Japanese", diff --git a/languages/khmer.json b/languages/khmer.json index aa347aa..4cd5c55 100644 --- a/languages/khmer.json +++ b/languages/khmer.json @@ -1,608 +1,608 @@ { - "_TRANSLATION": { - "otversion": "v4.1.3", - "translators": ["yuuslokrobjakkroval"], - "lastedited": "19/04/2026", - "language": "Khmer", - "automated": false - }, - "checker": { - "system": { - "typeError": "[แž€แŸ†แž แžปแžŸ]", - "headerOpenTicket": "OPEN TICKET", - "typeWarning": "[แž–แŸ’แžšแž˜แžถแž“]", - "typeInfo": "[แž–แŸแžแŸŒแž˜แžถแž“]", - "headerConfigChecker": "CONFIG CHECKER", - "headerDescription": "แž–แžทแž“แžทแžแŸ’แž™แž˜แžพแž›แž€แŸ†แž แžปแžŸแž€แŸ’แž“แžปแž„แžฏแž€แžŸแžถแžšแž€แŸ†แžŽแžแŸ‹แžšแž…แž“แžถแžŸแž˜แŸ’แž–แŸแž“แŸ’แž’!", - "footerError": "แž”แžผแžแž“แžนแž„แž˜แžทแž“แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แž‘แŸ แžšแž แžผแžแžŠแž›แŸ‹ {0} แž‘แžถแŸ†แž„แžขแžŸแŸ‹แžแŸ’แžšแžผแžœแž”แžถแž“แž‡แžฝแžŸแž‡แžปแž›!", - "footerWarning": "แžœแžถแžแŸ’แžšแžผแžœแž”แžถแž“แžŽแŸ‚แž“แžถแŸ†แžฑแŸ’แž™แž‡แžฝแžŸแž‡แžปแž› {0} แž‘แžถแŸ†แž„แžขแžŸแŸ‹แž˜แžปแž“แž–แŸแž›แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜!", - "footerSupport": "แž‡แŸ†แž“แžฝแž™: {0} - แžฏแž€แžŸแžถแžš: {1}", - "compactInformation": "แž”แŸ’แžšแžพ {0} แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž–แŸแžแŸŒแž˜แžถแž“แž”แž“แŸ’แžแŸ‚แž˜!", - "dataPath": "แž•แŸ’แž›แžผแžœ", - "dataDocs": "แžฏแž€แžŸแžถแžš", - "dataMessages": "แžŸแžถแžš" + "_TRANSLATION":{ + "otversion":"v4.2.0", + "translators":["yuuslokrobjakkroval"], + "lastedited":"20/05/2026", + "language":"Khmer", + "automated":false }, - "messages": { - "stringTooShort": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แžแŸ’แž›แžธแž‡แžถแž„ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", - "stringTooLong": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แžœแŸ‚แž„แž‡แžถแž„ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", - "stringLengthInvalid": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž˜แžถแž“ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", - "stringStartsWith": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžŠแŸ„แž™ {0}!", - "stringEndsWith": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž”แž‰แŸ’แž…แž”แŸ‹แžŠแŸ„แž™ {0}!", - "stringContains": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž˜แžถแž“ {0}!", - "stringChoices": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžขแžถแž…แž‡แžถแžแž˜แŸ’แž›แŸƒแžŠแžผแž…แžแžถแž„แž€แŸ’แžšแŸ„แž˜แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡: {0}!", - "stringRegex": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", - "stringInvertedContains": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž˜แžถแž“ {0}!", - "stringLowercase": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แžŸแžšแžŸแŸแžšแž‡แžถแžขแž€แŸ’แžŸแžšแžแžผแž…แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡!", - "stringUppercase": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แžŸแžšแžŸแŸแžšแž‡แžถแžขแž€แŸ’แžŸแžšแž’แŸ†แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡!", - "stringSpecialCharacters": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž˜แžถแž“แžแžฝแžขแž€แŸ’แžŸแžšแž–แžทแžŸแŸแžŸ! (a-z, 0-9 แž“แžทแž„แžŠแž€แžƒแŸ’แž›แžถแž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡)", - "stringNoSpaces": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž˜แžถแž“แžŠแž€แžƒแŸ’แž›แžถ!", - "stringCapitalWord": "แžœแžถแžแŸ’แžšแžผแžœแž”แžถแž“แžŽแŸ‚แž“แžถแŸ†แžฑแŸ’แž™แž–แžถแž€แŸ’แž™แž“แžธแž˜แžฝแž™แŸ—แž€แŸ’แž“แžปแž„แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžŠแŸ„แž™แžขแž€แŸ’แžŸแžšแž’แŸ†!", - "stringCapitalSentence": "แžœแžถแž˜แžพแž›แž‘แŸ…แžแžถแž”แŸ’แžšแž™แŸ„แž‚แžแŸ’แž›แŸ‡แž€แŸ’แž“แžปแž„แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžŠแŸ„แž™แžขแž€แŸ’แžŸแžšแž’แŸ†!", - "stringPunctuation": "แžœแžถแž˜แžพแž›แž‘แŸ…แžแžถแž”แŸ’แžšแž™แŸ„แž‚แž€แŸ’แž“แžปแž„แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แž”แž‰แŸ’แž…แž”แŸ‹แžŠแŸ„แž™แžŸแž‰แŸ’แž‰แžถแžœแžŽแŸ’แžŽแž™แžปแžแŸ’แžแžท!", + "checker":{ + "system":{ + "typeError":"[แž€แŸ†แž แžปแžŸ]", + "headerOpenTicket":"OPEN TICKET", + "typeWarning":"[แž–แŸ’แžšแž˜แžถแž“]", + "typeInfo":"[แž–แŸแžแŸŒแž˜แžถแž“]", + "headerConfigChecker":"CONFIG CHECKER", + "headerDescription":"แž–แžทแž“แžทแžแŸ’แž™แž˜แžพแž›แž€แŸ†แž แžปแžŸแž€แŸ’แž“แžปแž„แžฏแž€แžŸแžถแžšแž€แŸ†แžŽแžแŸ‹แžšแž…แž“แžถแžŸแž˜แŸ’แž–แŸแž“แŸ’แž’!", + "footerError":"แž”แžผแžแž“แžนแž„แž˜แžทแž“แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แž‘แŸ แžšแž แžผแžแžŠแž›แŸ‹ {0} แž‘แžถแŸ†แž„แžขแžŸแŸ‹แžแŸ’แžšแžผแžœแž”แžถแž“แž‡แžฝแžŸแž‡แžปแž›!", + "footerWarning":"แžœแžถแžแŸ’แžšแžผแžœแž”แžถแž“แžŽแŸ‚แž“แžถแŸ†แžฑแŸ’แž™แž‡แžฝแžŸแž‡แžปแž› {0} แž‘แžถแŸ†แž„แžขแžŸแŸ‹แž˜แžปแž“แž–แŸแž›แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜!", + "footerSupport":"แž‡แŸ†แž“แžฝแž™: {0} - แžฏแž€แžŸแžถแžš: {1}", + "compactInformation":"แž”แŸ’แžšแžพ {0} แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž–แŸแžแŸŒแž˜แžถแž“แž”แž“แŸ’แžแŸ‚แž˜!", + "dataPath":"แž•แŸ’แž›แžผแžœ", + "dataDocs":"แžฏแž€แžŸแžถแžš", + "dataMessages":"แžŸแžถแžš" + }, + "messages":{ + "stringTooShort":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แžแŸ’แž›แžธแž‡แžถแž„ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", + "stringTooLong":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แžœแŸ‚แž„แž‡แžถแž„ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", + "stringLengthInvalid":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž˜แžถแž“ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", + "stringStartsWith":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžŠแŸ„แž™ {0}!", + "stringEndsWith":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž”แž‰แŸ’แž…แž”แŸ‹แžŠแŸ„แž™ {0}!", + "stringContains":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž˜แžถแž“ {0}!", + "stringChoices":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžขแžถแž…แž‡แžถแžแž˜แŸ’แž›แŸƒแžŠแžผแž…แžแžถแž„แž€แŸ’แžšแŸ„แž˜แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡: {0}!", + "stringRegex":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", + "stringInvertedContains":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž˜แžถแž“ {0}!", + "stringLowercase":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แžŸแžšแžŸแŸแžšแž‡แžถแžขแž€แŸ’แžŸแžšแžแžผแž…แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡!", + "stringUppercase":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แžŸแžšแžŸแŸแžšแž‡แžถแžขแž€แŸ’แžŸแžšแž’แŸ†แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡!", + "stringSpecialCharacters":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž˜แžถแž“แžแžฝแžขแž€แŸ’แžŸแžšแž–แžทแžŸแŸแžŸ! (a-z, 0-9 แž“แžทแž„แžŠแž€แžƒแŸ’แž›แžถแž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡)", + "stringNoSpaces":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž˜แžถแž“แžŠแž€แžƒแŸ’แž›แžถ!", + "stringCapitalWord":"แžœแžถแžแŸ’แžšแžผแžœแž”แžถแž“แžŽแŸ‚แž“แžถแŸ†แžฑแŸ’แž™แž–แžถแž€แŸ’แž™แž“แžธแž˜แžฝแž™แŸ—แž€แŸ’แž“แžปแž„แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžŠแŸ„แž™แžขแž€แŸ’แžŸแžšแž’แŸ†!", + "stringCapitalSentence":"แžœแžถแž˜แžพแž›แž‘แŸ…แžแžถแž”แŸ’แžšแž™แŸ„แž‚แžแŸ’แž›แŸ‡แž€แŸ’แž“แžปแž„แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžŠแŸ„แž™แžขแž€แŸ’แžŸแžšแž’แŸ†!", + "stringPunctuation":"แžœแžถแž˜แžพแž›แž‘แŸ…แžแžถแž”แŸ’แžšแž™แŸ„แž‚แž€แŸ’แž“แžปแž„แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แž˜แžทแž“แž”แž‰แŸ’แž…แž”แŸ‹แžŠแŸ„แž™แžŸแž‰แŸ’แž‰แžถแžœแžŽแŸ’แžŽแž™แžปแžแŸ’แžแžท!", - "numberTooShort": "แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แžแŸ’แž›แžธแž‡แžถแž„ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", - "numberTooLong": "แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แžœแŸ‚แž„แž‡แžถแž„ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", - "numberLengthInvalid": "แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž˜แžถแž“ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", - "numberTooSmall": "แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž™แŸ‰แžถแž„แž แŸ„แž…แžŽแžถแžŸแŸ‹ {0}!", - "numberTooLarge": "แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž {0}!", - "numberNotEqual": "แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž‡แžถ {0}!", - "numberStep": "แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž‡แžถแž…แŸ’แžšแžพแž“แžŠแž„ {0}!", - "numberStepOffset": "แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž‡แžถแž…แŸ’แžšแžพแž“แžŠแž„ {0} แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แž–แžธ {1}!", - "numberStartsWith": "แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžŠแŸ„แž™ {0}!", - "numberEndsWith": "แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž”แž‰แŸ’แž…แž”แŸ‹แžŠแŸ„แž™ {0}!", - "numberContains": "แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž˜แžถแž“ {0}!", - "numberChoices": "แž›แŸแžแž“แŸแŸ‡แžขแžถแž…แž‡แžถแžแž˜แŸ’แž›แŸƒแžŠแžผแž…แžแžถแž„แž€แŸ’แžšแŸ„แž˜แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡: {0}!", - "numberFloat": "แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถแž‘แžŸแž—แžถแž‚!", - "numberNegative": "แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถแžขแžœแžทแž‡แŸ’แž‡แž˜แžถแž“!", - "numberPositive": "แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถแžœแžทแž‡แŸ’แž‡แž˜แžถแž“!", - "numberZero": "แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถแžŸแžผแž“แŸ’แž™!", - "numberNan": "แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถ NaN (แž˜แžทแž“แž˜แŸ‚แž“แž‡แžถแž›แŸแž)!", - "numberInvertedContains": "แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž˜แžถแž“ {0}!", + "numberTooShort":"แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แžแŸ’แž›แžธแž‡แžถแž„ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", + "numberTooLong":"แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แžœแŸ‚แž„แž‡แžถแž„ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", + "numberLengthInvalid":"แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž˜แžถแž“ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", + "numberTooSmall":"แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž™แŸ‰แžถแž„แž แŸ„แž…แžŽแžถแžŸแŸ‹ {0}!", + "numberTooLarge":"แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž {0}!", + "numberNotEqual":"แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž‡แžถ {0}!", + "numberStep":"แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž‡แžถแž…แŸ’แžšแžพแž“แžŠแž„ {0}!", + "numberStepOffset":"แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž‡แžถแž…แŸ’แžšแžพแž“แžŠแž„ {0} แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แž–แžธ {1}!", + "numberStartsWith":"แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžŠแŸ„แž™ {0}!", + "numberEndsWith":"แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž”แž‰แŸ’แž…แž”แŸ‹แžŠแŸ„แž™ {0}!", + "numberContains":"แž›แŸแžแž“แŸแŸ‡แžแŸ’แžšแžผแžœแžแŸ‚แž˜แžถแž“ {0}!", + "numberChoices":"แž›แŸแžแž“แŸแŸ‡แžขแžถแž…แž‡แžถแžแž˜แŸ’แž›แŸƒแžŠแžผแž…แžแžถแž„แž€แŸ’แžšแŸ„แž˜แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡: {0}!", + "numberFloat":"แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถแž‘แžŸแž—แžถแž‚!", + "numberNegative":"แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถแžขแžœแžทแž‡แŸ’แž‡แž˜แžถแž“!", + "numberPositive":"แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถแžœแžทแž‡แŸ’แž‡แž˜แžถแž“!", + "numberZero":"แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถแžŸแžผแž“แŸ’แž™!", + "numberNan":"แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถ NaN (แž˜แžทแž“แž˜แŸ‚แž“แž‡แžถแž›แŸแž)!", + "numberInvertedContains":"แž›แŸแžแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž˜แžถแž“ {0}!", - "booleanTrue": "Boolean แž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถ true!", - "booleanFalse": "Boolean แž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถ false!", + "booleanTrue":"Boolean แž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถ true!", + "booleanFalse":"Boolean แž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถ false!", - "arrayEmptyDisabled": "แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž‘แž‘แŸ!", - "arrayEmptyRequired": "แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž‘แžถแž˜แž‘แžถแžšแžฑแŸ’แž™แž‘แž‘แŸ!", - "arrayTooShort": "แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžšแž”แŸ’แžšแžœแŸ‚แž„แž™แŸ‰แžถแž„แž แŸ„แž…แžŽแžถแžŸแŸ‹ {0}!", - "arrayTooLong": "แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžšแž”แŸ’แžšแžœแŸ‚แž„แž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž {0}!", - "arrayLengthInvalid": "แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžšแž”แŸ’แžšแžœแŸ‚แž„ {0}!", - "arrayInvalidTypes": "แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แžขแžถแž…แž˜แžถแž“แž”แŸ’แžšแž—แŸแž‘แžŠแžผแž…แžแžถแž„แž€แŸ’แžšแŸ„แž˜แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡: {0}!", - "arrayDouble": "แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แž˜แžทแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แžแž˜แŸ’แž›แŸƒแžŠแžผแž…แž‚แŸ’แž“แžถ!", + "arrayEmptyDisabled":"แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž‘แž‘แŸ!", + "arrayEmptyRequired":"แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž‘แžถแž˜แž‘แžถแžšแžฑแŸ’แž™แž‘แž‘แŸ!", + "arrayTooShort":"แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžšแž”แŸ’แžšแžœแŸ‚แž„แž™แŸ‰แžถแž„แž แŸ„แž…แžŽแžถแžŸแŸ‹ {0}!", + "arrayTooLong":"แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžšแž”แŸ’แžšแžœแŸ‚แž„แž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž {0}!", + "arrayLengthInvalid":"แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžšแž”แŸ’แžšแžœแŸ‚แž„ {0}!", + "arrayInvalidTypes":"แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แžขแžถแž…แž˜แžถแž“แž”แŸ’แžšแž—แŸแž‘แžŠแžผแž…แžแžถแž„แž€แŸ’แžšแŸ„แž˜แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡: {0}!", + "arrayDouble":"แžขแžถแžšแŸ‰แŸแž“แŸแŸ‡แž˜แžทแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แžแž˜แŸ’แž›แŸƒแžŠแžผแž…แž‚แŸ’แž“แžถ!", - "discordInvalidId": "แž“แŸแŸ‡แž‡แžถ discord {0} id แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", - "discordInvalidIdOptions": "แž“แŸแŸ‡แž‡แžถ discord {0} id แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ! แžขแŸ’แž“แž€แž€แŸแžขแžถแž…แž”แŸ’แžšแžพ: {1}!", - "discordInvalidToken": "แž“แŸแŸ‡แž‡แžถ discord token แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ (แž€แŸ’แž“แžปแž„แž‘แž˜แŸ’แžšแž„แŸ‹)!", - "colorInvalid": "แž“แŸแŸ‡แž‡แžถแž–แžŽแŸŒ hex แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", - "emojiTooShort": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžš emoji แž™แŸ‰แžถแž„แž แŸ„แž…แžŽแžถแžŸแŸ‹ {0}!", - "emojiTooLong": "แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžš emoji แž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž {0}!", - "emojiCustom": "Emoji แž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถ custom discord emoji!", - "emojiInvalid": "แž“แŸแŸ‡แž‡แžถ emoji แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", - "urlInvalid": "URL แž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", - "urlInvalidHttp": "URL แž“แŸแŸ‡แžขแžถแž…แž”แŸ’แžšแžพแžแŸ‚ https:// protocol แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡!", - "urlInvalidProtocol": "URL แž“แŸแŸ‡แžขแžถแž…แž”แŸ’แžšแžพแžแŸ‚ http:// แž“แžทแž„ https:// protocols แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡!", - "urlInvalidHostname": "URL แž“แŸแŸ‡แž˜แžถแž“ hostname แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแž!", - "urlInvalidExtension": "URL แž“แŸแŸ‡แž˜แžถแž“แž•แŸ’แž“แŸ‚แž€แž”แž“แŸ’แžแŸ‚แž˜แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ! แž‡แŸ’แžšแžพแžŸแžšแžพแžŸ: {0}!", - "urlInvalidPath": "URL แž“แŸแŸ‡แž˜แžถแž“แž•แŸ’แž›แžผแžœแž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", - "idNotUnique": "id แž“แŸแŸ‡แž˜แžทแž“แžแŸ‚แž˜แžฝแž™, แž”แŸ’แžšแžพ id แž•แŸ’แžŸแŸแž„แž‘แŸ€แž!", - "idNonExistent": "id {0} แž˜แžทแž“แž˜แžถแž“!", + "discordInvalidId":"แž“แŸแŸ‡แž‡แžถ discord {0} id แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", + "discordInvalidIdOptions":"แž“แŸแŸ‡แž‡แžถ discord {0} id แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ! แžขแŸ’แž“แž€แž€แŸแžขแžถแž…แž”แŸ’แžšแžพ: {1}!", + "discordInvalidToken":"แž“แŸแŸ‡แž‡แžถ discord token แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ (แž€แŸ’แž“แžปแž„แž‘แž˜แŸ’แžšแž„แŸ‹)!", + "colorInvalid":"แž“แŸแŸ‡แž‡แžถแž–แžŽแŸŒ hex แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", + "emojiTooShort":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžš emoji แž™แŸ‰แžถแž„แž แŸ„แž…แžŽแžถแžŸแŸ‹ {0}!", + "emojiTooLong":"แžแŸ’แžŸแŸ‚แžขแž€แŸ’แžŸแžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžš emoji แž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž {0}!", + "emojiCustom":"Emoji แž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถ custom discord emoji!", + "emojiInvalid":"แž“แŸแŸ‡แž‡แžถ emoji แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", + "urlInvalid":"URL แž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", + "urlInvalidHttp":"URL แž“แŸแŸ‡แžขแžถแž…แž”แŸ’แžšแžพแžแŸ‚ https:// protocol แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡!", + "urlInvalidProtocol":"URL แž“แŸแŸ‡แžขแžถแž…แž”แŸ’แžšแžพแžแŸ‚ http:// แž“แžทแž„ https:// protocols แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡!", + "urlInvalidHostname":"URL แž“แŸแŸ‡แž˜แžถแž“ hostname แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแž!", + "urlInvalidExtension":"URL แž“แŸแŸ‡แž˜แžถแž“แž•แŸ’แž“แŸ‚แž€แž”แž“แŸ’แžแŸ‚แž˜แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ! แž‡แŸ’แžšแžพแžŸแžšแžพแžŸ: {0}!", + "urlInvalidPath":"URL แž“แŸแŸ‡แž˜แžถแž“แž•แŸ’แž›แžผแžœแž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", + "idNotUnique":"id แž“แŸแŸ‡แž˜แžทแž“แžแŸ‚แž˜แžฝแž™, แž”แŸ’แžšแžพ id แž•แŸ’แžŸแŸแž„แž‘แŸ€แž!", + "idNonExistent":"id {0} แž˜แžทแž“แž˜แžถแž“!", - "invalidType": "แž›แž€แŸ’แžแžŽแŸˆแžŸแž˜แŸ’แž”แžแŸ’แžแžทแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž‡แžถแž”แŸ’แžšแž—แŸแž‘: {0}!", - "propertyMissing": "แž›แž€แŸ’แžแžŽแŸˆแžŸแž˜แŸ’แž”แžแŸ’แžแžท {0} แž”แžถแžแŸ‹แž–แžธแžœแžแŸ’แžแžปแž“แŸแŸ‡!", - "propertyOptional": "แž›แž€แŸ’แžแžŽแŸˆแžŸแž˜แŸ’แž”แžแŸ’แžแžท {0} แž‡แžถแž‡แž˜แŸ’แžšแžพแžŸแžŸแž˜แŸ’แžšแžถแž”แŸ‹แžœแžแŸ’แžแžปแž“แŸแŸ‡!", - "objectDisabled": "แžœแžแŸ’แžแžปแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘, แž”แžพแž€แžœแžถแžŠแŸ„แž™แž”แŸ’แžšแžพ {0}!", - "nullInvalid": "แž›แž€แŸ’แžแžŽแŸˆแžŸแž˜แŸ’แž”แžแŸ’แžแžทแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถ null!", - "switchInvalidType": "แž“แŸแŸ‡แžแŸ’แžšแžผแžœแž‡แžถแž”แŸ’แžšแž—แŸแž‘แžŠแžผแž…แžแžถแž„แž€แŸ’แžšแŸ„แž˜: {0}!", - "objectSwitchInvalid": "แžœแžแŸ’แžแžปแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž‡แžถแž”แŸ’แžšแž—แŸแž‘แžŠแžผแž…แžแžถแž„แž€แŸ’แžšแŸ„แž˜: {0}!", + "invalidType":"แž›แž€แŸ’แžแžŽแŸˆแžŸแž˜แŸ’แž”แžแŸ’แžแžทแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž‡แžถแž”แŸ’แžšแž—แŸแž‘: {0}!", + "propertyMissing":"แž›แž€แŸ’แžแžŽแŸˆแžŸแž˜แŸ’แž”แžแŸ’แžแžท {0} แž”แžถแžแŸ‹แž–แžธแžœแžแŸ’แžแžปแž“แŸแŸ‡!", + "propertyOptional":"แž›แž€แŸ’แžแžŽแŸˆแžŸแž˜แŸ’แž”แžแŸ’แžแžท {0} แž‡แžถแž‡แž˜แŸ’แžšแžพแžŸแžŸแž˜แŸ’แžšแžถแž”แŸ‹แžœแžแŸ’แžแžปแž“แŸแŸ‡!", + "objectDisabled":"แžœแžแŸ’แžแžปแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘, แž”แžพแž€แžœแžถแžŠแŸ„แž™แž”แŸ’แžšแžพ {0}!", + "nullInvalid":"แž›แž€แŸ’แžแžŽแŸˆแžŸแž˜แŸ’แž”แžแŸ’แžแžทแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž‡แžถ null!", + "switchInvalidType":"แž“แŸแŸ‡แžแŸ’แžšแžผแžœแž‡แžถแž”แŸ’แžšแž—แŸแž‘แžŠแžผแž…แžแžถแž„แž€แŸ’แžšแŸ„แž˜: {0}!", + "objectSwitchInvalid":"แžœแžแŸ’แžแžปแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž‡แžถแž”แŸ’แžšแž—แŸแž‘แžŠแžผแž…แžแžถแž„แž€แŸ’แžšแŸ„แž˜: {0}!", - "invalidLanguage": "แž“แŸแŸ‡แž‡แžถแž—แžถแžŸแžถแž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", - "invalidButton": "แž”แŸŠแžผแžแžปแž“แž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžš {0} แžฌ {1} แž™แŸ‰แžถแž„แžแžทแž…แžŽแžถแžŸแŸ‹!", - "unusedOption": "แž‡แž˜แŸ’แžšแžพแžŸ {0} แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžšแžพ!", - "unusedQuestion": "แžŸแŸ†แžŽแžฝแžš {0} แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžšแžพ!", - "dropdownOption": "Panel แžŠแŸ‚แž›แž˜แžถแž“ dropdown แž”แžพแž€แžขแžถแž…แž˜แžถแž“แžแŸ‚แž‡แž˜แŸ’แžšแžพแžŸแž”แŸ’แžšแž—แŸแž‘ 'ticket' แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡!", - "customInvalidVersion": "แž€แŸ†แžŽแŸ‚แžŠแŸ‚แž›แž”แžถแž“แž”แž‰แŸ’แž‡แžถแž€แŸ‹แž€แŸ’แž“แžปแž„ config แžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แž˜แžทแž“แžแŸ’แžšแžผแžœแž‚แŸ’แž“แžถ! แžŸแžผแž˜แž”แŸ’แžšแžถแž€แžŠแžแžถแžขแŸ’แž“แž€แž”แžถแž“แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž– config แž‘แŸ…แž€แŸ†แžŽแŸ‚แžแŸ’แž˜แžธแž”แŸ†แž•แžปแž!" + "invalidLanguage":"แž“แŸแŸ‡แž‡แžถแž—แžถแžŸแžถแž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", + "invalidButton":"แž”แŸŠแžผแžแžปแž“แž“แŸแŸ‡แžแŸ’แžšแžผแžœแž€แžถแžš {0} แžฌ {1} แž™แŸ‰แžถแž„แžแžทแž…แžŽแžถแžŸแŸ‹!", + "unusedOption":"แž‡แž˜แŸ’แžšแžพแžŸ {0} แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžšแžพ!", + "unusedQuestion":"แžŸแŸ†แžŽแžฝแžš {0} แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžšแžพ!", + "dropdownOption":"Panel แžŠแŸ‚แž›แž˜แžถแž“ dropdown แž”แžพแž€แžขแžถแž…แž˜แžถแž“แžแŸ‚แž‡แž˜แŸ’แžšแžพแžŸแž”แŸ’แžšแž—แŸแž‘ 'ticket' แž”แŸ‰แžปแžŽแŸ’แžŽแŸ„แŸ‡!", + "customInvalidVersion":"แž€แŸ†แžŽแŸ‚แžŠแŸ‚แž›แž”แžถแž“แž”แž‰แŸ’แž‡แžถแž€แŸ‹แž€แŸ’แž“แžปแž„ config แžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แž˜แžทแž“แžแŸ’แžšแžผแžœแž‚แŸ’แž“แžถ! แžŸแžผแž˜แž”แŸ’แžšแžถแž€แžŠแžแžถแžขแŸ’แž“แž€แž”แžถแž“แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž– config แž‘แŸ…แž€แŸ†แžŽแŸ‚แžแŸ’แž˜แžธแž”แŸ†แž•แžปแž!" + } + }, + "actions":{ + "buttons":{ + "create":"แž…แžผแž›แž˜แžพแž›แžŸแŸ†แž”แžปแžแŸ’แžš", + "close":"แž”แžทแž‘แžŸแŸ†แž”แžปแžแŸ’แžš", + "delete":"แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžš", + "reopen":"แž”แžพแž€แžŸแŸ†แž”แžปแžแŸ’แžšแžกแžพแž„แžœแžทแž‰", + "claim":"แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžš", + "unclaim":"แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžš", + "pin":"แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžš", + "unpin":"แžŠแž€แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžš", + "clear":"แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแž‘แžถแŸ†แž„แžขแžŸแŸ‹", + "helpSwitchSlash":"แž˜แžพแž›แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถ Slash", + "helpSwitchText":"แž˜แžพแž›แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžขแž€แŸ’แžŸแžš", + "helpPage":"แž‘แŸ†แž–แŸแžš {0}", + "withReason":"แž‡แžถแž˜แžฝแž™แž แŸแžแžปแž•แž›", + "withoutTranscript":"แž‚แŸ’แž˜แžถแž“ Transcript" + }, + "titles":{ + "created":"แž”แžถแž“แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš", + "close":"แž”แžถแž“แž”แžทแž‘แžŸแŸ†แž”แžปแžแŸ’แžš", + "delete":"แž”แžถแž“แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžš", + "reopen":"แž”แžถแž“แž”แžพแž€แžŸแŸ†แž”แžปแžแŸ’แžšแžกแžพแž„แžœแžทแž‰", + "claim":"แž”แžถแž“แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžš", + "unclaim":"แž”แžถแž“แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžš", + "pin":"แž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžš", + "unpin":"แž”แžถแž“แžŠแž€แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžš", + "rename":"แž”แžถแž“แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แžŸแŸ†แž”แžปแžแŸ’แžš", + "move":"แž”แžถแž“แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแžŸแŸ†แž”แžปแžแŸ’แžš", + "add":"แž”แžถแž“แž”แž“แŸ’แžแŸ‚แž˜แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžš", + "remove":"แž”แžถแž“แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžš", + + "help":"แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŠแŸ‚แž›แž˜แžถแž“", + "statsReset":"แž€แŸ†แžŽแžแŸ‹แžŸแŸ’แžแžทแžแžทแžกแžพแž„แžœแžทแž‰", + "blacklistAdd":"แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", + "blacklistRemove":"แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแŸ„แŸ‡แž›แŸ‚แž„", + "blacklistGet":"แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", + "blacklistView":"แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“", + "blacklistAddDm":"แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž“แŸ’แžแŸ‚แž˜แž‘แŸ…แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", + "blacklistRemoveDm":"แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", + "clear":"แž”แžถแž“แžŸแž˜แŸ’แžขแžถแžแžŸแŸ†แž”แžปแžแŸ’แžš", + "clearTickets":"แžŸแž˜แŸ’แžขแžถแžแžŸแŸ†แž”แžปแžแŸ’แžš", + "roles":"แžแžฝแž“แžถแž‘แžธแž”แžถแž“แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž–", + + "autoclose":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท", + "autocloseEnabled":"แž”แžถแž“แž”แžพแž€ Autoclose", + "autocloseDisabled":"แž”แžถแž“แž”แžทแž‘ Autoclose", + "autodelete":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท", + "autodeleteEnabled":"แž”แžถแž“แž”แžพแž€ Autodelete", + "autodeleteDisabled":"แž”แžถแž“แž”แžทแž‘ Autodelete", + + "topicSet":"แž”แžถแž“แž”แŸ’แžแžผแžšแž”แŸ’แžšแž’แžถแž“แž”แž‘", + "prioritySet":"แž”แžถแž“แž”แŸ’แžแžผแžšแžขแžถแž‘แžทแž—แžถแž–", + "priorityGet":"แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžš", + "transfer":"แž”แžถแž“แž•แŸ’แž‘แŸแžšแžŸแŸ†แž”แžปแžแŸ’แžš" + }, + "descriptions":{ + "create":"แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแŸ” แž…แžปแž…แž”แŸŠแžผแžแžปแž„แžแžถแž„แž€แŸ’แžšแŸ„แž˜แžŠแžพแž˜แŸ’แž”แžธแž…แžผแž›แž”แŸ’แžšแžพ!", + "close":"แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "delete":"แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "reopen":"แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž”แžพแž€แžกแžพแž„แžœแžทแž‰แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "claim":"แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž‘แž‘แžฝแž›แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "unclaim":"แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "pin":"แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "unpin":"แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž˜แŸ’แž‡แžปแž›แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "rename":"แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž‘แŸ… {0} แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "move":"แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแž‘แŸ… {0} แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "add":"{0} แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž“แŸ’แžแŸ‚แž˜แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "remove":"{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + + "helpExplanation":"`` => แž”แŸ‰แžถแžšแŸ‰แžถแž˜แŸ‰แŸ‚แžแŸ’แžšแž…แžถแŸ†แž”แžถแž…แŸ‹\n`[name]` => แž”แŸ‰แžถแžšแŸ‰แžถแž˜แŸ‰แŸ‚แžแŸ’แžšแžŸแŸ’แžšแŸแž…แž…แžทแžแŸ’แž", + "statsReset":"แžŸแŸ’แžแžทแžแžทแž”แžผแžแžแŸ’แžšแžผแžœแž”แžถแž“แž€แŸ†แžŽแžแŸ‹แžกแžพแž„แžœแžทแž‰แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "statsError":"แž˜แžทแž“แžขแžถแž…แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแžŸแŸ†แž”แžปแžแŸ’แžš!\n{0} แž˜แžทแž“แž˜แŸ‚แž“แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžš!", + "blacklistAdd":"{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "blacklistRemove":"{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแŸ„แŸ‡แž›แŸ‚แž„แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "blacklistGetSuccess":"{0} แžŸแŸ’แžแžทแžแž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“!", + "blacklistGetEmpty":"{0} แž˜แžทแž“แžŸแŸ’แžแžทแžแž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“!", + "blacklistViewEmpty":"แž˜แžทแž“แž‘แžถแž“แŸ‹แž˜แžถแž“แžขแŸ’แž“แž€แžŽแžถแžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…!", + "blacklistViewTip":"แž”แŸ’แžšแžพ \"/blacklist add\" แžŠแžพแž˜แŸ’แž”แžธแžŠแžถแž€แŸ‹แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…!", + "clearVerify":"แžแžพแžขแŸ’แž“แž€แž”แŸ’แžšแžถแž€แžŠแžแžถแž…แž„แŸ‹แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“?\nแžŸแž€แž˜แŸ’แž˜แž—แžถแž–แž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แžแŸ’แžšแžกแž”แŸ‹แž˜แž€แžœแžทแž‰!", + "clearReady":"แžŸแŸ†แž”แžปแžแŸ’แžš {0} แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "rolesEmpty":"แž˜แžทแž“แž˜แžถแž“แžแžฝแž“แžถแž‘แžธแžŽแžถแžแŸ’แžšแžผแžœแž”แžถแž“แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž–!", + + "autocloseLeave":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แž”แž„แŸ’แž€แžพแžแž”แžถแž“แž…แžถแž€แž…แŸแž‰แž–แžธแž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ!", + "autocloseTimeout":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แžŠแŸ„แž™แžŸแžถแžšแžœแžถแž˜แžทแž“แžŸแž€แž˜แŸ’แž˜แž›แžพแžŸแž–แžธ `{0}h`!", + "autodeleteLeave":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แž”แž„แŸ’แž€แžพแžแž”แžถแž“แž…แžถแž€แž…แŸแž‰แž–แžธแž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ!", + "autodeleteTimeout":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แžŠแŸ„แž™แžŸแžถแžšแžœแžถแž˜แžทแž“แžŸแž€แž˜แŸ’แž˜แž›แžพแžŸแž–แžธ `{0} แžแŸ’แž„แŸƒ`!", + "autocloseEnabled":"Autoclose แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžพแž€แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡!\nแžœแžถแž“แžนแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แž“แŸ…แž–แŸแž›แž˜แžทแž“แžŸแž€แž˜แŸ’แž˜แž›แžพแžŸแž–แžธ `{0}h`!", + "autocloseDisabled":"Autoclose แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡!\nแžœแžถแž“แžนแž„แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžทแž‘แŸ€แžแž‘แŸ!", + "autodeleteEnabled":"Autodelete แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžพแž€แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡!\nแžœแžถแž“แžนแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แž“แŸ…แž–แŸแž›แž˜แžทแž“แžŸแž€แž˜แŸ’แž˜แž›แžพแžŸแž–แžธ `{0} แžแŸ’แž„แŸƒ`!", + "autodeleteDisabled":"Autodelete แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡!\nแžœแžถแž“แžนแž„แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžทแž‘แŸ€แžแž‘แŸ!", + + "ticketMessageLimit":"แžขแŸ’แž“แž€แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš {0} แž€แŸ’แž“แžปแž„แž–แŸแž›แžแŸ‚แž˜แžฝแž™!", + "ticketMessageAutoclose":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž“แžนแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แž“แŸ…แž–แŸแž›แž˜แžทแž“แžŸแž€แž˜แŸ’แž˜ {0}h!", + "ticketMessageAutodelete":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž“แžนแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แž“แŸ…แž–แŸแž›แž˜แžทแž“แžŸแž€แž˜แŸ’แž˜ {0} แžแŸ’แž„แŸƒ!", + "panelReady":"Panel แž˜แžถแž“แž“แŸ…แž€แŸ’แž“แžปแž„แžŸแžถแžšแž”แž“แŸ’แž!\nแžŸแžถแžšแž“แŸแŸ‡แžขแžถแž…แž›แžปแž”แž”แžถแž“แž แžพแž™!", + + "topicSet":"แž”แŸ’แžšแž’แžถแž“แž”แž‘แž”แžŽแŸ’แžแžถแž‰แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแžŠแŸ„แž™ {0} แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "prioritySet":"แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแž‘แŸ… {0} แžŠแŸ„แž™ {1} แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", + "priorityGet":"แžขแžถแž‘แžทแž—แžถแž–แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แžšแž”แžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž‚แžบ {0}แŸ”", + "transfer":"แž—แžถแž–แž‡แžถแž˜แŸ’แž…แžถแžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž‘แŸแžšแž–แžธ {0} แž‘แŸ… {1} แžŠแŸ„แž™ {2} แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!" + }, + "modal":{ + "closePlaceholder":"แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แž”แžทแž‘แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?", + "deletePlaceholder":"แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?", + "reopenPlaceholder":"แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แž”แžพแž€แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžกแžพแž„แžœแžทแž‰?", + "claimPlaceholder":"แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?", + "unclaimPlaceholder":"แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?", + "pinPlaceholder":"แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?", + "unpinPlaceholder":"แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แžŠแž€แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?" + }, + "logs":{ + "createLog":"แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแžŠแŸ„แž™ {0}!", + "closeLog":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™ {0}!", + "closeDm":"แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "deleteLog":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™ {0}!", + "deleteDm":"แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "reopenLog":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžพแž€แžกแžพแž„แžœแžทแž‰แžŠแŸ„แž™ {0}!", + "reopenDm":"แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžพแž€แžกแžพแž„แžœแžทแž‰แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "claimLog":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž‘แž‘แžฝแž›แžŠแŸ„แž™ {0}!", + "claimDm":"แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž‘แž‘แžฝแž›แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "unclaimLog":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŠแŸ„แž™ {0}!", + "unclaimDm":"แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž›แŸ‚แž„แž‘แž‘แžฝแž›แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "pinLog":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŠแŸ„แž™ {0}!", + "pinDm":"แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "unpinLog":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž˜แŸ’แž‡แžปแž›แžŠแŸ„แž™ {0}!", + "unpinDm":"แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž˜แŸ’แž‡แžปแž›แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "renameLog":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž‘แŸ… {0} แžŠแŸ„แž™ {1}!", + "renameDm":"แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž‘แŸ… {0} แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "moveLog":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแž‘แŸ… {0} แžŠแŸ„แž™ {1}!", + "moveDm":"แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแž‘แŸ… {0} แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "addLog":"{0} แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž“แŸ’แžแŸ‚แž˜แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžŠแŸ„แž™ {1}!", + "addDm":"{0} แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž“แŸ’แžแŸ‚แž˜แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "removeLog":"{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžŠแŸ„แž™ {1}!", + "removeDm":"{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + + "blacklistAddLog":"{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แžŠแŸ„แž™ {1}!", + "blacklistRemoveLog":"{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แžŠแŸ„แž™ {1}!", + "blacklistAddDm":"แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!\nแž…แžถแž”แŸ‹แž–แžธแž–แŸแž›แž“แŸแŸ‡ แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš!", + "blacklistRemoveDm":"แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!\nแžฅแžกแžผแžœแžขแŸ’แž“แž€แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž˜แŸ’แžแž„แž‘แŸ€แž!", + "clearLog":"แžŸแŸ†แž”แžปแžแŸ’แžš {0} แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™ {1}!", + + "transferLog":"แž—แžถแž–แž‡แžถแž˜แŸ’แž…แžถแžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž‘แŸแžšแž–แžธ {0} แž‘แŸ… {1} แžŠแŸ„แž™ {2}!", + "transferDm":"แž—แžถแž–แž‡แžถแž˜แŸ’แž…แžถแžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž‘แŸแžšแž–แžธ {0} แž‘แŸ… {1} แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "prioritySetLog":"แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแž‘แŸ… {0} แžŠแŸ„แž™ {1}!", + "prioritySetDm":"แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแž‘แŸ… {0} แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", + "roleUpdateLog":"{0} แž”แžถแž“แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž–แžแžฝแž“แžถแž‘แžธแžšแž”แžŸแŸ‹แžแŸ’แž›แžฝแž“!", + "roleUpdateDm":"แžแžฝแž“แžถแž‘แžธแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž–!" + } + }, + "transcripts":{ + "success":{ + "visit":"แž…แžผแž›แž˜แžพแž› Transcript", + "ready":"Transcript แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแž", + "textFileDescription":"แž“แŸแŸ‡แž‡แžถ transcript แžขแž€แŸ’แžŸแžšแžšแž”แžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แž”แžถแž“แž›แžปแž”!", + "htmlProgress":"แžŸแžผแž˜แžšแž„แŸ‹แž…แžถแŸ† แžแžŽแŸˆ html transcript แž€แŸ†แž–แžปแž„แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแŸ†แžŽแžพแžšแž€แžถแžš...", + + "createdChannel":"transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ!", + "createdCreator":"transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแžŸแž˜แŸ’แžšแžถแž”แŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€!", + "createdParticipant":"transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แžขแŸ’แž“แž€แž”แžถแž“แž…แžผแž›แžšแžฝแž˜!", + "createdActiveAdmin":"transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แžขแŸ’แž“แž€แž”แžถแž“แž…แžผแž›แžšแžฝแž˜แž‡แžถแžขแŸ’แž“แž€แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„!", + "createdEveryAdmin":"transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แžขแŸ’แž“แž€แž‡แžถแžขแŸ’แž“แž€แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„!", + "createdOther":"transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแž!" + }, + "errors":{ + "retry":"แž–แŸ’แž™แžถแž™แžถแž˜แž˜แŸ’แžแž„แž‘แŸ€แž", + "continue":"แž›แžปแž”แžŠแŸ„แž™แž‚แŸ’แž˜แžถแž“ Transcript", + "backup":"แž”แž„แŸ’แž€แžพแž Transcript แž”แž˜แŸ’แžšแžปแž„", + "error":"แž˜แžถแž“แž”แž‰แŸ’แž แžถแžแŸ’แž›แŸ‡แž€แŸ’แž“แžปแž„แž€แžถแžšแž”แž„แŸ’แž€แžพแž transcriptแŸ”\nแžขแŸ’แž“แž€แž…แž„แŸ‹แž’แŸ’แžœแžพแžขแŸ’แžœแžธ?\n\nแžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž“แžนแž„แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แž‘แŸ แžšแž แžผแžแžขแŸ’แž“แž€แž…แžปแž…แž”แŸŠแžผแžแžปแž„แžŽแžถแž˜แžฝแž™แŸ”", + "title":"แž€แŸ†แž แžปแžŸ Transcript" + }, + "text":{ + "messagesTitle":"แžŸแžถแžš", + "embedTitle":"EMBED", + "fileTitle":"แžฏแž€แžŸแžถแžš", + "fieldsTitle":"แžœแžถแž›", + "reactionsTitle":"แž”แŸ’แžšแžแžทแž€แž˜แŸ’แž˜", + "statsTitle":"แžŸแŸ’แžแžทแžแžท", + "emptyContent":"<แž˜แžถแžแžทแž€แžถแž‘แž‘แŸ>", + "noTitle":"<แž‚แŸ’แž˜แžถแž“แž…แŸ†แžŽแž„แž‡แžพแž„>", + "noDesc":"<แž‚แŸ’แž˜แžถแž“แž€แžถแžšแž–แžทแž–แžŽแŸŒแž“แžถ>" + } + }, + "errors":{ + "titles":{ + "internalError":"แž€แŸ†แž แžปแžŸแž•แŸ’แž‘แŸƒแž€แŸ’แž“แžปแž„", + "optionMissing":"แž‡แž˜แŸ’แžšแžพแžŸแž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž”แžถแžแŸ‹", + "optionInvalid":"แž‡แž˜แŸ’แžšแžพแžŸแž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ", + "unknownCommand":"แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž˜แžทแž“แžŸแŸ’แž‚แžถแž›แŸ‹", + "noPermissions":"แž‚แŸ’แž˜แžถแž“แžŸแžทแž‘แŸ’แž’แžท", + "unknownTicket":"แžŸแŸ†แž”แžปแžแŸ’แžšแž˜แžทแž“แžŸแŸ’แž‚แžถแž›แŸ‹", + "deprecatedTicket":"แžŸแŸ†แž”แžปแžแŸ’แžšแž แžฝแžŸแžŸแž˜แŸแž™", + "unknownOption":"แž‡แž˜แŸ’แžšแžพแžŸแž˜แžทแž“แžŸแŸ’แž‚แžถแž›แŸ‹", + "unknownPanel":"Panel แž˜แžทแž“แžŸแŸ’แž‚แžถแž›แŸ‹", + "notInGuild":"แž˜แžทแž“แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ", + "channelRename":"แž˜แžทแž“แžขแžถแž…แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž”แžŽแŸ’แžแžถแž‰", + "busy":"แžŸแŸ†แž”แžปแžแŸ’แžšแž€แŸ†แž–แžปแž„แžŠแŸ†แžŽแžพแžšแž€แžถแžš", + "permissionError":"แž€แŸ†แž แžปแžŸแžŸแžทแž‘แŸ’แž’แžท" + }, + "descriptions":{ + "askForInfo":"แž‘แŸ†แž“แžถแž€แŸ‹แž‘แŸ†แž“แž„แž˜แŸ’แž…แžถแžŸแŸ‹แž”แžผแžแž“แŸแŸ‡แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž–แŸแžแŸŒแž˜แžถแž“แž”แž“แŸ’แžแŸ‚แž˜!", + "askForInfoResolve":"แž‘แŸ†แž“แžถแž€แŸ‹แž‘แŸ†แž“แž„แž˜แŸ’แž…แžถแžŸแŸ‹แž”แžผแžแž“แŸแŸ‡ แž”แŸ’แžšแžŸแžทแž“แž”แžพแž”แž‰แŸ’แž แžถแž“แŸแŸ‡แž˜แžทแž“แžŠแŸ„แŸ‡แžŸแŸ’แžšแžถแž™แž”แž“แŸ’แž‘แžถแž”แŸ‹แž–แžธแž–แŸ’แž™แžถแž™แžถแž˜แž˜แžฝแž™แž…แŸ†แž“แžฝแž“แŸ”", + "internalError":"แž”แžšแžถแž‡แŸแž™แž€แŸ’แž“แžปแž„แž€แžถแžšแž†แŸ’แž›แžพแž™แžแž” {0} แžŠแŸ„แž™แžŸแžถแžšแž€แŸ†แž แžปแžŸแž•แŸ’แž‘แŸƒแž€แŸ’แž“แžปแž„!", + "optionMissing":"แž”แŸ‰แžถแžšแŸ‰แžถแž˜แŸ‰แŸ‚แžแŸ’แžšแž…แžถแŸ†แž”แžถแž…แŸ‹แžแŸ’แžœแŸ‡แž€แŸ’แž“แžปแž„แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž“แŸแŸ‡!", + "optionInvalid":"แž”แŸ‰แžถแžšแŸ‰แžถแž˜แŸ‰แŸ‚แžแŸ’แžšแž€แŸ’แž“แžปแž„แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", + "optionInvalidChoose":"แž‡แŸ’แžšแžพแžŸแžšแžพแžŸแžšแžœแžถแž„", + "unknownCommand":"แžŸแžผแž˜แž…แžผแž›แž˜แžพแž›แž˜แŸ‰แžบแž“แžปแž™แž‡แŸ†แž“แžฝแž™แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž–แŸแžแŸŒแž˜แžถแž“แž”แž“แŸ’แžแŸ‚แž˜!", + "noPermissions":"แžขแŸ’แž“แž€แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž”แŸ’แžšแžพ {0} แž“แŸแŸ‡!", + "noPermissionsList":"แžŸแžทแž‘แŸ’แž’แžทแžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž€แžถแžš: (แž˜แžฝแž™แž€แŸ’แž“แžปแž„แž…แŸ†แžŽแŸ„แž˜)", + "noPermissionsCooldown":"แžขแŸ’แž“แž€แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž”แŸ’แžšแžพ {0} แž“แŸแŸ‡ แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แž˜แžถแž“แžšแž™แŸˆแž–แŸแž›แžแŸ’แžšแž‡แžถแž€แŸ‹!", + "noPermissionsBlacklist":"แžขแŸ’แž“แž€แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž”แŸ’แžšแžพ {0} แž“แŸแŸ‡ แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…!", + "noPermissionsLimitGlobal":"แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš แžŠแŸ„แž™แžŸแžถแžšแž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžˆแžถแž“แžŠแž›แŸ‹แžŠแŸ‚แž“แž€แŸ†แžŽแžแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž!", + "noPermissionsLimitGlobalUser":"แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แžˆแžถแž“แžŠแž›แŸ‹แžŠแŸ‚แž“แž€แŸ†แžŽแžแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž!", + "noPermissionsLimitOption":"แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš แžŠแŸ„แž™แžŸแžถแžšแž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžˆแžถแž“แžŠแž›แŸ‹แžŠแŸ‚แž“แž€แŸ†แžŽแžแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแžแžŸแž˜แŸ’แžšแžถแž”แŸ‹แž‡แž˜แŸ’แžšแžพแžŸแž“แŸแŸ‡!", + "noPermissionsLimitOptionUser":"แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แžˆแžถแž“แžŠแž›แŸ‹แžŠแŸ‚แž“แž€แŸ†แžŽแžแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแžแžŸแž˜แŸ’แžšแžถแž”แŸ‹แž‡แž˜แŸ’แžšแžพแžŸแž“แŸแŸ‡!", + "unknownTicket":"แžŸแžผแž˜แžŸแžถแž€แž›แŸ’แž”แž„แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž“แŸแŸ‡แž˜แŸ’แžแž„แž‘แŸ€แžแž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", + "deprecatedTicket":"แž”แžŽแŸ’แžแžถแž‰แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž˜แžทแž“แž˜แŸ‚แž“แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ! แžœแžถแž”แŸ’แžšแž แŸ‚แž›แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžšแž–แžธ Open Ticket แž€แŸ†แžŽแŸ‚แž…แžถแžŸแŸ‹!", + "notInGuild":"{0} แž“แŸแŸ‡แž˜แžทแž“แžŠแŸ†แžŽแžพแžšแž€แžถแžšแž€แŸ’แž“แžปแž„ DM! แžŸแžผแž˜แžŸแžถแž€แž›แŸ’แž”แž„แž˜แŸ’แžแž„แž‘แŸ€แžแž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ!", + "channelRename":"แžŠแŸ„แž™แžŸแžถแžš discord ratelimits, แžœแžถแž˜แžทแž“แžขแžถแž…แž‘แŸ…แž”แžถแž“แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž”แžผแžแž€แŸ’แž“แžปแž„แž€แžถแžšแž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž”แžŽแŸ’แžแžถแž‰แŸ” แž”แžŽแŸ’แžแžถแž‰แž“แžนแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แž€แŸ’แž“แžปแž„แžšแž™แŸˆแž–แŸแž› 10 แž“แžถแž‘แžธ แž”แŸ’แžšแžŸแžทแž“แž”แžพแž”แžผแžแž˜แžทแž“แž”แžถแž“แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžกแžพแž„แžœแžทแž‰แŸ”", + "channelRenameSource":"แž”แŸ’แžšแž—แž–แž€แŸ†แž แžปแžŸแž“แŸแŸ‡แž‚แžบ: {0}", + "busy":"แž˜แžทแž“แžขแžถแž…แž”แŸ’แžšแžพ {0} แž“แŸแŸ‡!\nแžŸแŸ†แž”แžปแžแŸ’แžšแž€แŸ†แž–แžปแž„แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแŸ†แžŽแžพแžšแž€แžถแžšแžŠแŸ„แž™แž”แžผแžแŸ”\n\nแžŸแžผแž˜แž–แŸ’แž™แžถแž™แžถแž˜แž˜แŸ’แžแž„แž‘แŸ€แžแž€แŸ’แž“แžปแž„แž–แžธแžšแž”แžธแžœแžทแž“แžถแž‘แžธ!", + "closeBeforeMessage":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž”แžทแž‘/แž›แžปแž”แž˜แžปแž“แž˜แžถแž“แžŸแžถแžšแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแŸ”", + "closeBeforeAdminMessage":"แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž”แžทแž‘/แž›แžปแž”แž˜แžปแž“แž˜แžถแž“แžŸแžถแžšแž–แžธแžขแŸ’แž“แž€แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„แžŸแŸ†แž”แžปแžแŸ’แžš แžฌแžŸแž˜แžถแž‡แžทแž€แž‡แŸ†แž“แžฝแž™แŸ”", + "unableToCreateTicket":"แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžšแŸ”" + }, + "optionInvalidReasons":{ + "stringRegex":"แžแž˜แŸ’แž›แŸƒแž˜แžทแž“แžแŸ’แžšแžผแžœแž“แžนแž„แž›แŸ†แž“แžถแŸ†!", + "stringMinLength":"แžแž˜แŸ’แž›แŸƒแžแŸ’แžšแžผแžœแž€แžถแžšแž™แŸ‰แžถแž„แž แŸ„แž…แžŽแžถแžŸแŸ‹ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", + "stringMaxLength":"แžแž˜แŸ’แž›แŸƒแžแŸ’แžšแžผแžœแž€แžถแžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž {0} แžแžฝแžขแž€แŸ’แžŸแžš!", + "numberInvalid":"แž›แŸแžแž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", + "numberMin":"แž›แŸแžแžแŸ’แžšแžผแžœแž€แžถแžšแž™แŸ‰แžถแž„แž แŸ„แž…แžŽแžถแžŸแŸ‹ {0}!", + "numberMax":"แž›แŸแžแžแŸ’แžšแžผแžœแž€แžถแžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž {0}!", + "numberDecimal":"แž›แŸแžแž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแž‡แžถแž‘แžŸแž—แžถแž‚!", + "numberNegative":"แž›แŸแžแž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแž‡แžถแžขแžœแžทแž‡แŸ’แž‡แž˜แžถแž“!", + "numberPositive":"แž›แŸแžแž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแž‡แžถแžœแžทแž‡แŸ’แž‡แž˜แžถแž“!", + "numberZero":"แž›แŸแžแž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแž‡แžถแžŸแžผแž“แŸ’แž™!", + "channelNotFound":"แžšแž€แž”แžŽแŸ’แžแžถแž‰แž˜แžทแž“แžƒแžพแž‰!", + "userNotFound":"แžšแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž˜แžทแž“แžƒแžพแž‰!", + "roleNotFound":"แžšแž€แžแžฝแž“แžถแž‘แžธแž˜แžทแž“แžƒแžพแž‰!", + "memberNotFound":"แžšแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž˜แžทแž“แžƒแžพแž‰!", + "mentionableNotFound":"แžšแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹ แžฌแžแžฝแž“แžถแž‘แžธแž˜แžทแž“แžƒแžพแž‰!", + "channelType":"แž”แŸ’แžšแž—แŸแž‘แž”แžŽแŸ’แžแžถแž‰แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", + "notInGuild":"แž‡แž˜แŸ’แžšแžพแžŸแž“แŸแŸ‡แž‘แžถแž˜แž‘แžถแžšแžฑแŸ’แž™แžขแŸ’แž“แž€แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ!" + }, + "permissions":{ + "developer":"แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž‡แžถ developer แžšแž”แžŸแŸ‹แž”แžผแžแŸ”", + "owner":"แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž‡แžถแž˜แŸ’แž…แžถแžŸแŸ‹แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", + "admin":"แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž‡แžถ admin แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", + "moderator":"แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž‡แžถแžขแŸ’แž“แž€แžŸแž˜แŸ’แžšแžปแŸ‡แžŸแž˜แŸ’แžšแžฝแž›แŸ”", + "support":"แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž“แŸ…แž€แŸ’แž“แžปแž„แž€แŸ’แžšแžปแž˜แž‡แŸ†แž“แžฝแž™แŸ”", + "member":"แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž‡แžถแžŸแž˜แžถแž‡แžทแž€แŸ”", + "discord-administrator":"แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž˜แžถแž“แžŸแžทแž‘แŸ’แž’แžท `ADMINISTRATOR`แŸ”" + }, + "actionInvalid":{ + "close":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แžทแž‘แž แžพแž™!", + "reopen":"แžŸแŸ†แž”แžปแžแŸ’แžšแž˜แžทแž“แž‘แžถแž“แŸ‹แž”แžทแž‘!", + "claim":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž‘แž‘แžฝแž›แž แžพแž™!", + "unclaim":"แžŸแŸ†แž”แžปแžแŸ’แžšแž˜แžทแž“แž‘แžถแž“แŸ‹แžแŸ’แžšแžผแžœแž”แžถแž“แž‘แž‘แžฝแž›!", + "pin":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แž แžพแž™!", + "unpin":"แžŸแŸ†แž”แžปแžแŸ’แžšแž˜แžทแž“แž‘แžถแž“แŸ‹แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›!", + "add":"แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž“แŸแŸ‡แžขแžถแž…แž…แžผแž›แž”แŸ’แžšแžพแžŸแŸ†แž”แžปแžแŸ’แžšแž แžพแž™!", + "remove":"แž˜แžทแž“แžขแžถแž…แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž“แŸแŸ‡แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžš!" + } + }, + "params":{ + "uppercase":{ + "ticket":"แžŸแŸ†แž”แžปแžแŸ’แžš", + "tickets":"แžŸแŸ†แž”แžปแžแŸ’แžš", + "reason":"แž˜แžผแž›แž แŸแžแžป", + "creator":"แžขแŸ’แž“แž€แž”แž„แŸ’แž€แžพแž", + "remaining":"แž–แŸแž›แžœแŸแž›แžถแž“แŸ…แžŸแž›แŸ‹", + "added":"แž”แž“แŸ’แžแŸ‚แž˜", + "removed":"แžŠแž€", + "filter":"แžแž˜แŸ’แžšแž„", + "method":"แžœแžทแž’แžธแžŸแžถแžŸแŸ’แžแŸ’แžš", + "type":"แž”แŸ’แžšแž—แŸแž‘", + "blacklisted":"แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", + "panel":"Panel", + "command":"แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถ", + "system":"แž”แŸ’แžšแž–แŸแž“แŸ’แž’", + "true":"แž–แžทแž", + "false":"แž˜แžทแž“แž–แžทแž", + "syntax":"แžœแžถแž€แŸ’แž™แžŸแž˜แŸ’แž–แŸแž“แŸ’แž’", + "originalName":"แžˆแŸ’แž˜แŸ„แŸ‡แžŠแžพแž˜", + "newName":"แžˆแŸ’แž˜แŸ„แŸ‡แžแŸ’แž˜แžธ", + "until":"แžšแž แžผแžแžŠแž›แŸ‹", + "validOptions":"แž‡แž˜แŸ’แžšแžพแžŸแžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ", + "validPanels":"Panel แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ", + "autoclose":"Autoclose", + "autodelete":"Autodelete", + "startupDate":"แž€แžถแž›แž”แžšแžทแž…แŸ’แž†แŸแž‘แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜", + "version":"แž€แŸ†แžŽแŸ‚", + "name":"แžˆแŸ’แž˜แŸ„แŸ‡", + "role":"แžแžฝแž“แžถแž‘แžธ", + "status":"แžŸแŸ’แžแžถแž“แž—แžถแž–", + "claimed":"แž‘แž‘แžฝแž›แž แžพแž™", + "pinned":"แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แž แžพแž™", + "creationDate":"แž€แžถแž›แž”แžšแžทแž…แŸ’แž†แŸแž‘แž”แž„แŸ’แž€แžพแž", + + "noone":"แž‚แŸ’แž˜แžถแž“แž“แžšแžŽแžถ", + "open":"แž”แžพแž€", + "closed":"แž”แžทแž‘", + "priority":"แžขแžถแž‘แžทแž—แžถแž–", + "participants":"แžขแŸ’แž“แž€แž…แžผแž›แžšแžฝแž˜", + "yes":"แž”แžถแž‘/แž…แžถแžŸ", + "no":"แž‘แŸ", + "option":"แž‡แž˜แŸ’แžšแžพแžŸ", + "topic":"แž”แŸ’แžšแž’แžถแž“แž”แž‘", + "uptime":"แž–แŸแž›แžŠแŸ†แžŽแžพแžšแž€แžถแžšแž”แŸ’แžšแž–แŸแž“แŸ’แž’", + "messages":"แžŸแžถแžš", + "embeds":"Embeds", + "files":"แžฏแž€แžŸแžถแžš", + "components":"แž’แžถแžแžป", + "cooldown":"แžšแž™แŸˆแž–แŸแž›แžแŸ’แžšแž‡แžถแž€แŸ‹", + "maxTickets":"แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž", + "admins":"แžขแŸ’แž“แž€แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„", + "roles":"แžแžฝแž“แžถแž‘แžธ", + "size":"แž‘แŸ†แž แŸ†" + }, + "lowercase":{ + "text":"แžขแž€แŸ’แžŸแžš", + "html":"html", + "command":"แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถ", + "modal":"modal", + "button":"แž”แŸŠแžผแžแžปแž„", + "dropdown":"dropdown", + "method":"แžœแžทแž’แžธแžŸแžถแžŸแŸ’แžแŸ’แžš" + } + }, + "commands":{ + "reason":"แž”แž‰แŸ’แž‡แžถแž€แŸ‹แž˜แžผแž›แž แŸแžแžปแžŸแŸ’แžšแŸแž…แž…แžทแžแŸ’แžแžŠแŸ‚แž›แž“แžนแž„แž”แž„แŸ’แž แžถแž‰แž€แŸ’แž“แžปแž„ logsแŸ”", + "help":"แž‘แž‘แžฝแž›แž”แžถแž“แž”แž‰แŸ’แž‡แžธแž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŠแŸ‚แž›แž˜แžถแž“แŸ”", + "panel":"แž”แž„แŸ’แž แžถแž‰แžŸแžถแžšแž‡แžถแž˜แžฝแž™ dropdown แžฌ buttons (แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž€แžถแžšแž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš)แŸ”", + "panelId":"แžขแŸ’แž“แž€แž€แŸ†แžŽแžแŸ‹แžขแžแŸ’แžแžŸแž‰แŸ’แž‰แžถแžŽ panel แžŠแŸ‚แž›แžขแŸ’แž“แž€แž…แž„แŸ‹แž”แž„แŸ’แž แžถแž‰แŸ”", + "panelAutoUpdate":"แžแžพแžขแŸ’แž“แž€แž…แž„แŸ‹ panel แž“แŸแŸ‡แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž–แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แž“แŸ…แž–แŸแž›แž€แŸ‚แž”แŸ’แžšแŸ‚?", + "ticket":"แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžšแž—แŸ’แž›แžถแž˜แŸ—แŸ”", + "ticketId":"แžขแŸ’แž“แž€แž€แŸ†แžŽแžแŸ‹แžขแžแŸ’แžแžŸแž‰แŸ’แž‰แžถแžŽแžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แžขแŸ’แž“แž€แž…แž„แŸ‹แž”แž„แŸ’แž€แžพแžแŸ”", + "close":"แž”แžทแž‘แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "delete":"แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "deleteNoTranscript":"แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžŠแŸ„แž™แž‚แŸ’แž˜แžถแž“แž€แžถแžšแž”แž„แŸ’แž€แžพแž transcriptแŸ”", + "reopen":"แž”แžพแž€แžŸแŸ†แž”แžปแžแŸ’แžšแžกแžพแž„แžœแžทแž‰แŸ”", + "claim":"แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "claimUser":"แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž‘แŸ…แžฑแŸ’แž™แž“แžšแžŽแžถแž˜แŸ’แž“แžถแž€แŸ‹แž•แŸ’แžŸแŸแž„แž‘แŸ€แž แž‡แŸ†แž“แžฝแžŸแžขแŸ’แž“แž€แŸ”", + "unclaim":"แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "pin":"แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "unpin":"แžŠแž€แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + + "move":"แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "moveId":"แžขแŸ’แž“แž€แž€แŸ†แžŽแžแŸ‹แžขแžแŸ’แžแžŸแž‰แŸ’แž‰แžถแžŽแž‡แž˜แŸ’แžšแžพแžŸแžŠแŸ‚แž›แžขแŸ’แž“แž€แž…แž„แŸ‹แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแž‘แŸ…แŸ”", + "rename":"แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "renameName":"แžˆแŸ’แž˜แŸ„แŸ‡แžแŸ’แž˜แžธแžŸแž˜แŸ’แžšแžถแž”แŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", + "add":"แž”แž“แŸ’แžแŸ‚แž˜แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "addUser":"แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž”แž“แŸ’แžแŸ‚แž˜แŸ”", + "remove":"แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "removeUser":"แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžŠแŸ‚แž›แžแŸ’แžšแžผแžœแžŠแž€แŸ”", + + "blacklist":"แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "blacklistView":"แž˜แžพแž›แž”แž‰แŸ’แž‡แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แŸ”", + "blacklistAdd":"แž”แž“แŸ’แžแŸ‚แž˜แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž‘แŸ…แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", + "blacklistRemove":"แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž…แŸแž‰แž–แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", + "blacklistGet":"แž‘แž‘แžฝแž›แž”แžถแž“แž–แŸแžแŸŒแž˜แžถแž“แž›แž˜แŸ’แžขแžทแžแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", + "blacklistGetUser":"แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž‘แž‘แžฝแž›แž–แŸแžแŸŒแž˜แžถแž“แž›แž˜แŸ’แžขแžทแžแŸ”", + "stats":"แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแž–แžธแž”แžผแž, แžŸแž˜แžถแž‡แžทแž€ แžฌแžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "statsReset":"แž€แŸ†แžŽแžแŸ‹แžŸแŸ’แžแžทแžแžทแž”แžผแžแž‘แžถแŸ†แž„แžขแžŸแŸ‹แžกแžพแž„แžœแžทแž‰ (แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžšแžถแž”แŸ‹แž–แžธแžŸแžผแž“แŸ’แž™)แŸ”", + "statsGlobal":"แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแžŸแžšแžปแž”แŸ”", + "statsUser":"แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", + "statsUserUser":"แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž˜แžพแž›แŸ”", + "statsTicket":"แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", + "statsTicketTicket":"แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž˜แžพแž›แŸ”", + + "clear":"แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž€แŸ’แž“แžปแž„แž–แŸแž›แžแŸ‚แž˜แžฝแž™แŸ”", + "clearFilter":"แžแž˜แŸ’แžšแž„แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž€แžถแžšแžŸแž˜แŸ’แžขแžถแžแžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "clearFilters":{ + "all":"แž‘แžถแŸ†แž„แžขแžŸแŸ‹", + "open":"แž”แžพแž€", + "close":"แž”แžทแž‘", + "claim":"แž‘แž‘แžฝแž›แž แžพแž™", + "unclaim":"แž˜แžทแž“แž‘แžถแž“แŸ‹แž‘แž‘แžฝแž›", + "pin":"แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แž แžพแž™", + "unpin":"แž˜แžทแž“แž‘แžถแž“แŸ‹แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›", + "autoclose":"แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท" + }, + + "autoclose":"แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„ autoclose แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "autocloseDisable":"แž”แžทแž‘ autoclose แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", + "autocloseEnable":"แž”แžพแž€ autoclose แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", + "autocloseEnableTime":"แž…แŸ†แž“แžฝแž“แž˜แŸ‰แŸ„แž„แžŠแŸ‚แž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž˜แžทแž“แžŸแž€แž˜แŸ’แž˜ แžŠแžพแž˜แŸ’แž”แžธแž”แžทแž‘แŸ”", + "autodelete":"แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„ autodelete แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "autodeleteDisable":"แž”แžทแž‘ autodelete แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", + "autodeleteEnable":"แž”แžพแž€ autodelete แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", + "autodeleteEnableTime":"แž…แŸ†แž“แžฝแž“แžแŸ’แž„แŸƒแžŠแŸ‚แž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž˜แžทแž“แžŸแž€แž˜แŸ’แž˜ แžŠแžพแž˜แŸ’แž”แžธแž›แžปแž”แŸ”", + + "topic":"แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„แž”แŸ’แžšแž’แžถแž“แž”แž‘แž”แžŽแŸ’แžแžถแž‰แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "topicSet":"แž€แŸ†แžŽแžแŸ‹แž”แŸ’แžšแž’แžถแž“แž”แž‘แž”แžŽแŸ’แžแžถแž‰แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "topicValue":"แž”แŸ’แžšแž’แžถแž“แž”แž‘แžแŸ’แž˜แžธแžšแž”แžŸแŸ‹แž”แžŽแŸ’แžแžถแž‰แŸ”", + "topicList":"แž‘แž‘แžฝแž›แž”แžถแž“แž”แž‰แŸ’แž‡แžธแžŸแŸ†แž”แžปแžแŸ’แžšแž‘แžถแŸ†แž„แžขแžŸแŸ‹ แž‡แžถแž˜แžฝแž™แž”แŸ’แžšแž’แžถแž“แž”แž‘ แž“แžทแž„แžŸแŸ’แžแžทแžแžทแŸ”", + "priority":"แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "prioritySet":"แž€แŸ†แžŽแžแŸ‹แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "priorityValue":"แžขแžถแž‘แžทแž—แžถแž–แžšแž”แžŸแŸ‹แž”แžŽแŸ’แžแžถแž‰แŸ”", + "priorityGet":"แž‘แž‘แžฝแž›แž”แžถแž“แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "priorityList":"แž‘แž‘แžฝแž›แž”แžถแž“แž”แž‰แŸ’แž‡แžธแžŸแŸ†แž”แžปแžแŸ’แžšแž‘แžถแŸ†แž„แžขแžŸแŸ‹ แž‡แžถแž˜แžฝแž™แžŸแŸ’แžแžถแž“แž—แžถแž–แžขแžถแž‘แžทแž—แžถแž–แŸ”", + "transfer":"แž•แŸ’แž‘แŸแžšแž—แžถแž–แž‡แžถแž˜แŸ’แž…แžถแžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž˜แŸ’แž“แžถแž€แŸ‹แž‘แŸ…แž˜แŸ’แž“แžถแž€แŸ‹แž‘แŸ€แžแŸ”", + "transferUser":"แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž•แŸ’แž‘แŸแžšแž‘แŸ…แŸ”" + }, + "helpMenu":{ + "help":"แž‘แž‘แžฝแž›แž”แžถแž“แž”แž‰แŸ’แž‡แžธแž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŠแŸ‚แž›แž˜แžถแž“แŸ”", + "ticket":"แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžšแž—แŸ’แž›แžถแž˜แŸ—แŸ”", + "close":"แž”แžทแž‘แžŸแŸ†แž”แžปแžแŸ’แžš, แžœแžทแž’แžธแž“แŸแŸ‡แž”แžทแž‘แž€แžถแžšแžŸแžšแžŸแŸแžšแž€แŸ’แž“แžปแž„แž”แžŽแŸ’แžแžถแž‰แŸ”", + "delete":"แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžš, แžœแžทแž’แžธแž“แŸแŸ‡แž”แž„แŸ’แž€แžพแž transcript แž“แŸ…แž–แŸแž›แž”แžพแž€แŸ”", + "reopen":"แž”แžพแž€แžŸแŸ†แž”แžปแžแŸ’แžšแžกแžพแž„แžœแžทแž‰, แžœแžทแž’แžธแž“แŸแŸ‡แž”แžพแž€แž€แžถแžšแžŸแžšแžŸแŸแžšแž€แŸ’แž“แžปแž„แž”แžŽแŸ’แžแžถแž‰แž˜แŸ’แžแž„แž‘แŸ€แžแŸ”", + "pin":"แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžœแžถแž“แžนแž„แž•แŸ’แž›แžถแžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž‘แŸ…แžแžถแž„แž›แžพ แž แžพแž™แž”แž“แŸ’แžแŸ‚แž˜ '๐Ÿ“Œ' แž‘แŸ…แžˆแŸ’แž˜แŸ„แŸ‡แŸ”", + "unpin":"แžŠแž€แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžŸแŸ†แž”แžปแžแŸ’แžšแž“แžนแž„แž“แŸ…แžแŸ†แžŽแŸ‚แž„แžŠแžŠแŸ‚แž› แž”แŸ‰แžปแž“แŸ’แžแŸ‚แž“แžนแž„แž”แžถแžแŸ‹ '๐Ÿ“Œ'แŸ”", + "move":"แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžœแžถแž“แžนแž„แž”แŸ’แžแžผแžšแž”แŸ’แžšแž—แŸแž‘แžšแž”แžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "rename":"แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžœแžถแž“แžนแž„แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž”แžŽแŸ’แžแžถแž‰แžšแž”แžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "claim":"แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžŠแŸ„แž™แžœแžทแž’แžธแž“แŸแŸ‡ แžขแŸ’แž“แž€แžขแžถแž…แž”แŸ’แžšแžถแž”แŸ‹แž€แŸ’แžšแžปแž˜แžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแžถแžขแŸ’แž“แž€แž€แŸ†แž–แžปแž„แžŠแŸ„แŸ‡แžŸแŸ’แžšแžถแž™แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", + "unclaim":"แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžŠแŸ„แž™แžœแžทแž’แžธแž“แŸแŸ‡ แžขแŸ’แž“แž€แžขแžถแž…แž”แŸ’แžšแžถแž”แŸ‹แž€แŸ’แžšแžปแž˜แžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแžถแžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž‘แŸ†แž“แŸแžšแŸ”", + "add":"แž”แž“แŸ’แžแŸ‚แž˜แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžœแžถแž“แžนแž„แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžขแžถแž“แž“แžทแž„แžŸแžšแžŸแŸแžšแž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "remove":"แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžœแžถแž“แžนแž„แžŠแž€แžŸแžทแž‘แŸ’แž’แžทแžขแžถแž“แž“แžทแž„แžŸแžšแžŸแŸแžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", + "panel":"แž”แž„แŸ’แž แžถแž‰แžŸแžถแžšแž‡แžถแž˜แžฝแž™ dropdown แžฌ buttons (แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž€แžถแžšแž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš)แŸ”", + "blacklistView":"แž˜แžพแž›แž”แž‰แŸ’แž‡แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แŸ”", + "blacklistAdd":"แž”แž“แŸ’แžแŸ‚แž˜แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž‘แŸ…แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", + "blacklistRemove":"แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž…แŸแž‰แž–แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", + "blacklistGet":"แž‘แž‘แžฝแž›แž”แžถแž“แž–แŸแžแŸŒแž˜แžถแž“แž›แž˜แŸ’แžขแžทแžแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", + "statsGlobal":"แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแžŸแžšแžปแž”แŸ”", + "statsTicket":"แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", + "statsUser":"แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", + "statsReset":"แž€แŸ†แžŽแžแŸ‹แžŸแŸ’แžแžทแžแžทแž”แžผแžแž‘แžถแŸ†แž„แžขแžŸแŸ‹แžกแžพแž„แžœแžทแž‰ (แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžšแžถแž”แŸ‹แž–แžธแžŸแžผแž“แŸ’แž™)แŸ”", + "autocloseDisable":"แž”แžทแž‘ autoclose แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", + "autocloseEnable":"แž”แžพแž€ autoclose แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", + "autodeleteDisable":"แž”แžทแž‘ autodelete แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", + "autodeleteEnable":"แž”แžพแž€ autodelete แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", + "categories":{ + "general":"แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž‘แžผแž‘แŸ…", + "basicTicket":"แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžšแž˜แžผแž›แžŠแŸ’แž‹แžถแž“", + "advancedTicket":"แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžšแž€แž˜แŸ’แžšแžทแžแžแŸ’แž–แžŸแŸ‹", + "userTicket":"แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžšแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹", + "admin":"แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถ Admin", + "advanced":"แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž€แž˜แŸ’แžšแžทแžแžแŸ’แž–แžŸแŸ‹", + "extra":"แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž”แž“แŸ’แžแŸ‚แž˜" + } + }, + "stats":{ + "scopes":{ + "global":"แžŸแŸ’แžแžทแžแžทแžŸแžšแžปแž”", + "system":"แžŸแŸ’แžแžทแžแžทแž”แŸ’แžšแž–แŸแž“แŸ’แž’", + "user":"แžŸแŸ’แžแžทแžแžทแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹", + "ticket":"แžŸแŸ’แžแžทแžแžทแžŸแŸ†แž”แžปแžแŸ’แžš", + "participants":"แžขแŸ’แž“แž€แž…แžผแž›แžšแžฝแž˜", + "messages":"แžŸแžถแžš" + }, + "properties":{ + "ticketsCreated":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แž„แŸ’แž€แžพแž", + "ticketsClosed":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แžทแž‘", + "ticketsDeleted":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž›แžปแž”", + "ticketsReopened":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แžพแž€แžกแžพแž„แžœแžทแž‰", + "ticketsAutoclosed":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท", + "ticketsClaimed":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž‘แž‘แžฝแž›", + "ticketsPinned":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›", + "ticketsMoved":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžš", + "usersBlacklisted":"แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", + "transcriptsCreated":"Transcript แž”แžถแž“แž”แž„แŸ’แž€แžพแž", + "ticketsAutodeleted":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท", + "ticketsTransferred":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž•แŸ’แž‘แŸแžš", + "ticketVolume":"แž…แŸ†แž“แžฝแž“แžŸแŸ†แž”แžปแžแŸ’แžš", + "averageTickets":"แž…แŸ†แž“แžฝแž“แžŸแŸ†แž”แžปแžแŸ’แžšแž‡แžถแž˜แž’แŸ’แž™แž˜/แžขแŸ’แž“แž€แž”แŸ’แžšแžพ", + "currentTickets":"แžŸแŸ†แž”แžปแžแŸ’แžšแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“", + "age":"แžขแžถแž™แžปแžŸแŸ†แž”แžปแžแŸ’แžš", + "responseTime":"แž–แŸแž›แž†แŸ’แž›แžพแž™แžแž”", + "resolutionTime":"แž–แŸแž›แžŠแŸ„แŸ‡แžŸแŸ’แžšแžถแž™", + "createdOn":"แž”แž„แŸ’แž€แžพแžแž“แŸ…", + "createdBy":"แž”แž„แŸ’แž€แžพแžแžŠแŸ„แž™", + "closedOn":"แž”แžทแž‘แž“แŸ…", + "closedBy":"แž”แžทแž‘แžŠแŸ„แž™", + "claimedOn":"แž‘แž‘แžฝแž›แž“แŸ…", + "claimedBy":"แž‘แž‘แžฝแž›แžŠแŸ„แž™", + "pinnedOn":"แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แž“แŸ…", + "pinnedBy":"แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŠแŸ„แž™", + "deletedOn":"แž›แžปแž”แž“แŸ…", + "deletedBy":"แž›แžปแž”แžŠแŸ„แž™" + }, + "roles":{ + "developer":"Developer", + "serverOwner":"แž˜แŸ’แž…แžถแžŸแŸ‹แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ", + "serverAdmin":"Admin แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ", + "moderator":"แž€แŸ’แžšแžปแž˜แžขแŸ’แž“แž€แžŸแž˜แŸ’แžšแžปแŸ‡แžŸแž˜แŸ’แžšแžฝแž›", + "support":"แž€แŸ’แžšแžปแž˜แž‡แŸ†แž“แžฝแž™", + "member":"แžŸแž˜แžถแž‡แžทแž€" + } + }, + "panel":{ + "selectTicket":"แž‡แŸ’แžšแžพแžŸแžšแžพแžŸแžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€", + "selectRole":"แž‡แŸ’แžšแžพแžŸแžšแžพแžŸแžแžฝแž“แžถแž‘แžธแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€", + "selectOption":"แž‡แŸ’แžšแžพแžŸแžšแžพแžŸแž‡แž˜แŸ’แžšแžพแžŸแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€" + }, + "priorities":{ + "urgent":"แž”แž“แŸ’แž‘แžถแž“แŸ‹แžแŸ’แž›แžถแŸ†แž„", + "veryHigh":"แžแŸ’แž–แžŸแŸ‹แžŽแžถแžŸแŸ‹", + "high":"แžแŸ’แž–แžŸแŸ‹", + "normal":"แž’แž˜แŸ’แž˜แžแžถ", + "low":"แž‘แžถแž”", + "veryLow":"แž‘แžถแž”แžŽแžถแžŸแŸ‹", + "none":"แž‚แŸ’แž˜แžถแž“" } - }, - "actions": { - "buttons": { - "create": "แž…แžผแž›แž˜แžพแž›แžŸแŸ†แž”แžปแžแŸ’แžš", - "close": "แž”แžทแž‘แžŸแŸ†แž”แžปแžแŸ’แžš", - "delete": "แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžš", - "reopen": "แž”แžพแž€แžŸแŸ†แž”แžปแžแŸ’แžšแžกแžพแž„แžœแžทแž‰", - "claim": "แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžš", - "unclaim": "แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžš", - "pin": "แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžš", - "unpin": "แžŠแž€แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžš", - "clear": "แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแž‘แžถแŸ†แž„แžขแžŸแŸ‹", - "helpSwitchSlash": "แž˜แžพแž›แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถ Slash", - "helpSwitchText": "แž˜แžพแž›แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžขแž€แŸ’แžŸแžš", - "helpPage": "แž‘แŸ†แž–แŸแžš {0}", - "withReason": "แž‡แžถแž˜แžฝแž™แž แŸแžแžปแž•แž›", - "withoutTranscript": "แž‚แŸ’แž˜แžถแž“ Transcript" - }, - "titles": { - "created": "แž”แžถแž“แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš", - "close": "แž”แžถแž“แž”แžทแž‘แžŸแŸ†แž”แžปแžแŸ’แžš", - "delete": "แž”แžถแž“แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžš", - "reopen": "แž”แžถแž“แž”แžพแž€แžŸแŸ†แž”แžปแžแŸ’แžšแžกแžพแž„แžœแžทแž‰", - "claim": "แž”แžถแž“แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžš", - "unclaim": "แž”แžถแž“แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžš", - "pin": "แž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžš", - "unpin": "แž”แžถแž“แžŠแž€แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžš", - "rename": "แž”แžถแž“แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แžŸแŸ†แž”แžปแžแŸ’แžš", - "move": "แž”แžถแž“แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแžŸแŸ†แž”แžปแžแŸ’แžš", - "add": "แž”แžถแž“แž”แž“แŸ’แžแŸ‚แž˜แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžš", - "remove": "แž”แžถแž“แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžš", - - "help": "แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŠแŸ‚แž›แž˜แžถแž“", - "statsReset": "แž€แŸ†แžŽแžแŸ‹แžŸแŸ’แžแžทแžแžทแžกแžพแž„แžœแžทแž‰", - "blacklistAdd": "แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", - "blacklistRemove": "แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแŸ„แŸ‡แž›แŸ‚แž„", - "blacklistGet": "แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", - "blacklistView": "แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“", - "blacklistAddDm": "แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž“แŸ’แžแŸ‚แž˜แž‘แŸ…แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", - "blacklistRemoveDm": "แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", - "clear": "แž”แžถแž“แžŸแž˜แŸ’แžขแžถแžแžŸแŸ†แž”แžปแžแŸ’แžš", - "clearTickets": "แžŸแž˜แŸ’แžขแžถแžแžŸแŸ†แž”แžปแžแŸ’แžš", - "roles": "แžแžฝแž“แžถแž‘แžธแž”แžถแž“แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž–", - - "autoclose": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท", - "autocloseEnabled": "แž”แžถแž“แž”แžพแž€ Autoclose", - "autocloseDisabled": "แž”แžถแž“แž”แžทแž‘ Autoclose", - "autodelete": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท", - "autodeleteEnabled": "แž”แžถแž“แž”แžพแž€ Autodelete", - "autodeleteDisabled": "แž”แžถแž“แž”แžทแž‘ Autodelete", - - "topicSet": "แž”แžถแž“แž”แŸ’แžแžผแžšแž”แŸ’แžšแž’แžถแž“แž”แž‘", - "prioritySet": "แž”แžถแž“แž”แŸ’แžแžผแžšแžขแžถแž‘แžทแž—แžถแž–", - "priorityGet": "แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžš", - "transfer": "แž”แžถแž“แž•แŸ’แž‘แŸแžšแžŸแŸ†แž”แžปแžแŸ’แžš" - }, - "descriptions": { - "create": "แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแŸ” แž…แžปแž…แž”แŸŠแžผแžแžปแž„แžแžถแž„แž€แŸ’แžšแŸ„แž˜แžŠแžพแž˜แŸ’แž”แžธแž…แžผแž›แž”แŸ’แžšแžพ!", - "close": "แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "delete": "แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "reopen": "แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž”แžพแž€แžกแžพแž„แžœแžทแž‰แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "claim": "แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž‘แž‘แžฝแž›แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "unclaim": "แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "pin": "แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "unpin": "แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž˜แŸ’แž‡แžปแž›แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "rename": "แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž‘แŸ… {0} แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "move": "แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแž‘แŸ… {0} แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "add": "{0} แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž“แŸ’แžแŸ‚แž˜แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "remove": "{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - - "helpExplanation": "`` => แž”แŸ‰แžถแžšแŸ‰แžถแž˜แŸ‰แŸ‚แžแŸ’แžšแž…แžถแŸ†แž”แžถแž…แŸ‹\n`[name]` => แž”แŸ‰แžถแžšแŸ‰แžถแž˜แŸ‰แŸ‚แžแŸ’แžšแžŸแŸ’แžšแŸแž…แž…แžทแžแŸ’แž", - "statsReset": "แžŸแŸ’แžแžทแžแžทแž”แžผแžแžแŸ’แžšแžผแžœแž”แžถแž“แž€แŸ†แžŽแžแŸ‹แžกแžพแž„แžœแžทแž‰แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "statsError": "แž˜แžทแž“แžขแžถแž…แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแžŸแŸ†แž”แžปแžแŸ’แžš!\n{0} แž˜แžทแž“แž˜แŸ‚แž“แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžš!", - "blacklistAdd": "{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "blacklistRemove": "{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแŸ„แŸ‡แž›แŸ‚แž„แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "blacklistGetSuccess": "{0} แžŸแŸ’แžแžทแžแž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“!", - "blacklistGetEmpty": "{0} แž˜แžทแž“แžŸแŸ’แžแžทแžแž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“!", - "blacklistViewEmpty": "แž˜แžทแž“แž‘แžถแž“แŸ‹แž˜แžถแž“แžขแŸ’แž“แž€แžŽแžถแžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…!", - "blacklistViewTip": "แž”แŸ’แžšแžพ \"/blacklist add\" แžŠแžพแž˜แŸ’แž”แžธแžŠแžถแž€แŸ‹แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…!", - "clearVerify": "แžแžพแžขแŸ’แž“แž€แž”แŸ’แžšแžถแž€แžŠแžแžถแž…แž„แŸ‹แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“?\nแžŸแž€แž˜แŸ’แž˜แž—แžถแž–แž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แžแŸ’แžšแžกแž”แŸ‹แž˜แž€แžœแžทแž‰!", - "clearReady": "แžŸแŸ†แž”แžปแžแŸ’แžš {0} แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "rolesEmpty": "แž˜แžทแž“แž˜แžถแž“แžแžฝแž“แžถแž‘แžธแžŽแžถแžแŸ’แžšแžผแžœแž”แžถแž“แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž–!", - - "autocloseLeave": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แž”แž„แŸ’แž€แžพแžแž”แžถแž“แž…แžถแž€แž…แŸแž‰แž–แžธแž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ!", - "autocloseTimeout": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แžŠแŸ„แž™แžŸแžถแžšแžœแžถแž˜แžทแž“แžŸแž€แž˜แŸ’แž˜แž›แžพแžŸแž–แžธ `{0}h`!", - "autodeleteLeave": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แž”แž„แŸ’แž€แžพแžแž”แžถแž“แž…แžถแž€แž…แŸแž‰แž–แžธแž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ!", - "autodeleteTimeout": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แžŠแŸ„แž™แžŸแžถแžšแžœแžถแž˜แžทแž“แžŸแž€แž˜แŸ’แž˜แž›แžพแžŸแž–แžธ `{0} แžแŸ’แž„แŸƒ`!", - "autocloseEnabled": "Autoclose แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžพแž€แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡!\nแžœแžถแž“แžนแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แž“แŸ…แž–แŸแž›แž˜แžทแž“แžŸแž€แž˜แŸ’แž˜แž›แžพแžŸแž–แžธ `{0}h`!", - "autocloseDisabled": "Autoclose แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡!\nแžœแžถแž“แžนแž„แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžทแž‘แŸ€แžแž‘แŸ!", - "autodeleteEnabled": "Autodelete แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžพแž€แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡!\nแžœแžถแž“แžนแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แž“แŸ…แž–แŸแž›แž˜แžทแž“แžŸแž€แž˜แŸ’แž˜แž›แžพแžŸแž–แžธ `{0} แžแŸ’แž„แŸƒ`!", - "autodeleteDisabled": "Autodelete แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡!\nแžœแžถแž“แžนแž„แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžทแž‘แŸ€แžแž‘แŸ!", - - "ticketMessageLimit": "แžขแŸ’แž“แž€แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš {0} แž€แŸ’แž“แžปแž„แž–แŸแž›แžแŸ‚แž˜แžฝแž™!", - "ticketMessageAutoclose": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž“แžนแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แž“แŸ…แž–แŸแž›แž˜แžทแž“แžŸแž€แž˜แŸ’แž˜ {0}h!", - "ticketMessageAutodelete": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž“แžนแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แž“แŸ…แž–แŸแž›แž˜แžทแž“แžŸแž€แž˜แŸ’แž˜ {0} แžแŸ’แž„แŸƒ!", - "panelReady": "Panel แž˜แžถแž“แž“แŸ…แž€แŸ’แž“แžปแž„แžŸแžถแžšแž”แž“แŸ’แž!\nแžŸแžถแžšแž“แŸแŸ‡แžขแžถแž…แž›แžปแž”แž”แžถแž“แž แžพแž™!", - - "topicSet": "แž”แŸ’แžšแž’แžถแž“แž”แž‘แž”แžŽแŸ’แžแžถแž‰แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแžŠแŸ„แž™ {0} แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "prioritySet": "แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแž‘แŸ… {0} แžŠแŸ„แž™ {1} แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!", - "priorityGet": "แžขแžถแž‘แžทแž—แžถแž–แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แžšแž”แžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž‚แžบ {0}แŸ”", - "transfer": "แž—แžถแž–แž‡แžถแž˜แŸ’แž…แžถแžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž‘แŸแžšแž–แžธ {0} แž‘แŸ… {1} แžŠแŸ„แž™ {2} แžŠแŸ„แž™แž‡แŸ„แž‚แž‡แŸแž™!" - }, - "modal": { - "closePlaceholder": "แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แž”แžทแž‘แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?", - "deletePlaceholder": "แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?", - "reopenPlaceholder": "แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แž”แžพแž€แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžกแžพแž„แžœแžทแž‰?", - "claimPlaceholder": "แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?", - "unclaimPlaceholder": "แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?", - "pinPlaceholder": "แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?", - "unpinPlaceholder": "แž แŸแžแžปแžขแŸ’แžœแžธแžขแŸ’แž“แž€แžŠแž€แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡?" - }, - "logs": { - "createLog": "แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแžŠแŸ„แž™ {0}!", - "closeLog": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™ {0}!", - "closeDm": "แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžทแž‘แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "deleteLog": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™ {0}!", - "deleteDm": "แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "reopenLog": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžพแž€แžกแžพแž„แžœแžทแž‰แžŠแŸ„แž™ {0}!", - "reopenDm": "แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž”แžพแž€แžกแžพแž„แžœแžทแž‰แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "claimLog": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž‘แž‘แžฝแž›แžŠแŸ„แž™ {0}!", - "claimDm": "แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž‘แž‘แžฝแž›แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "unclaimLog": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŠแŸ„แž™ {0}!", - "unclaimDm": "แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž›แŸ‚แž„แž‘แž‘แžฝแž›แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "pinLog": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŠแŸ„แž™ {0}!", - "pinDm": "แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "unpinLog": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž˜แŸ’แž‡แžปแž›แžŠแŸ„แž™ {0}!", - "unpinDm": "แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž˜แŸ’แž‡แžปแž›แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "renameLog": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž‘แŸ… {0} แžŠแŸ„แž™ {1}!", - "renameDm": "แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž‘แŸ… {0} แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "moveLog": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแž‘แŸ… {0} แžŠแŸ„แž™ {1}!", - "moveDm": "แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแž‘แŸ… {0} แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "addLog": "{0} แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž“แŸ’แžแŸ‚แž˜แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžŠแŸ„แž™ {1}!", - "addDm": "{0} แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž“แŸ’แžแŸ‚แž˜แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "removeLog": "{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžŠแŸ„แž™ {1}!", - "removeDm": "{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - - "blacklistAddLog": "{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แžŠแŸ„แž™ {1}!", - "blacklistRemoveLog": "{0} แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แžŠแŸ„แž™ {1}!", - "blacklistAddDm": "แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!\nแž…แžถแž”แŸ‹แž–แžธแž–แŸแž›แž“แŸแŸ‡ แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš!", - "blacklistRemoveDm": "แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแž€แž…แŸแž‰แž–แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!\nแžฅแžกแžผแžœแžขแŸ’แž“แž€แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž˜แŸ’แžแž„แž‘แŸ€แž!", - "clearLog": "แžŸแŸ†แž”แžปแžแŸ’แžš {0} แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™ {1}!", - - "transferLog": "แž—แžถแž–แž‡แžถแž˜แŸ’แž…แžถแžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž‘แŸแžšแž–แžธ {0} แž‘แŸ… {1} แžŠแŸ„แž™ {2}!", - "transferDm": "แž—แžถแž–แž‡แžถแž˜แŸ’แž…แžถแžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž•แŸ’แž‘แŸแžšแž–แžธ {0} แž‘แŸ… {1} แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "prioritySetLog": "แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแž‘แŸ… {0} แžŠแŸ„แž™ {1}!", - "prioritySetDm": "แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแž‘แŸ… {0} แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„!", - "roleUpdateLog": "{0} แž”แžถแž“แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž–แžแžฝแž“แžถแž‘แžธแžšแž”แžŸแŸ‹แžแŸ’แž›แžฝแž“!", - "roleUpdateDm": "แžแžฝแž“แžถแž‘แžธแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžšแž”แžŸแŸ‹แž™แžพแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž–!" - } - }, - "transcripts": { - "success": { - "visit": "แž…แžผแž›แž˜แžพแž› Transcript", - "ready": "Transcript แžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแž", - "textFileDescription": "แž“แŸแŸ‡แž‡แžถ transcript แžขแž€แŸ’แžŸแžšแžšแž”แžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แž”แžถแž“แž›แžปแž”!", - "htmlProgress": "แžŸแžผแž˜แžšแž„แŸ‹แž…แžถแŸ† แžแžŽแŸˆ html transcript แž€แŸ†แž–แžปแž„แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแŸ†แžŽแžพแžšแž€แžถแžš...", - - "createdChannel": "transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ!", - "createdCreator": "transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแžŸแž˜แŸ’แžšแžถแž”แŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€!", - "createdParticipant": "transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แžขแŸ’แž“แž€แž”แžถแž“แž…แžผแž›แžšแžฝแž˜!", - "createdActiveAdmin": "transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แžขแŸ’แž“แž€แž”แžถแž“แž…แžผแž›แžšแžฝแž˜แž‡แžถแžขแŸ’แž“แž€แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„!", - "createdEveryAdmin": "transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแžแž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แžขแŸ’แž“แž€แž‡แžถแžขแŸ’แž“แž€แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„!", - "createdOther": "transcript {0} แžแŸ’แž˜แžธแžแŸ’แžšแžผแžœแž”แžถแž“แž”แž„แŸ’แž€แžพแž!" - }, - "errors": { - "retry": "แž–แŸ’แž™แžถแž™แžถแž˜แž˜แŸ’แžแž„แž‘แŸ€แž", - "continue": "แž›แžปแž”แžŠแŸ„แž™แž‚แŸ’แž˜แžถแž“ Transcript", - "backup": "แž”แž„แŸ’แž€แžพแž Transcript แž”แž˜แŸ’แžšแžปแž„", - "error": "แž˜แžถแž“แž”แž‰แŸ’แž แžถแžแŸ’แž›แŸ‡แž€แŸ’แž“แžปแž„แž€แžถแžšแž”แž„แŸ’แž€แžพแž transcriptแŸ”\nแžขแŸ’แž“แž€แž…แž„แŸ‹แž’แŸ’แžœแžพแžขแŸ’แžœแžธ?\n\nแžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž“แžนแž„แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แž›แžปแž”แž‘แŸ แžšแž แžผแžแžขแŸ’แž“แž€แž…แžปแž…แž”แŸŠแžผแžแžปแž„แžŽแžถแž˜แžฝแž™แŸ”", - "title": "แž€แŸ†แž แžปแžŸ Transcript" - }, - "text": { - "messagesTitle": "แžŸแžถแžš", - "embedTitle": "EMBED", - "fileTitle": "แžฏแž€แžŸแžถแžš", - "fieldsTitle": "แžœแžถแž›", - "reactionsTitle": "แž”แŸ’แžšแžแžทแž€แž˜แŸ’แž˜", - "statsTitle": "แžŸแŸ’แžแžทแžแžท", - "emptyContent": "<แž˜แžถแžแžทแž€แžถแž‘แž‘แŸ>", - "noTitle": "<แž‚แŸ’แž˜แžถแž“แž…แŸ†แžŽแž„แž‡แžพแž„>", - "noDesc": "<แž‚แŸ’แž˜แžถแž“แž€แžถแžšแž–แžทแž–แžŽแŸŒแž“แžถ>" - } - }, - "errors": { - "titles": { - "internalError": "แž€แŸ†แž แžปแžŸแž•แŸ’แž‘แŸƒแž€แŸ’แž“แžปแž„", - "optionMissing": "แž‡แž˜แŸ’แžšแžพแžŸแž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž”แžถแžแŸ‹", - "optionInvalid": "แž‡แž˜แŸ’แžšแžพแžŸแž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ", - "unknownCommand": "แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž˜แžทแž“แžŸแŸ’แž‚แžถแž›แŸ‹", - "noPermissions": "แž‚แŸ’แž˜แžถแž“แžŸแžทแž‘แŸ’แž’แžท", - "unknownTicket": "แžŸแŸ†แž”แžปแžแŸ’แžšแž˜แžทแž“แžŸแŸ’แž‚แžถแž›แŸ‹", - "deprecatedTicket": "แžŸแŸ†แž”แžปแžแŸ’แžšแž แžฝแžŸแžŸแž˜แŸแž™", - "unknownOption": "แž‡แž˜แŸ’แžšแžพแžŸแž˜แžทแž“แžŸแŸ’แž‚แžถแž›แŸ‹", - "unknownPanel": "Panel แž˜แžทแž“แžŸแŸ’แž‚แžถแž›แŸ‹", - "notInGuild": "แž˜แžทแž“แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ", - "channelRename": "แž˜แžทแž“แžขแžถแž…แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž”แžŽแŸ’แžแžถแž‰", - "busy": "แžŸแŸ†แž”แžปแžแŸ’แžšแž€แŸ†แž–แžปแž„แžŠแŸ†แžŽแžพแžšแž€แžถแžš", - "permissionError": "แž€แŸ†แž แžปแžŸแžŸแžทแž‘แŸ’แž’แžท" - }, - "descriptions": { - "askForInfo": "แž‘แŸ†แž“แžถแž€แŸ‹แž‘แŸ†แž“แž„แž˜แŸ’แž…แžถแžŸแŸ‹แž”แžผแžแž“แŸแŸ‡แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž–แŸแžแŸŒแž˜แžถแž“แž”แž“แŸ’แžแŸ‚แž˜!", - "askForInfoResolve": "แž‘แŸ†แž“แžถแž€แŸ‹แž‘แŸ†แž“แž„แž˜แŸ’แž…แžถแžŸแŸ‹แž”แžผแžแž“แŸแŸ‡ แž”แŸ’แžšแžŸแžทแž“แž”แžพแž”แž‰แŸ’แž แžถแž“แŸแŸ‡แž˜แžทแž“แžŠแŸ„แŸ‡แžŸแŸ’แžšแžถแž™แž”แž“แŸ’แž‘แžถแž”แŸ‹แž–แžธแž–แŸ’แž™แžถแž™แžถแž˜แž˜แžฝแž™แž…แŸ†แž“แžฝแž“แŸ”", - "internalError": "แž”แžšแžถแž‡แŸแž™แž€แŸ’แž“แžปแž„แž€แžถแžšแž†แŸ’แž›แžพแž™แžแž” {0} แžŠแŸ„แž™แžŸแžถแžšแž€แŸ†แž แžปแžŸแž•แŸ’แž‘แŸƒแž€แŸ’แž“แžปแž„!", - "optionMissing": "แž”แŸ‰แžถแžšแŸ‰แžถแž˜แŸ‰แŸ‚แžแŸ’แžšแž…แžถแŸ†แž”แžถแž…แŸ‹แžแŸ’แžœแŸ‡แž€แŸ’แž“แžปแž„แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž“แŸแŸ‡!", - "optionInvalid": "แž”แŸ‰แžถแžšแŸ‰แžถแž˜แŸ‰แŸ‚แžแŸ’แžšแž€แŸ’แž“แžปแž„แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž“แŸแŸ‡แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", - "optionInvalidChoose": "แž‡แŸ’แžšแžพแžŸแžšแžพแžŸแžšแžœแžถแž„", - "unknownCommand": "แžŸแžผแž˜แž…แžผแž›แž˜แžพแž›แž˜แŸ‰แžบแž“แžปแž™แž‡แŸ†แž“แžฝแž™แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž–แŸแžแŸŒแž˜แžถแž“แž”แž“แŸ’แžแŸ‚แž˜!", - "noPermissions": "แžขแŸ’แž“แž€แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž”แŸ’แžšแžพ {0} แž“แŸแŸ‡!", - "noPermissionsList": "แžŸแžทแž‘แŸ’แž’แžทแžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž€แžถแžš: (แž˜แžฝแž™แž€แŸ’แž“แžปแž„แž…แŸ†แžŽแŸ„แž˜)", - "noPermissionsCooldown": "แžขแŸ’แž“แž€แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž”แŸ’แžšแžพ {0} แž“แŸแŸ‡ แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แž˜แžถแž“แžšแž™แŸˆแž–แŸแž›แžแŸ’แžšแž‡แžถแž€แŸ‹!", - "noPermissionsBlacklist": "แžขแŸ’แž“แž€แž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แž”แŸ’แžšแžพ {0} แž“แŸแŸ‡ แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…!", - "noPermissionsLimitGlobal": "แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš แžŠแŸ„แž™แžŸแžถแžšแž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžˆแžถแž“แžŠแž›แŸ‹แžŠแŸ‚แž“แž€แŸ†แžŽแžแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž!", - "noPermissionsLimitGlobalUser": "แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แžˆแžถแž“แžŠแž›แŸ‹แžŠแŸ‚แž“แž€แŸ†แžŽแžแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž!", - "noPermissionsLimitOption": "แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš แžŠแŸ„แž™แžŸแžถแžšแž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแžˆแžถแž“แžŠแž›แŸ‹แžŠแŸ‚แž“แž€แŸ†แžŽแžแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแžแžŸแž˜แŸ’แžšแžถแž”แŸ‹แž‡แž˜แŸ’แžšแžพแžŸแž“แŸแŸ‡!", - "noPermissionsLimitOptionUser": "แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš แžŠแŸ„แž™แžŸแžถแžšแžขแŸ’แž“แž€แžˆแžถแž“แžŠแž›แŸ‹แžŠแŸ‚แž“แž€แŸ†แžŽแžแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแžแžŸแž˜แŸ’แžšแžถแž”แŸ‹แž‡แž˜แŸ’แžšแžพแžŸแž“แŸแŸ‡!", - "unknownTicket": "แžŸแžผแž˜แžŸแžถแž€แž›แŸ’แž”แž„แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž“แŸแŸ‡แž˜แŸ’แžแž„แž‘แŸ€แžแž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", - "deprecatedTicket": "แž”แžŽแŸ’แžแžถแž‰แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž˜แžทแž“แž˜แŸ‚แž“แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžšแžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ! แžœแžถแž”แŸ’แžšแž แŸ‚แž›แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžšแž–แžธ Open Ticket แž€แŸ†แžŽแŸ‚แž…แžถแžŸแŸ‹!", - "notInGuild": "{0} แž“แŸแŸ‡แž˜แžทแž“แžŠแŸ†แžŽแžพแžšแž€แžถแžšแž€แŸ’แž“แžปแž„ DM! แžŸแžผแž˜แžŸแžถแž€แž›แŸ’แž”แž„แž˜แŸ’แžแž„แž‘แŸ€แžแž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ!", - "channelRename": "แžŠแŸ„แž™แžŸแžถแžš discord ratelimits, แžœแžถแž˜แžทแž“แžขแžถแž…แž‘แŸ…แž”แžถแž“แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž”แžผแžแž€แŸ’แž“แžปแž„แž€แžถแžšแž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž”แžŽแŸ’แžแžถแž‰แŸ” แž”แžŽแŸ’แžแžถแž‰แž“แžนแž„แžแŸ’แžšแžผแžœแž”แžถแž“แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แž€แŸ’แž“แžปแž„แžšแž™แŸˆแž–แŸแž› 10 แž“แžถแž‘แžธ แž”แŸ’แžšแžŸแžทแž“แž”แžพแž”แžผแžแž˜แžทแž“แž”แžถแž“แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžกแžพแž„แžœแžทแž‰แŸ”", - "channelRenameSource": "แž”แŸ’แžšแž—แž–แž€แŸ†แž แžปแžŸแž“แŸแŸ‡แž‚แžบ: {0}", - "busy": "แž˜แžทแž“แžขแžถแž…แž”แŸ’แžšแžพ {0} แž“แŸแŸ‡!\nแžŸแŸ†แž”แžปแžแŸ’แžšแž€แŸ†แž–แžปแž„แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแŸ†แžŽแžพแžšแž€แžถแžšแžŠแŸ„แž™แž”แžผแžแŸ”\n\nแžŸแžผแž˜แž–แŸ’แž™แžถแž™แžถแž˜แž˜แŸ’แžแž„แž‘แŸ€แžแž€แŸ’แž“แžปแž„แž–แžธแžšแž”แžธแžœแžทแž“แžถแž‘แžธ!", - "closeBeforeMessage": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž”แžทแž‘/แž›แžปแž”แž˜แžปแž“แž˜แžถแž“แžŸแžถแžšแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแŸ”", - "closeBeforeAdminMessage": "แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž˜แžทแž“แžขแžถแž…แž”แžทแž‘/แž›แžปแž”แž˜แžปแž“แž˜แžถแž“แžŸแžถแžšแž–แžธแžขแŸ’แž“แž€แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„แžŸแŸ†แž”แžปแžแŸ’แžš แžฌแžŸแž˜แžถแž‡แžทแž€แž‡แŸ†แž“แžฝแž™แŸ”", - "unableToCreateTicket": "แžขแŸ’แž“แž€แž˜แžทแž“แžขแžถแž…แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžšแŸ”" - }, - "optionInvalidReasons": { - "stringRegex": "แžแž˜แŸ’แž›แŸƒแž˜แžทแž“แžแŸ’แžšแžผแžœแž“แžนแž„แž›แŸ†แž“แžถแŸ†!", - "stringMinLength": "แžแž˜แŸ’แž›แŸƒแžแŸ’แžšแžผแžœแž€แžถแžšแž™แŸ‰แžถแž„แž แŸ„แž…แžŽแžถแžŸแŸ‹ {0} แžแžฝแžขแž€แŸ’แžŸแžš!", - "stringMaxLength": "แžแž˜แŸ’แž›แŸƒแžแŸ’แžšแžผแžœแž€แžถแžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž {0} แžแžฝแžขแž€แŸ’แžŸแžš!", - "numberInvalid": "แž›แŸแžแž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", - "numberMin": "แž›แŸแžแžแŸ’แžšแžผแžœแž€แžถแžšแž™แŸ‰แžถแž„แž แŸ„แž…แžŽแžถแžŸแŸ‹ {0}!", - "numberMax": "แž›แŸแžแžแŸ’แžšแžผแžœแž€แžถแžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž {0}!", - "numberDecimal": "แž›แŸแžแž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแž‡แžถแž‘แžŸแž—แžถแž‚!", - "numberNegative": "แž›แŸแžแž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแž‡แžถแžขแžœแžทแž‡แŸ’แž‡แž˜แžถแž“!", - "numberPositive": "แž›แŸแžแž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแž‡แžถแžœแžทแž‡แŸ’แž‡แž˜แžถแž“!", - "numberZero": "แž›แŸแžแž˜แžทแž“แžแŸ’แžšแžผแžœแž”แžถแž“แžขแž“แžปแž‰แŸ’แž‰แžถแžแž‡แžถแžŸแžผแž“แŸ’แž™!", - "channelNotFound": "แžšแž€แž”แžŽแŸ’แžแžถแž‰แž˜แžทแž“แžƒแžพแž‰!", - "userNotFound": "แžšแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž˜แžทแž“แžƒแžพแž‰!", - "roleNotFound": "แžšแž€แžแžฝแž“แžถแž‘แžธแž˜แžทแž“แžƒแžพแž‰!", - "memberNotFound": "แžšแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž˜แžทแž“แžƒแžพแž‰!", - "mentionableNotFound": "แžšแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹ แžฌแžแžฝแž“แžถแž‘แžธแž˜แžทแž“แžƒแžพแž‰!", - "channelType": "แž”แŸ’แžšแž—แŸแž‘แž”แžŽแŸ’แžแžถแž‰แž˜แžทแž“แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ!", - "notInGuild": "แž‡แž˜แŸ’แžšแžพแžŸแž“แŸแŸ‡แž‘แžถแž˜แž‘แžถแžšแžฑแŸ’แž™แžขแŸ’แž“แž€แž“แŸ…แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ!" - }, - "permissions": { - "developer": "แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž‡แžถ developer แžšแž”แžŸแŸ‹แž”แžผแžแŸ”", - "owner": "แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž‡แžถแž˜แŸ’แž…แžถแžŸแŸ‹แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", - "admin": "แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž‡แžถ admin แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", - "moderator": "แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž‡แžถแžขแŸ’แž“แž€แžŸแž˜แŸ’แžšแžปแŸ‡แžŸแž˜แŸ’แžšแžฝแž›แŸ”", - "support": "แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž“แŸ…แž€แŸ’แž“แžปแž„แž€แŸ’แžšแžปแž˜แž‡แŸ†แž“แžฝแž™แŸ”", - "member": "แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž‡แžถแžŸแž˜แžถแž‡แžทแž€แŸ”", - "discord-administrator": "แžขแŸ’แž“แž€แžแŸ’แžšแžผแžœแž˜แžถแž“แžŸแžทแž‘แŸ’แž’แžท `ADMINISTRATOR`แŸ”" - }, - "actionInvalid": { - "close": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แžทแž‘แž แžพแž™!", - "reopen": "แžŸแŸ†แž”แžปแžแŸ’แžšแž˜แžทแž“แž‘แžถแž“แŸ‹แž”แžทแž‘!", - "claim": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž‘แž‘แžฝแž›แž แžพแž™!", - "unclaim": "แžŸแŸ†แž”แžปแžแŸ’แžšแž˜แžทแž“แž‘แžถแž“แŸ‹แžแŸ’แžšแžผแžœแž”แžถแž“แž‘แž‘แžฝแž›!", - "pin": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แž แžพแž™!", - "unpin": "แžŸแŸ†แž”แžปแžแŸ’แžšแž˜แžทแž“แž‘แžถแž“แŸ‹แžแŸ’แžšแžผแžœแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›!", - "add": "แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž“แŸแŸ‡แžขแžถแž…แž…แžผแž›แž”แŸ’แžšแžพแžŸแŸ†แž”แžปแžแŸ’แžšแž แžพแž™!", - "remove": "แž˜แžทแž“แžขแžถแž…แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž“แŸแŸ‡แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžš!" - } - }, - "params": { - "uppercase": { - "ticket": "แžŸแŸ†แž”แžปแžแŸ’แžš", - "tickets": "แžŸแŸ†แž”แžปแžแŸ’แžš", - "reason": "แž˜แžผแž›แž แŸแžแžป", - "creator": "แžขแŸ’แž“แž€แž”แž„แŸ’แž€แžพแž", - "remaining": "แž–แŸแž›แžœแŸแž›แžถแž“แŸ…แžŸแž›แŸ‹", - "added": "แž”แž“แŸ’แžแŸ‚แž˜", - "removed": "แžŠแž€", - "filter": "แžแž˜แŸ’แžšแž„", - "method": "แžœแžทแž’แžธแžŸแžถแžŸแŸ’แžแŸ’แžš", - "type": "แž”แŸ’แžšแž—แŸแž‘", - "blacklisted": "แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", - "panel": "Panel", - "command": "แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถ", - "system": "แž”แŸ’แžšแž–แŸแž“แŸ’แž’", - "true": "แž–แžทแž", - "false": "แž˜แžทแž“แž–แžทแž", - "syntax": "แžœแžถแž€แŸ’แž™แžŸแž˜แŸ’แž–แŸแž“แŸ’แž’", - "originalName": "แžˆแŸ’แž˜แŸ„แŸ‡แžŠแžพแž˜", - "newName": "แžˆแŸ’แž˜แŸ„แŸ‡แžแŸ’แž˜แžธ", - "until": "แžšแž แžผแžแžŠแž›แŸ‹", - "validOptions": "แž‡แž˜แŸ’แžšแžพแžŸแžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ", - "validPanels": "Panel แžแŸ’แžšแžนแž˜แžแŸ’แžšแžผแžœ", - "autoclose": "Autoclose", - "autodelete": "Autodelete", - "startupDate": "แž€แžถแž›แž”แžšแžทแž…แŸ’แž†แŸแž‘แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜", - "version": "แž€แŸ†แžŽแŸ‚", - "name": "แžˆแŸ’แž˜แŸ„แŸ‡", - "role": "แžแžฝแž“แžถแž‘แžธ", - "status": "แžŸแŸ’แžแžถแž“แž—แžถแž–", - "claimed": "แž‘แž‘แžฝแž›แž แžพแž™", - "pinned": "แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แž แžพแž™", - "creationDate": "แž€แžถแž›แž”แžšแžทแž…แŸ’แž†แŸแž‘แž”แž„แŸ’แž€แžพแž", - - "noone": "แž‚แŸ’แž˜แžถแž“แž“แžšแžŽแžถ", - "open": "แž”แžพแž€", - "closed": "แž”แžทแž‘", - "priority": "แžขแžถแž‘แžทแž—แžถแž–", - "participants": "แžขแŸ’แž“แž€แž…แžผแž›แžšแžฝแž˜", - "yes": "แž”แžถแž‘/แž…แžถแžŸ", - "no": "แž‘แŸ", - "option": "แž‡แž˜แŸ’แžšแžพแžŸ", - "topic": "แž”แŸ’แžšแž’แžถแž“แž”แž‘", - "uptime": "แž–แŸแž›แžŠแŸ†แžŽแžพแžšแž€แžถแžšแž”แŸ’แžšแž–แŸแž“แŸ’แž’", - "messages": "แžŸแžถแžš", - "embeds": "Embeds", - "files": "แžฏแž€แžŸแžถแžš", - "components": "แž’แžถแžแžป", - "cooldown": "แžšแž™แŸˆแž–แŸแž›แžแŸ’แžšแž‡แžถแž€แŸ‹", - "maxTickets": "แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž”แŸ†แž•แžปแž", - "admins": "แžขแŸ’แž“แž€แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„", - "roles": "แžแžฝแž“แžถแž‘แžธ", - "size": "แž‘แŸ†แž แŸ†" - }, - "lowercase": { - "text": "แžขแž€แŸ’แžŸแžš", - "html": "html", - "command": "แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถ", - "modal": "modal", - "button": "แž”แŸŠแžผแžแžปแž„", - "dropdown": "dropdown", - "method": "แžœแžทแž’แžธแžŸแžถแžŸแŸ’แžแŸ’แžš" - } - }, - "commands": { - "reason": "แž”แž‰แŸ’แž‡แžถแž€แŸ‹แž˜แžผแž›แž แŸแžแžปแžŸแŸ’แžšแŸแž…แž…แžทแžแŸ’แžแžŠแŸ‚แž›แž“แžนแž„แž”แž„แŸ’แž แžถแž‰แž€แŸ’แž“แžปแž„ logsแŸ”", - "help": "แž‘แž‘แžฝแž›แž”แžถแž“แž”แž‰แŸ’แž‡แžธแž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŠแŸ‚แž›แž˜แžถแž“แŸ”", - "panel": "แž”แž„แŸ’แž แžถแž‰แžŸแžถแžšแž‡แžถแž˜แžฝแž™ dropdown แžฌ buttons (แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž€แžถแžšแž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš)แŸ”", - "panelId": "แžขแŸ’แž“แž€แž€แŸ†แžŽแžแŸ‹แžขแžแŸ’แžแžŸแž‰แŸ’แž‰แžถแžŽ panel แžŠแŸ‚แž›แžขแŸ’แž“แž€แž…แž„แŸ‹แž”แž„แŸ’แž แžถแž‰แŸ”", - "panelAutoUpdate": "แžแžพแžขแŸ’แž“แž€แž…แž„แŸ‹ panel แž“แŸแŸ‡แž’แŸ’แžœแžพแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แž—แžถแž–แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท แž“แŸ…แž–แŸแž›แž€แŸ‚แž”แŸ’แžšแŸ‚?", - "ticket": "แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžšแž—แŸ’แž›แžถแž˜แŸ—แŸ”", - "ticketId": "แžขแŸ’แž“แž€แž€แŸ†แžŽแžแŸ‹แžขแžแŸ’แžแžŸแž‰แŸ’แž‰แžถแžŽแžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แžขแŸ’แž“แž€แž…แž„แŸ‹แž”แž„แŸ’แž€แžพแžแŸ”", - "close": "แž”แžทแž‘แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "delete": "แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "deleteNoTranscript": "แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžŠแŸ„แž™แž‚แŸ’แž˜แžถแž“แž€แžถแžšแž”แž„แŸ’แž€แžพแž transcriptแŸ”", - "reopen": "แž”แžพแž€แžŸแŸ†แž”แžปแžแŸ’แžšแžกแžพแž„แžœแžทแž‰แŸ”", - "claim": "แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "claimUser": "แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž‘แŸ…แžฑแŸ’แž™แž“แžšแžŽแžถแž˜แŸ’แž“แžถแž€แŸ‹แž•แŸ’แžŸแŸแž„แž‘แŸ€แž แž‡แŸ†แž“แžฝแžŸแžขแŸ’แž“แž€แŸ”", - "unclaim": "แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "pin": "แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "unpin": "แžŠแž€แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - - "move": "แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "moveId": "แžขแŸ’แž“แž€แž€แŸ†แžŽแžแŸ‹แžขแžแŸ’แžแžŸแž‰แŸ’แž‰แžถแžŽแž‡แž˜แŸ’แžšแžพแžŸแžŠแŸ‚แž›แžขแŸ’แž“แž€แž…แž„แŸ‹แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแž‘แŸ…แŸ”", - "rename": "แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "renameName": "แžˆแŸ’แž˜แŸ„แŸ‡แžแŸ’แž˜แžธแžŸแž˜แŸ’แžšแžถแž”แŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", - "add": "แž”แž“แŸ’แžแŸ‚แž˜แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "addUser": "แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž”แž“แŸ’แžแŸ‚แž˜แŸ”", - "remove": "แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "removeUser": "แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžŠแŸ‚แž›แžแŸ’แžšแžผแžœแžŠแž€แŸ”", - - "blacklist": "แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "blacklistView": "แž˜แžพแž›แž”แž‰แŸ’แž‡แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แŸ”", - "blacklistAdd": "แž”แž“แŸ’แžแŸ‚แž˜แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž‘แŸ…แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", - "blacklistRemove": "แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž…แŸแž‰แž–แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", - "blacklistGet": "แž‘แž‘แžฝแž›แž”แžถแž“แž–แŸแžแŸŒแž˜แžถแž“แž›แž˜แŸ’แžขแžทแžแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", - "blacklistGetUser": "แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž‘แž‘แžฝแž›แž–แŸแžแŸŒแž˜แžถแž“แž›แž˜แŸ’แžขแžทแžแŸ”", - "stats": "แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแž–แžธแž”แžผแž, แžŸแž˜แžถแž‡แžทแž€ แžฌแžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "statsReset": "แž€แŸ†แžŽแžแŸ‹แžŸแŸ’แžแžทแžแžทแž”แžผแžแž‘แžถแŸ†แž„แžขแžŸแŸ‹แžกแžพแž„แžœแžทแž‰ (แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžšแžถแž”แŸ‹แž–แžธแžŸแžผแž“แŸ’แž™)แŸ”", - "statsGlobal": "แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแžŸแžšแžปแž”แŸ”", - "statsUser": "แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", - "statsUserUser": "แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž˜แžพแž›แŸ”", - "statsTicket": "แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", - "statsTicketTicket": "แžŸแŸ†แž”แžปแžแŸ’แžšแžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž˜แžพแž›แŸ”", - - "clear": "แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžšแž…แŸ’แžšแžพแž“แž€แŸ’แž“แžปแž„แž–แŸแž›แžแŸ‚แž˜แžฝแž™แŸ”", - "clearFilter": "แžแž˜แŸ’แžšแž„แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž€แžถแžšแžŸแž˜แŸ’แžขแžถแžแžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "clearFilters": { - "all": "แž‘แžถแŸ†แž„แžขแžŸแŸ‹", - "open": "แž”แžพแž€", - "close": "แž”แžทแž‘", - "claim": "แž‘แž‘แžฝแž›แž แžพแž™", - "unclaim": "แž˜แžทแž“แž‘แžถแž“แŸ‹แž‘แž‘แžฝแž›", - "pin": "แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แž แžพแž™", - "unpin": "แž˜แžทแž“แž‘แžถแž“แŸ‹แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›", - "autoclose": "แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท" - }, - - "autoclose": "แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„ autoclose แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "autocloseDisable": "แž”แžทแž‘ autoclose แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", - "autocloseEnable": "แž”แžพแž€ autoclose แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", - "autocloseEnableTime": "แž…แŸ†แž“แžฝแž“แž˜แŸ‰แŸ„แž„แžŠแŸ‚แž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž˜แžทแž“แžŸแž€แž˜แŸ’แž˜ แžŠแžพแž˜แŸ’แž”แžธแž”แžทแž‘แŸ”", - "autodelete": "แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„ autodelete แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "autodeleteDisable": "แž”แžทแž‘ autodelete แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", - "autodeleteEnable": "แž”แžพแž€ autodelete แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", - "autodeleteEnableTime": "แž…แŸ†แž“แžฝแž“แžแŸ’แž„แŸƒแžŠแŸ‚แž›แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แžแŸ’แžšแžผแžœแž˜แžทแž“แžŸแž€แž˜แŸ’แž˜ แžŠแžพแž˜แŸ’แž”แžธแž›แžปแž”แŸ”", - - "topic": "แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„แž”แŸ’แžšแž’แžถแž“แž”แž‘แž”แžŽแŸ’แžแžถแž‰แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "topicSet": "แž€แŸ†แžŽแžแŸ‹แž”แŸ’แžšแž’แžถแž“แž”แž‘แž”แžŽแŸ’แžแžถแž‰แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "topicValue": "แž”แŸ’แžšแž’แžถแž“แž”แž‘แžแŸ’แž˜แžธแžšแž”แžŸแŸ‹แž”แžŽแŸ’แžแžถแž‰แŸ”", - "topicList": "แž‘แž‘แžฝแž›แž”แžถแž“แž”แž‰แŸ’แž‡แžธแžŸแŸ†แž”แžปแžแŸ’แžšแž‘แžถแŸ†แž„แžขแžŸแŸ‹ แž‡แžถแž˜แžฝแž™แž”แŸ’แžšแž’แžถแž“แž”แž‘ แž“แžทแž„แžŸแŸ’แžแžทแžแžทแŸ”", - "priority": "แž‚แŸ’แžšแž”แŸ‹แž‚แŸ’แžšแž„แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "prioritySet": "แž€แŸ†แžŽแžแŸ‹แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "priorityValue": "แžขแžถแž‘แžทแž—แžถแž–แžšแž”แžŸแŸ‹แž”แžŽแŸ’แžแžถแž‰แŸ”", - "priorityGet": "แž‘แž‘แžฝแž›แž”แžถแž“แžขแžถแž‘แžทแž—แžถแž–แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "priorityList": "แž‘แž‘แžฝแž›แž”แžถแž“แž”แž‰แŸ’แž‡แžธแžŸแŸ†แž”แžปแžแŸ’แžšแž‘แžถแŸ†แž„แžขแžŸแŸ‹ แž‡แžถแž˜แžฝแž™แžŸแŸ’แžแžถแž“แž—แžถแž–แžขแžถแž‘แžทแž—แžถแž–แŸ”", - "transfer": "แž•แŸ’แž‘แŸแžšแž—แžถแž–แž‡แžถแž˜แŸ’แž…แžถแžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž˜แŸ’แž“แžถแž€แŸ‹แž‘แŸ…แž˜แŸ’แž“แžถแž€แŸ‹แž‘แŸ€แžแŸ”", - "transferUser": "แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžŠแŸ‚แž›แžแŸ’แžšแžผแžœแž•แŸ’แž‘แŸแžšแž‘แŸ…แŸ”" - }, - "helpMenu": { - "help": "แž‘แž‘แžฝแž›แž”แžถแž“แž”แž‰แŸ’แž‡แžธแž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŠแŸ‚แž›แž˜แžถแž“แŸ”", - "ticket": "แž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžšแž—แŸ’แž›แžถแž˜แŸ—แŸ”", - "close": "แž”แžทแž‘แžŸแŸ†แž”แžปแžแŸ’แžš, แžœแžทแž’แžธแž“แŸแŸ‡แž”แžทแž‘แž€แžถแžšแžŸแžšแžŸแŸแžšแž€แŸ’แž“แžปแž„แž”แžŽแŸ’แžแžถแž‰แŸ”", - "delete": "แž›แžปแž”แžŸแŸ†แž”แžปแžแŸ’แžš, แžœแžทแž’แžธแž“แŸแŸ‡แž”แž„แŸ’แž€แžพแž transcript แž“แŸ…แž–แŸแž›แž”แžพแž€แŸ”", - "reopen": "แž”แžพแž€แžŸแŸ†แž”แžปแžแŸ’แžšแžกแžพแž„แžœแžทแž‰, แžœแžทแž’แžธแž“แŸแŸ‡แž”แžพแž€แž€แžถแžšแžŸแžšแžŸแŸแžšแž€แŸ’แž“แžปแž„แž”แžŽแŸ’แžแžถแž‰แž˜แŸ’แžแž„แž‘แŸ€แžแŸ”", - "pin": "แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžœแžถแž“แžนแž„แž•แŸ’แž›แžถแžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแž‘แŸ…แžแžถแž„แž›แžพ แž แžพแž™แž”แž“แŸ’แžแŸ‚แž˜ '๐Ÿ“Œ' แž‘แŸ…แžˆแŸ’แž˜แŸ„แŸ‡แŸ”", - "unpin": "แžŠแž€แž˜แŸ’แž‡แžปแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžŸแŸ†แž”แžปแžแŸ’แžšแž“แžนแž„แž“แŸ…แžแŸ†แžŽแŸ‚แž„แžŠแžŠแŸ‚แž› แž”แŸ‰แžปแž“แŸ’แžแŸ‚แž“แžนแž„แž”แžถแžแŸ‹ '๐Ÿ“Œ'แŸ”", - "move": "แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžšแžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžœแžถแž“แžนแž„แž”แŸ’แžแžผแžšแž”แŸ’แžšแž—แŸแž‘แžšแž”แžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "rename": "แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžœแžถแž“แžนแž„แž”แŸ’แžแžผแžšแžˆแŸ’แž˜แŸ„แŸ‡แž”แžŽแŸ’แžแžถแž‰แžšแž”แžŸแŸ‹แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "claim": "แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžŠแŸ„แž™แžœแžทแž’แžธแž“แŸแŸ‡ แžขแŸ’แž“แž€แžขแžถแž…แž”แŸ’แžšแžถแž”แŸ‹แž€แŸ’แžšแžปแž˜แžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแžถแžขแŸ’แž“แž€แž€แŸ†แž–แžปแž„แžŠแŸ„แŸ‡แžŸแŸ’แžšแžถแž™แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", - "unclaim": "แž›แŸ‚แž„แž‘แž‘แžฝแž›แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžŠแŸ„แž™แžœแžทแž’แžธแž“แŸแŸ‡ แžขแŸ’แž“แž€แžขแžถแž…แž”แŸ’แžšแžถแž”แŸ‹แž€แŸ’แžšแžปแž˜แžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แžแžถแžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แž‘แŸ†แž“แŸแžšแŸ”", - "add": "แž”แž“แŸ’แžแŸ‚แž˜แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž‘แŸ…แžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžœแžถแž“แžนแž„แžขแž“แžปแž‰แŸ’แž‰แžถแžแžฑแŸ’แž™แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แžขแžถแž“แž“แžทแž„แžŸแžšแžŸแŸแžšแž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "remove": "แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž…แŸแž‰แž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแŸ” แžœแžถแž“แžนแž„แžŠแž€แžŸแžทแž‘แŸ’แž’แžทแžขแžถแž“แž“แžทแž„แžŸแžšแžŸแŸแžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแŸ”", - "panel": "แž”แž„แŸ’แž แžถแž‰แžŸแžถแžšแž‡แžถแž˜แžฝแž™ dropdown แžฌ buttons (แžŸแž˜แŸ’แžšแžถแž”แŸ‹แž€แžถแžšแž”แž„แŸ’แž€แžพแžแžŸแŸ†แž”แžปแžแŸ’แžš)แŸ”", - "blacklistView": "แž˜แžพแž›แž”แž‰แŸ’แž‡แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“แŸ”", - "blacklistAdd": "แž”แž“แŸ’แžแŸ‚แž˜แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž‘แŸ…แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", - "blacklistRemove": "แžŠแž€แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž…แŸแž‰แž–แžธแž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", - "blacklistGet": "แž‘แž‘แžฝแž›แž”แžถแž“แž–แŸแžแŸŒแž˜แžถแž“แž›แž˜แŸ’แžขแžทแžแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…แŸ”", - "statsGlobal": "แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแžŸแžšแžปแž”แŸ”", - "statsTicket": "แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแž–แžธแžŸแŸ†แž”แžปแžแŸ’แžšแž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", - "statsUser": "แž˜แžพแž›แžŸแŸ’แžแžทแžแžทแž–แžธแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพแŸ”", - "statsReset": "แž€แŸ†แžŽแžแŸ‹แžŸแŸ’แžแžทแžแžทแž”แžผแžแž‘แžถแŸ†แž„แžขแžŸแŸ‹แžกแžพแž„แžœแžทแž‰ (แž…แžถแž”แŸ‹แž•แŸ’แžแžพแž˜แžšแžถแž”แŸ‹แž–แžธแžŸแžผแž“แŸ’แž™)แŸ”", - "autocloseDisable": "แž”แžทแž‘ autoclose แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", - "autocloseEnable": "แž”แžพแž€ autoclose แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", - "autodeleteDisable": "แž”แžทแž‘ autodelete แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", - "autodeleteEnable": "แž”แžพแž€ autodelete แž€แŸ’แž“แžปแž„แžŸแŸ†แž”แžปแžแŸ’แžšแž“แŸแŸ‡แŸ”", - "categories": { - "general": "แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž‘แžผแž‘แŸ…", - "basicTicket": "แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžšแž˜แžผแž›แžŠแŸ’แž‹แžถแž“", - "advancedTicket": "แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžšแž€แž˜แŸ’แžšแžทแžแžแŸ’แž–แžŸแŸ‹", - "userTicket": "แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแžŸแŸ†แž”แžปแžแŸ’แžšแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹", - "admin": "แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถ Admin", - "advanced": "แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž€แž˜แŸ’แžšแžทแžแžแŸ’แž–แžŸแŸ‹", - "extra": "แž–แžถแž€แŸ’แž™แž”แž‰แŸ’แž‡แžถแž”แž“แŸ’แžแŸ‚แž˜" - } - }, - "stats": { - "scopes": { - "global": "แžŸแŸ’แžแžทแžแžทแžŸแžšแžปแž”", - "system": "แžŸแŸ’แžแžทแžแžทแž”แŸ’แžšแž–แŸแž“แŸ’แž’", - "user": "แžŸแŸ’แžแžทแžแžทแžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹", - "ticket": "แžŸแŸ’แžแžทแžแžทแžŸแŸ†แž”แžปแžแŸ’แžš", - "participants": "แžขแŸ’แž“แž€แž…แžผแž›แžšแžฝแž˜", - "messages": "แžŸแžถแžš" - }, - "properties": { - "ticketsCreated": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แž„แŸ’แž€แžพแž", - "ticketsClosed": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แžทแž‘", - "ticketsDeleted": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž›แžปแž”", - "ticketsReopened": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แžพแž€แžกแžพแž„แžœแžทแž‰", - "ticketsAutoclosed": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž”แžทแž‘แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท", - "ticketsClaimed": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž‘แž‘แžฝแž›", - "ticketsPinned": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›", - "ticketsMoved": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž•แŸ’แž›แžถแžŸแŸ‹แž”แŸ’แžแžผแžš", - "usersBlacklisted": "แžขแŸ’แž“แž€แž”แŸ’แžšแžพแž”แŸ’แžšแžถแžŸแŸ‹แž€แŸ’แž“แžปแž„แž”แž‰แŸ’แž‡แžธแžแŸ’แž˜แŸ…", - "transcriptsCreated": "Transcript แž”แžถแž“แž”แž„แŸ’แž€แžพแž", - "ticketsAutodeleted": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž›แžปแž”แžŠแŸ„แž™แžŸแŸ’แžœแŸแž™แž”แŸ’แžšแžœแžแŸ’แžแžท", - "ticketsTransferred": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แžถแž“แž•แŸ’แž‘แŸแžš", - "ticketVolume": "แž…แŸ†แž“แžฝแž“แžŸแŸ†แž”แžปแžแŸ’แžš", - "averageTickets": "แž…แŸ†แž“แžฝแž“แžŸแŸ†แž”แžปแžแŸ’แžšแž‡แžถแž˜แž’แŸ’แž™แž˜/แžขแŸ’แž“แž€แž”แŸ’แžšแžพ", - "currentTickets": "แžŸแŸ†แž”แžปแžแŸ’แžšแž”แž…แŸ’แž…แžปแž”แŸ’แž”แž“แŸ’แž“", - "age": "แžขแžถแž™แžปแžŸแŸ†แž”แžปแžแŸ’แžš", - "responseTime": "แž–แŸแž›แž†แŸ’แž›แžพแž™แžแž”", - "resolutionTime": "แž–แŸแž›แžŠแŸ„แŸ‡แžŸแŸ’แžšแžถแž™", - "createdOn": "แž”แž„แŸ’แž€แžพแžแž“แŸ…", - "createdBy": "แž”แž„แŸ’แž€แžพแžแžŠแŸ„แž™", - "closedOn": "แž”แžทแž‘แž“แŸ…", - "closedBy": "แž”แžทแž‘แžŠแŸ„แž™", - "claimedOn": "แž‘แž‘แžฝแž›แž“แŸ…", - "claimedBy": "แž‘แž‘แžฝแž›แžŠแŸ„แž™", - "pinnedOn": "แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แž“แŸ…", - "pinnedBy": "แžŠแžถแž€แŸ‹แž˜แŸ’แž‡แžปแž›แžŠแŸ„แž™", - "deletedOn": "แž›แžปแž”แž“แŸ…", - "deletedBy": "แž›แžปแž”แžŠแŸ„แž™" - }, - "roles": { - "developer": "Developer", - "serverOwner": "แž˜แŸ’แž…แžถแžŸแŸ‹แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ", - "serverAdmin": "Admin แž˜แŸ‰แžถแžŸแŸŠแžธแž“แž”แž˜แŸ’แžšแžพ", - "moderator": "แž€แŸ’แžšแžปแž˜แžขแŸ’แž“แž€แžŸแž˜แŸ’แžšแžปแŸ‡แžŸแž˜แŸ’แžšแžฝแž›", - "support": "แž€แŸ’แžšแžปแž˜แž‡แŸ†แž“แžฝแž™", - "member": "แžŸแž˜แžถแž‡แžทแž€" - } - }, - "panel": { - "selectTicket": "แž‡แŸ’แžšแžพแžŸแžšแžพแžŸแžŸแŸ†แž”แžปแžแŸ’แžšแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€", - "selectRole": "แž‡แŸ’แžšแžพแžŸแžšแžพแžŸแžแžฝแž“แžถแž‘แžธแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€", - "selectOption": "แž‡แŸ’แžšแžพแžŸแžšแžพแžŸแž‡แž˜แŸ’แžšแžพแžŸแžšแž”แžŸแŸ‹แžขแŸ’แž“แž€" - }, - "priorities": { - "urgent": "แž”แž“แŸ’แž‘แžถแž“แŸ‹แžแŸ’แž›แžถแŸ†แž„", - "veryHigh": "แžแŸ’แž–แžŸแŸ‹แžŽแžถแžŸแŸ‹", - "high": "แžแŸ’แž–แžŸแŸ‹", - "normal": "แž’แž˜แŸ’แž˜แžแžถ", - "low": "แž‘แžถแž”", - "veryLow": "แž‘แžถแž”แžŽแžถแžŸแŸ‹", - "none": "แž‚แŸ’แž˜แžถแž“" - } -} +} \ No newline at end of file diff --git a/languages/korean.json b/languages/korean.json index 00e2d6b..17234c6 100644 --- a/languages/korean.json +++ b/languages/korean.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], "lastedited":"16/02/2026", "language":"Korean", diff --git a/languages/kurdish.json b/languages/kurdish.json index 0b14b78..be9547e 100644 --- a/languages/kurdish.json +++ b/languages/kurdish.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], "lastedited":"16/02/2026", "language":"Kurdish", diff --git a/languages/latvian.json b/languages/latvian.json index 859e8ce..16755a1 100644 --- a/languages/latvian.json +++ b/languages/latvian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["NoOneNook"], "lastedited":"16/02/2026", "language":"Latvian", diff --git a/languages/lithuanian.json b/languages/lithuanian.json index ba21919..9c32861 100644 --- a/languages/lithuanian.json +++ b/languages/lithuanian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["TsgIndrius"], "lastedited":"16/02/2026", "language":"Lithuanian", diff --git a/languages/norwegian.json b/languages/norwegian.json index 42b6ff5..a87a119 100644 --- a/languages/norwegian.json +++ b/languages/norwegian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["NoOneNook"], "lastedited":"16/02/2026", "language":"Norwegian", diff --git a/languages/persian.json b/languages/persian.json index f262ff9..2d79b24 100644 --- a/languages/persian.json +++ b/languages/persian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["dysashop","zhavis"], "lastedited":"16/02/2026", "language":"Persian", diff --git a/languages/polish.json b/languages/polish.json index ae3bcc9..b30c1c0 100644 --- a/languages/polish.json +++ b/languages/polish.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["DanoGlez"], "lastedited":"16/02/2026", "language":"Polish", diff --git a/languages/portuguese.json b/languages/portuguese.json index 182a73d..3e3ad64 100644 --- a/languages/portuguese.json +++ b/languages/portuguese.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["quiradon"], "lastedited":"16/02/2026", "language":"Portuguese", diff --git a/languages/romanian.json b/languages/romanian.json index 36e26ee..3521dc7 100644 --- a/languages/romanian.json +++ b/languages/romanian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["SankeDev"], "lastedited":"16/02/2026", "language":"Romanian", diff --git a/languages/russian.json b/languages/russian.json index adc1081..16e833c 100644 --- a/languages/russian.json +++ b/languages/russian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["NoOneNook"], "lastedited":"16/02/2026", "language":"Russian", diff --git a/languages/simplified-chinese.json b/languages/simplified-chinese.json index 07457d7..4d1564c 100644 --- a/languages/simplified-chinese.json +++ b/languages/simplified-chinese.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], "lastedited":"16/02/2026", "language":"Simplified Chainese", diff --git a/languages/slovenian.json b/languages/slovenian.json index 6ec6988..305d3e4 100644 --- a/languages/slovenian.json +++ b/languages/slovenian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], "lastedited":"16/02/2026", "language":"Solvenian", diff --git a/languages/spanish.json b/languages/spanish.json index f9d7335..cf15d56 100644 --- a/languages/spanish.json +++ b/languages/spanish.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["Redactado","Josuens"], "lastedited":"16/02/2026", "language":"Spanish", diff --git a/languages/swedish.json b/languages/swedish.json index d71f506..15571cc 100644 --- a/languages/swedish.json +++ b/languages/swedish.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["NoOneNook"], "lastedited":"16/02/2026", "language":"Svenska", diff --git a/languages/tamil.json b/languages/tamil.json index 0a52857..9b68dc7 100644 --- a/languages/tamil.json +++ b/languages/tamil.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["HanumeshGupta","ChatGPT"], "lastedited":"16/02/2026", "language":"Tamil", diff --git a/languages/thai.json b/languages/thai.json index 71f1294..f2b4b6a 100644 --- a/languages/thai.json +++ b/languages/thai.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["modshd"], "lastedited":"16/02/2026", "language":"Thai", diff --git a/languages/traditional-chinese.json b/languages/traditional-chinese.json new file mode 100644 index 0000000..d44f21c --- /dev/null +++ b/languages/traditional-chinese.json @@ -0,0 +1,608 @@ +{ + "_TRANSLATION":{ + "otversion":"v4.2.0", + "translators":["me.october"], + "lastedited":"10/03/2026", + "language":"Traditional Chinese", + "automated":false + }, + "checker":{ + "system":{ + "typeError":"[้Œฏ่ชค]", + "headerOpenTicket":"้–‹ๅ•Ÿๅทฅๅ–ฎ", + "typeWarning":"[่ญฆๅ‘Š]", + "typeInfo":"[ไฟกๆฏ]", + "headerConfigChecker":"้…็ฝฎๆชขๆŸฅๅ™จ", + "headerDescription":"ๆชขๆŸฅๆ‚จ็š„้…็ฝฎๆ–‡ไปถไธญ็š„้Œฏ่ชค๏ผ", + "footerError":"ๆฉŸๅ™จไบบๅฐ‡็„กๆณ•ๅ•Ÿๅ‹•๏ผŒ็›ดๅˆฐๆ‰€ๆœ‰{0}้Œฏ่ชค่ขซไฟฎ่ค‡๏ผ", + "footerWarning":"ๅปบ่ญฐๅœจๅ•Ÿๅ‹•ๅ‰ไฟฎ่ค‡ๆ‰€ๆœ‰{0}่ญฆๅ‘Š๏ผ", + "footerSupport":"ๆ”ฏๆด๏ผš{0} - ๆ–‡ๆช”๏ผš{1}", + "compactInformation":"ไฝฟ็”จ{0}็ฒๅ–ๆ›ดๅคšไฟกๆฏ๏ผ", + "dataPath":"่ทฏๅพ‘", + "dataDocs":"ๆ–‡ๆช”", + "dataMessages":"ๆถˆๆฏ" + }, + "messages":{ + "stringTooShort":"ๆญคๅญ—็ฌฆไธฒไธ่ƒฝๅฐ‘ๆ–ผ{0}ๅ€‹ๅญ—็ฌฆ๏ผ", + "stringTooLong":"ๆญคๅญ—็ฌฆไธฒไธ่ƒฝ่ถ…้Ž{0}ๅ€‹ๅญ—็ฌฆ๏ผ", + "stringLengthInvalid":"ๆญคๅญ—็ฌฆไธฒ้•ทๅบฆๅฟ…้ ˆ็‚บ{0}ๅ€‹ๅญ—็ฌฆ๏ผ", + "stringStartsWith":"ๆญคๅญ—็ฌฆไธฒๅฟ…้ ˆไปฅ{0}้–‹้ ญ๏ผ", + "stringEndsWith":"ๆญคๅญ—็ฌฆไธฒๅฟ…้ ˆไปฅ{0}็ตๅฐพ๏ผ", + "stringContains":"ๆญคๅญ—็ฌฆไธฒๅฟ…้ ˆๅŒ…ๅซ{0}๏ผ", + "stringChoices":"ๆญคๅญ—็ฌฆไธฒ้šป่ƒฝๆ˜ฏไปฅไธ‹ๅ€ผไน‹ไธ€๏ผš{0}๏ผ", + "stringRegex":"ๆญคๅญ—็ฌฆไธฒ็„กๆ•ˆ๏ผ", + "stringInvertedContains":"ๆญคๅญ—็ฌฆไธฒไธ่ƒฝๅŒ…ๅซ {0}๏ผ", + "stringLowercase":"ๆญคๅญ—็ฌฆไธฒๅฟ…้ ˆๅ…จ้ƒจไฝฟ็”จๅฐๅฏซๅญ—ๆฏ๏ผ", + "stringUppercase":"ๆญคๅญ—็ฌฆไธฒๅฟ…้ ˆๅ…จ้ƒจไฝฟ็”จๅคงๅฏซๅญ—ๆฏ๏ผ", + "stringSpecialCharacters":"ๆญคๅญ—็ฌฆไธฒไธ่ƒฝๅŒ…ๅซไปปไฝ•็‰นๆฎŠๅญ—็ฌฆ๏ผ๏ผˆๅƒ…ๅ…่จฑ a-zใ€0-9 ๅ’Œ็ฉบๆ ผ๏ผ‰", + "stringNoSpaces":"ๆญคๅญ—็ฌฆไธฒไธ่ƒฝๅŒ…ๅซ็ฉบๆ ผ๏ผ", + "stringCapitalWord":"ๅปบ่ญฐๆญคๅญ—็ฌฆไธฒไธญ็š„ๆฏๅ€‹ๅ–ฎ่ฉž้ƒฝไปฅๅคงๅฏซๅญ—ๆฏ้–‹้ ญ๏ผ", + "stringCapitalSentence":"ๆญคๅญ—็ฌฆไธฒไธญ็š„ๆŸไบ›ๅฅๅญไผผไนŽๅ†‡ๆœ‰ไปฅๅคงๅฏซๅญ—ๆฏ้–‹้ ญ๏ผ", + "stringPunctuation":"ๆญคๅญ—็ฌฆไธฒไธญ็š„ๅฅๅญไผผไนŽๅ†‡ๆœ‰ไปฅๆจ™้ปž็ฌฆ่™Ÿ็ตๅฐพ๏ผ", + + "numberTooShort":"ๆญคๆ•ธๅญ—ไธ่ƒฝๅฐ‘ๆ–ผ{0}ไฝ๏ผ", + "numberTooLong":"ๆญคๆ•ธๅญ—ไธ่ƒฝ่ถ…้Ž{0}ไฝ๏ผ", + "numberLengthInvalid":"ๆญคๆ•ธๅญ—้•ทๅบฆๅฟ…้ ˆ็‚บ{0}ไฝ๏ผ", + "numberTooSmall":"ๆญคๆ•ธๅญ—่‡ณๅฐ‘็‚บ{0}๏ผ", + "numberTooLarge":"ๆญคๆ•ธๅญ—ๆœ€ๅคš็‚บ{0}๏ผ", + "numberNotEqual":"ๆญคๆ•ธๅญ—ๅฟ…้ ˆ็‚บ{0}๏ผ", + "numberStep":"ๆญคๆ•ธๅญ—ๅฟ…้ ˆๆ˜ฏ{0}็š„ๅ€ๆ•ธ๏ผ", + "numberStepOffset":"ๆญคๆ•ธๅญ—ๅฟ…้ ˆๆ˜ฏ{0}็š„ๅ€ๆ•ธ๏ผŒ่ตทๅง‹ๅ€ผ็‚บ{1}๏ผ", + "numberStartsWith":"ๆญคๆ•ธๅญ—ๅฟ…้ ˆไปฅ{0}้–‹้ ญ๏ผ", + "numberEndsWith":"ๆญคๆ•ธๅญ—ๅฟ…้ ˆไปฅ{0}็ตๅฐพ๏ผ", + "numberContains":"ๆญคๆ•ธๅญ—ๅฟ…้ ˆๅŒ…ๅซ{0}๏ผ", + "numberChoices":"ๆญคๆ•ธๅญ—้šป่ƒฝๆ˜ฏไปฅไธ‹ๅ€ผไน‹ไธ€๏ผš{0}๏ผ", + "numberFloat":"ๆญคๆ•ธๅญ—ไธ่ƒฝ็‚บๅฐๆ•ธ๏ผ", + "numberNegative":"ๆญคๆ•ธๅญ—ไธ่ƒฝ็‚บ่ฒ ๆ•ธ๏ผ", + "numberPositive":"ๆญคๆ•ธๅญ—ไธ่ƒฝ็‚บๆญฃๆ•ธ๏ผ", + "numberZero":"ๆญคๆ•ธๅญ—ไธ่ƒฝ็‚บ้›ถ๏ผ", + "numberNan":"ๆญคๆ•ธๅญ—ไธ่ƒฝ็‚บ NaN๏ผˆ้žๆ•ธๅญ—๏ผ‰๏ผ", + "numberInvertedContains":"ๆญคๆ•ธๅญ—ไธ่ƒฝๅŒ…ๅซ {0}๏ผ", + + "booleanTrue":"ๆญคไฝˆ็ˆพๅ€ผไธ่ƒฝ็‚บ็œŸ๏ผ", + "booleanFalse":"ๆญคไฝˆ็ˆพๅ€ผไธ่ƒฝ็‚บๅ‡๏ผ", + + "arrayEmptyDisabled":"ๆญคๆ•ธ็ต„ไธๅ…่จฑ็‚บ็ฉบ๏ผ", + "arrayEmptyRequired":"ๆญคๆ•ธ็ต„ๅฟ…้ ˆ็‚บ็ฉบ๏ผ", + "arrayTooShort":"ๆญคๆ•ธ็ต„้•ทๅบฆ่‡ณๅฐ‘็‚บ{0}๏ผ", + "arrayTooLong":"ๆญคๆ•ธ็ต„้•ทๅบฆๆœ€ๅคš็‚บ{0}๏ผ", + "arrayLengthInvalid":"ๆญคๆ•ธ็ต„้•ทๅบฆๅฟ…้ ˆ็‚บ{0}๏ผ", + "arrayInvalidTypes":"ๆญคๆ•ธ็ต„้šป่ƒฝๅŒ…ๅซไปฅไธ‹้กžๅž‹๏ผš{0}๏ผ", + "arrayDouble":"ๆญคๆ•ธ็ต„ไธๅ…่จฑ้‡่ค‡ๅ€ผ๏ผ", + + "discordInvalidId":"้€™ๆ˜ฏ็„กๆ•ˆ็š„Discord {0} ID๏ผ", + "discordInvalidIdOptions":"้€™ๆ˜ฏ็„กๆ•ˆ็š„Discord {0} ID๏ผๆ‚จ้‚„ๅฏไปฅไฝฟ็”จไปฅไธ‹ไน‹ไธ€๏ผš{1}๏ผ", + "discordInvalidToken":"้€™ๆ˜ฏ็„กๆ•ˆ็š„Discordไปค็‰Œ๏ผˆๆ–‡ๆณ•ไธŠ๏ผ‰๏ผ", + "colorInvalid":"้€™ๆ˜ฏ็„กๆ•ˆ็š„ๅๅ…ญ้€ฒ่ฃฝ้ก”่‰ฒ๏ผ", + "emojiTooShort":"ๆญคๅญ—็ฌฆไธฒ่‡ณๅฐ‘้œ€่ฆ{0}ๅ€‹้Œถๆƒ…็ฌฆ่™Ÿ๏ผ", + "emojiTooLong":"ๆญคๅญ—็ฌฆไธฒๆœ€ๅคš้šป่ƒฝๆœ‰{0}ๅ€‹้Œถๆƒ…็ฌฆ่™Ÿ๏ผ", + "emojiCustom":"ๆญค้Œถๆƒ…็ฌฆ่™Ÿไธ่ƒฝๆ˜ฏ่‡ชๅฎš็พฉDiscord้Œถๆƒ…็ฌฆ่™Ÿ๏ผ", + "emojiInvalid":"้€™ๆ˜ฏ็„กๆ•ˆ็š„้Œถๆƒ…็ฌฆ่™Ÿ๏ผ", + "urlInvalid":"ๆญคURL็„กๆ•ˆ๏ผ", + "urlInvalidHttp":"This url can only use the https:// protocol!", + "urlInvalidProtocol":"This url can only use the http:// & https:// protocols!", + "urlInvalidHostname":"ๆญคURL็š„ไธปๆฉŸๅไธ่ขซๅ…่จฑ๏ผ", + "urlInvalidExtension":"ๆญคURL็š„ๆ“ดๅฑ•ๅ็„กๆ•ˆ๏ผ่ซ‹้ธๆ“‡๏ผš{0}๏ผ", + "urlInvalidPath":"ๆญคURL็š„่ทฏๅพ‘็„กๆ•ˆ๏ผ", + "idNotUnique":"ๆญคIDไธๅ”ฏไธ€๏ผŒ่ซ‹ไฝฟ็”จๅ…ถไป–ID๏ผ", + "idNonExistent":"ID {0}ไธๅญ˜ๅœจ๏ผ", + + "invalidType":"ๆญคๅฑฌๆ€งๅฟ…้ ˆ็‚บ้กžๅž‹๏ผš{0}๏ผ", + "propertyMissing":"ๆญคๅฐ่ฑก็ผบๅฐ‘ๅฑฌๆ€ง{0}๏ผ", + "propertyOptional":"ๆญคๅฐ่ฑกไธญ็š„ๅฑฌๆ€ง{0}ๆ˜ฏๅฏ้ธ็š„๏ผ", + "objectDisabled":"ๆญคๅฐ่ฑกๅทฒ็ฆ็”จ๏ผŒ่ซ‹ไฝฟ็”จ{0}ๅ•Ÿ็”จ๏ผ", + "nullInvalid":"ๆญคๅฑฌๆ€งไธ่ƒฝ็‚บnull๏ผ", + "switchInvalidType":"ๆญคๅฑฌๆ€งๅฟ…้ ˆๆ˜ฏไปฅไธ‹้กžๅž‹ไน‹ไธ€๏ผš{0}๏ผ", + "objectSwitchInvalid":"ๆญคๅฐ่ฑกๅฟ…้ ˆๆ˜ฏไปฅไธ‹้กžๅž‹ไน‹ไธ€๏ผš{0}๏ผ", + + "invalidLanguage":"้€™ๆ˜ฏ็„กๆ•ˆ็š„่ชž่จ€๏ผ", + "invalidButton":"ๆญคๆŒ‰้ˆ•ๅฟ…้ ˆ่‡ณๅฐ‘ๆœ‰ไธ€ๅ€‹{0}ๆˆ–{1}๏ผ", + "unusedOption":"้ธ้ …{0}ๆœชๅœจไปปไฝ•ๅœฐๆ–นไฝฟ็”จ๏ผ", + "unusedQuestion":"ๅ•้กŒ{0}ๆœชๅœจไปปไฝ•ๅœฐๆ–นไฝฟ็”จ๏ผ", + "dropdownOption":"ๅ•Ÿ็”จไธ‹ๆ‹‰่œๅ–ฎ็š„้บต้—†้šป่ƒฝๅŒ…ๅซโ€œticketโ€้กžๅž‹็š„้ธ้ …๏ผ", + "customInvalidVersion":"้…็ฝฎๆ–‡ไปถไธญๆŒ‡ๅฎš็š„็‰ˆๆœฌไธๅŒน้…๏ผ่ซ‹็ขบไฟๆ‚จๅทฒๅฐ‡้…็ฝฎๆ›ดๆ–ฐ่‡ณๆœ€ๆ–ฐ็‰ˆๆœฌ๏ผ" + } + }, + "actions":{ + "buttons":{ + "create":"่จชๅ•ๅทฅๅ–ฎ", + "close":"้—œ้–‰ๅทฅๅ–ฎ", + "delete":"ๅˆช้™คๅทฅๅ–ฎ", + "reopen":"้‡ๆ–ฐ้–‹ๅ•Ÿๅทฅๅ–ฎ", + "claim":"่ช้ ˜ๅทฅๅ–ฎ", + "unclaim":"ๅ–ๆถˆ่ช้ ˜ๅทฅๅ–ฎ", + "pin":"็ฝฎ้ ‚ๅทฅๅ–ฎ", + "unpin":"ๅ–ๆถˆ็ฝฎ้ ‚ๅทฅๅ–ฎ", + "clear":"ๅˆช้™คๅทฅๅ–ฎ", + "helpSwitchSlash":"ๆชข่ฆ–ๆ–œๆ ๅ‘ฝไปค", + "helpSwitchText":"ๆชข่ฆ–ๆ–‡ๆœฌๅ‘ฝไปค", + "helpPage":"็ฌฌ{0}้ ", + "withReason":"ๅธถๅŽŸๅ› ", + "withoutTranscript":"ไธๅธถ่จ˜้Œ„" + }, + "titles":{ + "created":"ๅทฅๅ–ฎๅทฒๅ‰ตๅปบ", + "close":"ๅทฅๅ–ฎๅทฒ้—œ้–‰", + "delete":"ๅทฅๅ–ฎๅทฒๅˆช้™ค", + "reopen":"ๅทฅๅ–ฎๅทฒ้‡ๆ–ฐ้–‹ๅ•Ÿ", + "claim":"ๅทฅๅ–ฎๅทฒ่ช้ ˜", + "unclaim":"ๅทฅๅ–ฎๅทฒๅ–ๆถˆ่ช้ ˜", + "pin":"ๅทฅๅ–ฎๅทฒ็ฝฎ้ ‚", + "unpin":"ๅทฅๅ–ฎๅทฒๅ–ๆถˆ็ฝฎ้ ‚", + "rename":"ๅทฅๅ–ฎๅทฒ้‡ๅ‘ฝๅ", + "move":"ๅทฅๅ–ฎๅทฒ็งปๅ‹•", + "add":"ๅทฅๅ–ฎ็”จๆˆถๅทฒๆทปๅŠ ", + "remove":"ๅทฅๅ–ฎ็”จๆˆถๅทฒ็งป้™ค", + + "help":"ๅฏ็”จๅ‘ฝไปค", + "statsReset":"้‡็ฝฎ็ตฑ่จˆ", + "blacklistAdd":"็”จๆˆถๅทฒๆ‹‰้ป‘", + "blacklistRemove":"็”จๆˆถๅทฒ่งฃ็ฆ", + "blacklistGet":"ๅทฒๆ‹‰้ป‘็”จๆˆถ", + "blacklistView":"็•ถๅ‰้ป‘ๅๅ–ฎ", + "blacklistAddDm":"ๅทฒๅŠ ๅ…ฅ้ป‘ๅๅ–ฎ", + "blacklistRemoveDm":"ๅทฒๅพž้ป‘ๅๅ–ฎ็งป้™ค", + "clear":"ๅทฅๅ–ฎๅทฒๆธ…้™ค", + "clearTickets":"ๆธ…้™คๅทฅๅ–ฎ", + "roles":"่ง’่‰ฒๅทฒๆ›ดๆ–ฐ", + + "autoclose":"ๅทฅๅ–ฎ่‡ชๅ‹•้—œ้–‰", + "autocloseEnabled":"่‡ชๅ‹•้—œ้–‰ๅทฒๅ•Ÿ็”จ", + "autocloseDisabled":"่‡ชๅ‹•้—œ้–‰ๅทฒ็ฆ็”จ", + "autodelete":"ๅทฅๅ–ฎ่‡ชๅ‹•ๅˆช้™ค", + "autodeleteEnabled":"่‡ชๅ‹•ๅˆช้™คๅทฒๅ•Ÿ็”จ", + "autodeleteDisabled":"่‡ชๅ‹•ๅˆช้™คๅทฒ็ฆ็”จ", + + "topicSet":"ไธป้กŒๅทฒๆ›ดๆ”น", + "prioritySet":"ๅ„ชๅ…ˆ็ดšๅทฒๆ›ดๆ”น", + "priorityGet":"ๅทฅๅ–ฎๅ„ชๅ…ˆ็ดš", + "transfer":"ๅทฅๅ–ฎๅทฒ่ฝ‰็งป" + }, + "descriptions":{ + "create":"ๆ‚จ็š„ๅทฅๅ–ฎๅทฒๅ‰ตๅปบใ€‚้ปž้ธไธ‹ๆ–นๆŒ‰้ˆ•่จชๅ•๏ผ", + "close":"ๅทฅๅ–ฎๅทฒๆˆๅŠŸ้—œ้–‰๏ผ", + "delete":"ๅทฅๅ–ฎๅทฒๆˆๅŠŸๅˆช้™ค๏ผ", + "reopen":"ๅทฅๅ–ฎๅทฒๆˆๅŠŸ้‡ๆ–ฐ้–‹ๅ•Ÿ๏ผ", + "claim":"ๅทฅๅ–ฎๅทฒๆˆๅŠŸ่ช้ ˜๏ผ", + "unclaim":"ๅทฅๅ–ฎๅทฒๆˆๅŠŸๅ–ๆถˆ่ช้ ˜๏ผ", + "pin":"ๅทฅๅ–ฎๅทฒๆˆๅŠŸ็ฝฎ้ ‚๏ผ", + "unpin":"ๅทฅๅ–ฎๅทฒๆˆๅŠŸๅ–ๆถˆ็ฝฎ้ ‚๏ผ", + "rename":"ๅทฅๅ–ฎๅทฒๆˆๅŠŸ้‡ๅ‘ฝๅ็‚บ{0}๏ผ", + "move":"ๅทฅๅ–ฎๅทฒๆˆๅŠŸ็งปๅ‹•ๅˆฐ{0}๏ผ", + "add":"{0}ๅทฒๆˆๅŠŸๆทปๅŠ ๅˆฐๅทฅๅ–ฎ๏ผ", + "remove":"{0}ๅทฒๆˆๅŠŸๅพžๅทฅๅ–ฎ็งป้™ค๏ผ", + + "helpExplanation":"`<ๅ็จฑ>` => ๅฟ…ๅกซๅƒๆ•ธ\n`[ๅ็จฑ]` => ๅฏ้ธๅƒๆ•ธ", + "statsReset":"ๆฉŸๅ™จไบบ็ตฑ่จˆๅทฒๆˆๅŠŸ้‡็ฝฎ๏ผ", + "statsError":"็„กๆณ•ๆชข่ฆ–ๅทฅๅ–ฎ็ตฑ่จˆ๏ผ\n{0}ไธๆ˜ฏๅทฅๅ–ฎ๏ผ", + "blacklistAdd":"{0}ๅทฒๆˆๅŠŸๆ‹‰้ป‘๏ผ", + "blacklistRemove":"{0}ๅทฒๆˆๅŠŸ่งฃ็ฆ๏ผ", + "blacklistGetSuccess":"{0}็•ถๅ‰ๅทฒ่ขซๆ‹‰้ป‘๏ผ", + "blacklistGetEmpty":"{0}็•ถๅ‰ๆœช่ขซๆ‹‰้ป‘๏ผ", + "blacklistViewEmpty":"ๅฐšๆœชๆœ‰ไบบ่ขซๆ‹‰้ป‘๏ผ", + "blacklistViewTip":"ไฝฟ็”จโ€œ/blacklist addโ€ๆ‹‰้ป‘็”จๆˆถ๏ผ", + "clearVerify":"็ขบๅฎš่ฆๅˆช้™คๅคšๅ€‹ๅทฅๅ–ฎๅ—Ž๏ผŸ\nๆญคๆ“ไฝœ็„กๆณ•ๆ’ค้Šท๏ผ", + "clearReady":"{0}ๅ€‹ๅทฅๅ–ฎๅทฒๆˆๅŠŸๅˆช้™ค๏ผ", + "rolesEmpty":"ๆœชๆ›ดๆ–ฐไปปไฝ•่ง’่‰ฒ๏ผ", + + "autocloseLeave":"ๆญคๅทฅๅ–ฎๅทฒ่‡ชๅ‹•้—œ้–‰๏ผŒๅ› ็‚บๅ‰ตๅปบ่€…้›ข้–‹ไบ†ๆœๅ‹™ๅ™จ๏ผ", + "autocloseTimeout":"ๆญคๅทฅๅ–ฎๅทฒ่‡ชๅ‹•้—œ้–‰๏ผŒๅ› ็‚บๅฎƒๅทฒ่ถ…้Ž`{0}ๅฐๆ™‚`ๆœชๆดปๅ‹•๏ผ", + "autodeleteLeave":"ๆญคๅทฅๅ–ฎๅทฒ่‡ชๅ‹•ๅˆช้™ค๏ผŒๅ› ็‚บๅ‰ตๅปบ่€…้›ข้–‹ไบ†ๆœๅ‹™ๅ™จ๏ผ", + "autodeleteTimeout":"ๆญคๅทฅๅ–ฎๅทฒ่‡ชๅ‹•ๅˆช้™ค๏ผŒๅ› ็‚บๅฎƒๅทฒ่ถ…้Ž`{0}ๅคฉ`ๆœชๆดปๅ‹•๏ผ", + "autocloseEnabled":"ๆญคๅทฅๅ–ฎๅทฒๅ•Ÿ็”จ่‡ชๅ‹•้—œ้–‰๏ผ\n่ถ…้Ž`{0}ๅฐๆ™‚`ๆœชๆดปๅ‹•ๅพŒๅฐ‡่‡ชๅ‹•้—œ้–‰๏ผ", + "autocloseDisabled":"ๆญคๅทฅๅ–ฎๅทฒ็ฆ็”จ่‡ชๅ‹•้—œ้–‰๏ผ\nไธๅ†่‡ชๅ‹•้—œ้–‰๏ผ", + "autodeleteEnabled":"ๆญคๅทฅๅ–ฎๅทฒๅ•Ÿ็”จ่‡ชๅ‹•ๅˆช้™ค๏ผ\n่ถ…้Ž`{0}ๅคฉ`ๆœชๆดปๅ‹•ๅพŒๅฐ‡่‡ชๅ‹•ๅˆช้™ค๏ผ", + "autodeleteDisabled":"ๆญคๅทฅๅ–ฎๅทฒ็ฆ็”จ่‡ชๅ‹•ๅˆช้™ค๏ผ\nไธๅ†่‡ชๅ‹•ๅˆช้™ค๏ผ", + + "ticketMessageLimit":"ๆ‚จ้šป่ƒฝๅŒๆ™‚ๅ‰ตๅปบ{0}ๅ€‹ๅทฅๅ–ฎ๏ผ", + "ticketMessageAutoclose":"ๆญคๅทฅๅ–ฎๅฐ‡ๅœจ่ถ…้Ž{0}ๅฐๆ™‚ๆœชๆดปๅ‹•ๅพŒ่‡ชๅ‹•้—œ้–‰๏ผ", + "ticketMessageAutodelete":"ๆญคๅทฅๅ–ฎๅฐ‡ๅœจ่ถ…้Ž{0}ๅคฉๆœชๆดปๅ‹•ๅพŒ่‡ชๅ‹•ๅˆช้™ค๏ผ", + "panelReady":"้บต้—†ๅทฒๅœจๅพŒ็บŒๆถˆๆฏไธญๅฏ็”จ๏ผ\nๆญคๆถˆๆฏ็พๅœจๅฏไปฅๅˆช้™ค๏ผ", + + "topicSet":"้ ป้“ไธป้กŒๅทฒ็”ฑ {0} ๆˆๅŠŸๆ›ดๆ”น๏ผ", + "prioritySet":"ๅทฅๅ–ฎๅ„ชๅ…ˆ็ดšๅทฒ็”ฑ {1} ๆˆๅŠŸๆ›ดๆ”น็‚บ {0}๏ผ", + "priorityGet":"ๆญคๅทฅๅ–ฎ็•ถๅ‰็š„ๅ„ชๅ…ˆ็ดš็‚บ {0}ใ€‚", + "transfer":"ๅทฅๅ–ฎๆ‰€ๆœ‰ๆฌŠๅทฒ็”ฑ {2} ๆˆๅŠŸๅพž {0} ่ฝ‰็งป่‡ณ {1}๏ผ" + }, + "modal":{ + "closePlaceholder":"ๆ‚จ็‚บไป€้บผ้—œ้–‰ๆญคๅทฅๅ–ฎ๏ผŸ", + "deletePlaceholder":"ๆ‚จ็‚บไป€้บผๅˆช้™คๆญคๅทฅๅ–ฎ๏ผŸ", + "reopenPlaceholder":"ๆ‚จ็‚บไป€้บผ้‡ๆ–ฐ้–‹ๅ•Ÿๆญคๅทฅๅ–ฎ๏ผŸ", + "claimPlaceholder":"ๆ‚จ็‚บไป€้บผ่ช้ ˜ๆญคๅทฅๅ–ฎ๏ผŸ", + "unclaimPlaceholder":"ๆ‚จ็‚บไป€้บผๅ–ๆถˆ่ช้ ˜ๆญคๅทฅๅ–ฎ๏ผŸ", + "pinPlaceholder":"ๆ‚จ็‚บไป€้บผ็ฝฎ้ ‚ๆญคๅทฅๅ–ฎ๏ผŸ", + "unpinPlaceholder":"ๆ‚จ็‚บไป€้บผๅ–ๆถˆ็ฝฎ้ ‚ๆญคๅทฅๅ–ฎ๏ผŸ" + }, + "logs":{ + "createLog":"{0}ๅ‰ตๅปบไบ†ไธ€ๅ€‹ๆ–ฐๅทฅๅ–ฎ๏ผ", + "closeLog":"ๆญคๅทฅๅ–ฎๅทฒ่ขซ{0}้—œ้–‰๏ผ", + "closeDm":"ๆ‚จ็š„ๅทฅๅ–ฎๅทฒๅœจๆˆ‘ๅ€‘็š„ๆœๅ‹™ๅ™จไธญ้—œ้–‰๏ผ", + "deleteLog":"ๆญคๅทฅๅ–ฎๅทฒ่ขซ{0}ๅˆช้™ค๏ผ", + "deleteDm":"ๆ‚จ็š„ๅทฅๅ–ฎๅทฒๅœจๆˆ‘ๅ€‘็š„ๆœๅ‹™ๅ™จไธญๅˆช้™ค๏ผ", + "reopenLog":"ๆญคๅทฅๅ–ฎๅทฒ่ขซ{0}้‡ๆ–ฐ้–‹ๅ•Ÿ๏ผ", + "reopenDm":"ๆ‚จ็š„ๅทฅๅ–ฎๅทฒๅœจๆˆ‘ๅ€‘็š„ๆœๅ‹™ๅ™จไธญ้‡ๆ–ฐ้–‹ๅ•Ÿ๏ผ", + "claimLog":"ๆญคๅทฅๅ–ฎๅทฒ่ขซ{0}่ช้ ˜๏ผ", + "claimDm":"ๆ‚จ็š„ๅทฅๅ–ฎๅทฒๅœจๆˆ‘ๅ€‘็š„ๆœๅ‹™ๅ™จไธญ่ขซ่ช้ ˜๏ผ", + "unclaimLog":"ๆญคๅทฅๅ–ฎๅทฒ่ขซ{0}ๅ–ๆถˆ่ช้ ˜๏ผ", + "unclaimDm":"ๆ‚จ็š„ๅทฅๅ–ฎๅทฒๅœจๆˆ‘ๅ€‘็š„ๆœๅ‹™ๅ™จไธญๅ–ๆถˆ่ช้ ˜๏ผ", + "pinLog":"ๆญคๅทฅๅ–ฎๅทฒ่ขซ{0}็ฝฎ้ ‚๏ผ", + "pinDm":"ๆ‚จ็š„ๅทฅๅ–ฎๅทฒๅœจๆˆ‘ๅ€‘็š„ๆœๅ‹™ๅ™จไธญ็ฝฎ้ ‚๏ผ", + "unpinLog":"ๆญคๅทฅๅ–ฎๅทฒ่ขซ{0}ๅ–ๆถˆ็ฝฎ้ ‚๏ผ", + "unpinDm":"ๆ‚จ็š„ๅทฅๅ–ฎๅทฒๅœจๆˆ‘ๅ€‘็š„ๆœๅ‹™ๅ™จไธญๅ–ๆถˆ็ฝฎ้ ‚๏ผ", + "renameLog":"ๆญคๅทฅๅ–ฎๅทฒ่ขซ{1}้‡ๅ‘ฝๅ็‚บ{0}๏ผ", + "renameDm":"ๆ‚จ็š„ๅทฅๅ–ฎๅทฒๅœจๆˆ‘ๅ€‘็š„ๆœๅ‹™ๅ™จไธญ้‡ๅ‘ฝๅ็‚บ{0}๏ผ", + "moveLog":"ๆญคๅทฅๅ–ฎๅทฒ่ขซ{1}็งปๅ‹•ๅˆฐ{0}๏ผ", + "moveDm":"ๆ‚จ็š„ๅทฅๅ–ฎๅทฒๅœจๆˆ‘ๅ€‘็š„ๆœๅ‹™ๅ™จไธญ็งปๅ‹•ๅˆฐ{0}๏ผ", + "addLog":"{0}ๅทฒ่ขซ{1}ๆทปๅŠ ๅˆฐๆญคๅทฅๅ–ฎ๏ผ", + "addDm":"{0}ๅทฒ่ขซๆทปๅŠ ๅˆฐๆ‚จ็š„ๅทฅๅ–ฎไธญ๏ผ", + "removeLog":"{0}ๅทฒ่ขซ{1}ๅพžๆญคๅทฅๅ–ฎ็งป้™ค๏ผ", + "removeDm":"{0}ๅทฒ่ขซๅพžๆ‚จ็š„ๅทฅๅ–ฎไธญ็งป้™ค๏ผ", + + "blacklistAddLog":"{0}่ขซ{1}ๆ‹‰้ป‘๏ผ", + "blacklistRemoveLog":"{0}่ขซ{1}ๅพž้ป‘ๅๅ–ฎ็งป้™ค๏ผ", + "blacklistAddDm":"ๆ‚จๅทฒ่ขซๆˆ‘ๅ€‘็š„ๆœๅ‹™ๅ™จๆ‹‰้ป‘๏ผ\nๅพž็พๅœจ่ตท๏ผŒๆ‚จ็„กๆณ•ๅ‰ตๅปบๅทฅๅ–ฎ๏ผ", + "blacklistRemoveDm":"ๆ‚จๅทฒ่ขซๆˆ‘ๅ€‘็š„ๆœๅ‹™ๅ™จ่งฃ็ฆ๏ผ\n็พๅœจๆ‚จๅฏไปฅๅ†ๆฌกๅ‰ตๅปบๅทฅๅ–ฎ๏ผ", + "clearLog":"{0}ๅ€‹ๅทฅๅ–ฎๅทฒ่ขซ{1}ๅˆช้™ค๏ผ", + + "transferLog":"ๆญคๅทฅๅ–ฎ็š„ๆ‰€ๆœ‰ๆฌŠๅทฒ็”ฑ {2} ๅพž {0} ่ฝ‰็งป่‡ณ {1}๏ผ", + "transferDm":"ๆ‚จๅœจๆœๅ‹™ๅ™จไธญ็š„ๅทฅๅ–ฎๆ‰€ๆœ‰ๆฌŠๅทฒๅพž {0} ่ฝ‰็งป่‡ณ {1}๏ผ", + "prioritySetLog":"ๆญคๅทฅๅ–ฎ็š„ๅ„ชๅ…ˆ็ดšๅทฒ็”ฑ {1} ๆ›ดๆ”น็‚บ {0}๏ผ", + "prioritySetDm":"ๆ‚จๅœจๆœๅ‹™ๅ™จไธญ็š„ๅทฅๅ–ฎๅ„ชๅ…ˆ็ดšๅทฒๆ›ดๆ”น็‚บ {0}๏ผ", + "roleUpdateLog":"{0} ๅทฒๆ›ดๆ–ฐๅ…ถ่ง’่‰ฒ๏ผ", + "roleUpdateDm":"ๆ‚จๅœจๆœๅ‹™ๅ™จไธญ็š„่ง’่‰ฒๅทฒๆ›ดๆ–ฐ๏ผ" + } + }, + "transcripts":{ + "success":{ + "visit":"ๆชข่ฆ–่จ˜้Œ„", + "ready":"่จ˜้Œ„ๅทฒๅ‰ตๅปบ", + "textFileDescription":"้€™ๆ˜ฏๅทฒๅˆช้™คๅทฅๅ–ฎ็š„ๆ–‡ๆœฌ่จ˜้Œ„๏ผ", + "htmlProgress":"่ซ‹็ญ‰ๅพ…ๆญคHTML่จ˜้Œ„่™•็†ๅฎŒๆˆ...", + + "createdChannel":"ๆœๅ‹™ๅ™จไธญๅทฒๅ‰ตๅปบไธ€ๅ€‹ๆ–ฐ็š„{0}่จ˜้Œ„๏ผ", + "createdCreator":"ๅทฒ็‚บๆ‚จ็š„ๅทฅๅ–ฎๅ‰ตๅปบไธ€ๅ€‹ๆ–ฐ็š„{0}่จ˜้Œ„๏ผ", + "createdParticipant":"ๆ‚จๅƒ่ˆ‡็š„ๅทฅๅ–ฎไธญๅทฒๅ‰ตๅปบไธ€ๅ€‹ๆ–ฐ็š„{0}่จ˜้Œ„๏ผ", + "createdActiveAdmin":"ๆ‚จไฝœ็‚บ็ฎก็†ๅ“กๅƒ่ˆ‡็š„ๅทฅๅ–ฎไธญๅทฒๅ‰ตๅปบไธ€ๅ€‹ๆ–ฐ็š„{0}่จ˜้Œ„๏ผ", + "createdEveryAdmin":"ๆ‚จๆ›พๆ“”ไปป็ฎก็†ๅ“ก็š„ๅทฅๅ–ฎไธญๅทฒๅ‰ตๅปบไธ€ๅ€‹ๆ–ฐ็š„{0}่จ˜้Œ„๏ผ", + "createdOther":"ๅทฒๅ‰ตๅปบไธ€ๅ€‹ๆ–ฐ็š„{0}่จ˜้Œ„๏ผ" + }, + "errors":{ + "retry":"้‡่ฉฆ", + "continue":"ๅˆช้™ค็„ก่จ˜้Œ„", + "backup":"ๅ‰ตๅปบๅ‚™ไปฝ่จ˜้Œ„", + "error":"ๅ‰ตๅปบ่จ˜้Œ„ๆ™‚ๅ‡บ้Œฏใ€‚\nๆ‚จๆƒณๆ€Ž้บผๅš๏ผŸ\n\nๅœจๆ‚จ้ปž้ธไปฅไธ‹ๆŒ‰้ˆ•ไน‹ๅ‰๏ผŒๆญคๅทฅๅ–ฎไธๆœƒ่ขซๅˆช้™คใ€‚", + "title":"่จ˜้Œ„้Œฏ่ชค" + }, + "text":{ + "messagesTitle":"ๆถˆๆฏ", + "embedTitle":"EMBED", + "fileTitle":"ๆ–‡ไปถ", + "fieldsTitle":"ๅญ—ๆฎต", + "reactionsTitle":"ๅๆ‡‰", + "statsTitle":"็ตฑ่จˆ", + "emptyContent":"<ๅ…งๅฎน็‚บ็ฉบ>", + "noTitle":"<็„กๆจ™้กŒ>", + "noDesc":"<็„กๆ่ฟฐ>" + } + }, + "errors":{ + "titles":{ + "internalError":"ๅ…ง้ƒจ้Œฏ่ชค", + "optionMissing":"ๅ‘ฝไปค้ธ้ …็ผบๅคฑ", + "optionInvalid":"ๅ‘ฝไปค้ธ้ …็„กๆ•ˆ", + "unknownCommand":"ๆœช็Ÿฅๅ‘ฝไปค", + "noPermissions":"็„กๆฌŠ้™", + "unknownTicket":"ๆœช็Ÿฅๅทฅๅ–ฎ", + "deprecatedTicket":"้Žๆ™‚ๅทฅๅ–ฎ", + "unknownOption":"ๆœช็Ÿฅ้ธ้ …", + "unknownPanel":"ๆœช็Ÿฅ้บต้—†", + "notInGuild":"ไธๅœจๆœๅ‹™ๅ™จไธญ", + "channelRename":"็„กๆณ•้‡ๅ‘ฝๅ้ ป้“", + "busy":"ๅทฅๅ–ฎ็นๅฟ™", + "permissionError":"ๆฌŠ้™้Œฏ่ชค" + }, + "descriptions":{ + "askForInfo":"่ซ‹่ฏไฟ‚ๆญคๆฉŸๅ™จไบบ็š„ๆ‰€ๆœ‰่€…็ฒๅ–ๆ›ดๅคšไฟกๆฏ๏ผ", + "askForInfoResolve":"ๅฆ‚ๆžœๆญคๅ•้กŒๅœจๅคšๆฌกๅ˜—่ฉฆๅพŒไปๆœช่งฃๆฑบ๏ผŒ่ซ‹่ฏไฟ‚ๆญคๆฉŸๅ™จไบบ็š„ๆ‰€ๆœ‰่€…ใ€‚", + "internalError":"็”ฑๆ–ผๅ…ง้ƒจ้Œฏ่ชค๏ผŒ็„กๆณ•ๅ›ž้Ÿฟๆญค{0}๏ผ", + "optionMissing":"ๆญคๅ‘ฝไปค็ผบๅฐ‘ๅฟ…ๅกซๅƒๆ•ธ๏ผ", + "optionInvalid":"ๆญคๅ‘ฝไปคไธญ็š„ๅƒๆ•ธ็„กๆ•ˆ๏ผ", + "optionInvalidChoose":"่ซ‹้ธๆ“‡", + "unknownCommand":"่ซ‹่จชๅ•ๅนซๅŠฉ่œๅ–ฎ็ฒๅ–ๆ›ดๅคšไฟกๆฏ๏ผ", + "noPermissions":"ๆ‚จ็„กๆฌŠไฝฟ็”จๆญค{0}๏ผ", + "noPermissionsList":"ๆ‰€้œ€ๆฌŠ้™๏ผš๏ผˆๅ…ถไธญไน‹ไธ€๏ผ‰", + "noPermissionsCooldown":"ๆ‚จๅ› ๅ†ทๅปๆ™‚้–“็„กๆณ•ไฝฟ็”จๆญค{0}๏ผ", + "noPermissionsBlacklist":"ๆ‚จๅ› ่ขซๆ‹‰้ป‘็„กๆณ•ไฝฟ็”จๆญค{0}๏ผ", + "noPermissionsLimitGlobal":"ๆ‚จ็„กๆณ•ๅ‰ตๅปบๅทฅๅ–ฎ๏ผŒๅ› ็‚บๆœๅ‹™ๅ™จๅทฒ้”ๅˆฐๆœ€ๅคงๅทฅๅ–ฎ้™่ฃฝ๏ผ", + "noPermissionsLimitGlobalUser":"ๆ‚จ็„กๆณ•ๅ‰ตๅปบๅทฅๅ–ฎ๏ผŒๅ› ็‚บๆ‚จๅทฒ้”ๅˆฐๆœ€ๅคงๅทฅๅ–ฎ้™่ฃฝ๏ผ", + "noPermissionsLimitOption":"ๆ‚จ็„กๆณ•ๅ‰ตๅปบๅทฅๅ–ฎ๏ผŒๅ› ็‚บๆœๅ‹™ๅ™จๅทฒ้”ๅˆฐๆญค้ธ้ …็š„ๆœ€ๅคงๅทฅๅ–ฎ้™่ฃฝ๏ผ", + "noPermissionsLimitOptionUser":"ๆ‚จ็„กๆณ•ๅ‰ตๅปบๅทฅๅ–ฎ๏ผŒๅ› ็‚บๆ‚จๅทฒ้”ๅˆฐๆญค้ธ้ …็š„ๆœ€ๅคงๅทฅๅ–ฎ้™่ฃฝ๏ผ", + "unknownTicket":"่ซ‹ๅœจๆœ‰ๆ•ˆๅทฅๅ–ฎไธญ้‡่ฉฆๆญคๅ‘ฝไปค๏ผ", + "deprecatedTicket":"็•ถๅ‰้ ป้“ไธๆ˜ฏๆœ‰ๆ•ˆๅทฅๅ–ฎ๏ผๅฏ่ƒฝๆ˜ฏ่ˆŠ็‰ˆOpen Ticket็š„ๅทฅๅ–ฎ๏ผ", + "notInGuild":"ๆญค{0}ๅœจ็งไฟกไธญ็„กๆ•ˆ๏ผ่ซ‹ๅœจๆœๅ‹™ๅ™จไธญ้‡่ฉฆ๏ผ", + "channelRename":"็”ฑๆ–ผDiscord้€Ÿ็އ้™่ฃฝ๏ผŒๆฉŸๅ™จไบบ็›ฎๅ‰็„กๆณ•้‡ๅ‘ฝๅ้ ป้“ใ€‚ๅฆ‚ๆžœๆฉŸๅ™จไบบๆœช้‡ๅ•Ÿ๏ผŒ้ ป้“ๅฐ‡ๅœจ10ๅˆ†้พๅพŒ่‡ชๅ‹•้‡ๅ‘ฝๅใ€‚", + "channelRenameSource":"ๆญค้Œฏ่ชค็š„ไพ†ๆบๆ˜ฏ๏ผš{0}", + "busy":"็„กๆณ•ไฝฟ็”จๆญค{0}๏ผ\nๅทฅๅ–ฎ็•ถๅ‰ๆญฃๅœจ่ขซๆฉŸๅ™จไบบ่™•็†ใ€‚\n\n่ซ‹ๅนพ็ง’ๅพŒ้‡่ฉฆ๏ผ", + "closeBeforeMessage":"็”จๆˆถ็™ผ้€ๆถˆๆฏไน‹ๅ‰๏ผŒ็„กๆณ•้—œ้–‰ๆˆ–ๅˆช้™คๆญคๅทฅๅ–ฎใ€‚", + "closeBeforeAdminMessage":"ๅทฅๅ–ฎ็ฎก็†ๅ“กๆˆ–ๆ”ฏๆดๆˆๅ“ก็™ผ้€ๆถˆๆฏไน‹ๅ‰๏ผŒ็„กๆณ•้—œ้–‰ๆˆ–ๅˆช้™คๆญคๅทฅๅ–ฎใ€‚", + "unableToCreateTicket":"ๆ‚จ็„กๆณ•ๅ‰ตๅปบๅทฅๅ–ฎใ€‚" + }, + "optionInvalidReasons":{ + "stringRegex":"ๅ€ผไธ็ฌฆๅˆๆจกๅผ๏ผ", + "stringMinLength":"ๅ€ผ่‡ณๅฐ‘้œ€่ฆ{0}ๅ€‹ๅญ—็ฌฆ๏ผ", + "stringMaxLength":"ๅ€ผๆœ€ๅคš้œ€่ฆ{0}ๅ€‹ๅญ—็ฌฆ๏ผ", + "numberInvalid":"็„กๆ•ˆๆ•ธๅญ—๏ผ", + "numberMin":"ๆ•ธๅญ—่‡ณๅฐ‘็‚บ{0}๏ผ", + "numberMax":"ๆ•ธๅญ—ๆœ€ๅคš็‚บ{0}๏ผ", + "numberDecimal":"ๆ•ธๅญ—ไธ่ƒฝ็‚บๅฐๆ•ธ๏ผ", + "numberNegative":"ๆ•ธๅญ—ไธ่ƒฝ็‚บ่ฒ ๆ•ธ๏ผ", + "numberPositive":"ๆ•ธๅญ—ไธ่ƒฝ็‚บๆญฃๆ•ธ๏ผ", + "numberZero":"ๆ•ธๅญ—ไธ่ƒฝ็‚บ้›ถ๏ผ", + "channelNotFound":"็„กๆณ•ๆ‰พๅˆฐ้ ป้“๏ผ", + "userNotFound":"็„กๆณ•ๆ‰พๅˆฐ็”จๆˆถ๏ผ", + "roleNotFound":"็„กๆณ•ๆ‰พๅˆฐ่ง’่‰ฒ๏ผ", + "memberNotFound":"็„กๆณ•ๆ‰พๅˆฐ็”จๆˆถ๏ผ", + "mentionableNotFound":"็„กๆณ•ๆ‰พๅˆฐ็”จๆˆถๆˆ–่ง’่‰ฒ๏ผ", + "channelType":"็„กๆ•ˆ็š„้ ป้“้กžๅž‹๏ผ", + "notInGuild":"ๆญค้ธ้ …้œ€่ฆๆ‚จๅœจๆœๅ‹™ๅ™จไธญ๏ผ" + }, + "permissions":{ + "developer":"ๆ‚จ้œ€่ฆๆ˜ฏๆฉŸๅ™จไบบ็š„้–‹็™ผ่€…ใ€‚", + "owner":"ๆ‚จ้œ€่ฆๆ˜ฏๆœๅ‹™ๅ™จๆ‰€ๆœ‰่€…ใ€‚", + "admin":"ๆ‚จ้œ€่ฆๆ˜ฏๆœๅ‹™ๅ™จ็ฎก็†ๅ“กใ€‚", + "moderator":"ๆ‚จ้œ€่ฆๆ˜ฏ็‰ˆไธปใ€‚", + "support":"ๆ‚จ้œ€่ฆๆ˜ฏๆ”ฏๆดๅœ˜้šŠๆˆๅ“กใ€‚", + "member":"ๆ‚จ้œ€่ฆๆ˜ฏๆˆๅ“กใ€‚", + "discord-administrator":"ๆ‚จ้œ€่ฆๆ“ๆœ‰`ADMINISTRATOR`ๆฌŠ้™ใ€‚" + }, + "actionInvalid":{ + "close":"ๅทฅๅ–ฎๅทฒ้—œ้–‰๏ผ", + "reopen":"ๅทฅๅ–ฎๆœช้—œ้–‰๏ผ", + "claim":"ๅทฅๅ–ฎๅทฒ่ช้ ˜๏ผ", + "unclaim":"ๅทฅๅ–ฎๆœช่ช้ ˜๏ผ", + "pin":"ๅทฅๅ–ฎๅทฒ็ฝฎ้ ‚๏ผ", + "unpin":"ๅทฅๅ–ฎๆœช็ฝฎ้ ‚๏ผ", + "add":"ๆญค็”จๆˆถๅทฒๅฏ่จชๅ•ๅทฅๅ–ฎ๏ผ", + "remove":"็„กๆณ•ๅพžๆญคๅทฅๅ–ฎ็งป้™คๆญค็”จๆˆถ๏ผ" + } + }, + "params":{ + "uppercase":{ + "ticket":"ๅทฅๅ–ฎ", + "tickets":"ๅทฅๅ–ฎ", + "reason":"ๅŽŸๅ› ", + "creator":"ๅ‰ตๅปบ่€…", + "remaining":"ๅ‰ฉ้ค˜ๆ™‚้–“", + "added":"ๅทฒๆทปๅŠ ", + "removed":"ๅทฒ็งป้™ค", + "filter":"็ฏฉ้ธ", + "method":"ๆ–นๅผ", + "type":"้กžๅž‹", + "blacklisted":"ๅทฒๆ‹‰้ป‘", + "panel":"้บต้—†", + "command":"ๅ‘ฝไปค", + "system":"ไฟ‚็ตฑ", + "true":"ๆ˜ฏ", + "false":"ๅฆ", + "syntax":"ๆ–‡ๆณ•", + "originalName":"ๅŽŸๅ็จฑ", + "newName":"ๆ–ฐๅ็จฑ", + "until":"็›ดๅˆฐ", + "validOptions":"ๆœ‰ๆ•ˆ้ธ้ …", + "validPanels":"ๆœ‰ๆ•ˆ้บต้—†", + "autoclose":"่‡ชๅ‹•้—œ้–‰", + "autodelete":"่‡ชๅ‹•ๅˆช้™ค", + "startupDate":"ๅ•Ÿๅ‹•ๆ—ฅๆœŸ", + "version":"็‰ˆๆœฌ", + "name":"ๅ็จฑ", + "role":"่ง’่‰ฒ", + "status":"็‹€ๆ…‹", + "claimed":"ๅทฒ่ช้ ˜", + "pinned":"ๅทฒ็ฝฎ้ ‚", + "creationDate":"ๅ‰ตๅปบๆ—ฅๆœŸ", + + "noone":"็„กไบบ", + "open":"้–‹ๅ•Ÿ", + "closed":"้—œ้–‰", + "priority":"ๅ„ชๅ…ˆ็ดš", + "participants":"ๅƒ่ˆ‡่€…", + "yes":"ๆ˜ฏ", + "no":"ๅฆ", + "option":"้ธ้ …", + "topic":"ไธป้กŒ", + "uptime":"ไฟ‚็ตฑ้‹่กŒๆ™‚้–“", + "messages":"ๆถˆๆฏ", + "embeds":"ๅตŒๅ…ฅ", + "files":"ๆ–‡ไปถ", + "components":"็ต„ไปถ", + "cooldown":"ๅ†ทๅปๆ™‚้–“", + "maxTickets":"ๆœ€ๅคงๅทฅๅ–ฎๆ•ธ", + "admins":"็ฎก็†ๅ“ก", + "roles":"่ง’่‰ฒ", + "size":"ๅคงๅฐ" + }, + "lowercase":{ + "text":"ๆ–‡ๆœฌ", + "html":"HTML", + "command":"ๅ‘ฝไปค", + "modal":"ๆจกๆ…‹ๆก†", + "button":"ๆŒ‰้ˆ•", + "dropdown":"ไธ‹ๆ‹‰่œๅ–ฎ", + "method":"ๆ–นๅผ" + } + }, + "commands":{ + "reason":"ๆŒ‡ๅฎšไธ€ๅ€‹ๅฏ้ธๅŽŸๅ› ๏ผŒ่ฉฒๅŽŸๅ› ๅฐ‡ๅœจๆ—ฅ่ชŒไธญๅฏ่ฆ‹ใ€‚", + "help":"็ฒๅ–ๆ‰€ๆœ‰ๅฏ็”จๅ‘ฝไปค็š„ๅˆ—้Œถใ€‚", + "panel":"็”Ÿๆˆๅธถๆœ‰ไธ‹ๆ‹‰่œๅ–ฎๆˆ–ๆŒ‰้ˆ•็š„ๆถˆๆฏ๏ผˆ็”จๆ–ผๅทฅๅ–ฎๅ‰ตๅปบ๏ผ‰ใ€‚", + "panelId":"ๆ‚จๆƒณ่ฆ็”Ÿๆˆ็š„้บต้—†็š„ๆจ™่ญ˜็ฌฆใ€‚", + "panelAutoUpdate":"ๆ‚จๅธŒๆœ›ๆญค้บต้—†ๅœจ็ทจ่ผฏๆ™‚่‡ชๅ‹•ๆ›ดๆ–ฐๅ—Ž๏ผŸ", + "ticket":"็ซ‹ๅณๅ‰ตๅปบไธ€ๅ€‹ๅทฅๅ–ฎใ€‚", + "ticketId":"ๆ‚จๆƒณ่ฆๅ‰ตๅปบ็š„ๅทฅๅ–ฎ็š„ๆจ™่ญ˜็ฌฆใ€‚", + "close":"้—œ้–‰ไธ€ๅ€‹ๅทฅๅ–ฎใ€‚", + "delete":"ๅˆช้™คไธ€ๅ€‹ๅทฅๅ–ฎใ€‚", + "deleteNoTranscript":"ๅˆช้™คๆญคๅทฅๅ–ฎ่€Œไธๅ‰ตๅปบ่จ˜้Œ„ใ€‚", + "reopen":"้‡ๆ–ฐ้–‹ๅ•Ÿไธ€ๅ€‹ๅทฅๅ–ฎใ€‚", + "claim":"่ช้ ˜ไธ€ๅ€‹ๅทฅๅ–ฎใ€‚", + "claimUser":"ๅฐ‡ๆญคๅทฅๅ–ฎ่ช้ ˜็ตฆๅ…ถไป–ไบบ่€Œไธๆ˜ฏๆ‚จ่‡ชๅทฑใ€‚", + "unclaim":"ๅ–ๆถˆ่ช้ ˜ไธ€ๅ€‹ๅทฅๅ–ฎใ€‚", + "pin":"็ฝฎ้ ‚ไธ€ๅ€‹ๅทฅๅ–ฎใ€‚", + "unpin":"ๅ–ๆถˆ็ฝฎ้ ‚ไธ€ๅ€‹ๅทฅๅ–ฎใ€‚", + + "move":"็งปๅ‹•ไธ€ๅ€‹ๅทฅๅ–ฎใ€‚", + "moveId":"ๆ‚จๆƒณ่ฆ็งปๅ‹•ๅˆฐ็š„้ธ้ …็š„ๆจ™่ญ˜็ฌฆใ€‚", + "rename":"้‡ๅ‘ฝๅไธ€ๅ€‹ๅทฅๅ–ฎใ€‚", + "renameName":"ๆญคๅทฅๅ–ฎ็š„ๆ–ฐๅ็จฑใ€‚", + "add":"ๅฐ‡็”จๆˆถๆทปๅŠ ๅˆฐๅทฅๅ–ฎใ€‚", + "addUser":"่ฆๆทปๅŠ ็š„็”จๆˆถใ€‚", + "remove":"ๅพžๅทฅๅ–ฎไธญ็งป้™ค็”จๆˆถใ€‚", + "removeUser":"่ฆ็งป้™ค็š„็”จๆˆถใ€‚", + + "blacklist":"็ฎก็†ๅทฅๅ–ฎ้ป‘ๅๅ–ฎใ€‚", + "blacklistView":"ๆชข่ฆ–็•ถๅ‰้ป‘ๅๅ–ฎ็š„ๅˆ—้Œถใ€‚", + "blacklistAdd":"ๅฐ‡็”จๆˆถๆทปๅŠ ๅˆฐ้ป‘ๅๅ–ฎใ€‚", + "blacklistRemove":"ๅพž้ป‘ๅๅ–ฎไธญ็งป้™ค็”จๆˆถใ€‚", + "blacklistGet":"็ฒๅ–่ขซๆ‹‰้ป‘็”จๆˆถ็š„่ฉณ็ดฐไฟกๆฏใ€‚", + "blacklistGetUser":"่ฆ็ฒๅ–่ฉณ็ดฐไฟกๆฏ็š„็”จๆˆถใ€‚", + "stats":"ๆชข่ฆ–ๆฉŸๅ™จไบบใ€ๆˆๅ“กๆˆ–ๅทฅๅ–ฎ็š„็ตฑ่จˆไฟกๆฏใ€‚", + "statsReset":"้‡็ฝฎๆฉŸๅ™จไบบ็š„ๆ‰€ๆœ‰็ตฑ่จˆไฟกๆฏ๏ผˆไธฆๅพž้›ถ้–‹ๅง‹่จˆๆ•ธ๏ผ‰ใ€‚", + "statsGlobal":"ๆชข่ฆ–ๅ…จๅฑ€็ตฑ่จˆไฟกๆฏใ€‚", + "statsUser":"ๆชข่ฆ–ๆœๅ‹™ๅ™จไธญ็”จๆˆถ็š„็ตฑ่จˆไฟกๆฏใ€‚", + "statsUserUser":"่ฆๆชข่ฆ–็š„็”จๆˆถใ€‚", + "statsTicket":"ๆชข่ฆ–ๆœๅ‹™ๅ™จไธญๅทฅๅ–ฎ็š„็ตฑ่จˆไฟกๆฏใ€‚", + "statsTicketTicket":"่ฆๆชข่ฆ–็š„ๅทฅๅ–ฎใ€‚", + + "clear":"ๅŒๆ™‚ๅˆช้™คๅคšๅ€‹ๅทฅๅ–ฎใ€‚", + "clearFilter":"ๆธ…้™คๅทฅๅ–ฎ็š„็ฏฉ้ธๆขไปถใ€‚", + "clearFilters":{ + "all":"ๅ…จ้ƒจ", + "open":"้–‹ๅ•Ÿ", + "close":"้—œ้–‰", + "claim":"ๅทฒ่ช้ ˜", + "unclaim":"ๆœช่ช้ ˜", + "pin":"ๅทฒ็ฝฎ้ ‚", + "unpin":"ๆœช็ฝฎ้ ‚", + "autoclose":"่‡ชๅ‹•้—œ้–‰" + }, + + "autoclose":"็ฎก็†ๅทฅๅ–ฎไธญ็š„่‡ชๅ‹•้—œ้–‰ใ€‚", + "autocloseDisable":"็ฆ็”จๆญคๅทฅๅ–ฎ็š„่‡ชๅ‹•้—œ้–‰ใ€‚", + "autocloseEnable":"ๅ•Ÿ็”จๆญคๅทฅๅ–ฎ็š„่‡ชๅ‹•้—œ้–‰ใ€‚", + "autocloseEnableTime":"ๅทฅๅ–ฎ้œ€่ฆๅคšๅฐ‘ๅฐๆ™‚ไธๆดปๅ‹•ๆ‰่ƒฝ่‡ชๅ‹•้—œ้–‰ใ€‚", + "autodelete":"็ฎก็†ๅทฅๅ–ฎไธญ็š„่‡ชๅ‹•ๅˆช้™คใ€‚", + "autodeleteDisable":"็ฆ็”จๆญคๅทฅๅ–ฎ็š„่‡ชๅ‹•ๅˆช้™คใ€‚", + "autodeleteEnable":"ๅ•Ÿ็”จๆญคๅทฅๅ–ฎ็š„่‡ชๅ‹•ๅˆช้™คใ€‚", + "autodeleteEnableTime":"ๅทฅๅ–ฎ้œ€่ฆๅคšๅฐ‘ๅคฉไธๆดปๅ‹•ๆ‰่ƒฝ่‡ชๅ‹•ๅˆช้™คใ€‚", + + "topic":"็ฎก็†ๅทฅๅ–ฎ้ ป้“็š„ไธป้กŒใ€‚", + "topicSet":"่จญๅฎšๅทฅๅ–ฎ้ ป้“็š„ไธป้กŒใ€‚", + "topicValue":"้ ป้“็š„ๆ–ฐไธป้กŒใ€‚", + "topicList":"็ฒๅ–ๆ‰€ๆœ‰ๅทฅๅ–ฎๅŠๅ…ถไธป้กŒๅ’Œ็ตฑ่จˆไฟกๆฏ็š„ๅˆ—้Œถใ€‚", + "priority":"็ฎก็†ๅทฅๅ–ฎ็š„ๅ„ชๅ…ˆ็ดšใ€‚", + "prioritySet":"่จญๅฎšๅทฅๅ–ฎ็š„ๅ„ชๅ…ˆ็ดšใ€‚", + "priorityValue":"้ ป้“็š„ๅ„ชๅ…ˆ็ดšใ€‚", + "priorityGet":"็ฒๅ–ๅทฅๅ–ฎ็š„ๅ„ชๅ…ˆ็ดšใ€‚", + "priorityList":"็ฒๅ–ๆ‰€ๆœ‰ๅทฅๅ–ฎ็š„ๅ„ชๅ…ˆ็ดš็‹€ๆ…‹ๅˆ—้Œถใ€‚", + "transfer":"ๅฐ‡ๅทฅๅ–ฎๆ‰€ๆœ‰ๆฌŠๅพžไธ€ไฝ็”จๆˆถ่ฝ‰็งป่‡ณๅฆไธ€ไฝ็”จๆˆถใ€‚", + "transferUser":"่ฆ่ฝ‰็งป็ตฆ็š„็”จๆˆถใ€‚" + }, + "helpMenu":{ + "help":"็ฒๅ–ๆ‰€ๆœ‰ๅฏ็”จๅ‘ฝไปค็š„ๅˆ—้Œถใ€‚", + "ticket":"็ซ‹ๅณๅ‰ตๅปบไธ€ๅ€‹ๅทฅๅ–ฎใ€‚", + "close":"้—œ้–‰ไธ€ๅ€‹ๅทฅๅ–ฎ๏ผŒ้€™ๅฐ‡็ฆ็”จๆญค้ ป้“็š„ๅฏซๅ…ฅๆฌŠ้™ใ€‚", + "delete":"ๅˆช้™คไธ€ๅ€‹ๅทฅๅ–ฎ๏ผŒๅ•Ÿ็”จๆ™‚ๆœƒๅ‰ตๅปบ่จ˜้Œ„ใ€‚", + "reopen":"้‡ๆ–ฐ้–‹ๅ•Ÿไธ€ๅ€‹ๅทฅๅ–ฎ๏ผŒ้€™ๅฐ‡้‡ๆ–ฐๅ•Ÿ็”จๆญค้ ป้“็š„ๅฏซๅ…ฅๆฌŠ้™ใ€‚", + "pin":"็ฝฎ้ ‚ไธ€ๅ€‹ๅทฅๅ–ฎใ€‚้€™ๅฐ‡ๆŠŠๅทฅๅ–ฎ็งปๅ‹•ๅˆฐ้ ‚้ƒจ๏ผŒไธฆๅœจๅ็จฑๅ‰ๆทปๅŠ '๐Ÿ“Œ'้Œถๆƒ…็ฌฆ่™Ÿใ€‚", + "unpin":"ๅ–ๆถˆ็ฝฎ้ ‚ไธ€ๅ€‹ๅทฅๅ–ฎใ€‚ๅทฅๅ–ฎๅฐ‡ไฟๆŒ็•ถๅ‰ไฝ็ฝฎ๏ผŒไฝ†ๆœƒ็งป้™ค'๐Ÿ“Œ'้Œถๆƒ…็ฌฆ่™Ÿใ€‚", + "move":"็งปๅ‹•ไธ€ๅ€‹ๅทฅๅ–ฎใ€‚้€™ๅฐ‡ๆ›ดๆ”นๆญคๅทฅๅ–ฎ็š„้กžๅž‹ใ€‚", + "rename":"้‡ๅ‘ฝๅไธ€ๅ€‹ๅทฅๅ–ฎใ€‚้€™ๅฐ‡ๆ›ดๆ”นๆญคๅทฅๅ–ฎ็š„้ ป้“ๅ็จฑใ€‚", + "claim":"่ช้ ˜ไธ€ๅ€‹ๅทฅๅ–ฎใ€‚้€š้Žๆญคๆ“ไฝœ๏ผŒๆ‚จๅฏไปฅๅ‘Š็Ÿฅๅœ˜้šŠๆ‚จๆญฃๅœจ่™•็†ๆญคๅทฅๅ–ฎใ€‚", + "unclaim":"ๅ–ๆถˆ่ช้ ˜ไธ€ๅ€‹ๅทฅๅ–ฎใ€‚้€š้Žๆญคๆ“ไฝœ๏ผŒๆ‚จๅฏไปฅๅ‘Š็Ÿฅๅœ˜้šŠๆญคๅทฅๅ–ฎๅทฒ็ฉบ้–’ใ€‚", + "add":"ๅฐ‡็”จๆˆถๆทปๅŠ ๅˆฐๅทฅๅ–ฎใ€‚้€™ๅฐ‡ๅ…่จฑ็”จๆˆถๅœจๆญคๅทฅๅ–ฎไธญ่ฎ€ๅ–ๅ’Œๅฏซๅ…ฅใ€‚", + "remove":"ๅพžๅทฅๅ–ฎไธญ็งป้™ค็”จๆˆถใ€‚้€™ๅฐ‡็งป้™ค็”จๆˆถๅœจๆญคๅทฅๅ–ฎไธญ่ฎ€ๅ–ๅ’Œๅฏซๅ…ฅ็š„ๆฌŠ้™ใ€‚", + "panel":"็”Ÿๆˆๅธถๆœ‰ไธ‹ๆ‹‰่œๅ–ฎๆˆ–ๆŒ‰้ˆ•็š„ๆถˆๆฏ๏ผˆ็”จๆ–ผๅทฅๅ–ฎๅ‰ตๅปบ๏ผ‰ใ€‚", + "blacklistView":"ๆชข่ฆ–็•ถๅ‰้ป‘ๅๅ–ฎ็š„ๅˆ—้Œถใ€‚", + "blacklistAdd":"ๅฐ‡็”จๆˆถๆทปๅŠ ๅˆฐ้ป‘ๅๅ–ฎใ€‚", + "blacklistRemove":"ๅพž้ป‘ๅๅ–ฎไธญ็งป้™ค็”จๆˆถใ€‚", + "blacklistGet":"็ฒๅ–่ขซๆ‹‰้ป‘็”จๆˆถ็š„่ฉณ็ดฐไฟกๆฏใ€‚", + "statsGlobal":"ๆชข่ฆ–ๅ…จๅฑ€็ตฑ่จˆไฟกๆฏใ€‚", + "statsTicket":"ๆชข่ฆ–ๆœๅ‹™ๅ™จไธญๅทฅๅ–ฎ็š„็ตฑ่จˆไฟกๆฏใ€‚", + "statsUser":"ๆชข่ฆ–ๆœๅ‹™ๅ™จไธญ็”จๆˆถ็š„็ตฑ่จˆไฟกๆฏใ€‚", + "statsReset":"้‡็ฝฎๆฉŸๅ™จไบบ็š„ๆ‰€ๆœ‰็ตฑ่จˆไฟกๆฏ๏ผˆไธฆๅพž้›ถ้–‹ๅง‹่จˆๆ•ธ๏ผ‰ใ€‚", + "autocloseDisable":"็ฆ็”จๆญคๅทฅๅ–ฎ็š„่‡ชๅ‹•้—œ้–‰ใ€‚", + "autocloseEnable":"ๅ•Ÿ็”จๆญคๅทฅๅ–ฎ็š„่‡ชๅ‹•้—œ้–‰ใ€‚", + "autodeleteDisable":"็ฆ็”จๆญคๅทฅๅ–ฎ็š„่‡ชๅ‹•ๅˆช้™คใ€‚", + "autodeleteEnable":"ๅ•Ÿ็”จๆญคๅทฅๅ–ฎ็š„่‡ชๅ‹•ๅˆช้™คใ€‚", + "categories":{ + "general":"้€š็”จๅ‘ฝไปค", + "basicTicket":"ๅŸบ็คŽๅทฅๅ–ฎๅ‘ฝไปค", + "advancedTicket":"้ซ˜็ดšๅทฅๅ–ฎๅ‘ฝไปค", + "userTicket":"็”จๆˆถๅทฅๅ–ฎๅ‘ฝไปค", + "admin":"็ฎก็†ๅ“กๅ‘ฝไปค", + "advanced":"้ซ˜็ดšๅ‘ฝไปค", + "extra":"้กๅค–ๅ‘ฝไปค" + } + }, + "stats":{ + "scopes":{ + "global":"ๅ…จๅฑ€็ตฑ่จˆ", + "system":"ไฟ‚็ตฑ็ตฑ่จˆ", + "user":"็”จๆˆถ็ตฑ่จˆ", + "ticket":"ๅทฅๅ–ฎ็ตฑ่จˆ", + "participants":"ๅƒ่ˆ‡่€…", + "messages":"ๆถˆๆฏ" + }, + "properties":{ + "ticketsCreated":"ๅทฅๅ–ฎๅทฒๅ‰ตๅปบ", + "ticketsClosed":"ๅทฅๅ–ฎๅทฒ้—œ้–‰", + "ticketsDeleted":"ๅทฅๅ–ฎๅทฒๅˆช้™ค", + "ticketsReopened":"ๅทฅๅ–ฎๅทฒ้‡ๆ–ฐ้–‹ๅ•Ÿ", + "ticketsAutoclosed":"ๅทฅๅ–ฎๅทฒ่‡ชๅ‹•้—œ้–‰", + "ticketsClaimed":"ๅทฅๅ–ฎๅทฒ่ช้ ˜", + "ticketsPinned":"ๅทฅๅ–ฎๅทฒ็ฝฎ้ ‚", + "ticketsMoved":"ๅทฅๅ–ฎๅทฒ็งปๅ‹•", + "usersBlacklisted":"็”จๆˆถๅทฒๆ‹‰้ป‘", + "transcriptsCreated":"่จ˜้Œ„ๅทฒๅ‰ตๅปบ", + "ticketsAutodeleted":"่‡ชๅ‹•ๅˆช้™ค็š„ๅทฅๅ–ฎ", + "ticketsTransferred":"ๅทฒ่ฝ‰็งป็š„ๅทฅๅ–ฎ", + "ticketVolume":"ๅทฅๅ–ฎ้‡", + "averageTickets":"ๅนณๅ‡ๅทฅๅ–ฎๆ•ธ/็”จๆˆถ", + "currentTickets":"็•ถๅ‰ๅทฅๅ–ฎ", + "age":"ๅทฅๅ–ฎๆ™‚้•ท", + "responseTime":"ๅ›ž้Ÿฟๆ™‚้–“", + "resolutionTime":"่งฃๆฑบๆ™‚้–“", + "createdOn":"ๅ‰ตๅปบๆ–ผ", + "createdBy":"ๅ‰ตๅปบ่€…", + "closedOn":"้—œ้–‰ๆ–ผ", + "closedBy":"้—œ้–‰่€…", + "claimedOn":"่ช้ ˜ๆ–ผ", + "claimedBy":"่ช้ ˜่€…", + "pinnedOn":"ๅ›บๅฎšๆ–ผ", + "pinnedBy":"ๅ›บๅฎš่€…", + "deletedOn":"ๅˆช้™คๆ–ผ", + "deletedBy":"ๅˆช้™ค่€…" + }, + "roles":{ + "developer":"้–‹็™ผ่€…", + "serverOwner":"ๆœๅ‹™ๅ™จๆ‰€ๆœ‰่€…", + "serverAdmin":"ๆœๅ‹™ๅ™จ็ฎก็†ๅ“ก", + "moderator":"็ฎก็†ๅœ˜้šŠ", + "support":"ๆ”ฏๆดๅœ˜้šŠ", + "member":"ๆˆๅ“ก" + } + }, + "panel":{ + "selectTicket":"้ธๆ“‡ๆ‚จ็š„ๅทฅๅ–ฎ", + "selectRole":"้ธๆ“‡ๆ‚จ็š„่ง’่‰ฒ", + "selectOption":"้ธๆ“‡ๆ‚จ็š„้ธ้ …" + }, + "priorities":{ + "urgent":"็ทŠๆ€ฅ", + "veryHigh":"้žๅธธ้ซ˜", + "high":"้ซ˜", + "normal":"ๆ™ฎ้€š", + "low":"ไฝŽ", + "veryLow":"้žๅธธไฝŽ", + "none":"็„ก" + } +} \ No newline at end of file diff --git a/languages/turkish.json b/languages/turkish.json index ee66fc5..09e949b 100644 --- a/languages/turkish.json +++ b/languages/turkish.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["palestinian"], "lastedited":"16/02/2026", "language":"Turkish", diff --git a/languages/ukrainian.json b/languages/ukrainian.json index 416da30..c1c2a19 100644 --- a/languages/ukrainian.json +++ b/languages/ukrainian.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["Anderskiy"], "lastedited":"16/02/2026", "language":"Ukrainian", diff --git a/languages/vietnamese.json b/languages/vietnamese.json index 12d6318..24f43c0 100644 --- a/languages/vietnamese.json +++ b/languages/vietnamese.json @@ -1,6 +1,6 @@ { "_TRANSLATION":{ - "otversion":"v4.1.3", + "otversion":"v4.2.0", "translators":["ngocdiep2006"], "lastedited":"16/02/2026", "language":"Vietnamese", diff --git a/package.json b/package.json index 087e7b3..92ca691 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "open-ticket", "author": "DJdj Development", - "version": "4.1.3", + "version": "4.2.0", "description": "The most advanced open-source discord ticket bot with HTML transcripts, plugins, questions, claiming, pinning & more! Using discord.js v14 & JSON database! ", "keywords": [ "ticket-bot", @@ -19,20 +19,21 @@ "test": "node index.js --dev-config --dev-database --soft-plugins", "testsetup": "node index.js --cli --dev-config --dev-database --soft-plugins", "testnc": "node index.js --no-compile --dev-config --dev-database --soft-plugins", - "docs": "npx typedoc --options .docs/typedoc-config.json && node .docs/createDocs.js", - "mergelang": "node .docs/mergeTranslations.js" + "docs": "npx typedoc --options .tools/typedoc-config.json && node .tools/createDocs.js", + "mergelang": "node .tools/mergeTranslations.js" }, - "type": "commonjs", + "type": "module", "license": "GPL-3.0-only", "dependencies": { - "@discordjs/rest": "^2.6.0", + "@discordjs/rest": "^2.6.1", + "@open-discord-bots/framework": "^0.4.4", "@types/node": "^22.5.0", "@types/terminal-kit": "^2.5.7", "ansis": "^4.2.0", - "discord.js": "^14.24.2", - "formatted-json-stringify": "^1.2.1", + "discord.js": "^14.26.4", + "formatted-json-stringify": "^1.3.2", "terminal-kit": "^3.1.2", - "typescript": "^5.9.3" + "typescript": "^6.0.3" }, "repository": { "type": "git", @@ -45,7 +46,7 @@ "homepage": "https://openticket.dj-dj.be", "imports": { "#opendiscord": "./dist/src/index.js", - "#opendiscord-types": "./dist/src/core/api/api.js" + "#opendiscord-types": "./dist/src/core/api.js" }, "funding": { "type": "individual", diff --git a/plugins/example-plugin/index.ts b/plugins/example-plugin/index.ts index 5441b05..68b5b6a 100644 --- a/plugins/example-plugin/index.ts +++ b/plugins/example-plugin/index.ts @@ -6,15 +6,15 @@ import * as discord from "discord.js" //// Enable it in the plugin.json file! //// ///////////////////////////////////////////// -if (utilities.project != "openticket") throw new api.ODPluginError("This plugin only works in Open Ticket!") +if (opendiscord.project != "openticket") throw new api.ODPluginError("This plugin only works in Open Ticket!") //Add Typescript autocomplete support for plugin data. (!!!OPTIONAL!!!) declare module "#opendiscord-types" { - export interface ODPluginManagerIds_Default { + export interface ODPluginManagerIdMappings { "example-plugin":api.ODPlugin } - export interface ODConfigManagerIds_Default { - "example-plugin:config":api.ODJsonConfig + export interface ODConfigManagerIdMappings { + "example-plugin:config":api.ODJsonConfig<{testVariable1:boolean,testVariable2:number,testVariable3:string}> } } @@ -30,7 +30,7 @@ opendiscord.events.get("onConfigLoad").listen((configManager) => { //Let's also log it to the console to let us know it worked! const ourConfig = configManager.get("example-plugin:config") opendiscord.log("The example config loaded successfully!","plugin",[ - {key:"var-1",value:ourConfig.data.testVariable1}, + {key:"var-1",value:ourConfig.data.testVariable1.toString()}, {key:"var-2",value:ourConfig.data.testVariable2.toString()}, {key:"var-3",value:ourConfig.data.testVariable3.toString()} ]) diff --git a/src/actions/addTicketUser.ts b/src/actions/addTicketUser.ts index d595597..27da7ce 100644 --- a/src/actions/addTicketUser.ts +++ b/src/actions/addTicketUser.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //TICKET ADD USER SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:add-ticket-user")) opendiscord.actions.get("opendiscord:add-ticket-user").workers.add([ - new api.ODWorker("opendiscord:add-ticket-user",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:add-ticket-user",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params if (channel.isThread()) throw new api.ODSystemError("Unable to add user to ticket! Open Ticket doesn't support threads!") @@ -34,44 +34,31 @@ export const registerActions = async () => { opendiscord.log("Failed to add channel permission overwrites on add-ticket-user","error") } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket user adding!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value,hidden:true} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:add-message").build(source,{guild,channel,user,ticket,reason,data})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:add-message").build(origin,{guild,channel,user,ticket,reason,data})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketUserAdded").emit([ticket,user,data,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.adding.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.adding.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"add",reason,additionalData:data})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"add",reason,additionalData:data})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.adding.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"add",reason,additionalData:data})) + if (creator && generalConfig.data.logs.logMessages.adding.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"add",reason,additionalData:data})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket,data} = params opendiscord.log(user.displayName+" added "+data.displayName+" to a ticket!","info",[ @@ -80,7 +67,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/calculateTicketCategory.ts b/src/actions/calculateTicketCategory.ts new file mode 100644 index 0000000..dcc2c49 --- /dev/null +++ b/src/actions/calculateTicketCategory.ts @@ -0,0 +1,129 @@ +/////////////////////////////////////// +//CALCULATE TICKET CATEGORY SYSTEM +/////////////////////////////////////// +import {opendiscord, api, utilities, openticketUtils} from "../index.js" +import * as discord from "discord.js" + +const generalConfig = opendiscord.configs.get("opendiscord:general") + +export async function registerActions(){ + opendiscord.actions.add(new api.ODAction("opendiscord:calculate-ticket-category")) + opendiscord.actions.get("opendiscord:calculate-ticket-category").workers.add([ + new api.ODWorker("opendiscord:default-category",2,async (instance,params,origin,cancel) => { + //handle default category + const {guild,user,channel,option,ticket,currentCategoryId} = params + + const defaultCategoryId = option.get("opendiscord:channel-category").value + if (!defaultCategoryId){ + //default category is disabled + instance.newCategoryId = null + instance.newCategoryMode = null + instance.newCategory = null + instance.shouldChangeCategory = (instance.newCategoryId !== currentCategoryId) + }else{ + const defaultCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,defaultCategoryId) + if (defaultCategory){ + //default category is enabled + instance.newCategoryId = defaultCategoryId + instance.newCategoryMode = "default" + instance.newCategory = defaultCategory + instance.shouldChangeCategory = (instance.newCategoryId !== currentCategoryId) + }else{ + //default category is not found (do not switch categories) + opendiscord.log("Unable to find ticket category '"+defaultCategoryId+"' #1","error",[ + {key:"categoryid",value:defaultCategoryId}, + {key:"type",value:"default"} + ]) + instance.newCategoryId = null + instance.newCategoryMode = null + instance.newCategory = null + instance.shouldChangeCategory = false + } + } + }), + new api.ODWorker("opendiscord:close-category",1,async (instance,params,origin,cancel) => { + //handle close category + const {guild,user,channel,option,ticket,currentCategoryId} = params + if (!ticket) return + if (!ticket.get("opendiscord:closed").value) return + if (!generalConfig.data.ticketSystem.closedCategory.enabled) return + + const closeCategoryId = generalConfig.data.ticketSystem.closedCategory.categoryId + if (!closeCategoryId) return + const closeCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,closeCategoryId) + if (closeCategory){ + //close category is enabled + instance.newCategoryId = closeCategoryId + instance.newCategoryMode = "close" + instance.newCategory = closeCategory + instance.shouldChangeCategory = (instance.newCategoryId !== currentCategoryId) + }else{ + //close category is not found (do not switch categories) + opendiscord.log("Unable to find ticket category '"+closeCategoryId+"' #2","error",[ + {key:"categoryid",value:closeCategoryId}, + {key:"type",value:"close"} + ]) + instance.newCategoryId = null + instance.newCategoryMode = null + instance.newCategory = null + instance.shouldChangeCategory = false + } + }), + new api.ODWorker("opendiscord:claim-category",0,async (instance,params,origin,cancel) => { + //handle claim category + const {guild,user,channel,option,ticket,currentCategoryId} = params + if (!ticket) return + if (!ticket.get("opendiscord:claimed").value) return + + const claimedCategoryIds = generalConfig.data.ticketSystem.claimedCategories + const claimCategoryId = claimedCategoryIds.find((c) => c.user == user.id)?.category + if (!claimCategoryId) return + const claimCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,claimCategoryId) + if (claimCategory){ + //claim category is enabled + instance.newCategoryId = claimCategoryId + instance.newCategoryMode = "claim" + instance.newCategory = claimCategory + instance.shouldChangeCategory = (instance.newCategoryId !== currentCategoryId) + }else{ + //claim category is not found (do not switch categories) + opendiscord.log("Unable to find ticket category '"+claimCategoryId+"' #3","error",[ + {key:"categoryid",value:claimCategoryId}, + {key:"type",value:"claim"} + ]) + instance.newCategoryId = null + instance.newCategoryMode = null + instance.newCategory = null + instance.shouldChangeCategory = false + } + }), + new api.ODWorker("opendiscord:backup-category",-100,async (instance,params,origin,cancel) => { + //handle backup category + const {guild,user,channel,option,ticket,currentCategoryId} = params + if (!instance.newCategory || !instance.newCategoryId || !instance.shouldChangeCategory) return + if (instance.newCategory.children.cache.size < 50) return + if (!generalConfig.data.ticketSystem.backupCategory.enabled) return + + const backupCategoryId = generalConfig.data.ticketSystem.backupCategory.categoryId + if (!backupCategoryId) return + const backupCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,backupCategoryId) + if (backupCategory){ + //backup category is enabled + instance.newCategoryId = backupCategoryId + instance.newCategoryMode = "backup" + instance.newCategory = backupCategory + instance.shouldChangeCategory = (instance.newCategoryId !== currentCategoryId) + }else{ + //backup category is not found (do not switch categories) + opendiscord.log("Unable to find ticket category '"+backupCategoryId+"' #4","error",[ + {key:"categoryid",value:backupCategoryId}, + {key:"type",value:"backup"} + ]) + instance.newCategoryId = null + instance.newCategoryMode = null + instance.newCategory = null + instance.shouldChangeCategory = false + } + }) + ]) +} \ No newline at end of file diff --git a/src/actions/calculateTicketName.ts b/src/actions/calculateTicketName.ts new file mode 100644 index 0000000..f6d1777 --- /dev/null +++ b/src/actions/calculateTicketName.ts @@ -0,0 +1,30 @@ +/////////////////////////////////////// +//CALCULATE TICKET NAME SYSTEM +/////////////////////////////////////// +import {opendiscord, api, utilities, openticketUtils} from "../index.js" +import * as discord from "discord.js" + +const generalConfig = opendiscord.configs.get("opendiscord:general") + +export async function registerActions(){ + opendiscord.actions.add(new api.ODAction("opendiscord:calculate-ticket-name")) + opendiscord.actions.get("opendiscord:calculate-ticket-name").workers.add([ + new api.ODWorker("opendiscord:calculate-ticket-name",0,async (instance,params,origin,cancel) => { + const {guild,user,channel,option,ticket,currentChannelName} = params + + //calculate base channel name + const channelPrefix = option.get("opendiscord:channel-prefix").value + const channelSuffix = (ticket) ? ticket.get("opendiscord:channel-suffix").value : (await opendiscord.options.suffix.getSuffixFromOption(option,user,guild) ?? "unknown") + const channelRenamed = (ticket && ticket.exists("opendiscord:channel-renamed")) ? ticket.get("opendiscord:channel-renamed").value : null + const baseChannelName = (channelRenamed) ? channelRenamed : channelPrefix+channelSuffix + + //calculate status emojis + const pinEmoji = (ticket && ticket.get("opendiscord:pinned").value) ? generalConfig.data.ticketSystem.pinEmoji : "" + const priorityEmoji = (ticket) ? (opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "") : "" + + instance.newChannelName = pinEmoji+priorityEmoji+baseChannelName + instance.newChannelSuffix = channelSuffix + instance.shouldChangeName = (instance.newChannelName !== currentChannelName) + }) + ]) +} \ No newline at end of file diff --git a/src/actions/claimTicket.ts b/src/actions/claimTicket.ts index 502db01..e6a8c79 100644 --- a/src/actions/claimTicket.ts +++ b/src/actions/claimTicket.ts @@ -1,15 +1,16 @@ /////////////////////////////////////// //TICKET CLAIMING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:claim-ticket")) opendiscord.actions.get("opendiscord:claim-ticket").workers.add([ - new api.ODWorker("opendiscord:claim-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:claim-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to claim ticket! Open Ticket doesn't support threads!") @@ -22,67 +23,66 @@ export const registerActions = async () => { ticket.get("opendiscord:busy").value = true //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-claimed",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-claimed",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-claimed",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-claimed",user.id,1,"increase") - //update category + //calculate & update category if (typeof params.allowCategoryChange == "boolean" ? params.allowCategoryChange : true){ - const rawClaimCategory = ticket.option.get("opendiscord:channel-categories-claimed").value.find((c) => c.user == user.id) - const claimCategory = (rawClaimCategory) ? rawClaimCategory.category : null - if (claimCategory){ - try { - channel.setParent(claimCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "claimed" - ticket.get("opendiscord:category").value = claimCategory - }catch(e){ - opendiscord.log("Unable to move ticket to 'claimed category'!","error",[ + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("claim-ticket",{guild,user,option:ticket.option,channel,ticket,currentCategoryId:channel.parentId}) + if (categoryResult && categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined" && typeof categoryResult.newCategoryMode !== "undefined" && typeof categoryResult.newCategory !== "undefined"){ + const originalCategoryName = channel.parent?.name ?? "" + const newCategoryName = categoryResult.newCategory?.name ?? "" + try{ + await utilities.timedAwait(channel.setParent(categoryResult.newCategoryId,{lockPermissions:false}),3000,(err) => { + process.emit("uncaughtException",new Error("Error: Unable to change channel parent: "+err)) + }) + ticket.get("opendiscord:category-mode").value = categoryResult.newCategoryMode + ticket.get("opendiscord:category").value = categoryResult.newCategoryId + }catch(err){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-category").build("ticket-claim",{guild,channel,user,originalCategory:originalCategoryName,newCategory:newCategoryName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + opendiscord.log("Unable to move ticket to claimed category.","error",[ {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, - {key:"categoryid",value:claimCategory} + {key:"categoryid",value:categoryResult.newCategoryId ?? "/"} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) } } } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket claiming!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"claim-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketClaimed").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.claiming.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.claiming.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"claim",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"claim",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.claiming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"claim",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.claiming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"claim",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" claimed a ticket!","info",[ @@ -91,7 +91,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) @@ -99,184 +99,4 @@ export const registerActions = async () => { //set busy to false in case of crash or cancel params.ticket.get("opendiscord:busy").value = false }) -} - -export const registerVerifyBars = async () => { - //CLAIM TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:claim-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:claim-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.claim - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when already claimed - if (ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.claim"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start claiming ticket - if (params.data == "reason"){ - //claim with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:claim-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //claim without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:claim-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:claim-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //CLAIM TICKET UNCLAIM MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:claim-ticket-unclaim-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-unclaim-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:claim-ticket-unclaim-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.claim - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when already claimed - if (ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.claim"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start claiming ticket - if (params.data == "reason"){ - //claim with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:claim-ticket-reason").build("unclaim-message",{guild,channel,user,ticket})) - }else{ - //claim without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:claim-ticket").run("unclaim-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build("unclaim-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:claim-ticket-unclaim-message").failure.add([ - new api.ODWorker("opendiscord:back-to-unclaim-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) } \ No newline at end of file diff --git a/src/actions/clearTickets.ts b/src/actions/clearTickets.ts index f2fac57..3c15e2b 100644 --- a/src/actions/clearTickets.ts +++ b/src/actions/clearTickets.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //CLEAR TICKETS SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:clear-tickets")) opendiscord.actions.get("opendiscord:clear-tickets").workers.add([ - new api.ODWorker("opendiscord:clear-tickets",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:clear-tickets",2,async (instance,params,origin,cancel) => { const {guild,channel,user,filter,list} = params await opendiscord.events.get("onTicketsClear").emit([list,user,channel,filter]) @@ -43,21 +43,21 @@ export const registerActions = async () => { instance.list = nameList await opendiscord.events.get("afterTicketsCleared").emit([list,user,channel,filter]) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,filter,list} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.deleting.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.deleting.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:clear-logs").build(source,{guild,channel,user,filter,list:instance.list ?? []})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:clear-logs").build(origin,{guild,channel,user,filter,list:instance.list ?? []})) } }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,user,filter,list} = params opendiscord.log(user.displayName+" cleared "+list.length+" tickets!","info",[ {key:"user",value:user.username}, {key:"userid",value:user.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"filter",value:filter} ]) }) diff --git a/src/actions/closeTicket.ts b/src/actions/closeTicket.ts index e09bb2c..0c1e959 100644 --- a/src/actions/closeTicket.ts +++ b/src/actions/closeTicket.ts @@ -1,16 +1,17 @@ /////////////////////////////////////// //TICKET CLOSING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:close-ticket")) opendiscord.actions.get("opendiscord:close-ticket").workers.add([ - new api.ODWorker("opendiscord:close-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:close-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to close ticket! Open Ticket doesn't support threads!") @@ -25,29 +26,34 @@ export const registerActions = async () => { ticket.get("opendiscord:reopened-by").value = null ticket.get("opendiscord:reopened-on").value = null - if (source == "autoclose") ticket.get("opendiscord:autoclosed").value = true + if (origin == "autoclose") ticket.get("opendiscord:autoclosed").value = true ticket.get("opendiscord:open").value = false ticket.get("opendiscord:busy").value = true //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-closed",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-closed",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-closed",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-closed",user.id,1,"increase") - //update category + //calculate & update category if (typeof params.allowCategoryChange == "boolean" ? params.allowCategoryChange : true){ - const closeCategory = ticket.option.get("opendiscord:channel-category-closed").value - if (closeCategory !== ""){ - try { - channel.setParent(closeCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "closed" - ticket.get("opendiscord:category").value = closeCategory - }catch(e){ - opendiscord.log("Unable to move ticket to 'closed category'!","error",[ + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("close-ticket",{guild,user,option:ticket.option,channel,ticket,currentCategoryId:channel.parentId}) + if (categoryResult && categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined" && typeof categoryResult.newCategoryMode !== "undefined" && typeof categoryResult.newCategory !== "undefined"){ + const originalCategoryName = channel.parent?.name ?? "" + const newCategoryName = categoryResult.newCategory?.name ?? "" + try{ + await utilities.timedAwait(channel.setParent(categoryResult.newCategoryId,{lockPermissions:false}),3000,(err) => { + process.emit("uncaughtException",new Error("Error: Unable to change channel parent: "+err)) + }) + ticket.get("opendiscord:category-mode").value = categoryResult.newCategoryMode + ticket.get("opendiscord:category").value = categoryResult.newCategoryId + }catch(err){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-category").build("ticket-close",{guild,channel,user,originalCategory:originalCategoryName,newCategory:newCategoryName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + opendiscord.log("Unable to move ticket to closed category.","error",[ {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, - {key:"categoryid",value:closeCategory} + {key:"categoryid",value:categoryResult.newCategoryId ?? "/"} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) } } } @@ -93,7 +99,7 @@ export const registerActions = async () => { ticket.get("opendiscord:participants").value.forEach((participant) => { //all participants that aren't roles/admins => readonly (OR non-viewable when enabled) if (participant.type == "user"){ - if (generalConfig.data.system.removeParticipantsOnClose) permissions.push({ + if (generalConfig.data.ticketSystem.removeParticipantsOnClose) permissions.push({ type:discord.OverwriteType.Member, id:participant.id, allow:[], @@ -109,44 +115,39 @@ export const registerActions = async () => { }) channel.permissionOverwrites.set(permissions) - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket closing!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:close-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:close-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"close-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketClosed").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.closing.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.closing.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"close",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"close",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.closing.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"close",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.closing.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"close",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" closed a ticket!","info",[ @@ -155,7 +156,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) @@ -163,172 +164,4 @@ export const registerActions = async () => { //set busy to false in case of crash or cancel params.ticket.get("opendiscord:busy").value = false }) -} - -export const registerVerifyBars = async () => { - //CLOSE TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:close-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:close-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.close,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when already closed - if (ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:lang.getTranslation("errors.actionInvalid.close"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start closing ticket - if (params.data == "reason"){ - //close with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:close-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //close without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:close-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:close-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //CLOSE TICKET REOPEN MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:close-ticket-reopen-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-reopen-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:close-ticket-reopen-message").success.add([ - new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.close,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when already closed - if (ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:lang.getTranslation("errors.actionInvalid.close"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start closing ticket - if (params.data == "reason"){ - //close with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:close-ticket-reason").build("reopen-message",{guild,channel,user,ticket})) - }else{ - //close without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:close-ticket").run("reopen-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("reopen-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:close-ticket-reopen-message").failure.add([ - new api.ODWorker("opendiscord:back-to-reopen-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) } \ No newline at end of file diff --git a/src/actions/createTicket.ts b/src/actions/createTicket.ts index 2e62699..c8ff9ca 100644 --- a/src/actions/createTicket.ts +++ b/src/actions/createTicket.ts @@ -1,63 +1,36 @@ /////////////////////////////////////// //TICKET CREATION SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:create-ticket")) opendiscord.actions.get("opendiscord:create-ticket").workers.add([ - new api.ODWorker("opendiscord:create-ticket",3,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:create-ticket",3,async (instance,params,origin,cancel) => { const {guild,user,answers,option} = params await opendiscord.events.get("onTicketCreate").emit([user]) await opendiscord.events.get("onTicketChannelCreation").emit([option,user]) //get channel properties - const channelPrefix = option.get("opendiscord:channel-prefix").value - const channelCategory = option.get("opendiscord:channel-category").value - const channelBackupCategory = option.get("opendiscord:channel-category-backup").value const channelTopicText = option.get("opendiscord:channel-topic").value - const channelSuffix = await opendiscord.options.suffix.getSuffixFromOption(option,user,guild) - const channelName = channelPrefix+channelSuffix - //handle category - let category: string|null = null - let categoryMode: "backup"|"normal"|null = null - if (channelCategory != ""){ - //category enabled - const normalCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelCategory) - if (!normalCategory){ - //default category was not found - opendiscord.log("Ticket Creation Error: Unable to find category! #1","error",[ - {key:"categoryid",value:channelCategory}, - {key:"backup",value:"false"} - ]) - }else{ - //default category was found - if (normalCategory.children.cache.size >= 50 && channelBackupCategory != ""){ - //use backup category - const backupCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelBackupCategory) - if (!backupCategory){ - //default category was not found - opendiscord.log("Ticket Creation Error: Unable to find category! #2","error",[ - {key:"categoryid",value:channelBackupCategory}, - {key:"backup",value:"true"} - ]) - }else{ - category = backupCategory.id - categoryMode = "backup" - } - }else{ - //use default category - category = normalCategory.id - categoryMode = "normal" - } - } - } + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("create-ticket",{guild,user,option,channel:null,ticket:null,currentChannelName:null}) + if (!channelNameResult) return opendiscord.log("Ticket Creation Error: Unable to calculate ticket name.","error") + const channelName = (channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined") ? channelNameResult.newChannelName : "ot-unnamed-ticket" + const channelSuffix = (typeof channelNameResult.newChannelSuffix !== "undefined") ? channelNameResult.newChannelSuffix : "unknown" + + //calculate category + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("create-ticket",{guild,user,option,channel:null,ticket:null,currentCategoryId:null}) + if (!categoryResult) return opendiscord.log("Ticket Creation Error: Unable to calculate ticket category.","error") + const ticketCategoryId = (categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined") ? categoryResult.newCategoryId : undefined + const ticketCategoryMode = (categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryMode !== "undefined") ? categoryResult.newCategoryMode : undefined //handle permissions const permissions: discord.OverwriteResolvable[] = [{ @@ -118,15 +91,15 @@ export const registerActions = async () => { //handle channel topic const channelTopics: string[] = [] - if (generalConfig.data.system.channelTopic.showOptionName) channelTopics.push(option.get("opendiscord:name").value) - if (generalConfig.data.system.channelTopic.showOptionDescription) channelTopics.push(option.get("opendiscord:description").value) - if (generalConfig.data.system.channelTopic.showOptionTopic) channelTopics.push(channelTopicText) - if (generalConfig.data.system.channelTopic.showPriority) channelTopics.push("**"+lang.getTranslation("params.uppercase.priority")+":** "+opendiscord.priorities.get("opendiscord:none").renderDisplayName()) - if (generalConfig.data.system.channelTopic.showClosed) channelTopics.push("**"+lang.getTranslation("params.uppercase.status")+":** "+lang.getTranslation("params.uppercase.open")) - if (generalConfig.data.system.channelTopic.showClaimed) channelTopics.push("**"+lang.getTranslation("stats.properties.claimedBy")+":** "+lang.getTranslation("params.uppercase.noone")) - if (generalConfig.data.system.channelTopic.showPinned) channelTopics.push("**"+lang.getTranslation("params.uppercase.pinned")+":** "+lang.getTranslation("params.uppercase.no")) - if (generalConfig.data.system.channelTopic.showCreator) channelTopics.push("**"+lang.getTranslation("params.uppercase.creator")+":** "+discord.userMention(user.id)) - if (generalConfig.data.system.channelTopic.showParticipants) channelTopics.push("**"+lang.getTranslation("params.uppercase.participants")+":** "+participants.map((p) => (p.type == "user") ? discord.userMention(p.id) : discord.roleMention(p.id)).join(", ")) + if (generalConfig.data.ticketSystem.channelTopic.showOptionName) channelTopics.push(option.get("opendiscord:name").value) + if (generalConfig.data.ticketSystem.channelTopic.showOptionDescription) channelTopics.push(option.get("opendiscord:description").value) + if (generalConfig.data.ticketSystem.channelTopic.showOptionTopic) channelTopics.push(channelTopicText) + if (generalConfig.data.ticketSystem.channelTopic.showPriority) channelTopics.push("**"+lang.getTranslation("params.uppercase.priority")+":** "+opendiscord.priorities.get("opendiscord:none").renderDisplayName()) + if (generalConfig.data.ticketSystem.channelTopic.showClosed) channelTopics.push("**"+lang.getTranslation("params.uppercase.status")+":** "+lang.getTranslation("params.uppercase.open")) + if (generalConfig.data.ticketSystem.channelTopic.showClaimed) channelTopics.push("**"+lang.getTranslation("stats.properties.claimedBy")+":** "+lang.getTranslation("params.uppercase.noone")) + if (generalConfig.data.ticketSystem.channelTopic.showPinned) channelTopics.push("**"+lang.getTranslation("params.uppercase.pinned")+":** "+lang.getTranslation("params.uppercase.no")) + if (generalConfig.data.ticketSystem.channelTopic.showCreator) channelTopics.push("**"+lang.getTranslation("params.uppercase.creator")+":** "+discord.userMention(user.id)) + if (generalConfig.data.ticketSystem.channelTopic.showParticipants) channelTopics.push("**"+lang.getTranslation("params.uppercase.participants")+":** "+participants.map((p) => (p.type == "user") ? discord.userMention(p.id) : discord.roleMention(p.id)).join(", ")) //create channel const channel = await guild.channels.create({ @@ -134,7 +107,7 @@ export const registerActions = async () => { name:channelName, nsfw:false, topic:(channelTopics.length > 0) ? channelTopics.join(" โ€ข ") : undefined, - parent:category, + parent:ticketCategoryId, reason:"Ticket Created By "+user.displayName, permissionOverwrites:permissions, rateLimitPerUser:slowMode @@ -148,6 +121,7 @@ export const registerActions = async () => { new api.ODTicketData("opendiscord:ticket-message",null), new api.ODTicketData("opendiscord:participants",participants), new api.ODTicketData("opendiscord:channel-suffix",channelSuffix), + new api.ODTicketData("opendiscord:channel-renamed",null), new api.ODTicketData("opendiscord:previous-creators",[]), new api.ODTicketData("opendiscord:open",true), @@ -167,8 +141,8 @@ export const registerActions = async () => { new api.ODTicketData("opendiscord:pinned-on",null), new api.ODTicketData("opendiscord:for-deletion",false), - new api.ODTicketData("opendiscord:category",category), - new api.ODTicketData("opendiscord:category-mode",categoryMode), + new api.ODTicketData("opendiscord:category",ticketCategoryId ?? null), + new api.ODTicketData("opendiscord:category-mode",ticketCategoryMode ?? null), new api.ODTicketData("opendiscord:autoclose-enabled",option.get("opendiscord:autoclose-enable-hours").value), new api.ODTicketData("opendiscord:autoclose-hours",(option.get("opendiscord:autoclose-enable-hours").value ? option.get("opendiscord:autoclose-hours").value : 0)), @@ -184,8 +158,8 @@ export const registerActions = async () => { ]) //manage stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-created",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-created",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-created",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-created",user.id,1,"increase") //manage bot permissions await opendiscord.events.get("onTicketPermissionsCreated").emit([option,opendiscord.permissions,channel,user]) @@ -197,7 +171,7 @@ export const registerActions = async () => { instance.ticket = ticket opendiscord.tickets.add(ticket) }), - new api.ODWorker("opendiscord:send-ticket-message",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:send-ticket-message",2,async (instance,params,origin,cancel) => { const {guild,user,answers,option} = params const {ticket,channel} = instance @@ -207,17 +181,23 @@ export const registerActions = async () => { //check if ticket message is enabled if (!option.get("opendiscord:ticket-message-enabled").value) return try { - const msg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build(source,{guild,channel,user,ticket})).message) + const ticketMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build(origin,{guild,channel,user,ticket})).message) - ticket.get("opendiscord:ticket-message").value = msg.id + if (ticketMsg) await interactiveMsgState.setMsgState({channel,message:ticketMsg},{ + messageType:"ticket-message", + messageOrigin:"other", + messageAuthor:user.id + },false) + + ticket.get("opendiscord:ticket-message").value = ticketMsg.id //pin ticket message (if required) - if (generalConfig.data.system.pinFirstTicketMessage && msg.pinnable) await msg.pin("Ticket Message") + if (generalConfig.data.ticketSystem.pinFirstTicketMessage && ticketMsg.pinnable) await ticketMsg.pin("Ticket Message") //manage stats - await opendiscord.stats.get("opendiscord:ticket").setStat("opendiscord:messages-sent",ticket.id.value,1,"increase") + await opendiscord.statistics.get("opendiscord:ticket").setStat("opendiscord:messages-sent",ticket.id.value,1,"increase") - await opendiscord.events.get("afterTicketMainMessageCreated").emit([ticket,msg,channel,user]) + await opendiscord.events.get("afterTicketMainMessageCreated").emit([ticket,ticketMsg,channel,user]) }catch(err){ process.emit("uncaughtException",err) //something went wrong while sending the ticket message @@ -225,20 +205,22 @@ export const registerActions = async () => { } await opendiscord.events.get("afterTicketCreated").emit([ticket,user,channel]) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,user,answers,option} = params const {ticket,channel} = instance + if (!ticket || !channel) return opendiscord.log("Ticket Creation Error: Unable to send ticket message. Previous worker failed!","error") + //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.creation.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.creation.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created-logs").build(source,{guild,channel,user,ticket})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created-logs").build(origin,{guild,channel,user,ticket})) } //to dm - if (generalConfig.data.system.messages.creation.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:ticket-created-dm").build(source,{guild,channel,user,ticket})) + if (generalConfig.data.logs.logMessages.creation.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:ticket-created-dm").build(origin,{guild,channel,user,ticket})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,user,answers,option} = params const {ticket,channel} = instance @@ -249,7 +231,7 @@ export const registerActions = async () => { {key:"userid",value:user.id,hidden:true}, {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"option",value:option.id.value} ]) }) diff --git a/src/actions/createTicketPermissions.ts b/src/actions/createTicketPermissions.ts index 2c1bb06..0617124 100644 --- a/src/actions/createTicketPermissions.ts +++ b/src/actions/createTicketPermissions.ts @@ -1,13 +1,13 @@ /////////////////////////////////////// //TICKET CREATION SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:create-ticket-permissions")) opendiscord.actions.get("opendiscord:create-ticket-permissions").workers.add([ - new api.ODWorker("opendiscord:check-blacklist",4,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:check-blacklist",4,(instance,params,origin,cancel) => { if (!params.option.get("opendiscord:allow-blacklisted-users").value && opendiscord.blacklist.exists(params.user.id)){ instance.valid = false instance.reason = "blacklist" @@ -19,7 +19,7 @@ export const registerActions = async () => { return cancel() } }), - new api.ODWorker("opendiscord:check-cooldown",3,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:check-cooldown",3,(instance,params,origin,cancel) => { const cooldown = opendiscord.cooldowns.get("opendiscord:option-cooldown_"+params.option.id.value) if (cooldown && cooldown instanceof api.ODTimeoutCooldown && cooldown.use(params.user.id)){ instance.valid = false @@ -36,16 +36,16 @@ export const registerActions = async () => { return cancel() } }), - new api.ODWorker("opendiscord:check-global-limits",2,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:check-global-limits",2,(instance,params,origin,cancel) => { const generalConfig = opendiscord.configs.get("opendiscord:general") - if (!generalConfig.data.system.limits.enabled) return + if (!generalConfig.data.ticketSystem.limits.enabled) return const allTickets = opendiscord.tickets.getAll() const globalTicketCount = allTickets.length const userTickets = opendiscord.tickets.getFiltered((ticket) => ticket.exists("opendiscord:opened-by") && (ticket.get("opendiscord:opened-by").value == params.user.id)) const userTicketCount = userTickets.length - if (globalTicketCount >= generalConfig.data.system.limits.globalMaximum){ + if (globalTicketCount >= generalConfig.data.ticketSystem.limits.globalMaximum){ instance.valid = false instance.reason = "global-limit" opendiscord.log(params.user.displayName+" tried to create a ticket but reached the limit!","info",[ @@ -55,7 +55,7 @@ export const registerActions = async () => { {key:"limit",value:"global"} ]) return cancel() - }else if (userTicketCount >= generalConfig.data.system.limits.userMaximum){ + }else if (userTicketCount >= generalConfig.data.ticketSystem.limits.userMaximum){ instance.valid = false instance.reason = "global-user-limit" opendiscord.log(params.user.displayName+" tried to create a ticket, but reached the limit!","info",[ @@ -67,7 +67,7 @@ export const registerActions = async () => { return cancel() } }), - new api.ODWorker("opendiscord:check-option-limits",1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:check-option-limits",1,(instance,params,origin,cancel) => { if (!params.option.exists("opendiscord:limits-enabled") || !params.option.get("opendiscord:limits-enabled").value) return const allTickets = opendiscord.tickets.getFiltered((ticket) => ticket.option.id.value == params.option.id.value) @@ -97,7 +97,7 @@ export const registerActions = async () => { return cancel() } }), - new api.ODWorker("opendiscord:valid",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:valid",0,(instance,params,origin,cancel) => { instance.valid = true instance.reason = null cancel() diff --git a/src/actions/createTranscript.ts b/src/actions/createTranscript.ts index 825a142..2bbcb3f 100644 --- a/src/actions/createTranscript.ts +++ b/src/actions/createTranscript.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //TRANSCRIPT CREATION SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:create-transcript")) opendiscord.actions.get("opendiscord:create-transcript").workers.add([ - new api.ODWorker("opendiscord:select-compiler",4,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:select-compiler",4,async (instance,params,origin,cancel) => { const {channel,user,ticket} = params if (channel.type != discord.ChannelType.GuildText) return cancel() if (!transcriptConfig.data.general.enabled) return cancel() @@ -29,10 +29,15 @@ export const registerActions = async () => { if (transcriptConfig.data.general.mode == "text") instance.compiler = opendiscord.transcripts.get("opendiscord:text-compiler") else if (transcriptConfig.data.general.mode == "html") instance.compiler = opendiscord.transcripts.get("opendiscord:html-compiler") }), - new api.ODWorker("opendiscord:init-transcript",3,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:init-transcript",3,async (instance,params,origin,cancel) => { const {channel,user,ticket} = params if (channel.type != discord.ChannelType.GuildText) return cancel() if (!transcriptConfig.data.general.enabled) return cancel() + if (!instance.compiler){ + instance.success = false + cancel() + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:init-transcript) => Instance is missing transcript compiler!") + } //run transcript compiler init() await opendiscord.events.get("onTranscriptInit").emit([opendiscord.transcripts,ticket,channel,user]) @@ -63,7 +68,7 @@ export const registerActions = async () => { } await opendiscord.events.get("afterTranscriptInitiated").emit([opendiscord.transcripts,ticket,channel,user]) }), - new api.ODWorker("opendiscord:compile-transcript",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:compile-transcript",2,async (instance,params,origin,cancel) => { const {channel,user,ticket} = params if (channel.type != discord.ChannelType.GuildText) return cancel() if (!instance.compiler){ @@ -71,6 +76,11 @@ export const registerActions = async () => { cancel() throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:compile-transcript) => Instance is missing transcript compiler!") } + if (typeof instance.initData == "undefined"){ + instance.success = false + cancel() + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:compile-transcript) => Instance is missing transcript initData!") + } //run transcript compiler compile() await opendiscord.events.get("onTranscriptCompile").emit([opendiscord.transcripts,ticket,channel,user]) @@ -93,15 +103,31 @@ export const registerActions = async () => { } await opendiscord.events.get("afterTranscriptCompiled").emit([opendiscord.transcripts,ticket,channel,user]) }), - new api.ODWorker("opendiscord:ready-transcript",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:ready-transcript",1,async (instance,params,origin,cancel) => { + if (!instance.compiler){ + instance.success = false + cancel() + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript compiler! (1)") + } if (!instance.result){ instance.success = false cancel() - throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript result!") + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript result! (1)") } //run transcript compiler ready() utilities.runAsync(async () => { + if (!instance.compiler){ + instance.success = false + cancel() + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript compiler! (2)") + } + if (!instance.result){ + instance.success = false + cancel() + throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript result! (2)") + } + await opendiscord.events.get("onTranscriptReady").emit([opendiscord.transcripts,instance.result.ticket,instance.result.channel,instance.result.user]) if (instance.compiler.ready){ try{ @@ -109,9 +135,9 @@ export const registerActions = async () => { //send channel message if (transcriptConfig.data.general.enableChannel && channelMessage){ - if (instance.pendingMessage && instance.pendingMessage.message && instance.pendingMessage.success){ + if (instance.pendingMessage && instance.pendingMessage.success){ //edit "pending" message to be the "ready" message - instance.pendingMessage.message.edit(channelMessage.message) + instance.pendingMessage.message.edit(utilities.getMessageFromBuildResult(channelMessage,"message")) }else{ //send ready message to channel const post = opendiscord.posts.get("opendiscord:transcripts") @@ -149,11 +175,11 @@ export const registerActions = async () => { }) //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:transcripts-created",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:transcripts-created",params.user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:transcripts-created",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:transcripts-created",params.user.id,1,"increase") await opendiscord.events.get("afterTranscriptCreated").emit([opendiscord.transcripts,instance.result.ticket,instance.result.channel,instance.result.user]) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {user,channel,ticket} = params opendiscord.log(user.displayName+" created a transcript!","info",[ {key:"user",value:user.username}, @@ -161,8 +187,8 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"option",value:ticket.option.id.value}, - {key:"method",value:source,hidden:true}, - {key:"compiler",value:instance.compiler.id.value}, + {key:"method",value:origin,hidden:true}, + {key:"compiler",value:instance.compiler?.id.value ?? ""}, ]) }) ]) diff --git a/src/actions/deleteTicket.ts b/src/actions/deleteTicket.ts index 68b0d16..500ab61 100644 --- a/src/actions/deleteTicket.ts +++ b/src/actions/deleteTicket.ts @@ -1,16 +1,16 @@ /////////////////////////////////////// //TICKET DELETION SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:delete-ticket")) opendiscord.actions.get("opendiscord:delete-ticket").workers.add([ - new api.ODWorker("opendiscord:delete-ticket",3,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:delete-ticket",3,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to delete ticket! Open Ticket doesn't support threads!") @@ -20,31 +20,18 @@ export const registerActions = async () => { ticket.get("opendiscord:for-deletion").value = true ticket.get("opendiscord:busy").value = true - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket deletion!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(origin,{guild,channel,user,ticket,reason})).message) //create transcript if (!params.withoutTranscript){ - const transcriptRes = await opendiscord.actions.get("opendiscord:create-transcript").run(source,{guild,channel,user,ticket}) + const transcriptRes = await opendiscord.actions.get("opendiscord:create-transcript").run(origin,{guild,channel,user,ticket}) //transcript failure if (typeof transcriptRes.success == "boolean" && !transcriptRes.success && transcriptRes.compiler){ const {compiler} = transcriptRes - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:transcript-error").build(source,{guild,channel,user,ticket,compiler,reason:transcriptRes.errorReason ?? null})).message) + await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:transcript-error").build(origin,{guild,channel,user,ticket,compiler,reason:transcriptRes.errorReason ?? null})).message) .catch((reason) => opendiscord.log("Unable to send transcript failure to ticket channel!","error",[{key:"id",value:channel.id}])) //undo deletion @@ -59,8 +46,8 @@ export const registerActions = async () => { } //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-deleted",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-deleted",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-deleted",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-deleted",user.id,1,"increase") //delete ticket from manager opendiscord.tickets.remove(ticket.id) @@ -68,21 +55,21 @@ export const registerActions = async () => { //delete permissions from manager await (await import("../data/framework/permissionLoader.js")).removeTicketPermissions(ticket) }), - new api.ODWorker("opendiscord:discord-logs",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",2,async (instance,params,origin,cancel) => { //logs before channel deletion => channel might still be used in log embeds const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.deleting.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.deleting.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"delete",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"delete",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.deleting.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"delete",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.deleting.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"delete",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:delete-channel",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:delete-channel",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //delete channel & events await opendiscord.events.get("onTicketChannelDeletion").emit([ticket,channel,user]) @@ -94,7 +81,7 @@ export const registerActions = async () => { await opendiscord.events.get("afterTicketDeleted").emit([ticket,user,reason]) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" deleted a ticket!","info",[ @@ -103,7 +90,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"transcript",value:(!params.withoutTranscript).toString()}, ]) }) @@ -113,357 +100,4 @@ export const registerActions = async () => { params.ticket.get("opendiscord:busy").value = false params.ticket.get("opendiscord:for-deletion").value = false }) -} - -export const registerVerifyBars = async () => { - //DELETE TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:delete-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:delete-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.delete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //don't allow deleteWithoutTranscript to non-global-admins when enabled - if (params.data == "no-transcript" && generalConfig.data.system.adminOnlyDeleteWithoutTranscript){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - } - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start deleting ticket - if (params.data == "reason"){ - //delete with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:delete-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //delete without reason - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true,withoutTranscript:(params.data == "no-transcript")}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:delete-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //DELETE TICKET CLOSE MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:delete-ticket-close-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-close-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:delete-ticket-close-message").success.add([ - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.delete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //don't allow deleteWithoutTranscript to non-global-admins when enabled - if (params.data == "no-transcript" && generalConfig.data.system.adminOnlyDeleteWithoutTranscript){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - } - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start deleting ticket - if (params.data == "reason"){ - //delete with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:delete-ticket-reason").build("close-message",{guild,channel,user,ticket})) - }else{ - //delete without reason - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run("close-message",{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript:(params.data == "no-transcript")}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("close-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:delete-ticket-close-message").failure.add([ - new api.ODWorker("opendiscord:back-to-close-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - - //DELETE TICKET REOPEN MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:delete-ticket-reopen-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-reopen-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:delete-ticket-reopen-message").success.add([ - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.delete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //don't allow deleteWithoutTranscript to non-global-admins when enabled - if (params.data == "no-transcript" && generalConfig.data.system.adminOnlyDeleteWithoutTranscript){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - } - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start deleting ticket - if (params.data == "reason"){ - //delete with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:delete-ticket-reason").build("reopen-message",{guild,channel,user,ticket})) - }else{ - //delete without reason - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run("reopen-message",{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript:(params.data == "no-transcript")}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("reopen-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:delete-ticket-reopen-message").failure.add([ - new api.ODWorker("opendiscord:back-to-reopen-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - - //DELETE TICKET AUTOCLOSE MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:delete-ticket-autoclose-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-autoclose-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:delete-ticket-autoclose-message").success.add([ - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.delete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //don't allow deleteWithoutTranscript to non-global-admins when enabled - if (params.data == "no-transcript" && generalConfig.data.system.adminOnlyDeleteWithoutTranscript){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - } - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } - - //start deleting ticket - if (params.data == "reason"){ - //delete with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:delete-ticket-reason").build("autoclose-message",{guild,channel,user,ticket})) - }else{ - //delete without reason - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run("autoclose-message",{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript:(params.data == "no-transcript")}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("autoclose-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:delete-ticket-autoclose-message").failure.add([ - new api.ODWorker("opendiscord:back-to-autoclose-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("other",{guild,channel,user,ticket})) - }) - ]) } \ No newline at end of file diff --git a/src/actions/handleTranscriptErrors.ts b/src/actions/handleTranscriptErrors.ts index bc0cf9a..d9548d0 100644 --- a/src/actions/handleTranscriptErrors.ts +++ b/src/actions/handleTranscriptErrors.ts @@ -1,76 +1,43 @@ /////////////////////////////////////// //TRANSCRIPT ERROR SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" +import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //TRANSCRIPT ERROR RETRY opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:transcript-error-retry",/^od:transcript-error-retry_([^_]+)/)) opendiscord.responders.buttons.get("opendiscord:transcript-error-retry").workers.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.delete - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const originalSource = instance.interaction.customId.split("_")[1] as api.ODActionManagerIds_Default["opendiscord:delete-ticket"]["source"] + new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,member} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data + const originalOrigin = instance.interaction.customId.split("_")[1] as api.ODActionManagerIdMappings["opendiscord:delete-ticket"]["origin"] + //start deleting ticket (without reason) await instance.defer("update",false) //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason:"Transcript Error (Retried)",sendMessage:false,withoutTranscript:false}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true + opendiscord.actions.get("opendiscord:delete-ticket").run(originalOrigin,{guild,channel,user,ticket,reason:"Transcript Error (Retried)",sendMessage:false,withoutTranscript:false}) + + ticket.get("opendiscord:for-deletion").value = true //disable ticket message buttons await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("other",{guild,channel,user,ticket,reason:"Transcript Error (Retried)"})) - }), - new api.ODWorker("opendiscord:logs",-1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,async (instance,params,origin,cancel) => { const {user,channel} = instance if (channel.isDMBased()) return opendiscord.log(user.displayName+" retried deleting a ticket with transcript!","info",[ @@ -78,7 +45,7 @@ export const registerButtonResponders = async () => { {key:"userid",value:user.id,hidden:true}, {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) @@ -86,68 +53,35 @@ export const registerButtonResponders = async () => { //TRANSCRIPT ERROR CONTINUE opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:transcript-error-continue",/^od:transcript-error-continue_([^_]+)/)) opendiscord.responders.buttons.get("opendiscord:transcript-error-continue").workers.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.delete - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,origin,cancel) => { const {guild,channel,user} = instance - const originalSource = instance.interaction.customId.split("_")[1] as api.ODActionManagerIds_Default["opendiscord:delete-ticket"]["source"] - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() - //start deleting ticket (without reason) + //fetch data + const originalOrigin = instance.interaction.customId.split("_")[1] as api.ODActionManagerIdMappings["opendiscord:delete-ticket"]["origin"] + + //start deleting ticket (without reason & without transcript) await instance.defer("update",false) //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason:"Transcript Error (Continued)",sendMessage:false,withoutTranscript:true}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true + opendiscord.actions.get("opendiscord:delete-ticket").run(originalOrigin,{guild,channel,user,ticket,reason:"Transcript Error (Continued)",sendMessage:false,withoutTranscript:true}) + + ticket.get("opendiscord:for-deletion").value = true //disable ticket message buttons await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("other",{guild,channel,user,ticket,reason:"Transcript Error (Continued)"})) }), - new api.ODWorker("opendiscord:logs",-1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,async (instance,params,origin,cancel) => { const {user,channel} = instance if (channel.isDMBased()) return opendiscord.log(user.displayName+" continued deleting a ticket without transcript!","info",[ @@ -155,7 +89,7 @@ export const registerButtonResponders = async () => { {key:"userid",value:user.id,hidden:true}, {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/handleVerifyBar.ts b/src/actions/handleVerifyBar.ts index 2794d71..3eb6960 100644 --- a/src/actions/handleVerifyBar.ts +++ b/src/actions/handleVerifyBar.ts @@ -1,32 +1,22 @@ /////////////////////////////////////// //VERIFYBAR SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" +import * as discord from "discord.js" -export const registerButtonResponders = async () => { - //VERIFYBAR SUCCESS - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:verifybar-success",/^od:verifybar-success_([^_]+)/)) - opendiscord.responders.buttons.get("opendiscord:verifybar-success").workers.add( - new api.ODWorker("opendiscord:handle-verifybar",0,async (instance,params,source,cancel) => { - const id = instance.interaction.customId.split("_")[1] - const customData = instance.interaction.customId.split("_")[2] as string|undefined +export async function registerButtonResponders(){ + //HANDLE VERIFYBAR BUTTON + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:verifybar-button",/^od:verifybar\|([^|]+)\|([^|]+)/)) + opendiscord.responders.buttons.get("opendiscord:verifybar-button").workers.add( + new api.ODWorker("opendiscord:handle-verifybar",0,async (instance,params,origin,cancel) => { + const match = /^od:verifybar\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const verifyBarId = match[1] + const verifyButtonId = match[2] - const verifybar = opendiscord.verifybars.get(id) + const verifybar = opendiscord.verifybars.get(verifyBarId) if (!verifybar) return - if (verifybar.success) await verifybar.success.executeWorkers(instance,"verifybar",{data:customData ?? null,verifybarMessage:instance.message}) - }) - ) - - //VERIFYBAR FAILURE - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:verifybar-failure",/^od:verifybar-failure_([^_]+)/)) - opendiscord.responders.buttons.get("opendiscord:verifybar-failure").workers.add( - new api.ODWorker("opendiscord:handle-verifybar",0,async (instance,params,source,cancel) => { - const id = instance.interaction.customId.split("_")[1] - const customData = instance.interaction.customId.split("_")[2] as string|undefined - - const verifybar = opendiscord.verifybars.get(id) - if (!verifybar) return - if (verifybar.failure) await verifybar.failure.executeWorkers(instance,"verifybar",{data:customData ?? null,verifybarMessage:instance.message}) + await verifybar.activate(instance,verifyButtonId) }) ) } \ No newline at end of file diff --git a/src/actions/moveTicket.ts b/src/actions/moveTicket.ts index 591aef6..2679368 100644 --- a/src/actions/moveTicket.ts +++ b/src/actions/moveTicket.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //TICKET MOVING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:move-ticket")) opendiscord.actions.get("opendiscord:move-ticket").workers.add([ - new api.ODWorker("opendiscord:move-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:move-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params if (channel.isThread()) throw new api.ODSystemError("Unable to move ticket! Open Ticket doesn't support threads!") @@ -17,78 +17,31 @@ export const registerActions = async () => { ticket.option = data //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-moved",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-moved",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-moved",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-moved",user.id,1,"increase") - //get new channel properties - const channelPrefix = ticket.option.get("opendiscord:channel-prefix").value - const channelSuffix = ticket.get("opendiscord:channel-suffix").value - const channelCategory = ticket.option.get("opendiscord:channel-category").value - const channelBackupCategory = ticket.option.get("opendiscord:channel-category-backup").value - const rawClaimCategory = ticket.option.get("opendiscord:channel-categories-claimed").value.find((c) => c.user == user.id) - const claimCategory = (rawClaimCategory) ? rawClaimCategory.category : null - const closeCategory = ticket.option.get("opendiscord:channel-category-closed").value - const channelTopic = ticket.option.get("opendiscord:channel-topic").value - - //handle category - let category: string|null = null - let categoryMode: "backup"|"normal"|"closed"|"claimed"|null = null - if (claimCategory){ - //use claim category - category = claimCategory - categoryMode = "claimed" - }else if (closeCategory != "" && ticket.get("opendiscord:closed").value){ - //use close category - category = closeCategory - categoryMode = "closed" - }else if (channelCategory != ""){ - //category enabled - const normalCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelCategory) - if (!normalCategory){ - //default category was not found - opendiscord.log("Ticket Move Error: Unable to find category! #1","error",[ - {key:"categoryid",value:channelCategory}, - {key:"backup",value:"false"} + //calculate & update category + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("move-ticket",{guild,user,option:ticket.option,channel,ticket,currentCategoryId:channel.parentId}) + if (categoryResult && categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined" && typeof categoryResult.newCategoryMode !== "undefined" && typeof categoryResult.newCategory !== "undefined"){ + const originalCategoryName = channel.parent?.name ?? "" + const newCategoryName = categoryResult.newCategory?.name ?? "" + try{ + await utilities.timedAwait(channel.setParent(categoryResult.newCategoryId,{lockPermissions:false}),3000,(err) => { + process.emit("uncaughtException",new Error("Error: Unable to change channel parent: "+err)) + }) + ticket.get("opendiscord:category-mode").value = categoryResult.newCategoryMode + ticket.get("opendiscord:category").value = categoryResult.newCategoryId + }catch(err){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-category").build("ticket-move",{guild,channel,user,originalCategory:originalCategoryName,newCategory:newCategoryName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + opendiscord.log("Unable to move ticket to moved category.","error",[ + {key:"channel",value:"#"+channel.name}, + {key:"channelid",value:channel.id,hidden:true}, + {key:"categoryid",value:categoryResult.newCategoryId ?? "/"} ]) - }else{ - //default category was found - if (normalCategory.children.cache.size >= 50 && channelBackupCategory != ""){ - //use backup category - const backupCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelBackupCategory) - if (!backupCategory){ - //default category was not found - opendiscord.log("Ticket Move Error: Unable to find category! #2","error",[ - {key:"categoryid",value:channelBackupCategory}, - {key:"backup",value:"true"} - ]) - }else{ - category = backupCategory.id - categoryMode = "backup" - } - }else{ - //use default category - category = normalCategory.id - categoryMode = "normal" - } } } - try { - //only move category when not the same. - if (channel.parentId != category) await utilities.timedAwait(channel.setParent(category,{lockPermissions:false}),2500,(err) => { - opendiscord.log("Failed to change channel category on ticket move","error") - }) - ticket.get("opendiscord:category-mode").value = categoryMode - ticket.get("opendiscord:category").value = category - }catch(e){ - opendiscord.log("Unable to move ticket to 'moved category'!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"categoryid",value:category ?? "/"} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - //handle permissions const permissions: discord.OverwriteResolvable[] = [{ type:discord.OverwriteType.Role, @@ -153,58 +106,50 @@ export const registerActions = async () => { ticket.get("opendiscord:participants").value = participants ticket.get("opendiscord:participants").refreshDatabase() - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "" - - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(channelPrefix+channelSuffix) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket move","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-move",{guild,channel,user,originalName,newName:newName})).message) - } - - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("move-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket moving!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket move","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while moving ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-move",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message } } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) + //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:move-message").build(source,{guild,channel,user,ticket,reason,data})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:move-message").build(origin,{guild,channel,user,ticket,reason,data})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketMoved").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.moving.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.moving.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"move",reason,additionalData:data})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"move",reason,additionalData:data})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.moving.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"move",reason,additionalData:data})) + if (creator && generalConfig.data.logs.logMessages.moving.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"move",reason,additionalData:data})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" moved a ticket!","info",[ @@ -213,7 +158,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/pinTicket.ts b/src/actions/pinTicket.ts index a388ae0..6d52953 100644 --- a/src/actions/pinTicket.ts +++ b/src/actions/pinTicket.ts @@ -1,15 +1,16 @@ /////////////////////////////////////// //TICKET PINNING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:pin-ticket")) opendiscord.actions.get("opendiscord:pin-ticket").workers.add([ - new api.ODWorker("opendiscord:pin-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:pin-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to pin ticket! Open Ticket doesn't support threads!") @@ -22,66 +23,66 @@ export const registerActions = async () => { ticket.get("opendiscord:busy").value = true //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-pinned",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-pinned",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-pinned",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-pinned",user.id,1,"increase") //move to top of category if (channel.parent){ await channel.setPosition(0,{reason:"Ticket Pinned!"}) } - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "" - - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(channel.name) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket pin","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-pin",{guild,channel,user,originalName,newName})).message) - } - - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("pin-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket pinning!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"message",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket pin","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while pinning ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-pin",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message } } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) + //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"pin-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketPinned").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.pinning.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.pinning.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"pin",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"pin",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.pinning.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"pin",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.pinning.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"pin",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" pinned a ticket!","info",[ @@ -90,192 +91,8 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) -} - -export const registerVerifyBars = async () => { - //PIN TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:pin-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:pin-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.pin - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when already pinned - if (ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.pin"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start pinning ticket - if (params.data == "reason"){ - //pin with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:pin-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //pin without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:pin-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:pin-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //PIN TICKET UNPIN MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:pin-ticket-unpin-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-unpin-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:pin-ticket-unpin-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.pin - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when already pinned - if (ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.pin"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start pinning ticket - if (params.data == "reason"){ - //pin with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:pin-ticket-reason").build("unpin-message",{guild,channel,user,ticket})) - }else{ - //pin without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:pin-ticket").run("unpin-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build("unpin-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:pin-ticket-unpin-message").failure.add([ - new api.ODWorker("opendiscord:back-to-unpin-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - opendiscord.actions.get("opendiscord:pin-ticket").workers.backupWorker = new api.ODWorker("opendiscord:cancel-busy",0,(instance,params) => { - //set busy to false in case of crash or cancel - params.ticket.get("opendiscord:busy").value = false - }) } \ No newline at end of file diff --git a/src/actions/reactionRole.ts b/src/actions/reactionRole.ts index 84760e1..8a0a116 100644 --- a/src/actions/reactionRole.ts +++ b/src/actions/reactionRole.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //REACTION ROLE SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:reaction-role")) opendiscord.actions.get("opendiscord:reaction-role").workers.add([ - new api.ODWorker("opendiscord:reaction-role",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:reaction-role",2,async (instance,params,origin,cancel) => { const {guild,user,option,overwriteMode} = params const role = opendiscord.roles.get(option.id) if (!role) throw new api.ODSystemError("ODAction(ot:reaction-role) => Unknown reaction role (ODRole)") @@ -83,25 +83,25 @@ export const registerActions = async () => { instance.result = result await opendiscord.events.get("afterRolesUpdated").emit([user,role]) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,user,option,overwriteMode} = params if (!instance.role || !instance.result) return //to logs - if (generalConfig.data.system.logs.enabled && (generalConfig.data.system.messages.reactionRole.logs)){ + if (generalConfig.data.logs.enabled && (generalConfig.data.logs.logMessages.reactionRole.logs)){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:reaction-role-logs").build(source,{guild,user,role:instance.role,result:instance.result})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:reaction-role-logs").build(origin,{guild,user,role:instance.role,result:instance.result})) } //to dm - if (generalConfig.data.system.messages.reactionRole.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:reaction-role-dm").build(source,{guild,user,role:instance.role,result:instance.result})) + if (generalConfig.data.logs.logMessages.reactionRole.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:reaction-role-dm").build(origin,{guild,user,role:instance.role,result:instance.result})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,user,option} = params opendiscord.log(user.displayName+" updated his roles!","info",[ {key:"user",value:user.username}, {key:"userid",value:user.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"option",value:option.id.value} ]) }) diff --git a/src/actions/removeTicketUser.ts b/src/actions/removeTicketUser.ts index 9a8ef9f..93737fc 100644 --- a/src/actions/removeTicketUser.ts +++ b/src/actions/removeTicketUser.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //TICKET REMOVE USER SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:remove-ticket-user")) opendiscord.actions.get("opendiscord:remove-ticket-user").workers.add([ - new api.ODWorker("opendiscord:remove-ticket-user",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:remove-ticket-user",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params if (channel.isThread()) throw new api.ODSystemError("Unable to remove user from ticket! Open Ticket doesn't support threads!") @@ -29,43 +29,31 @@ export const registerActions = async () => { opendiscord.log("Failed to remove channel permission overwrites on remove-ticket-user","error") } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket user removal!","error",[ - {key:"channel",value:channel.id}, - {key:"message",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:remove-message").build(source,{guild,channel,user,ticket,reason,data})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:remove-message").build(origin,{guild,channel,user,ticket,reason,data})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketUserRemoved").emit([ticket,user,data,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.removing.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.removing.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"remove",reason,additionalData:data})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"remove",reason,additionalData:data})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.removing.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"remove",reason,additionalData:data})) + if (creator && generalConfig.data.logs.logMessages.removing.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"remove",reason,additionalData:data})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket,data} = params opendiscord.log(user.displayName+" removed "+data.displayName+" from a ticket!","info",[ @@ -74,7 +62,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/renameTicket.ts b/src/actions/renameTicket.ts index a110f16..7ffd071 100644 --- a/src/actions/renameTicket.ts +++ b/src/actions/renameTicket.ts @@ -1,72 +1,67 @@ /////////////////////////////////////// //TICKET RENAMING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:rename-ticket")) opendiscord.actions.get("opendiscord:rename-ticket").workers.add([ - new api.ODWorker("opendiscord:rename-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:rename-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params if (channel.isThread()) throw new api.ODSystemError("Unable to rename ticket! Open Ticket doesn't support threads!") await opendiscord.events.get("onTicketRename").emit([ticket,user,channel,reason]) - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "" + //update ticket + ticket.get("opendiscord:channel-renamed").value = data - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(data) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket rename","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-rename",{guild,channel,user,originalName,newName:data})).message) - } - - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("rename-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket renaming!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket rename","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while renaming ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-rename",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message } } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) + //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:rename-message").build(source,{guild,channel,user,ticket,reason,data})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:rename-message").build(origin,{guild,channel,user,ticket,reason,data})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketRenamed").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,data} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.renaming.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.renaming.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"rename",reason,additionalData:data})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"rename",reason,additionalData:data})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.renaming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"rename",reason,additionalData:data})) + if (creator && generalConfig.data.logs.logMessages.renaming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"rename",reason,additionalData:data})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" renamed a ticket!","info",[ @@ -75,7 +70,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/reopenTicket.ts b/src/actions/reopenTicket.ts index 5112f66..75d06da 100644 --- a/src/actions/reopenTicket.ts +++ b/src/actions/reopenTicket.ts @@ -1,15 +1,16 @@ /////////////////////////////////////// //TICKET REOPENING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:reopen-ticket")) opendiscord.actions.get("opendiscord:reopen-ticket").workers.add([ - new api.ODWorker("opendiscord:reopen-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:reopen-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to reopen ticket! Open Ticket doesn't support threads!") @@ -28,65 +29,37 @@ export const registerActions = async () => { ticket.get("opendiscord:open").value = true ticket.get("opendiscord:busy").value = true - if (generalConfig.data.system.disableAutocloseAfterReopen){ + if (generalConfig.data.ticketSystem.disableAutocloseAfterReopen){ //disable autoclose after reopen ticket.get("opendiscord:autoclose-enabled").value = false ticket.get("opendiscord:autoclose-hours").value = 0 } //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-reopened",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-reopened",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-reopened",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-reopened",user.id,1,"increase") - //update category + //calculate & update category if (typeof params.allowCategoryChange == "boolean" ? params.allowCategoryChange : true){ - const channelCategory = ticket.option.get("opendiscord:channel-category").value - const channelBackupCategory = ticket.option.get("opendiscord:channel-category-backup").value - if (channelCategory !== ""){ - //category enabled - try { - const normalCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelCategory) - if (!normalCategory){ - //default category was not found - opendiscord.log("Ticket Reopening Error: Unable to find category! #1","error",[ - {key:"categoryid",value:channelCategory}, - {key:"backup",value:"false"} - ]) - }else{ - //default category was found - if (normalCategory.children.cache.size >= 49 && channelBackupCategory != ""){ - //use backup category - const backupCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelBackupCategory) - if (!backupCategory){ - //default category was not found - opendiscord.log("Ticket Reopening Error: Unable to find category! #2","error",[ - {key:"categoryid",value:channelBackupCategory}, - {key:"backup",value:"true"} - ]) - }else{ - //use backup category - channel.setParent(backupCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "backup" - ticket.get("opendiscord:category").value = backupCategory.id - } - }else{ - //use default category - channel.setParent(normalCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "normal" - ticket.get("opendiscord:category").value = normalCategory.id - } - } - }catch(e){ - opendiscord.log("Unable to move ticket to 'reopened category'!","error",[ + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("reopen-ticket",{guild,user,option:ticket.option,channel,ticket,currentCategoryId:channel.parentId}) + if (categoryResult && categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined" && typeof categoryResult.newCategoryMode !== "undefined" && typeof categoryResult.newCategory !== "undefined"){ + const originalCategoryName = channel.parent?.name ?? "" + const newCategoryName = categoryResult.newCategory?.name ?? "" + try{ + await utilities.timedAwait(channel.setParent(categoryResult.newCategoryId,{lockPermissions:false}),3000,(err) => { + process.emit("uncaughtException",new Error("Error: Unable to change channel parent: "+err)) + }) + ticket.get("opendiscord:category-mode").value = categoryResult.newCategoryMode + ticket.get("opendiscord:category").value = categoryResult.newCategoryId + }catch(err){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-category").build("ticket-reopen",{guild,channel,user,originalCategory:originalCategoryName,newCategory:newCategoryName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + opendiscord.log("Unable to move ticket to reopened category.","error",[ {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, + {key:"categoryid",value:categoryResult.newCategoryId ?? "/"} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) } - }else{ - channel.setParent(null,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = null - ticket.get("opendiscord:category").value = null } } @@ -141,43 +114,38 @@ export const registerActions = async () => { }) channel.permissionOverwrites.set(permissions) - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket reopening!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"reopen-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketReopened").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.reopening.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.reopening.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"reopen",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"reopen",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.reopening.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"reopen",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.reopening.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"reopen",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" reopened a ticket!","info",[ @@ -186,280 +154,8 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) -} - -export const registerVerifyBars = async () => { - //REOPEN TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:reopen-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:reopen-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.reopen - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not closed - if (!ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.reopen"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start reopening ticket - if (params.data == "reason"){ - //reopen with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:reopen-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //reopen without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:reopen-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //REOPEN TICKET CLOSE MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:reopen-ticket-close-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-close-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:reopen-ticket-close-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.reopen - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not closed - if (!ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.reopen"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start reopening ticket - if (params.data == "reason"){ - //reopen with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:reopen-ticket-reason").build("close-message",{guild,channel,user,ticket})) - }else{ - //reopen without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run("close-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("close-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:reopen-ticket-close-message").failure.add([ - new api.ODWorker("opendiscord:back-to-close-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - - //REOPEN TICKET AUTOCLOSE MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:reopen-ticket-autoclose-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-autoclose-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:reopen-ticket-autoclose-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.reopen - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not closed - if (!ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.reopen"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start reopening ticket - if (params.data == "reason"){ - //reopen with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:reopen-ticket-reason").build("autoclose-message",{guild,channel,user,ticket})) - }else{ - //reopen without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run("autoclose-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("autoclose-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:reopen-ticket-autoclose-message").failure.add([ - new api.ODWorker("opendiscord:back-to-autoclose-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("other",{guild,channel,user,ticket})) - }) - ]) - opendiscord.actions.get("opendiscord:reopen-ticket").workers.backupWorker = new api.ODWorker("opendiscord:cancel-busy",0,(instance,params) => { - //set busy to false in case of crash or cancel - params.ticket.get("opendiscord:busy").value = false - }) } \ No newline at end of file diff --git a/src/actions/transferTicket.ts b/src/actions/transferTicket.ts index 85887dc..59ae605 100644 --- a/src/actions/transferTicket.ts +++ b/src/actions/transferTicket.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //TICKET TRANSFER SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:transfer-ticket")) opendiscord.actions.get("opendiscord:transfer-ticket").workers.add([ - new api.ODWorker("opendiscord:transfer-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:transfer-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason,newCreator} = params if (channel.isThread()) throw new api.ODSystemError("Unable to transfer ticket! Open Ticket doesn't support threads!") @@ -33,12 +33,8 @@ export const registerActions = async () => { } //update stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-transferred",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-transferred",user.id,1,"increase") - - //get new channel properties - const channelPrefix = ticket.option.get("opendiscord:channel-prefix").value - const channelSuffix = ticket.get("opendiscord:channel-suffix").value + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-transferred",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:tickets-transferred",user.id,1,"increase") //handle permissions const permissions: discord.OverwriteResolvable[] = [{ @@ -93,48 +89,40 @@ export const registerActions = async () => { opendiscord.log("Failed to reset channel permissions on ticket transfer!","error") } - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "" - - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(channelPrefix+channelSuffix) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket transfer","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-transfer",{guild,channel,user,originalName,newName:newName})).message) - } - - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("transfer-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket transferring!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket transfer","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while transferring ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-transfer",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message } } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) + //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:transfer-message").build(source,{guild,channel,user,ticket,oldCreator,newCreator,reason})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:transfer-message").build(origin,{guild,channel,user,ticket,oldCreator,newCreator,reason})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketTransferred").emit([ticket,user,channel,oldCreator,newCreator,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,newCreator,reason} = params }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket,newCreator} = params opendiscord.log(user.displayName+" transferred a ticket to '"+newCreator.displayName+"'!","info",[ @@ -143,7 +131,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/unclaimTicket.ts b/src/actions/unclaimTicket.ts index 6a04c1e..e91b4ef 100644 --- a/src/actions/unclaimTicket.ts +++ b/src/actions/unclaimTicket.ts @@ -1,15 +1,16 @@ /////////////////////////////////////// //TICKET UNCLAIMING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:unclaim-ticket")) opendiscord.actions.get("opendiscord:unclaim-ticket").workers.add([ - new api.ODWorker("opendiscord:unclaim-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:unclaim-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to unclaim ticket! Open Ticket doesn't support threads!") @@ -21,97 +22,63 @@ export const registerActions = async () => { ticket.get("opendiscord:claimed-on").value = null ticket.get("opendiscord:busy").value = true - //update category + //calculate & update category if (typeof params.allowCategoryChange == "boolean" ? params.allowCategoryChange : true){ - const channelCategory = ticket.option.get("opendiscord:channel-category").value - const channelBackupCategory = ticket.option.get("opendiscord:channel-category-backup").value - if (channelCategory !== ""){ - //category enabled - try { - const normalCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelCategory) - if (!normalCategory){ - //default category was not found - opendiscord.log("Ticket Unclaiming Error: Unable to find category! #1","error",[ - {key:"categoryid",value:channelCategory}, - {key:"backup",value:"false"} - ]) - }else{ - //default category was found - if (normalCategory.children.cache.size >= 49 && channelBackupCategory != ""){ - //use backup category - const backupCategory = await opendiscord.client.fetchGuildCategoryChannel(guild,channelBackupCategory) - if (!backupCategory){ - //default category was not found - opendiscord.log("Ticket Unclaiming Error: Unable to find category! #2","error",[ - {key:"categoryid",value:channelBackupCategory}, - {key:"backup",value:"true"} - ]) - }else{ - //use backup category - channel.setParent(backupCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "backup" - ticket.get("opendiscord:category").value = backupCategory.id - } - }else{ - //use default category - channel.setParent(normalCategory,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = "normal" - ticket.get("opendiscord:category").value = normalCategory.id - } - } - - }catch(e){ - opendiscord.log("Unable to move ticket to 'unclaimed category'!","error",[ + const categoryResult = await opendiscord.actions.get("opendiscord:calculate-ticket-category").run("unclaim-ticket",{guild,user,option:ticket.option,channel,ticket,currentCategoryId:channel.parentId}) + if (categoryResult && categoryResult.shouldChangeCategory && typeof categoryResult.newCategoryId !== "undefined" && typeof categoryResult.newCategoryMode !== "undefined" && typeof categoryResult.newCategory !== "undefined"){ + const originalCategoryName = channel.parent?.name ?? "" + const newCategoryName = categoryResult.newCategory?.name ?? "" + try{ + await utilities.timedAwait(channel.setParent(categoryResult.newCategoryId,{lockPermissions:false}),3000,(err) => { + process.emit("uncaughtException",new Error("Error: Unable to change channel parent: "+err)) + }) + ticket.get("opendiscord:category-mode").value = categoryResult.newCategoryMode + ticket.get("opendiscord:category").value = categoryResult.newCategoryId + }catch(err){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-category").build("ticket-unclaim",{guild,channel,user,originalCategory:originalCategoryName,newCategory:newCategoryName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + opendiscord.log("Unable to move ticket to unclaimed category.","error",[ {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true} + {key:"channelid",value:channel.id,hidden:true}, + {key:"categoryid",value:categoryResult.newCategoryId ?? "/"} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) } - }else{ - channel.setParent(null,{lockPermissions:false}) - ticket.get("opendiscord:category-mode").value = null - ticket.get("opendiscord:category").value = null } } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket unclaiming!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"unclaim-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketUnclaimed").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.claiming.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.claiming.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"unclaim",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"unclaim",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.claiming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"unclaim",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.claiming.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"unclaim",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" unclaimed a ticket!","info",[ @@ -120,192 +87,8 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) -} - -export const registerVerifyBars = async () => { - //UNCLAIM TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unclaim-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:unclaim-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.unclaim - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not claimed - if (!ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unclaim"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start unclaiming ticket - if (params.data == "reason"){ - //unclaim with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:unclaim-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //unclaim without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:unclaim-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //UNCLAIM TICKET CLAIM MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unclaim-ticket-claim-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-claim-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:unclaim-ticket-claim-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.unclaim - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not claimed - if (!ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unclaim"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start unclaiming ticket - if (params.data == "reason"){ - //unclaim with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:unclaim-ticket-reason").build("claim-message",{guild,channel,user,ticket})) - }else{ - //unclaim without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run("claim-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build("claim-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:unclaim-ticket-claim-message").failure.add([ - new api.ODWorker("opendiscord:back-to-claim-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - opendiscord.actions.get("opendiscord:unclaim-ticket").workers.backupWorker = new api.ODWorker("opendiscord:cancel-busy",0,(instance,params) => { - //set busy to false in case of crash or cancel - params.ticket.get("opendiscord:busy").value = false - }) } \ No newline at end of file diff --git a/src/actions/unpinTicket.ts b/src/actions/unpinTicket.ts index 931ef38..747754a 100644 --- a/src/actions/unpinTicket.ts +++ b/src/actions/unpinTicket.ts @@ -1,15 +1,16 @@ /////////////////////////////////////// //TICKET UNPINNING SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:unpin-ticket")) opendiscord.actions.get("opendiscord:unpin-ticket").workers.add([ - new api.ODWorker("opendiscord:unpin-ticket",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:unpin-ticket",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params if (channel.isThread()) throw new api.ODSystemError("Unable to unpin ticket! Open Ticket doesn't support threads!") @@ -21,58 +22,58 @@ export const registerActions = async () => { ticket.get("opendiscord:pinned-on").value = null ticket.get("opendiscord:busy").value = true - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "" - - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(channel.name) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket unpin","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-unpin",{guild,channel,user,originalName,newName})).message) - } - - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("unpin-ticket",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on ticket unpinning!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket unpin","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while unpinning ticket! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-unpin",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message } } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) + //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build(source,{guild,channel,user,ticket,reason})).message) + if (params.sendMessage){ + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build(origin,{guild,channel,user,ticket,reason})).message) + if (sentMsg) await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"unpin-message", + messageOrigin:"other", + messageAuthor:user.id, + messageReason:reason + },false) + } ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketUnpinned").emit([ticket,user,channel,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,reason} = params //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.pinning.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.pinning.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,ticket,mode:"unpin",reason,additionalData:null})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,ticket,mode:"unpin",reason,additionalData:null})) } //to dm const creator = await opendiscord.tickets.getTicketUser(ticket,"creator") - if (creator && generalConfig.data.system.messages.pinning.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,ticket,mode:"unpin",reason,additionalData:null})) + if (creator && generalConfig.data.logs.logMessages.pinning.dm) await opendiscord.client.sendUserDm(creator,await opendiscord.builders.messages.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,ticket,mode:"unpin",reason,additionalData:null})) }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params opendiscord.log(user.displayName+" unpinned a ticket!","info",[ @@ -81,192 +82,8 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"reason",value:params.reason ?? "/"}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) -} - -export const registerVerifyBars = async () => { - //UNPIN TICKET TICKET MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unpin-ticket-ticket-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-ticket-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:unpin-ticket-ticket-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.unpin - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not pinned - if (!ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unpin"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start unpining ticket - if (params.data == "reason"){ - //unpin with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:unpin-ticket-reason").build("ticket-message",{guild,channel,user,ticket})) - }else{ - //unpin without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run("ticket-message",{guild,channel,user,ticket,reason:null,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:unpin-ticket-ticket-message").failure.add([ - new api.ODWorker("opendiscord:back-to-ticket-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }) - ]) - - //UNPIN TICKET PIN MESSAGE - opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unpin-ticket-pin-message",opendiscord.builders.messages.getSafe("opendiscord:verifybar-pin-message"),!generalConfig.data.system.disableVerifyBars)) - opendiscord.verifybars.get("opendiscord:unpin-ticket-pin-message").success.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.unpin - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when not pinned - if (!ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unpin"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //start unpinning ticket - if (params.data == "reason"){ - //unpin with reason - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:unpin-ticket-reason").build("pin-message",{guild,channel,user,ticket})) - }else{ - //unpin without reason - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run("pin-message",{guild,channel,user,ticket,reason:null,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build("pin-message",{guild,channel,user,ticket,reason:null})) - } - }) - ]) - opendiscord.verifybars.get("opendiscord:unpin-ticket-pin-message").failure.add([ - new api.ODWorker("opendiscord:back-to-pin-message",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - const {verifybarMessage} = params - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - const rawReason = (verifybarMessage && verifybarMessage.embeds[0] && verifybarMessage.embeds[0].fields[0]) ? verifybarMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build("other",{guild,channel,user,ticket,reason})) - }) - ]) - opendiscord.actions.get("opendiscord:unpin-ticket").workers.backupWorker = new api.ODWorker("opendiscord:cancel-busy",0,(instance,params) => { - //set busy to false in case of crash or cancel - params.ticket.get("opendiscord:busy").value = false - }) } \ No newline at end of file diff --git a/src/actions/updateTicketPriority.ts b/src/actions/updateTicketPriority.ts index de328a7..76be4ec 100644 --- a/src/actions/updateTicketPriority.ts +++ b/src/actions/updateTicketPriority.ts @@ -1,15 +1,15 @@ /////////////////////////////////////// //TICKET TOPIC SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:update-ticket-priority")) opendiscord.actions.get("opendiscord:update-ticket-priority").workers.add([ - new api.ODWorker("opendiscord:update-ticket-priority",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:update-ticket-priority",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,newPriority,reason} = params if (channel.isThread() || !(channel instanceof discord.TextChannel)) throw new api.ODSystemError("Unable to set priority of ticket! Open Ticket doesn't support threads!") @@ -20,32 +20,37 @@ export const registerActions = async () => { ticket.get("opendiscord:busy").value = true if (newPriority) ticket.get("opendiscord:priority").value = newPriority.priority - //rename channel (and give error when crashed) - const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" - const priorityEmoji = newPriority.channelEmoji ?? "" - - const originalName = channel.name - const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(channel.name) - try{ - await utilities.timedAwait(channel.setName(newName),2500,(err) => { - opendiscord.log("Failed to rename channel on ticket priority update","error") - }) - }catch(err){ - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-priority",{guild,channel,user,originalName,newName})).message) + //calculate channel name + const channelNameResult = await opendiscord.actions.get("opendiscord:calculate-ticket-name").run("priority-change",{guild,user,option:ticket.option,channel,ticket,currentChannelName:channel.name}) + if (channelNameResult && channelNameResult.shouldChangeName && typeof channelNameResult.newChannelName !== "undefined"){ + const originalName = channel.name + const newName = channelNameResult.newChannelName + try{ + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on priority change","error") + }) + }catch(err){ + opendiscord.log("Unable to rename channel while updating ticket priority! Waiting until ratelimit expires...","warning",[ + {key:"oldName",value:originalName}, + {key:"newName",value:newName} + ]) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-priority",{guild,channel,user,originalName,newName})).message) + setTimeout(() => {if (sentMsg.deletable) sentMsg.delete()},7000) //autodelete error message + } } //reply with new message - if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:priority-set").build(source,{guild,channel,user,ticket,priority:newPriority,reason})).message) + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:priority-set").build(origin,{guild,channel,user,ticket,priority:newPriority,reason})).message) ticket.get("opendiscord:busy").value = false await opendiscord.events.get("afterTicketPriorityChanged").emit([ticket,user,channel,oldPriority,newPriority,reason]) //update channel topic await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket,newPriority} = params opendiscord.log(user.displayName+" changed the priority of a ticket!","info",[ @@ -54,7 +59,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"priority",value:newPriority.id.value}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/updateTicketTopic.ts b/src/actions/updateTicketTopic.ts index 1b544b0..207a5d7 100644 --- a/src/actions/updateTicketTopic.ts +++ b/src/actions/updateTicketTopic.ts @@ -1,16 +1,16 @@ /////////////////////////////////////// //TICKET TOPIC SYSTEM /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages -export const registerActions = async () => { +export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:update-ticket-topic")) opendiscord.actions.get("opendiscord:update-ticket-topic").workers.add([ - new api.ODWorker("opendiscord:update-ticket-topic",2,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:update-ticket-topic",2,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket,newTopic} = params if (channel.isThread() || !(channel instanceof discord.TextChannel)) throw new api.ODSystemError("Unable to set topic of ticket! Open Ticket doesn't support threads!") @@ -29,28 +29,28 @@ export const registerActions = async () => { //handle channel topic const channelTopics: string[] = [] - if (generalConfig.data.system.channelTopic.showOptionName) channelTopics.push(ticket.option.get("opendiscord:name").value) - if (generalConfig.data.system.channelTopic.showOptionDescription) channelTopics.push(ticket.option.get("opendiscord:description").value) - if (generalConfig.data.system.channelTopic.showOptionTopic) channelTopics.push(ticket.get("opendiscord:topic").value) - if (generalConfig.data.system.channelTopic.showPriority) channelTopics.push("**"+lang.getTranslation("params.uppercase.priority")+":** "+opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).renderDisplayName()) - if (generalConfig.data.system.channelTopic.showClosed) channelTopics.push("**"+lang.getTranslation("params.uppercase.status")+":** "+(closed ? lang.getTranslation("params.uppercase.closed") : lang.getTranslation("params.uppercase.open"))) - if (generalConfig.data.system.channelTopic.showClaimed) channelTopics.push("**"+lang.getTranslation("stats.properties.claimedBy")+":** "+(claimedBy ? discord.userMention(claimedBy) : lang.getTranslation("params.uppercase.noone"))) - if (generalConfig.data.system.channelTopic.showPinned) channelTopics.push("**"+lang.getTranslation("params.uppercase.pinned")+":** "+(pinned ? lang.getTranslation("params.uppercase.yes") : lang.getTranslation("params.uppercase.no"))) - if (generalConfig.data.system.channelTopic.showCreator) channelTopics.push("**"+lang.getTranslation("params.uppercase.creator")+":** "+discord.userMention(creator)) - if (generalConfig.data.system.channelTopic.showParticipants) channelTopics.push("**"+lang.getTranslation("params.uppercase.participants")+":** "+ticket.get("opendiscord:participants").value.map((p) => (p.type == "user") ? discord.userMention(p.id) : discord.roleMention(p.id)).join(", ")) + if (generalConfig.data.ticketSystem.channelTopic.showOptionName) channelTopics.push(ticket.option.get("opendiscord:name").value) + if (generalConfig.data.ticketSystem.channelTopic.showOptionDescription) channelTopics.push(ticket.option.get("opendiscord:description").value) + if (generalConfig.data.ticketSystem.channelTopic.showOptionTopic) channelTopics.push(ticket.get("opendiscord:topic").value) + if (generalConfig.data.ticketSystem.channelTopic.showPriority) channelTopics.push("**"+lang.getTranslation("params.uppercase.priority")+":** "+opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).renderDisplayName()) + if (generalConfig.data.ticketSystem.channelTopic.showClosed) channelTopics.push("**"+lang.getTranslation("params.uppercase.status")+":** "+(closed ? lang.getTranslation("params.uppercase.closed") : lang.getTranslation("params.uppercase.open"))) + if (generalConfig.data.ticketSystem.channelTopic.showClaimed) channelTopics.push("**"+lang.getTranslation("stats.properties.claimedBy")+":** "+(claimedBy ? discord.userMention(claimedBy) : lang.getTranslation("params.uppercase.noone"))) + if (generalConfig.data.ticketSystem.channelTopic.showPinned) channelTopics.push("**"+lang.getTranslation("params.uppercase.pinned")+":** "+(pinned ? lang.getTranslation("params.uppercase.yes") : lang.getTranslation("params.uppercase.no"))) + if (generalConfig.data.ticketSystem.channelTopic.showCreator) channelTopics.push("**"+lang.getTranslation("params.uppercase.creator")+":** "+discord.userMention(creator)) + if (generalConfig.data.ticketSystem.channelTopic.showParticipants) channelTopics.push("**"+lang.getTranslation("params.uppercase.participants")+":** "+ticket.get("opendiscord:participants").value.map((p) => (p.type == "user") ? discord.userMention(p.id) : discord.roleMention(p.id)).join(", ")) //update channel channel.setTopic(channelTopics.join(" โ€ข "),"Topic Changed") //reply with new message - if (params.sendMessage && newTopic) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:topic-set").build(source,{guild,channel,user,ticket,topic:newTopic})).message) + if (params.sendMessage && newTopic) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:topic-set").build(origin,{guild,channel,user,ticket,topic:newTopic})).message) ticket.get("opendiscord:busy").value = false if (newTopic) await opendiscord.events.get("afterTicketTopicChanged").emit([ticket,user,channel,oldTopic,newTopic]) }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user,ticket} = params }), - new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",0,(instance,params,origin,cancel) => { const {guild,channel,user,ticket,newTopic} = params if (newTopic) opendiscord.log(user.displayName+" changed the topic of a ticket!","info",[ @@ -59,7 +59,7 @@ export const registerActions = async () => { {key:"channel",value:"#"+channel.name}, {key:"channelid",value:channel.id,hidden:true}, {key:"topic",value:newTopic}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/actions/utilities.ts b/src/actions/utilities.ts new file mode 100644 index 0000000..8f87003 --- /dev/null +++ b/src/actions/utilities.ts @@ -0,0 +1,176 @@ +/////////////////////////////////////// +//OPEN TICKET UTILITY FUNCTIONS +/////////////////////////////////////// +import {opendiscord, api, utilities} from "../index.js" +import * as discord from "discord.js" + +/**Fetch the interactive message state. If not present, auto replies with error and returns `null`. When `null` is received, the worker should be returned and canceled. */ +export async function replyInteractiveMessageState(instance:api.ODButtonResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",channel:discord.TextBasedChannel,message:discord.Message|null,replacementCommandName:string){ + //check message state + const {user,member,guild} = instance + const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") + + if (!message){ + //TODO TRANSLATION!!! + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:"Unable to locate message of interaction. Use the command `{0}` instead.".replace("{0}",replacementCommandName),layout:"simple",customTitle:"Message State Error"})) + return null + } + + const state = await interactiveMsgState.getMsgState({channel,message}) + if (!state){ + //TODO TRANSLATION!!! + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:"This interaction is no longer valid or has expired. Use the command `{0}` instead. It is normal to receive this error after a major Open Ticket update.".replace("{0}",replacementCommandName),layout:"simple",customTitle:"Message State Expired"})) + return null + }else return state +} + +/**Check the permissions for this command. If not allowed, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyHasPermissions(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",commandName:keyof api.ODGeneralJsonConfig_SystemPermissions,settings?:api.ODPermissionSettings) { + //check permissions + const {user,member,channel,guild} = instance + const generalConfig = opendiscord.configs.get("opendiscord:general") + const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.permissions[commandName],"support",user,member,channel,guild,settings) + + if (!permsResult.hasPerms){ + if (permsResult.reason == "not-in-server" && channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) + else if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(origin,{guild,channel,user,permissions:["support"]})) + return false + }else return true +} + +/**Check if channel is in guild/server. If not, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyIsInGuild(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other"){ + //check is in guild/server + const {user,member,channel,guild} = instance + + if (!guild){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(origin,{channel,user})) + return false + }else return true +} + +/**Check if channel is valid ticket. If not, auto replies with error and returns `null`. When `null` is received, the worker should be returned and canceled. */ +export async function replyIsTicket(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other") { + //check if ticket exists + const {user,member,channel,guild} = instance + if (!channel) return null + const ticket = opendiscord.tickets.get(channel.id) + + if (!ticket || channel.isDMBased()){ + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build(origin,{guild,channel,user})) + return null + }else return ticket +} + +/**Check if ticket is not busy and available. If not, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketIsAvailable(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when busy + const {user,member,channel,guild} = instance + + if (ticket.get("opendiscord:busy").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build(origin,{guild,channel,user})) + return false + }else return true +} + +/**Check if ticket is open. If closed, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBeOpen(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket){ + //return when already closed + const {user,member,channel,guild} = instance + if (ticket.get("opendiscord:closed").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.close"),layout:"simple"})) + return false + }else return true +} + +/**Check if ticket is closed. If open, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBeClosed(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when already open + const {user,member,channel,guild} = instance + if (!ticket.get("opendiscord:closed").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.reopen"),layout:"simple"})) + return false + }else return true +} + +/**Check if ticket is unclaimed. If claimed, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBeUnclaimed(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when already claimed + const {user,member,channel,guild} = instance + if (ticket.get("opendiscord:claimed").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.claim"),layout:"simple"})) + return false + }else return true +} + +/**Check if ticket is claimed. If unclaimed, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBeClaimed(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when already unclaimed + const {user,member,channel,guild} = instance + if (!ticket.get("opendiscord:claimed").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unclaim"),layout:"simple"})) + return false + }else return true +} + +/**Check if ticket is unpinned. If pinned, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBeUnpinned(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when already pinned + const {user,member,channel,guild} = instance + if (ticket.get("opendiscord:pinned").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.pin"),layout:"simple"})) + return false + }else return true +} + +/**Check if ticket is pinned. If unpinned, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyTicketMustBePinned(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket) { + //return when already unpinned + const {user,member,channel,guild} = instance + if (!ticket.get("opendiscord:pinned").value){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unpin"),layout:"simple"})) + return false + }else return true +} + +/**Check if the ticket can be closed/deleted before a message has been sent by an admin or user. If not passing the test, auto replies with error and returns `false`. When `false` is received, the worker should be returned and canceled. */ +export async function replyMessageMustBeSentBeforeClose(instance:api.ODButtonResponderInstance|api.ODCommandResponderInstance|api.ODDropdownResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",ticket:api.ODTicket,commandName:keyof api.ODGeneralJsonConfig_SystemPermissions) { + //return when not allowed because of missing messages + const {user,member,channel,guild} = instance + const generalConfig = opendiscord.configs.get("opendiscord:general") + const lang = opendiscord.languages + const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.permissions[commandName],"support",user,member,channel,guild) + + if (!permsResult.hasPerms) throw new api.ODSystemError("Please check permissions before using replyMessageMustBeSentBeforeClose()") + if (!guild || channel.isDMBased()) throw new api.ODSystemError("replyMessageMustBeSentBeforeClose() must be used in a guild and not a DM channel.") + + if (!permsResult.isAdmin && (!generalConfig.data.ticketSystem.allowCloseBeforeMessage || !generalConfig.data.ticketSystem.allowCloseBeforeAdminMessage)){ + const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) + if (analysis && !generalConfig.data.ticketSystem.allowCloseBeforeMessage && analysis.totalMessages < 1){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) + return false + } + if (analysis && !generalConfig.data.ticketSystem.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ + if (channel) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) + return false + } + return true + }else return true +} + +/**Update the ticket message from a given ticket channel. This should be used after every change to the `ODTicket` after an action. */ +export async function updateTicketMessage(guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:api.ODTicket){ + const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) + if (!ticketMessage || !user || !channel || !guild || channel.isDMBased()) return + try{ + await ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) + }catch(e){ + opendiscord.log("Unable to edit ticket message!","error",[ + {key:"channel",value:"#"+channel.name}, + {key:"channelid",value:channel.id,hidden:true}, + {key:"messageid",value:ticketMessage.id}, + {key:"option",value:ticket.option.id.value} + ]) + opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + } +} \ No newline at end of file diff --git a/src/builders/buttons.ts b/src/builders/buttons.ts index cc9cba9..b4ae6cd 100644 --- a/src/builders/buttons.ts +++ b/src/builders/buttons.ts @@ -1,15 +1,37 @@ /////////////////////////////////////// //BUTTON BUILDERS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" const buttons = opendiscord.builders.buttons const lang = opendiscord.languages const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerAllButtons = async () => { - verifybarButtons() +export async function registerAllButtons(){ + //VERIFYBAR BUTTON + buttons.add(new api.ODButton("opendiscord:verifybar-button")) + buttons.get("opendiscord:verifybar-button").workers.add( + new api.ODWorker("opendiscord:verifybar-button",0,async (instance,params) => { + const {verifybar,verifyButtonId} = params + if (params.verifyButtonId.length > 40) throw new api.ODSystemError("ODButton:opendiscord:verifybar-button => verifyButtonId '"+verifyButtonId+"' exceeds 40 characters limit!") + + const color = ("customColor" in params && params.customColor) ? params.customColor : "gray" + //TODO TRANSLATION!!! + const defaultLabel = (params.defaultButtonType == "โœ…") ? "Accept" : "Cancel" + const defaultEmoji = (params.defaultButtonType == "โœ…") ? "โœ…" : "โŒ" + + const label = ("customLabel" in params && params.customLabel) ? params.customLabel : (params.useDefaultLabels ? defaultLabel : null) + const emoji = ("customEmoji" in params && params.customEmoji) ? params.customEmoji : defaultEmoji + + instance.setCustomId("od:verifybar|"+verifybar.id.value+"|"+verifyButtonId) + instance.setMode("button") + instance.setColor(color) + if (label) instance.setLabel(label) + if (emoji) instance.setEmoji(emoji) + }) + ) + errorButtons() helpMenuButtons() panelButtons() @@ -18,50 +40,6 @@ export const registerAllButtons = async () => { clearButtons() } -const verifybarButtons = () => { - //VERIFYBAR SUCCESS - buttons.add(new api.ODButton("opendiscord:verifybar-success")) - buttons.get("opendiscord:verifybar-success").workers.add( - new api.ODWorker("opendiscord:verifybar-success",0,async (instance,params) => { - const {verifybar,customData,customColor,customLabel,customEmoji} = params - - if (customData && customData.length > 40) throw new api.ODSystemError("ODButton:opendiscord:verifybar-success => customData exceeds 40 characters limit!") - - const newData = (customData) ? "_"+customData : "" - const newColor = customColor ?? "gray" - const newLabel = customLabel ?? "" - const newEmoji = customEmoji ?? "โœ…" - - instance.setCustomId("od:verifybar-success_"+verifybar.id.value+newData) - instance.setMode("button") - instance.setColor(newColor) - if (newLabel) instance.setLabel(newLabel) - if (newEmoji) instance.setEmoji(newEmoji) - }) - ) - - //VERIFYBAR FAILURE - buttons.add(new api.ODButton("opendiscord:verifybar-failure")) - buttons.get("opendiscord:verifybar-failure").workers.add( - new api.ODWorker("opendiscord:verifybar-failure",0,async (instance,params) => { - const {verifybar,customData,customColor,customLabel,customEmoji} = params - - if (customData && customData.length > 40) throw new api.ODSystemError("ODButton:opendiscord:verifybar-success => customData exceeds 40 characters limit!") - - const newData = (customData) ? "_"+customData : "" - const newColor = customColor ?? "gray" - const newLabel = customLabel ?? "" - const newEmoji = customEmoji ?? "โŒ" - - instance.setCustomId("od:verifybar-failure_"+verifybar.id.value+newData) - instance.setMode("button") - instance.setColor(newColor) - if (newLabel) instance.setLabel(newLabel) - if (newEmoji) instance.setEmoji(newEmoji) - }) - ) -} - const errorButtons = () => { //ERROR TICKET DEPRECATED TRANSCRIPT buttons.add(new api.ODButton("opendiscord:error-ticket-deprecated-transcript")) @@ -201,11 +179,11 @@ const ticketButtons = () => { //CLOSE TICKET buttons.add(new api.ODButton("opendiscord:close-ticket")) buttons.get("opendiscord:close-ticket").workers.add( - new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:close-ticket_"+source) + instance.setCustomId("od:close-ticket") instance.setColor("gray") instance.setEmoji("๐Ÿ”’") instance.setLabel(lang.getTranslation("actions.buttons.close")) @@ -215,11 +193,11 @@ const ticketButtons = () => { //DELETE TICKET buttons.add(new api.ODButton("opendiscord:delete-ticket")) buttons.get("opendiscord:delete-ticket").workers.add( - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:delete-ticket_"+source) + instance.setCustomId("od:delete-ticket") instance.setColor("red") instance.setEmoji("โœ–") instance.setLabel(lang.getTranslation("actions.buttons.delete")) @@ -229,11 +207,11 @@ const ticketButtons = () => { //REOPEN TICKET buttons.add(new api.ODButton("opendiscord:reopen-ticket")) buttons.get("opendiscord:reopen-ticket").workers.add( - new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:reopen-ticket_"+source) + instance.setCustomId("od:reopen-ticket") instance.setColor("green") instance.setEmoji("๐Ÿ”“") instance.setLabel(lang.getTranslation("actions.buttons.reopen")) @@ -243,11 +221,11 @@ const ticketButtons = () => { //CLAIM TICKET buttons.add(new api.ODButton("opendiscord:claim-ticket")) buttons.get("opendiscord:claim-ticket").workers.add( - new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:claim-ticket_"+source) + instance.setCustomId("od:claim-ticket") instance.setColor("green") instance.setEmoji("๐Ÿ‘‹") instance.setLabel(lang.getTranslation("actions.buttons.claim")) @@ -257,11 +235,11 @@ const ticketButtons = () => { //UNCLAIM TICKET buttons.add(new api.ODButton("opendiscord:unclaim-ticket")) buttons.get("opendiscord:unclaim-ticket").workers.add( - new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:unclaim-ticket_"+source) + instance.setCustomId("od:unclaim-ticket") instance.setColor("green") instance.setEmoji("โ†ฉ๏ธ") instance.setLabel(lang.getTranslation("actions.buttons.unclaim")) @@ -271,13 +249,13 @@ const ticketButtons = () => { //PIN TICKET buttons.add(new api.ODButton("opendiscord:pin-ticket")) buttons.get("opendiscord:pin-ticket").workers.add( - new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:pin-ticket_"+source) + instance.setCustomId("od:pin-ticket") instance.setColor("gray") - instance.setEmoji(generalConfig.data.system.pinEmoji) + instance.setEmoji(generalConfig.data.ticketSystem.pinEmoji) instance.setLabel(lang.getTranslation("actions.buttons.pin")) }) ) @@ -285,13 +263,13 @@ const ticketButtons = () => { //UNPIN TICKET buttons.add(new api.ODButton("opendiscord:unpin-ticket")) buttons.get("opendiscord:unpin-ticket").workers.add( - new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,origin) => { const {guild,channel,ticket} = params instance.setMode("button") - instance.setCustomId("od:unpin-ticket_"+source) + instance.setCustomId("od:unpin-ticket") instance.setColor("gray") - instance.setEmoji(generalConfig.data.system.pinEmoji) + instance.setEmoji(generalConfig.data.ticketSystem.pinEmoji) instance.setLabel(lang.getTranslation("actions.buttons.unpin")) }) ) @@ -301,7 +279,7 @@ const transcriptButtons = () => { //TRANSCRIPT HTML VISIT buttons.add(new api.ODButton("opendiscord:transcript-html-visit")) buttons.get("opendiscord:transcript-html-visit").workers.add( - new api.ODWorker("opendiscord:transcript-html-visit",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-html-visit",0,async (instance,params,origin) => { const {result} = params instance.setMode("url") if (result.data) instance.setUrl(result.data.url) @@ -314,9 +292,9 @@ const transcriptButtons = () => { //TRANSCRIPT ERROR RETRY buttons.add(new api.ODButton("opendiscord:transcript-error-retry")) buttons.get("opendiscord:transcript-error-retry").workers.add( - new api.ODWorker("opendiscord:transcript-error-retry",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-error-retry",0,async (instance,params,origin) => { instance.setMode("button") - instance.setCustomId("od:transcript-error-retry_"+source) + instance.setCustomId("od:transcript-error-retry_"+origin) instance.setColor("gray") instance.setEmoji("๐Ÿ”„") instance.setLabel(lang.getTranslation("transcripts.errors.retry")) @@ -326,9 +304,9 @@ const transcriptButtons = () => { //TRANSCRIPT ERROR CONTINUE buttons.add(new api.ODButton("opendiscord:transcript-error-continue")) buttons.get("opendiscord:transcript-error-continue").workers.add( - new api.ODWorker("opendiscord:transcript-error-continue",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-error-continue",0,async (instance,params,origin) => { instance.setMode("button") - instance.setCustomId("od:transcript-error-continue_"+source) + instance.setCustomId("od:transcript-error-continue_"+origin) instance.setColor("red") instance.setEmoji("โœ–") instance.setLabel(lang.getTranslation("transcripts.errors.continue")) @@ -340,12 +318,13 @@ const clearButtons = () => { //CLEAR CONTINUE buttons.add(new api.ODButton("opendiscord:clear-continue")) buttons.get("opendiscord:clear-continue").workers.add( - new api.ODWorker("opendiscord:clear-continue",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-continue",0,async (instance,params,origin) => { instance.setMode("button") - instance.setCustomId("od:clear-continue_"+source+"_"+params.filter) + instance.setCustomId("od:clear-continue") instance.setColor("red") instance.setEmoji("โœ–") instance.setLabel(lang.getTranslation("actions.buttons.clear")) + instance.setDisabled(params.inProgress) }) ) } \ No newline at end of file diff --git a/src/builders/dropdowns.ts b/src/builders/dropdowns.ts index 2f675c5..c83ebd7 100644 --- a/src/builders/dropdowns.ts +++ b/src/builders/dropdowns.ts @@ -1,12 +1,12 @@ /////////////////////////////////////// //DROPDOWN BUILDERS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" const dropdowns = opendiscord.builders.dropdowns -export const registerAllDropdowns = async () => { +export async function registerAllDropdowns(){ panelDropdowns() } diff --git a/src/builders/embeds.ts b/src/builders/embeds.ts index 5d7cc2c..c8e7f7e 100644 --- a/src/builders/embeds.ts +++ b/src/builders/embeds.ts @@ -1,7 +1,7 @@ /////////////////////////////////////// //EMBED BUILDERS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" import nodepath from "path" @@ -9,7 +9,7 @@ const embeds = opendiscord.builders.embeds const lang = opendiscord.languages const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerAllEmbeds = async () => { +export async function registerAllEmbeds(){ errorEmbeds() helpMenuEmbeds() statsEmbeds() @@ -23,12 +23,12 @@ export const registerAllEmbeds = async () => { extraEmbeds() } -/**Utility function to get the translated "method" from the source. Mostly used in error embeds. */ -const getMethodFromSource = (source:"slash"|"text"|"button"|"dropdown"|"modal"|"other"): string => { - if (source == "slash" || source == "text") return lang.getTranslation("params.lowercase.command") - else if (source == "button") return lang.getTranslation("params.lowercase.button") - else if (source == "dropdown") return lang.getTranslation("params.lowercase.dropdown") - else if (source == "modal") return lang.getTranslation("params.lowercase.modal") +/**Utility function to get the translated "method" from the origin. Mostly used in error embeds. */ +const getMethodFromOrigin = (origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other"): string => { + if (origin == "slash" || origin == "text") return lang.getTranslation("params.lowercase.command") + else if (origin == "button") return lang.getTranslation("params.lowercase.button") + else if (origin == "dropdown") return lang.getTranslation("params.lowercase.dropdown") + else if (origin == "modal") return lang.getTranslation("params.lowercase.modal") else return lang.getTranslation("params.lowercase.method") } //lang.getTranslation() @@ -37,12 +37,12 @@ const errorEmbeds = () => { //ERROR embeds.add(new api.ODEmbed("opendiscord:error")) embeds.get("opendiscord:error").workers.add( - new api.ODWorker("opendiscord:error",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error",0,async (instance,params,origin) => { const {user,error,layout,customTitle} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",customTitle ?? lang.getTranslation("errors.titles.internalError"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.internalError",[method]) + (layout == "simple") ? "\n"+error : "") @@ -75,7 +75,7 @@ const errorEmbeds = () => { }) const commandSyntax = "**"+error.prefix+error.name+" "+optionSyntax.join(" ")+"**" - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.optionMissing"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.optionMissing")) @@ -130,7 +130,7 @@ const errorEmbeds = () => { else if (error.reason == "channel_type" && error.option.type == "channel") reasonValue = lang.getTranslation("errors.optionInvalidReasons.channelType") else if (error.reason == "not_in_guild") reasonValue = lang.getTranslation("errors.optionInvalidReasons.notInGuild") - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.optionInvalid"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.optionInvalid")+"\n"+reasonTitle+": `"+reasonValue+"`") @@ -144,7 +144,7 @@ const errorEmbeds = () => { new api.ODWorker("opendiscord:error-unknown-command",0,async (instance,params) => { const {user} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.unknownCommand"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.unknownCommand")) @@ -154,10 +154,10 @@ const errorEmbeds = () => { //ERROR NO PERMISSIONS embeds.add(new api.ODEmbed("opendiscord:error-no-permissions")) embeds.get("opendiscord:error-no-permissions").workers.add( - new api.ODWorker("opendiscord:error-no-permissions",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions",0,async (instance,params,origin) => { const {user,permissions} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) const renderedPerms = permissions.map((perm) => { if (perm == "developer") return "- "+lang.getTranslation("errors.permissions.developer") @@ -169,7 +169,7 @@ const errorEmbeds = () => { else if (perm == "discord-administrator") return "- "+lang.getTranslation("errors.permissions.discord-administrator") }).join("\n") - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.noPermissions"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.noPermissions",[method])) @@ -180,12 +180,12 @@ const errorEmbeds = () => { //ERROR NO PERMISSIONS COOLDOWN embeds.add(new api.ODEmbed("opendiscord:error-no-permissions-cooldown")) embeds.get("opendiscord:error-no-permissions-cooldown").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-cooldown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-cooldown",0,async (instance,params,origin) => { const {user} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.noPermissions"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.noPermissionsCooldown",[method])) @@ -196,12 +196,12 @@ const errorEmbeds = () => { //ERROR NO PERMISSIONS BLACKLISTED embeds.add(new api.ODEmbed("opendiscord:error-no-permissions-blacklisted")) embeds.get("opendiscord:error-no-permissions-blacklisted").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-blacklisted",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-blacklisted",0,async (instance,params,origin) => { const {user} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.noPermissions"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.noPermissionsBlacklist",[method])) @@ -211,10 +211,10 @@ const errorEmbeds = () => { //ERROR NO PERMISSIONS LIMITS embeds.add(new api.ODEmbed("opendiscord:error-no-permissions-limits")) embeds.get("opendiscord:error-no-permissions-limits").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-limits",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-limits",0,async (instance,params,origin) => { const {user,limit} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.noPermissions"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) if (limit == "global") instance.setDescription(lang.getTranslation("errors.descriptions.noPermissionsLimitGlobal")) @@ -227,12 +227,12 @@ const errorEmbeds = () => { //ERROR RESPONDER TIMEOUT embeds.add(new api.ODEmbed("opendiscord:error-responder-timeout")) embeds.get("opendiscord:error-responder-timeout").workers.add( - new api.ODWorker("opendiscord:error-responder-timeout",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-responder-timeout",0,async (instance,params,origin) => { const {user} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.internalError"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.internalError",[method])) @@ -244,10 +244,10 @@ const errorEmbeds = () => { //ERROR TICKET UNKNOWN embeds.add(new api.ODEmbed("opendiscord:error-ticket-unknown")) embeds.get("opendiscord:error-ticket-unknown").workers.add( - new api.ODWorker("opendiscord:error-ticket-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-unknown",0,async (instance,params,origin) => { const {user} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.unknownTicket"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.unknownTicket")) @@ -258,10 +258,10 @@ const errorEmbeds = () => { //ERROR TICKET DEPRECATED embeds.add(new api.ODEmbed("opendiscord:error-ticket-deprecated")) embeds.get("opendiscord:error-ticket-deprecated").workers.add( - new api.ODWorker("opendiscord:error-ticket-deprecated",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-deprecated",0,async (instance,params,origin) => { const {user} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.deprecatedTicket"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.deprecatedTicket")) @@ -272,7 +272,7 @@ const errorEmbeds = () => { //ERROR OPTION UNKNOWN embeds.add(new api.ODEmbed("opendiscord:error-option-unknown")) embeds.get("opendiscord:error-option-unknown").workers.add( - new api.ODWorker("opendiscord:error-option-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-option-unknown",0,async (instance,params,origin) => { const {user} = params const renderedTicketOptions = opendiscord.options.getAll().map((option) => { @@ -281,7 +281,7 @@ const errorEmbeds = () => { }else return "- `"+option.id.value+"`" }).join("\n") - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.unknownOption"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setFooter(lang.getTranslation("errors.descriptions.askForInfo")) @@ -292,7 +292,7 @@ const errorEmbeds = () => { //ERROR PANEL UNKNOWN embeds.add(new api.ODEmbed("opendiscord:error-panel-unknown")) embeds.get("opendiscord:error-panel-unknown").workers.add( - new api.ODWorker("opendiscord:error-panel-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-panel-unknown",0,async (instance,params,origin) => { const {user} = params const renderedPanels = opendiscord.panels.getAll().map((panel) => { @@ -301,7 +301,7 @@ const errorEmbeds = () => { }else return "- `"+panel.id.value+"`" }).join("\n") - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.unknownPanel"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.addFields({name:lang.getTranslation("params.uppercase.validPanels")+":",value:renderedPanels}) @@ -312,12 +312,12 @@ const errorEmbeds = () => { //ERROR NOT IN GUILD embeds.add(new api.ODEmbed("opendiscord:error-not-in-guild")) embeds.get("opendiscord:error-not-in-guild").workers.add( - new api.ODWorker("opendiscord:error-not-in-guild",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-not-in-guild",0,async (instance,params,origin) => { const {user} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.notInGuild"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.notInGuild",[method])) @@ -327,19 +327,43 @@ const errorEmbeds = () => { //ERROR CHANNEL RENAME embeds.add(new api.ODEmbed("opendiscord:error-channel-rename")) embeds.get("opendiscord:error-channel-rename").workers.add( - new api.ODWorker("opendiscord:error-channel-rename",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-channel-rename",0,async (instance,params,origin) => { const {channel,user,originalName,newName} = params - const method = (source == "ticket-move" || source == "ticket-pin" || source == "ticket-rename" || source == "ticket-unpin" || source == "ticket-priority" || source == "ticket-transfer") ? source : getMethodFromSource(source) + const method = (origin == "ticket-move" || origin == "ticket-pin" || origin == "ticket-rename" || origin == "ticket-unpin" || origin == "ticket-priority" || origin == "ticket-transfer") ? origin : getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.channelRename"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslation("errors.descriptions.channelRename")) instance.setFooter(lang.getTranslationWithParams("errors.descriptions.channelRenameSource",[method])) instance.addFields( - {name:lang.getTranslation("params.uppercase.originalName")+":",value:"```#"+originalName+"```",inline:false}, - {name:lang.getTranslation("params.uppercase.newName")+":",value:"```#"+newName+"```",inline:false} + {name:lang.getTranslation("params.uppercase.originalName")+":",value:"```#"+originalName+"```",inline:true}, + {name:lang.getTranslation("params.uppercase.newName")+":",value:"```#"+newName+"```",inline:true} + ) + }) + ) + + //ERROR CHANNEL CATEGORY + embeds.add(new api.ODEmbed("opendiscord:error-channel-category")) + embeds.get("opendiscord:error-channel-category").workers.add( + new api.ODWorker("opendiscord:error-channel-category",0,async (instance,params,origin) => { + const {channel,user,originalCategory,newCategory} = params + + const method = (origin == "ticket-create" || origin == "ticket-close" || origin == "ticket-reopen" || origin == "ticket-claim" || origin == "ticket-unclaim" || origin == "ticket-move") ? origin : getMethodFromOrigin(origin) + + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + //TODO TRANSLATION!!! + instance.setTitle(utilities.emojiTitle("โŒ","Unable To Change Category")) + instance.setAuthor(user.displayName,user.displayAvatarURL()) + //TODO TRANSLATION!!! + instance.setDescription("Due to Discord rate limits, the channel category could not be changed immediately. It will be changed automatically within 10 minutes if the bot remains online.") + instance.setFooter(lang.getTranslationWithParams("errors.descriptions.channelRenameSource",[method])) + instance.addFields( + //TODO TRANSLATION!!! + {name:"Original Category"+":",value:"```"+originalCategory+"```",inline:true}, + //TODO TRANSLATION!!! + {name:"New Category"+":",value:"```"+newCategory+"```",inline:true} ) }) ) @@ -347,12 +371,12 @@ const errorEmbeds = () => { //ERROR TICKET BUSY embeds.add(new api.ODEmbed("opendiscord:error-ticket-busy")) embeds.get("opendiscord:error-ticket-busy").workers.add( - new api.ODWorker("opendiscord:error-ticket-busy",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-busy",0,async (instance,params,origin) => { const {user} = params - const method = getMethodFromSource(source) + const method = getMethodFromOrigin(origin) - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.busy"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("errors.descriptions.busy",[method])) @@ -387,7 +411,7 @@ const statsEmbeds = () => { new api.ODWorker("opendiscord:stats-global",0,async (instance,params) => { const {guild,channel,user} = params - const scope = opendiscord.stats.get("opendiscord:global") + const scope = opendiscord.statistics.get("opendiscord:global") if (!scope) return const data = await scope.render("GLOBAL",guild,channel,user) @@ -397,7 +421,7 @@ const statsEmbeds = () => { if (opendiscord.permissions.hasPermissions("owner",await opendiscord.permissions.getPermissions(user,channel,guild))){ //show system data when owner or developer - const systemScope = opendiscord.stats.get("opendiscord:system") + const systemScope = opendiscord.statistics.get("opendiscord:system") if (!systemScope) return const systemData = await systemScope.render("GLOBAL",guild,channel,user) instance.addFields({name:systemScope.name,value:systemData,inline:false}) @@ -411,9 +435,9 @@ const statsEmbeds = () => { new api.ODWorker("opendiscord:stats-ticket",0,async (instance,params) => { const {guild,channel,user,scopeData} = params - const scope = opendiscord.stats.get("opendiscord:ticket") - const participantsScope = opendiscord.stats.get("opendiscord:participants") - const messagesScope = opendiscord.stats.get("opendiscord:messages") + const scope = opendiscord.statistics.get("opendiscord:ticket") + const participantsScope = opendiscord.statistics.get("opendiscord:participants") + const messagesScope = opendiscord.statistics.get("opendiscord:messages") if (!scope || !participantsScope || !messagesScope) return const data = await scope.render(scopeData.id.value,guild,channel,user) const participantsData = await participantsScope.render(scopeData.id.value,guild,channel,user) @@ -433,7 +457,7 @@ const statsEmbeds = () => { new api.ODWorker("opendiscord:stats-user",0,async (instance,params) => { const {guild,channel,user,scopeData} = params - const scope = opendiscord.stats.get("opendiscord:user") + const scope = opendiscord.statistics.get("opendiscord:user") if (!scope) return const data = await scope.render(scopeData.id,guild,channel,user) @@ -477,7 +501,7 @@ const statsEmbeds = () => { new api.ODWorker("opendiscord:stats-ticket-unknown",0,async (instance,params) => { const {user,id} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("errors.titles.unknownTicket"))) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.statsError",[discord.channelMention(id)])) @@ -510,8 +534,8 @@ const panelEmbeds = () => { instance.setDescription(embedOptions.description) } - if (panel.get("opendiscord:enable-max-tickets-warning-embed").value && generalConfig.data.system.limits.enabled){ - instance.setDescription(instance.data.description+"\n\n*"+lang.getTranslationWithParams("actions.descriptions.ticketMessageLimit",[generalConfig.data.system.limits.userMaximum.toString()])+"*") + if (panel.get("opendiscord:enable-max-tickets-warning-embed").value && generalConfig.data.ticketSystem.limits.enabled){ + instance.setDescription(instance.data.description+"\n\n*"+lang.getTranslationWithParams("actions.descriptions.ticketMessageLimit",[generalConfig.data.ticketSystem.limits.userMaximum.toString()])+"*") } if (panel.get("opendiscord:describe-options-in-embed-fields").value){ @@ -529,7 +553,7 @@ const ticketEmbeds = () => { //TICKET CREATED embeds.add(new api.ODEmbed("opendiscord:ticket-created")) embeds.get("opendiscord:ticket-created").workers.add( - new api.ODWorker("opendiscord:ticket-created",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created",0,async (instance,params,origin) => { const {user} = params instance.setColor(generalConfig.data.mainColor) @@ -543,7 +567,7 @@ const ticketEmbeds = () => { //TICKET CREATED DM embeds.add(new api.ODEmbed("opendiscord:ticket-created-dm")) embeds.get("opendiscord:ticket-created-dm").workers.add( - new api.ODWorker("opendiscord:ticket-created-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created-dm",0,async (instance,params,origin) => { const {user,ticket} = params const embedOptions = ticket.option.get("opendiscord:dm-message-embed").value @@ -563,10 +587,10 @@ const ticketEmbeds = () => { //TICKET CREATED LOGS embeds.add(new api.ODEmbed("opendiscord:ticket-created-logs")) embeds.get("opendiscord:ticket-created-logs").workers.add( - new api.ODWorker("opendiscord:ticket-created-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created-logs",0,async (instance,params,origin) => { const {user,ticket} = params - const method = (source == "panel-button" || source == "panel-dropdown") ? lang.getTranslation("params.uppercase.panel") : (source == "slash" || source == "text") ? lang.getTranslation("params.uppercase.command") : lang.getTranslation("params.uppercase.system") + const method = (origin == "panel-button" || origin == "panel-dropdown") ? lang.getTranslation("params.uppercase.panel") : (origin == "slash" || origin == "text") ? lang.getTranslation("params.uppercase.command") : lang.getTranslation("params.uppercase.system") const blacklisted = opendiscord.blacklist.exists(user.id) ? lang.getTranslation("params.uppercase.true") : lang.getTranslation("params.uppercase.false") instance.setColor(generalConfig.data.mainColor) @@ -586,7 +610,7 @@ const ticketEmbeds = () => { //TICKET MESSAGE embeds.add(new api.ODEmbed("opendiscord:ticket-message")) embeds.get("opendiscord:ticket-message").workers.add( - new api.ODWorker("opendiscord:ticket-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-message",0,async (instance,params,origin) => { const {user,ticket} = params const embedOptions = ticket.option.get("opendiscord:ticket-message-embed").value @@ -599,12 +623,12 @@ const ticketEmbeds = () => { if (ticket.option.get("opendiscord:questions").value.length > 0){ //show config fields if mixing is allowed - if (generalConfig.data.system.displayFieldsWithQuestions) instance.addFields(...embedOptions.fields) + if (generalConfig.data.ticketSystem.displayFieldsWithQuestions) instance.addFields(...embedOptions.fields) const answers = ticket.get("opendiscord:answers").value answers.forEach((answer) => { if (!answer.value || answer.value.length == 0) return - if (generalConfig.data.system.questionFieldsInCodeBlock) instance.addFields({name:answer.name,value:"```"+answer.value+"```",inline:false}) + if (generalConfig.data.ticketSystem.questionFieldsInCodeBlock) instance.addFields({name:answer.name,value:"```"+answer.value.slice(0,1024-6)+"```",inline:false}) else instance.addFields({name:answer.name,value:answer.value,inline:false}) }) }else if (embedOptions.fields){ @@ -625,161 +649,161 @@ const ticketEmbeds = () => { //TICKET CLOSED embeds.add(new api.ODEmbed("opendiscord:close-message")) embeds.get("opendiscord:close-message").workers.add( - new api.ODWorker("opendiscord:close-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:close-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿ”’",lang.getTranslation("actions.titles.close"))) instance.setDescription(lang.getTranslation("actions.descriptions.close")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET REOPENED embeds.add(new api.ODEmbed("opendiscord:reopen-message")) embeds.get("opendiscord:reopen-message").workers.add( - new api.ODWorker("opendiscord:reopen-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reopen-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿ”“",lang.getTranslation("actions.titles.reopen"))) instance.setDescription(lang.getTranslation("actions.descriptions.reopen")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET DELETED embeds.add(new api.ODEmbed("opendiscord:delete-message")) embeds.get("opendiscord:delete-message").workers.add( - new api.ODWorker("opendiscord:delete-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:delete-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿ—‘๏ธ",lang.getTranslation("actions.titles.delete"))) instance.setDescription(lang.getTranslation("actions.descriptions.delete")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET CLAIMED embeds.add(new api.ODEmbed("opendiscord:claim-message")) embeds.get("opendiscord:claim-message").workers.add( - new api.ODWorker("opendiscord:claim-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:claim-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿ‘‹",lang.getTranslation("actions.titles.claim"))) instance.setDescription(lang.getTranslation("actions.descriptions.claim")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET UNCLAIMED embeds.add(new api.ODEmbed("opendiscord:unclaim-message")) embeds.get("opendiscord:unclaim-message").workers.add( - new api.ODWorker("opendiscord:unclaim-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unclaim-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โ†ฉ๏ธ",lang.getTranslation("actions.titles.unclaim"))) instance.setDescription(lang.getTranslation("actions.descriptions.unclaim")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET PINNED embeds.add(new api.ODEmbed("opendiscord:pin-message")) embeds.get("opendiscord:pin-message").workers.add( - new api.ODWorker("opendiscord:pin-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:pin-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.pin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.pin"))) instance.setDescription(lang.getTranslation("actions.descriptions.pin")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET UNPINNED embeds.add(new api.ODEmbed("opendiscord:unpin-message")) embeds.get("opendiscord:unpin-message").workers.add( - new api.ODWorker("opendiscord:unpin-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unpin-message",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.unpin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.unpin"))) instance.setDescription(lang.getTranslation("actions.descriptions.unpin")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET RENAMED embeds.add(new api.ODEmbed("opendiscord:rename-message")) embeds.get("opendiscord:rename-message").workers.add( - new api.ODWorker("opendiscord:rename-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:rename-message",0,async (instance,params,origin) => { const {user,ticket,reason,data} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿ”„",lang.getTranslation("actions.titles.rename"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.rename",["`#"+data+"`"])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET MOVED embeds.add(new api.ODEmbed("opendiscord:move-message")) embeds.get("opendiscord:move-message").workers.add( - new api.ODWorker("opendiscord:move-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:move-message",0,async (instance,params,origin) => { const {user,ticket,reason,data} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿ”€",lang.getTranslation("actions.titles.move"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.move",["`"+data.get("opendiscord:name").value+"`"])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET USER ADDED embeds.add(new api.ODEmbed("opendiscord:add-message")) embeds.get("opendiscord:add-message").workers.add( - new api.ODWorker("opendiscord:add-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:add-message",0,async (instance,params,origin) => { const {user,ticket,reason,data} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿ‘ค",lang.getTranslation("actions.titles.add"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.add",[discord.userMention(data.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET USER REMOVED embeds.add(new api.ODEmbed("opendiscord:remove-message")) embeds.get("opendiscord:remove-message").workers.add( - new api.ODWorker("opendiscord:remove-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:remove-message",0,async (instance,params,origin) => { const {user,ticket,reason,data} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿ‘ค",lang.getTranslation("actions.titles.remove"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.remove",[discord.userMention(data.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //TICKET ACTION DM embeds.add(new api.ODEmbed("opendiscord:ticket-action-dm")) embeds.get("opendiscord:ticket-action-dm").workers.add( - new api.ODWorker("opendiscord:ticket-action-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-action-dm",0,async (instance,params,origin) => { const {user,mode,ticket,reason,additionalData} = params const channel = await opendiscord.tickets.getTicketChannel(ticket) @@ -804,10 +828,10 @@ const ticketEmbeds = () => { instance.setTitle(utilities.emojiTitle("โ†ฉ๏ธ",lang.getTranslation("actions.titles.unclaim"))) instance.setDescription(lang.getTranslation("actions.logs.unclaimDm")) }else if (mode == "pin"){ - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.pin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.pin"))) instance.setDescription(lang.getTranslation("actions.logs.pinDm")) }else if (mode == "unpin"){ - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.unpin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.unpin"))) instance.setDescription(lang.getTranslation("actions.logs.unpinDm")) }else if (mode == "rename"){ instance.setTitle(utilities.emojiTitle("๐Ÿ”„",lang.getTranslation("actions.titles.rename"))) @@ -828,7 +852,7 @@ const ticketEmbeds = () => { //TICKET ACTION LOGS embeds.add(new api.ODEmbed("opendiscord:ticket-action-logs")) embeds.get("opendiscord:ticket-action-logs").workers.add( - new api.ODWorker("opendiscord:ticket-action-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-action-logs",0,async (instance,params,origin) => { const {user,mode,ticket,reason,additionalData} = params const channel = await opendiscord.tickets.getTicketChannel(ticket) @@ -840,7 +864,7 @@ const ticketEmbeds = () => { {name:lang.getTranslation("params.uppercase.ticket")+":",value:"```#"+(channel ? channel.name : "")+"```",inline:false}, {name:lang.getTranslation("params.uppercase.option")+":",value:"```"+(ticket.option.get("opendiscord:name").value)+"```",inline:false}, ) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```",inline:false}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```",inline:false}) if (mode == "close"){ instance.setTitle(utilities.emojiTitle("๐Ÿ”’",lang.getTranslation("actions.titles.close"))) @@ -858,10 +882,10 @@ const ticketEmbeds = () => { instance.setTitle(utilities.emojiTitle("โ†ฉ๏ธ",lang.getTranslation("actions.titles.unclaim"))) instance.setDescription(lang.getTranslationWithParams("actions.logs.unclaimLog",[discord.userMention(user.id)])) }else if (mode == "pin"){ - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.pin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.pin"))) instance.setDescription(lang.getTranslationWithParams("actions.logs.pinLog",[discord.userMention(user.id)])) }else if (mode == "unpin"){ - instance.setTitle(utilities.emojiTitle(generalConfig.data.system.pinEmoji,lang.getTranslation("actions.titles.unpin"))) + instance.setTitle(utilities.emojiTitle(generalConfig.data.ticketSystem.pinEmoji,lang.getTranslation("actions.titles.unpin"))) instance.setDescription(lang.getTranslationWithParams("actions.logs.unpinLog",[discord.userMention(user.id)])) }else if (mode == "rename"){ instance.setTitle(utilities.emojiTitle("๐Ÿ”„",lang.getTranslation("actions.titles.rename"))) @@ -884,7 +908,7 @@ const blacklistEmbeds = () => { //BLACKLIST VIEW embeds.add(new api.ODEmbed("opendiscord:blacklist-view")) embeds.get("opendiscord:blacklist-view").workers.add( - new api.ODWorker("opendiscord:blacklist-view",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-view",0,async (instance,params,origin) => { const {user} = params const renderedUsers: string[] = [] @@ -904,7 +928,7 @@ const blacklistEmbeds = () => { //BLACKLIST GET embeds.add(new api.ODEmbed("opendiscord:blacklist-get")) embeds.get("opendiscord:blacklist-get").workers.add( - new api.ODWorker("opendiscord:blacklist-get",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-get",0,async (instance,params,origin) => { const {user,data} = params const blacklist = opendiscord.blacklist.get(data.id) @@ -914,7 +938,7 @@ const blacklistEmbeds = () => { if (blacklist){ instance.setDescription(lang.getTranslationWithParams("actions.descriptions.blacklistGetSuccess",[discord.userMention(data.id)])) - if (blacklist.reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(blacklist.reason ?? "/")+"```"}) + if (blacklist.reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(blacklist.reason ?? "/")+"```"}) }else instance.setDescription("*"+lang.getTranslationWithParams("actions.descriptions.blacklistGetEmpty",[discord.userMention(data.id)])+"*") }) @@ -923,35 +947,35 @@ const blacklistEmbeds = () => { //BLACKLIST ADD embeds.add(new api.ODEmbed("opendiscord:blacklist-add")) embeds.get("opendiscord:blacklist-add").workers.add( - new api.ODWorker("opendiscord:blacklist-add",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-add",0,async (instance,params,origin) => { const {user,data,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿšซ",lang.getTranslation("actions.titles.blacklistAdd"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.blacklistAdd",[discord.userMention(data.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //BLACKLIST REMOVE embeds.add(new api.ODEmbed("opendiscord:blacklist-remove")) embeds.get("opendiscord:blacklist-remove").workers.add( - new api.ODWorker("opendiscord:blacklist-remove",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-remove",0,async (instance,params,origin) => { const {user,data,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿ†“",lang.getTranslation("actions.titles.blacklistRemove"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.blacklistRemove",[discord.userMention(data.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //BLACKLIST DM embeds.add(new api.ODEmbed("opendiscord:blacklist-dm")) embeds.get("opendiscord:blacklist-dm").workers.add( - new api.ODWorker("opendiscord:blacklist-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-dm",0,async (instance,params,origin) => { const {user,mode,data,reason} = params const title = (mode == "add") ? lang.getTranslation("actions.titles.blacklistAddDm") : lang.getTranslation("actions.titles.blacklistRemoveDm") @@ -961,14 +985,14 @@ const blacklistEmbeds = () => { instance.setTitle(utilities.emojiTitle((mode == "add") ? "๐Ÿšซ" : "๐Ÿ†“",title)) instance.setTimestamp(new Date()) instance.setDescription(text) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //BLACKLIST LOGS embeds.add(new api.ODEmbed("opendiscord:blacklist-logs")) embeds.get("opendiscord:blacklist-logs").workers.add( - new api.ODWorker("opendiscord:blacklist-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-logs",0,async (instance,params,origin) => { const {user,mode,data,reason} = params const title = (mode == "add") ? lang.getTranslation("actions.titles.blacklistAdd") : lang.getTranslation("actions.titles.blacklistRemove") @@ -980,7 +1004,7 @@ const blacklistEmbeds = () => { instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setTimestamp(new Date()) instance.setDescription(text) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) } @@ -989,7 +1013,7 @@ const transcriptEmbeds = () => { //TRANSCRIPT TEXT READY embeds.add(new api.ODEmbed("opendiscord:transcript-text-ready")) embeds.get("opendiscord:transcript-text-ready").workers.add( - new api.ODWorker("opendiscord:transcript-text-ready",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-text-ready",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler} = params const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") @@ -1007,11 +1031,11 @@ const transcriptEmbeds = () => { }catch{} } - if (source == "channel") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdChannel",[lang.getTranslation("params.lowercase.text")])) - else if (source == "creator-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdCreator",[lang.getTranslation("params.lowercase.text")])) - else if (source == "participant-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdParticipant",[lang.getTranslation("params.lowercase.text")])) - else if (source == "active-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdActiveAdmin",[lang.getTranslation("params.lowercase.text")])) - else if (source == "every-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdEveryAdmin",[lang.getTranslation("params.lowercase.text")])) + if (origin == "channel") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdChannel",[lang.getTranslation("params.lowercase.text")])) + else if (origin == "creator-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdCreator",[lang.getTranslation("params.lowercase.text")])) + else if (origin == "participant-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdParticipant",[lang.getTranslation("params.lowercase.text")])) + else if (origin == "active-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdActiveAdmin",[lang.getTranslation("params.lowercase.text")])) + else if (origin == "every-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdEveryAdmin",[lang.getTranslation("params.lowercase.text")])) else instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdOther",[lang.getTranslation("params.lowercase.text")])) }) ) @@ -1019,7 +1043,7 @@ const transcriptEmbeds = () => { //TRANSCRIPT HTML READY embeds.add(new api.ODEmbed("opendiscord:transcript-html-ready")) embeds.get("opendiscord:transcript-html-ready").workers.add( - new api.ODWorker("opendiscord:transcript-html-ready",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-html-ready",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,result} = params const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") @@ -1038,11 +1062,11 @@ const transcriptEmbeds = () => { }catch{} } - if (source == "channel") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdChannel",[lang.getTranslation("params.lowercase.html")])) - else if (source == "creator-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdCreator",[lang.getTranslation("params.lowercase.html")])) - else if (source == "participant-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdParticipant",[lang.getTranslation("params.lowercase.html")])) - else if (source == "active-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdActiveAdmin",[lang.getTranslation("params.lowercase.html")])) - else if (source == "every-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdEveryAdmin",[lang.getTranslation("params.lowercase.html")])) + if (origin == "channel") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdChannel",[lang.getTranslation("params.lowercase.html")])) + else if (origin == "creator-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdCreator",[lang.getTranslation("params.lowercase.html")])) + else if (origin == "participant-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdParticipant",[lang.getTranslation("params.lowercase.html")])) + else if (origin == "active-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdActiveAdmin",[lang.getTranslation("params.lowercase.html")])) + else if (origin == "every-admin-dm") instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdEveryAdmin",[lang.getTranslation("params.lowercase.html")])) else instance.setDescription(lang.getTranslationWithParams("transcripts.success.createdOther",[lang.getTranslation("params.lowercase.html")])) }) ) @@ -1050,7 +1074,7 @@ const transcriptEmbeds = () => { //TRANSCRIPT HTML PROGRESS embeds.add(new api.ODEmbed("opendiscord:transcript-html-progress")) embeds.get("opendiscord:transcript-html-progress").workers.add( - new api.ODWorker("opendiscord:transcript-html-progress",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-html-progress",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,remaining} = params const remainingDate = new Date(new Date().getTime()+remaining) @@ -1076,15 +1100,15 @@ const transcriptEmbeds = () => { //TRANSCRIPT ERROR embeds.add(new api.ODEmbed("opendiscord:transcript-error")) embeds.get("opendiscord:transcript-error").workers.add( - new api.ODWorker("opendiscord:transcript-error",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-error",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,reason} = params - instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) + instance.setColor(generalConfig.data.ticketSystem.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โŒ",lang.getTranslation("transcripts.errors.title"))) instance.setTimestamp(new Date()) instance.setDescription(lang.getTranslation("transcripts.errors.error")) instance.setFooter(lang.getTranslation("errors.descriptions.askForInfo")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) } @@ -1093,7 +1117,7 @@ const roleEmbeds = () => { //REACTION ROLE embeds.add(new api.ODEmbed("opendiscord:reaction-role")) embeds.get("opendiscord:reaction-role").workers.add( - new api.ODWorker("opendiscord:reaction-role",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role",0,async (instance,params,origin) => { const {guild,user,role,result} = params instance.setColor(generalConfig.data.mainColor) @@ -1117,7 +1141,7 @@ const roleEmbeds = () => { //REACTION ROLE DM embeds.add(new api.ODEmbed("opendiscord:reaction-role-dm")) embeds.get("opendiscord:reaction-role-dm").workers.add( - new api.ODWorker("opendiscord:reaction-role-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role-dm",0,async (instance,params,origin) => { const {guild,user,role,result} = params instance.setColor(generalConfig.data.mainColor) @@ -1143,7 +1167,7 @@ const roleEmbeds = () => { //REACTION ROLE LOGS embeds.add(new api.ODEmbed("opendiscord:reaction-role-logs")) embeds.get("opendiscord:reaction-role-logs").workers.add( - new api.ODWorker("opendiscord:reaction-role-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role-logs",0,async (instance,params,origin) => { const {guild,user,role,result} = params instance.setColor(generalConfig.data.mainColor) @@ -1171,7 +1195,7 @@ const clearEmbeds = () => { //CLEAR VERIFY MESSAGE embeds.add(new api.ODEmbed("opendiscord:clear-verify-message")) embeds.get("opendiscord:clear-verify-message").workers.add( - new api.ODWorker("opendiscord:clear-verify-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-verify-message",0,async (instance,params,origin) => { const {guild,channel,user,filter,list} = params instance.setColor(generalConfig.data.mainColor) @@ -1189,7 +1213,7 @@ const clearEmbeds = () => { //CLEAR MESSAGE embeds.add(new api.ODEmbed("opendiscord:clear-message")) embeds.get("opendiscord:clear-message").workers.add( - new api.ODWorker("opendiscord:clear-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-message",0,async (instance,params,origin) => { const {guild,channel,user,filter,list} = params instance.setColor(generalConfig.data.mainColor) @@ -1206,7 +1230,7 @@ const clearEmbeds = () => { //CLEAR LOGS embeds.add(new api.ODEmbed("opendiscord:clear-logs")) embeds.get("opendiscord:clear-logs").workers.add( - new api.ODWorker("opendiscord:clear-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-logs",0,async (instance,params,origin) => { const {guild,channel,user,filter,list} = params instance.setColor(generalConfig.data.mainColor) @@ -1226,10 +1250,10 @@ const autoEmbeds = () => { //AUTOCLOSE MESSAGE embeds.add(new api.ODEmbed("opendiscord:autoclose-message")) embeds.get("opendiscord:autoclose-message").workers.add( - new api.ODWorker("opendiscord:autoclose-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-message",0,async (instance,params,origin) => { const {user,ticket} = params const hours: number = ticket.get("opendiscord:autoclose-hours").value - const description = (source == "leave") ? lang.getTranslation("actions.descriptions.autocloseLeave") : lang.getTranslationWithParams("actions.descriptions.autocloseTimeout",[hours.toString()]) + const description = (origin == "leave") ? lang.getTranslation("actions.descriptions.autocloseLeave") : lang.getTranslationWithParams("actions.descriptions.autocloseTimeout",[hours.toString()]) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) @@ -1242,10 +1266,10 @@ const autoEmbeds = () => { //AUTODELETE MESSAGE embeds.add(new api.ODEmbed("opendiscord:autodelete-message")) embeds.get("opendiscord:autodelete-message").workers.add( - new api.ODWorker("opendiscord:autodelete-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-message",0,async (instance,params,origin) => { const {user,ticket} = params const days: number = ticket.get("opendiscord:autodelete-days").value - const description = (source == "leave") ? lang.getTranslation("actions.descriptions.autodeleteLeave") : lang.getTranslationWithParams("actions.descriptions.autodeleteTimeout",[days.toString()]) + const description = (origin == "leave") ? lang.getTranslation("actions.descriptions.autodeleteLeave") : lang.getTranslationWithParams("actions.descriptions.autodeleteTimeout",[days.toString()]) instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) @@ -1258,56 +1282,56 @@ const autoEmbeds = () => { //AUTOCLOSE ENABLE embeds.add(new api.ODEmbed("opendiscord:autoclose-enable")) embeds.get("opendiscord:autoclose-enable").workers.add( - new api.ODWorker("opendiscord:autoclose-enable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-enable",0,async (instance,params,origin) => { const {user,ticket,reason,time} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โฑ๏ธ",lang.getTranslation("actions.titles.autocloseEnabled"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.autocloseEnabled",[time.toString()])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //AUTODELETE ENABLE embeds.add(new api.ODEmbed("opendiscord:autodelete-enable")) embeds.get("opendiscord:autodelete-enable").workers.add( - new api.ODWorker("opendiscord:autodelete-enable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-enable",0,async (instance,params,origin) => { const {user,ticket,reason,time} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โฑ๏ธ",lang.getTranslation("actions.titles.autodeleteEnabled"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.autodeleteEnabled",[time.toString()])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //AUTOCLOSE DISABLE embeds.add(new api.ODEmbed("opendiscord:autoclose-disable")) embeds.get("opendiscord:autoclose-disable").workers.add( - new api.ODWorker("opendiscord:autoclose-disable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-disable",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โฑ๏ธ",lang.getTranslation("actions.titles.autocloseDisabled"))) instance.setDescription(lang.getTranslation("actions.descriptions.autocloseDisabled")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //AUTODELETE DISABLE embeds.add(new api.ODEmbed("opendiscord:autodelete-disable")) embeds.get("opendiscord:autodelete-disable").workers.add( - new api.ODWorker("opendiscord:autodelete-disable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-disable",0,async (instance,params,origin) => { const {user,ticket,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("โฑ๏ธ",lang.getTranslation("actions.titles.autodeleteDisabled"))) instance.setDescription(lang.getTranslation("actions.descriptions.autodeleteDisabled")) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) } @@ -1316,7 +1340,7 @@ const extraEmbeds = () => { //TOPIC SET embeds.add(new api.ODEmbed("opendiscord:topic-set")) embeds.get("opendiscord:topic-set").workers.add( - new api.ODWorker("opendiscord:topic-set",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:topic-set",0,async (instance,params,origin) => { const {user,topic} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) @@ -1330,21 +1354,21 @@ const extraEmbeds = () => { //PRIORITY SET embeds.add(new api.ODEmbed("opendiscord:priority-set")) embeds.get("opendiscord:priority-set").workers.add( - new api.ODWorker("opendiscord:priority-set",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:priority-set",0,async (instance,params,origin) => { const {user,priority,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿšจ",lang.getTranslation("actions.titles.prioritySet"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.prioritySet",["**"+priority.renderDisplayName()+"**",discord.userMention(user.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) //PRIORITY GET embeds.add(new api.ODEmbed("opendiscord:priority-get")) embeds.get("opendiscord:priority-get").workers.add( - new api.ODWorker("opendiscord:priority-get",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:priority-get",0,async (instance,params,origin) => { const {user,priority} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) @@ -1357,14 +1381,14 @@ const extraEmbeds = () => { //TRANSFER MESSAGE embeds.add(new api.ODEmbed("opendiscord:transfer-message")) embeds.get("opendiscord:transfer-message").workers.add( - new api.ODWorker("opendiscord:transfer-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transfer-message",0,async (instance,params,origin) => { const {user,oldCreator,newCreator,reason} = params instance.setAuthor(user.displayName,user.displayAvatarURL()) instance.setColor(generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("๐Ÿ”€",lang.getTranslation("actions.titles.transfer"))) instance.setDescription(lang.getTranslationWithParams("actions.descriptions.transfer",[discord.userMention(oldCreator.id),discord.userMention(newCreator.id),discord.userMention(user.id)])) - if (reason || generalConfig.data.system.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) + if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) } \ No newline at end of file diff --git a/src/builders/files.ts b/src/builders/files.ts index 9a30548..252a719 100644 --- a/src/builders/files.ts +++ b/src/builders/files.ts @@ -1,14 +1,14 @@ /////////////////////////////////////// //FILE BUILDERS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" const files = opendiscord.builders.files const lang = opendiscord.languages const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") -export const registerAllFiles = async () => { +export async function registerAllFiles(){ transcriptFiles() } @@ -17,7 +17,7 @@ const transcriptFiles = () => { //TEXT TRANSCRIPT files.add(new api.ODFile("opendiscord:text-transcript")) files.get("opendiscord:text-transcript").workers.add( - new api.ODWorker("opendiscord:text-transcript",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:text-transcript",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,result} = params const fileMode = transcriptConfig.data.textTranscriptStyle.fileMode diff --git a/src/builders/messages.ts b/src/builders/messages.ts index 0b89c1b..7b49b23 100644 --- a/src/builders/messages.ts +++ b/src/builders/messages.ts @@ -1,7 +1,7 @@ /////////////////////////////////////// //MESSAGE BUILDERS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" const messages = opendiscord.builders.messages @@ -12,8 +12,7 @@ const embeds = opendiscord.builders.embeds const lang = opendiscord.languages const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerAllMessages = async () => { - verifyBarMessages() +export async function registerAllMessages(){ errorMessages() helpMenuMessages() statsMessages() @@ -27,314 +26,13 @@ export const registerAllMessages = async () => { extraMessages() } -const verifyBarMessages = () => { - //VERIFYBAR TICKET MESSAGE - messages.add(new api.ODMessage("opendiscord:verifybar-ticket-message")) - messages.get("opendiscord:verifybar-ticket-message").workers.add( - new api.ODWorker("opendiscord:verifybar-ticket-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-ticket-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-ticket-message`") - return - } - const option = ticket.option - - //add pings - const pingOptions = option.get("opendiscord:ticket-message-ping").value - const pings: string[] = [] - const creator = ticket.get("opendiscord:opened-by").value - if (creator) pings.push(discord.userMention(creator)) - if (pingOptions["@everyone"]) pings.push("@everyone") - if (pingOptions["@here"]) pings.push("@here") - pingOptions.custom.forEach((ping) => pings.push(discord.roleMention(ping))) - const pingText = (pings.length > 0) ? pings.join(" ")+"\n" : "" - - //add text - const text = option.get("opendiscord:ticket-message-text").value - if (text !== "") instance.setContent(pingText+text) - else instance.setContent(pingText) - - //add embed - if (option.get("opendiscord:ticket-message-embed").value.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:claim-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:unclaim-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:pin-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:unpin-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:close-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:reopen-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:delete-ticket-ticket-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - if (generalConfig.data.system.enableDeleteWithoutTranscript) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"no-transcript",customEmoji:"๐Ÿ“„",customLabel:lang.getTranslation("actions.buttons.withoutTranscript"),customColor:"red"})) - } - }) - ) - - //TICKET CLOSED - messages.add(new api.ODMessage("opendiscord:verifybar-close-message")) - messages.get("opendiscord:verifybar-close-message").workers.add( - new api.ODWorker("opendiscord:verifybar-close-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-close-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-close-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:close-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:reopen-ticket-close-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:delete-ticket-close-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - if (generalConfig.data.system.enableDeleteWithoutTranscript) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"no-transcript",customEmoji:"๐Ÿ“„",customLabel:lang.getTranslation("actions.buttons.withoutTranscript"),customColor:"red"})) - } - }) - ) - - //TICKET REOPENED - messages.add(new api.ODMessage("opendiscord:verifybar-reopen-message")) - messages.get("opendiscord:verifybar-reopen-message").workers.add( - new api.ODWorker("opendiscord:verifybar-reopen-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-reopen-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-reopen-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:reopen-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:close-ticket-reopen-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:delete-ticket-reopen-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - if (generalConfig.data.system.enableDeleteWithoutTranscript) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"no-transcript",customEmoji:"๐Ÿ“„",customLabel:lang.getTranslation("actions.buttons.withoutTranscript"),customColor:"red"})) - } - }) - ) - - //TICKET CLAIM - messages.add(new api.ODMessage("opendiscord:verifybar-claim-message")) - messages.get("opendiscord:verifybar-claim-message").workers.add( - new api.ODWorker("opendiscord:verifybar-claim-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-claim-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-claim-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:claim-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:unclaim-ticket-claim-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - } - }) - ) - - //TICKET UNCLAIM - messages.add(new api.ODMessage("opendiscord:verifybar-unclaim-message")) - messages.get("opendiscord:verifybar-unclaim-message").workers.add( - new api.ODWorker("opendiscord:verifybar-unclaim-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-unclaim-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-unclaim-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:unclaim-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:claim-ticket-unclaim-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - } - }) - ) - - //TICKET PIN - messages.add(new api.ODMessage("opendiscord:verifybar-pin-message")) - messages.get("opendiscord:verifybar-pin-message").workers.add( - new api.ODWorker("opendiscord:verifybar-pin-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-pin-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-pin-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:pin-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:unpin-ticket-pin-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - } - }) - ) - - //TICKET UNPIN - messages.add(new api.ODMessage("opendiscord:verifybar-unpin-message")) - messages.get("opendiscord:verifybar-unpin-message").workers.add( - new api.ODWorker("opendiscord:verifybar-unpin-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-unpin-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-unpin-message`") - return - } - - const rawReason = (originalMessage.embeds[0] && originalMessage.embeds[0].fields[0]) ? originalMessage.embeds[0].fields[0].value : null - const reason = (rawReason == null) ? null : rawReason.substring(3,rawReason.length-3) - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:unpin-message").build("other",{guild,channel,user,ticket,reason})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:pin-ticket-unpin-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - } - }) - ) - - //TICKET AUTOCLOSED - messages.add(new api.ODMessage("opendiscord:verifybar-autoclose-message")) - messages.get("opendiscord:verifybar-autoclose-message").workers.add( - new api.ODWorker("opendiscord:verifybar-autoclose-message",0,async (instance,params,source) => { - const {guild,channel,user,verifybar,originalMessage} = params - if (!guild || channel.isDMBased()){ - instance.setContent("ODError: Not In Guild => `opendiscord:verifybar-autoclose-message`") - return - } - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket){ - instance.setContent("ODError: Unknown Ticket => `opendiscord:verifybar-autoclose-message`") - return - } - - //add embed - instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-message").build("other",{guild,channel,user,ticket})) - - //add verifybar components - if (verifybar.id.value == "opendiscord:reopen-ticket-autoclose-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - - }else if (verifybar.id.value == "opendiscord:delete-ticket-autoclose-message"){ - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar})) - instance.addComponent(await buttons.getSafe("opendiscord:verifybar-failure").build("verifybar",{guild,channel,user,verifybar})) - if (generalConfig.data.system.enableTicketActionWithReason) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"reason",customEmoji:"โœ๏ธ",customLabel:lang.getTranslation("actions.buttons.withReason"),customColor:"blue"})) - if (generalConfig.data.system.enableDeleteWithoutTranscript) instance.addComponent(await buttons.getSafe("opendiscord:verifybar-success").build("verifybar",{guild,channel,user,verifybar,customData:"no-transcript",customEmoji:"๐Ÿ“„",customLabel:lang.getTranslation("actions.buttons.withoutTranscript"),customColor:"red"})) - } - }) - ) -} - const errorMessages = () => { //ERROR messages.add(new api.ODMessage("opendiscord:error")) messages.get("opendiscord:error").workers.add( - new api.ODWorker("opendiscord:error",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error",0,async (instance,params,origin) => { const {guild,channel,user,error,layout,customTitle} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error").build(source,{guild,channel,user,error,layout,customTitle})) + instance.addEmbed(await embeds.getSafe("opendiscord:error").build(origin,{guild,channel,user,error,layout,customTitle})) instance.setEphemeral(true) }) ) @@ -342,9 +40,9 @@ const errorMessages = () => { //ERROR OPTION MISSING messages.add(new api.ODMessage("opendiscord:error-option-missing")) messages.get("opendiscord:error-option-missing").workers.add( - new api.ODWorker("opendiscord:error-option-missing",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-option-missing",0,async (instance,params,origin) => { const {guild,channel,user,error} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-option-missing").build(source,{guild,channel,user,error})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-option-missing").build(origin,{guild,channel,user,error})) instance.setEphemeral(true) }) ) @@ -352,9 +50,9 @@ const errorMessages = () => { //ERROR OPTION INVALID messages.add(new api.ODMessage("opendiscord:error-option-invalid")) messages.get("opendiscord:error-option-invalid").workers.add( - new api.ODWorker("opendiscord:error-option-invalid",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-option-invalid",0,async (instance,params,origin) => { const {guild,channel,user,error} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-option-invalid").build(source,{guild,channel,user,error})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-option-invalid").build(origin,{guild,channel,user,error})) instance.setEphemeral(true) }) ) @@ -362,9 +60,9 @@ const errorMessages = () => { //ERROR UNKNOWN COMMAND messages.add(new api.ODMessage("opendiscord:error-unknown-command")) messages.get("opendiscord:error-unknown-command").workers.add( - new api.ODWorker("opendiscord:error-unknown-command",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-unknown-command",0,async (instance,params,origin) => { const {guild,channel,user,error} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-unknown-command").build(source,{guild,channel,user,error})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-unknown-command").build(origin,{guild,channel,user,error})) instance.setEphemeral(true) }) ) @@ -372,9 +70,9 @@ const errorMessages = () => { //ERROR NO PERMISSIONS messages.add(new api.ODMessage("opendiscord:error-no-permissions")) messages.get("opendiscord:error-no-permissions").workers.add( - new api.ODWorker("opendiscord:error-no-permissions",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions",0,async (instance,params,origin) => { const {guild,channel,user,permissions} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions").build(origin,{guild,channel,user,permissions})) instance.setEphemeral(true) }) ) @@ -382,9 +80,9 @@ const errorMessages = () => { //ERROR NO PERMISSIONS COOLDOWN messages.add(new api.ODMessage("opendiscord:error-no-permissions-cooldown")) messages.get("opendiscord:error-no-permissions-cooldown").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-cooldown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-cooldown",0,async (instance,params,origin) => { const {guild,channel,user,until} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-cooldown").build(source,{guild,channel,user,until})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-cooldown").build(origin,{guild,channel,user,until})) instance.setEphemeral(true) }) ) @@ -392,9 +90,9 @@ const errorMessages = () => { //ERROR NO PERMISSIONS BLACKLISTED messages.add(new api.ODMessage("opendiscord:error-no-permissions-blacklisted")) messages.get("opendiscord:error-no-permissions-blacklisted").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-blacklisted",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-blacklisted",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-blacklisted").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-blacklisted").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -402,9 +100,9 @@ const errorMessages = () => { //ERROR NO PERMISSIONS LIMITS messages.add(new api.ODMessage("opendiscord:error-no-permissions-limits")) messages.get("opendiscord:error-no-permissions-limits").workers.add( - new api.ODWorker("opendiscord:error-no-permissions-limits",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-no-permissions-limits",0,async (instance,params,origin) => { const {guild,channel,user,limit} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-limits").build(source,{guild,channel,user,limit})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-no-permissions-limits").build(origin,{guild,channel,user,limit})) instance.setEphemeral(true) }) ) @@ -412,9 +110,9 @@ const errorMessages = () => { //ERROR RESPONDER TIMEOUT messages.add(new api.ODMessage("opendiscord:error-responder-timeout")) messages.get("opendiscord:error-responder-timeout").workers.add( - new api.ODWorker("opendiscord:error-responder-timeout",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-responder-timeout",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-responder-timeout").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-responder-timeout").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -422,9 +120,9 @@ const errorMessages = () => { //ERROR TICKET UNKNOWN messages.add(new api.ODMessage("opendiscord:error-ticket-unknown")) messages.get("opendiscord:error-ticket-unknown").workers.add( - new api.ODWorker("opendiscord:error-ticket-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-unknown",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-unknown").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-unknown").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -432,10 +130,10 @@ const errorMessages = () => { //ERROR TICKET DEPRECATED messages.add(new api.ODMessage("opendiscord:error-ticket-deprecated")) messages.get("opendiscord:error-ticket-deprecated").workers.add( - new api.ODWorker("opendiscord:error-ticket-deprecated",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-deprecated",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-deprecated").build(source,{guild,channel,user})) - instance.addComponent(await buttons.getSafe("opendiscord:error-ticket-deprecated-transcript").build(source,{})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-deprecated").build(origin,{guild,channel,user})) + instance.addComponent(await buttons.getSafe("opendiscord:error-ticket-deprecated-transcript").build(origin,{})) instance.setEphemeral(true) }) ) @@ -443,9 +141,9 @@ const errorMessages = () => { //ERROR OPTION UNKNOWN messages.add(new api.ODMessage("opendiscord:error-option-unknown")) messages.get("opendiscord:error-option-unknown").workers.add( - new api.ODWorker("opendiscord:error-option-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-option-unknown",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-option-unknown").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-option-unknown").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -453,9 +151,9 @@ const errorMessages = () => { //ERROR PANEL UNKNOWN messages.add(new api.ODMessage("opendiscord:error-panel-unknown")) messages.get("opendiscord:error-panel-unknown").workers.add( - new api.ODWorker("opendiscord:error-panel-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-panel-unknown",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-panel-unknown").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-panel-unknown").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -463,9 +161,9 @@ const errorMessages = () => { //ERROR NOT IN GUILD messages.add(new api.ODMessage("opendiscord:error-not-in-guild")) messages.get("opendiscord:error-not-in-guild").workers.add( - new api.ODWorker("opendiscord:error-not-in-guild",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-not-in-guild",0,async (instance,params,origin) => { const {channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-not-in-guild").build(origin,{channel,user})) instance.setEphemeral(true) }) ) @@ -473,9 +171,19 @@ const errorMessages = () => { //ERROR CHANNEL RENAME messages.add(new api.ODMessage("opendiscord:error-channel-rename")) messages.get("opendiscord:error-channel-rename").workers.add( - new api.ODWorker("opendiscord:error-channel-rename",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-channel-rename",0,async (instance,params,origin) => { const {guild,channel,user,originalName,newName} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-channel-rename").build(source,{guild,channel,user,originalName,newName})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-channel-rename").build(origin,{guild,channel,user,originalName,newName})) + instance.setEphemeral(true) + }) + ) + + //ERROR CHANNEL CATEGORY + messages.add(new api.ODMessage("opendiscord:error-channel-category")) + messages.get("opendiscord:error-channel-category").workers.add( + new api.ODWorker("opendiscord:error-channel-category",0,async (instance,params,origin) => { + const {guild,channel,user,originalCategory,newCategory} = params + instance.addEmbed(await embeds.getSafe("opendiscord:error-channel-category").build(origin,{guild,channel,user,originalCategory,newCategory})) instance.setEphemeral(true) }) ) @@ -483,9 +191,9 @@ const errorMessages = () => { //ERROR TICKET BUSY messages.add(new api.ODMessage("opendiscord:error-ticket-busy")) messages.get("opendiscord:error-ticket-busy").workers.add( - new api.ODWorker("opendiscord:error-ticket-busy",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:error-ticket-busy",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-busy").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:error-ticket-busy").build(origin,{guild,channel,user})) instance.setEphemeral(true) }) ) @@ -495,20 +203,20 @@ const helpMenuMessages = () => { //HELP MENU messages.add(new api.ODMessage("opendiscord:help-menu")) messages.get("opendiscord:help-menu").workers.add( - new api.ODWorker("opendiscord:help-menu",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:help-menu",0,async (instance,params,origin) => { const {mode,page} = params const totalPages = (await opendiscord.helpmenu.render(mode)).length - const embed = await embeds.getSafe("opendiscord:help-menu").build(source,{mode,page}) + const embed = await embeds.getSafe("opendiscord:help-menu").build(origin,{mode,page}) instance.addEmbed(embed) if (totalPages > 1){ //when more than 1 page - instance.addComponent(await buttons.getSafe("opendiscord:help-menu-previous").build(source,{mode,page})) - instance.addComponent(await buttons.getSafe("opendiscord:help-menu-page").build(source,{mode,page})) - instance.addComponent(await buttons.getSafe("opendiscord:help-menu-next").build(source,{mode,page})) + instance.addComponent(await buttons.getSafe("opendiscord:help-menu-previous").build(origin,{mode,page})) + instance.addComponent(await buttons.getSafe("opendiscord:help-menu-page").build(origin,{mode,page})) + instance.addComponent(await buttons.getSafe("opendiscord:help-menu-next").build(origin,{mode,page})) instance.addComponent(buttons.getNewLine("opendiscord:help-menu-divider")) } - if (generalConfig.data.textCommands && generalConfig.data.slashCommands) instance.addComponent(await buttons.get("opendiscord:help-menu-switch").build(source,{mode,page})) + if (generalConfig.data.textCommands && generalConfig.data.slashCommands) instance.addComponent(await buttons.get("opendiscord:help-menu-switch").build(origin,{mode,page})) }) ) } @@ -517,45 +225,45 @@ const statsMessages = () => { //STATS GLOBAL messages.add(new api.ODMessage("opendiscord:stats-global")) messages.get("opendiscord:stats-global").workers.add( - new api.ODWorker("opendiscord:stats-global",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:stats-global",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:stats-global").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:stats-global").build(origin,{guild,channel,user})) }) ) //STATS TICKET messages.add(new api.ODMessage("opendiscord:stats-ticket")) messages.get("opendiscord:stats-ticket").workers.add( - new api.ODWorker("opendiscord:stats-ticket",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:stats-ticket",0,async (instance,params,origin) => { const {guild,channel,user,scopeData} = params - instance.addEmbed(await embeds.getSafe("opendiscord:stats-ticket").build(source,{guild,channel,user,scopeData})) + instance.addEmbed(await embeds.getSafe("opendiscord:stats-ticket").build(origin,{guild,channel,user,scopeData})) }) ) //STATS USER messages.add(new api.ODMessage("opendiscord:stats-user")) messages.get("opendiscord:stats-user").workers.add( - new api.ODWorker("opendiscord:stats-user",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:stats-user",0,async (instance,params,origin) => { const {guild,channel,user,scopeData} = params - instance.addEmbed(await embeds.getSafe("opendiscord:stats-user").build(source,{guild,channel,user,scopeData})) + instance.addEmbed(await embeds.getSafe("opendiscord:stats-user").build(origin,{guild,channel,user,scopeData})) }) ) //STATS RESET messages.add(new api.ODMessage("opendiscord:stats-reset")) messages.get("opendiscord:stats-reset").workers.add( - new api.ODWorker("opendiscord:stats-reset",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:stats-reset",0,async (instance,params,origin) => { const {guild,channel,user,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:stats-reset").build(source,{guild,channel,user,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:stats-reset").build(origin,{guild,channel,user,reason})) }) ) //STATS TICKET UNKNOWN messages.add(new api.ODMessage("opendiscord:stats-ticket-unknown")) messages.get("opendiscord:stats-ticket-unknown").workers.add( - new api.ODWorker("opendiscord:stats-ticket-unknown",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:stats-ticket-unknown",0,async (instance,params,origin) => { const {guild,channel,user,id} = params - instance.addEmbed(await embeds.getSafe("opendiscord:stats-ticket-unknown").build(source,{guild,channel,user,id})) + instance.addEmbed(await embeds.getSafe("opendiscord:stats-ticket-unknown").build(origin,{guild,channel,user,id})) instance.setEphemeral(true) }) ) @@ -565,7 +273,7 @@ const panelMessages = () => { //PANEL messages.add(new api.ODMessage("opendiscord:panel")) messages.get("opendiscord:panel").workers.add([ - new api.ODWorker("opendiscord:panel-layout",1,async (instance,params,source) => { + new api.ODWorker("opendiscord:panel-layout",1,async (instance,params,origin) => { const {guild,channel,user,panel} = params //add text @@ -578,15 +286,15 @@ const panelMessages = () => { instance.setContent(text) } - if (panel.get("opendiscord:enable-max-tickets-warning-text").value && generalConfig.data.system.limits.enabled){ - instance.setContent(instance.data.content+"\n\n*"+lang.getTranslationWithParams("actions.descriptions.ticketMessageLimit",[generalConfig.data.system.limits.userMaximum.toString()])+"*") + if (panel.get("opendiscord:enable-max-tickets-warning-text").value && generalConfig.data.ticketSystem.limits.enabled){ + instance.setContent(instance.data.content+"\n\n*"+lang.getTranslationWithParams("actions.descriptions.ticketMessageLimit",[generalConfig.data.ticketSystem.limits.userMaximum.toString()])+"*") } //add embed const embedOptions = panel.get("opendiscord:embed").value - if (embedOptions.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:panel").build(source,{guild,channel,user,panel})) + if (embedOptions.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:panel").build(origin,{guild,channel,user,panel})) }), - new api.ODWorker("opendiscord:panel-components",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:panel-components",0,async (instance,params,origin) => { const {guild,channel,user,panel} = params const options: api.ODOption[] = [] panel.get("opendiscord:options").value.forEach((id) => { @@ -600,13 +308,13 @@ const panelMessages = () => { options.forEach((option) => { if (option instanceof api.ODTicketOption) ticketOptions.push(option) }) - instance.addComponent(await dropdowns.getSafe("opendiscord:panel-dropdown-tickets").build(source,{guild,channel,user,panel,options:ticketOptions})) + instance.addComponent(await dropdowns.getSafe("opendiscord:panel-dropdown-tickets").build(origin,{guild,channel,user,panel,options:ticketOptions})) }else{ //buttons for (const option of options){ - if (option instanceof api.ODTicketOption) instance.addComponent(await buttons.getSafe("opendiscord:ticket-option").build(source,{guild,channel,user,panel,option})) - else if (option instanceof api.ODWebsiteOption) instance.addComponent(await buttons.getSafe("opendiscord:website-option").build(source,{guild,channel,user,panel,option})) - else if (option instanceof api.ODRoleOption) instance.addComponent(await buttons.getSafe("opendiscord:role-option").build(source,{guild,channel,user,panel,option})) + if (option instanceof api.ODTicketOption) instance.addComponent(await buttons.getSafe("opendiscord:ticket-option").build(origin,{guild,channel,user,panel,option})) + else if (option instanceof api.ODWebsiteOption) instance.addComponent(await buttons.getSafe("opendiscord:website-option").build(origin,{guild,channel,user,panel,option})) + else if (option instanceof api.ODRoleOption) instance.addComponent(await buttons.getSafe("opendiscord:role-option").build(origin,{guild,channel,user,panel,option})) } } }) @@ -615,7 +323,7 @@ const panelMessages = () => { //PANEL READY messages.add(new api.ODMessage("opendiscord:panel-ready")) messages.get("opendiscord:panel-ready").workers.add( - new api.ODWorker("opendiscord:panel-ready",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:panel-ready",0,async (instance,params,origin) => { instance.setContent("## "+lang.getTranslation("actions.descriptions.panelReady")) instance.setEphemeral(true) }) @@ -626,10 +334,10 @@ const ticketMessages = () => { //TICKET CREATED messages.add(new api.ODMessage("opendiscord:ticket-created")) messages.get("opendiscord:ticket-created").workers.add( - new api.ODWorker("opendiscord:ticket-created",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params - instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created").build(source,{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created").build(origin,{guild,channel,user,ticket})) instance.addComponent(await buttons.getSafe("opendiscord:visit-ticket").build("ticket-created",{guild,channel,user,ticket})) instance.setEphemeral(true) }) @@ -638,7 +346,7 @@ const ticketMessages = () => { //TICKET CREATED DM messages.add(new api.ODMessage("opendiscord:ticket-created-dm")) messages.get("opendiscord:ticket-created-dm").workers.add( - new api.ODWorker("opendiscord:ticket-created-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created-dm",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params //add text @@ -646,7 +354,7 @@ const ticketMessages = () => { if (text !== "") instance.setContent(text) //add embed - if (ticket.option.get("opendiscord:dm-message-embed").value.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created-dm").build(source,{guild,channel,user,ticket})) + if (ticket.option.get("opendiscord:dm-message-embed").value.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created-dm").build(origin,{guild,channel,user,ticket})) //add components instance.addComponent(await buttons.getSafe("opendiscord:visit-ticket").build("ticket-created",{guild,channel,user,ticket})) @@ -656,10 +364,10 @@ const ticketMessages = () => { //TICKET CREATED LOGS messages.add(new api.ODMessage("opendiscord:ticket-created-logs")) messages.get("opendiscord:ticket-created-logs").workers.add( - new api.ODWorker("opendiscord:ticket-created-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-created-logs",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params - instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created-logs").build(source,{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:ticket-created-logs").build(origin,{guild,channel,user,ticket})) instance.addComponent(await buttons.getSafe("opendiscord:visit-ticket").build("ticket-created",{guild,channel,user,ticket})) }) ) @@ -667,7 +375,7 @@ const ticketMessages = () => { //TICKET MESSAGE messages.add(new api.ODMessage("opendiscord:ticket-message")) messages.get("opendiscord:ticket-message").workers.add([ - new api.ODWorker("opendiscord:ticket-message-layout",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-message-layout",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params //add pings @@ -686,12 +394,12 @@ const ticketMessages = () => { else instance.setContent(pingText) //add embed - if (ticket.option.get("opendiscord:ticket-message-embed").value.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:ticket-message").build(source,{guild,channel,user,ticket})) + if (ticket.option.get("opendiscord:ticket-message-embed").value.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:ticket-message").build(origin,{guild,channel,user,ticket})) }), - new api.ODWorker("opendiscord:ticket-message-components",1,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-message-components",1,async (instance,params,origin) => { const {guild,channel,user,ticket} = params //add components - if (generalConfig.data.system.enableTicketClaimButtons && !ticket.get("opendiscord:closed").value){ + if (generalConfig.data.ticketSystem.enableTicketClaimButtons && !ticket.get("opendiscord:closed").value){ //enable ticket claiming if (ticket.get("opendiscord:claimed").value){ instance.addComponent(await buttons.getSafe("opendiscord:unclaim-ticket").build("ticket-message",{guild,channel,user,ticket})) @@ -699,7 +407,7 @@ const ticketMessages = () => { instance.addComponent(await buttons.getSafe("opendiscord:claim-ticket").build("ticket-message",{guild,channel,user,ticket})) } } - if (generalConfig.data.system.enableTicketPinButtons && !ticket.get("opendiscord:closed").value){ + if (generalConfig.data.ticketSystem.enableTicketPinButtons && !ticket.get("opendiscord:closed").value){ //enable ticket pinning if (ticket.get("opendiscord:pinned").value){ instance.addComponent(await buttons.getSafe("opendiscord:unpin-ticket").build("ticket-message",{guild,channel,user,ticket})) @@ -707,7 +415,7 @@ const ticketMessages = () => { instance.addComponent(await buttons.getSafe("opendiscord:pin-ticket").build("ticket-message",{guild,channel,user,ticket})) } } - if (generalConfig.data.system.enableTicketCloseButtons){ + if (generalConfig.data.ticketSystem.enableTicketCloseButtons){ //enable ticket closing if (ticket.get("opendiscord:closed").value){ instance.addComponent(await buttons.getSafe("opendiscord:reopen-ticket").build("ticket-message",{guild,channel,user,ticket})) @@ -716,9 +424,9 @@ const ticketMessages = () => { } } //enable ticket deletion - if (generalConfig.data.system.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("ticket-message",{guild,channel,user,ticket})) + if (generalConfig.data.ticketSystem.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("ticket-message",{guild,channel,user,ticket})) }), - new api.ODWorker("opendiscord:ticket-message-disable-components",2,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-message-disable-components",2,async (instance,params,origin) => { const {ticket} = params if (ticket.get("opendiscord:for-deletion").value){ //disable all buttons when ticket is being prepared for deletion @@ -734,125 +442,125 @@ const ticketMessages = () => { //TICKET CLOSED messages.add(new api.ODMessage("opendiscord:close-message")) messages.get("opendiscord:close-message").workers.add( - new api.ODWorker("opendiscord:close-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:close-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:close-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:reopen-ticket").build("close-message",{guild,channel,user,ticket})) - if (generalConfig.data.system.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("close-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:close-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:reopen-ticket").build("close-message",{guild,channel,user,ticket})) + if (generalConfig.data.ticketSystem.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("close-message",{guild,channel,user,ticket})) }) ) //TICKET REOPENED messages.add(new api.ODMessage("opendiscord:reopen-message")) messages.get("opendiscord:reopen-message").workers.add( - new api.ODWorker("opendiscord:reopen-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reopen-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:reopen-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:close-ticket").build("reopen-message",{guild,channel,user,ticket})) - if (generalConfig.data.system.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("reopen-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:reopen-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:close-ticket").build("reopen-message",{guild,channel,user,ticket})) + if (generalConfig.data.ticketSystem.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("reopen-message",{guild,channel,user,ticket})) }) ) //TICKET DELETED messages.add(new api.ODMessage("opendiscord:delete-message")) messages.get("opendiscord:delete-message").workers.add( - new api.ODWorker("opendiscord:delete-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:delete-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:delete-message").build(source,{guild,channel,user,ticket,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:delete-message").build(origin,{guild,channel,user,ticket,reason})) }) ) //TICKET CLAIMED messages.add(new api.ODMessage("opendiscord:claim-message")) messages.get("opendiscord:claim-message").workers.add( - new api.ODWorker("opendiscord:claim-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:claim-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:claim-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketClaimButtons) instance.addComponent(await buttons.getSafe("opendiscord:unclaim-ticket").build("claim-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:claim-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketClaimButtons) instance.addComponent(await buttons.getSafe("opendiscord:unclaim-ticket").build("claim-message",{guild,channel,user,ticket})) }) ) //TICKET UNCLAIMED messages.add(new api.ODMessage("opendiscord:unclaim-message")) messages.get("opendiscord:unclaim-message").workers.add( - new api.ODWorker("opendiscord:unclaim-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unclaim-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:unclaim-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketClaimButtons) instance.addComponent(await buttons.getSafe("opendiscord:claim-ticket").build("unclaim-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:unclaim-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketClaimButtons) instance.addComponent(await buttons.getSafe("opendiscord:claim-ticket").build("unclaim-message",{guild,channel,user,ticket})) }) ) //TICKET PINNED messages.add(new api.ODMessage("opendiscord:pin-message")) messages.get("opendiscord:pin-message").workers.add( - new api.ODWorker("opendiscord:pin-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:pin-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:pin-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketPinButtons) instance.addComponent(await buttons.getSafe("opendiscord:unpin-ticket").build("pin-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:pin-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketPinButtons) instance.addComponent(await buttons.getSafe("opendiscord:unpin-ticket").build("pin-message",{guild,channel,user,ticket})) }) ) //TICKET UNPINNED messages.add(new api.ODMessage("opendiscord:unpin-message")) messages.get("opendiscord:unpin-message").workers.add( - new api.ODWorker("opendiscord:unpin-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:unpin-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:unpin-message").build(source,{guild,channel,user,ticket,reason})) - if (generalConfig.data.system.enableTicketPinButtons) instance.addComponent(await buttons.getSafe("opendiscord:pin-ticket").build("unpin-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:unpin-message").build(origin,{guild,channel,user,ticket,reason})) + if (generalConfig.data.ticketSystem.enableTicketPinButtons) instance.addComponent(await buttons.getSafe("opendiscord:pin-ticket").build("unpin-message",{guild,channel,user,ticket})) }) ) //TICKET RENAMED messages.add(new api.ODMessage("opendiscord:rename-message")) messages.get("opendiscord:rename-message").workers.add( - new api.ODWorker("opendiscord:rename-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:rename-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,data} = params - instance.addEmbed(await embeds.getSafe("opendiscord:rename-message").build(source,{guild,channel,user,ticket,reason,data})) + instance.addEmbed(await embeds.getSafe("opendiscord:rename-message").build(origin,{guild,channel,user,ticket,reason,data})) }) ) //TICKET MOVED messages.add(new api.ODMessage("opendiscord:move-message")) messages.get("opendiscord:move-message").workers.add( - new api.ODWorker("opendiscord:move-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:move-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,data} = params - instance.addEmbed(await embeds.getSafe("opendiscord:move-message").build(source,{guild,channel,user,ticket,reason,data})) + instance.addEmbed(await embeds.getSafe("opendiscord:move-message").build(origin,{guild,channel,user,ticket,reason,data})) }) ) //TICKET USER ADDED messages.add(new api.ODMessage("opendiscord:add-message")) messages.get("opendiscord:add-message").workers.add( - new api.ODWorker("opendiscord:add-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:add-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,data} = params - instance.addEmbed(await embeds.getSafe("opendiscord:add-message").build(source,{guild,channel,user,ticket,reason,data})) + instance.addEmbed(await embeds.getSafe("opendiscord:add-message").build(origin,{guild,channel,user,ticket,reason,data})) }) ) //TICKET USER REMOVED messages.add(new api.ODMessage("opendiscord:remove-message")) messages.get("opendiscord:remove-message").workers.add( - new api.ODWorker("opendiscord:remove-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:remove-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,data} = params - instance.addEmbed(await embeds.getSafe("opendiscord:remove-message").build(source,{guild,channel,user,ticket,reason,data})) + instance.addEmbed(await embeds.getSafe("opendiscord:remove-message").build(origin,{guild,channel,user,ticket,reason,data})) }) ) //TICKET ACTION DM messages.add(new api.ODMessage("opendiscord:ticket-action-dm")) messages.get("opendiscord:ticket-action-dm").workers.add( - new api.ODWorker("opendiscord:ticket-action-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-action-dm",0,async (instance,params,origin) => { const {guild,channel,user,mode,ticket,reason,additionalData} = params - instance.addEmbed(await embeds.getSafe("opendiscord:ticket-action-dm").build(source,{guild,channel,user,mode,ticket,reason,additionalData})) + instance.addEmbed(await embeds.getSafe("opendiscord:ticket-action-dm").build(origin,{guild,channel,user,mode,ticket,reason,additionalData})) }) ) //TICKET ACTION LOGS messages.add(new api.ODMessage("opendiscord:ticket-action-logs")) messages.get("opendiscord:ticket-action-logs").workers.add( - new api.ODWorker("opendiscord:ticket-action-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-action-logs",0,async (instance,params,origin) => { const {guild,channel,user,mode,ticket,reason,additionalData} = params - instance.addEmbed(await embeds.getSafe("opendiscord:ticket-action-logs").build(source,{guild,channel,user,mode,ticket,reason,additionalData})) + instance.addEmbed(await embeds.getSafe("opendiscord:ticket-action-logs").build(origin,{guild,channel,user,mode,ticket,reason,additionalData})) }) ) } @@ -861,54 +569,54 @@ const blacklistMessages = () => { //BLACKLIST VIEW messages.add(new api.ODMessage("opendiscord:blacklist-view")) messages.get("opendiscord:blacklist-view").workers.add( - new api.ODWorker("opendiscord:blacklist-view",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-view",0,async (instance,params,origin) => { const {guild,channel,user} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-view").build(source,{guild,channel,user})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-view").build(origin,{guild,channel,user})) }) ) //BLACKLIST GET messages.add(new api.ODMessage("opendiscord:blacklist-get")) messages.get("opendiscord:blacklist-get").workers.add( - new api.ODWorker("opendiscord:blacklist-get",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-get",0,async (instance,params,origin) => { const {guild,channel,user,data} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-get").build(source,{guild,channel,user,data})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-get").build(origin,{guild,channel,user,data})) }) ) //BLACKLIST ADD messages.add(new api.ODMessage("opendiscord:blacklist-add")) messages.get("opendiscord:blacklist-add").workers.add( - new api.ODWorker("opendiscord:blacklist-add",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-add",0,async (instance,params,origin) => { const {guild,channel,user,data,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-add").build(source,{guild,channel,user,data,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-add").build(origin,{guild,channel,user,data,reason})) }) ) //BLACKLIST REMOVE messages.add(new api.ODMessage("opendiscord:blacklist-remove")) messages.get("opendiscord:blacklist-remove").workers.add( - new api.ODWorker("opendiscord:blacklist-remove",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-remove",0,async (instance,params,origin) => { const {guild,channel,user,data,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-remove").build(source,{guild,channel,user,data,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-remove").build(origin,{guild,channel,user,data,reason})) }) ) //BLACKLIST DM messages.add(new api.ODMessage("opendiscord:blacklist-dm")) messages.get("opendiscord:blacklist-dm").workers.add( - new api.ODWorker("opendiscord:blacklist-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-dm",0,async (instance,params,origin) => { const {guild,channel,user,mode,data,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-dm").build(source,{guild,channel,user,mode,data,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-dm").build(origin,{guild,channel,user,mode,data,reason})) }) ) //BLACKLIST LOGS messages.add(new api.ODMessage("opendiscord:blacklist-logs")) messages.get("opendiscord:blacklist-logs").workers.add( - new api.ODWorker("opendiscord:blacklist-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:blacklist-logs",0,async (instance,params,origin) => { const {guild,channel,user,mode,data,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-logs").build(source,{guild,channel,user,mode,data,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:blacklist-logs").build(origin,{guild,channel,user,mode,data,reason})) }) ) } @@ -917,40 +625,40 @@ const transcriptMessages = () => { //TRANSCRIPT TEXT READY messages.add(new api.ODMessage("opendiscord:transcript-text-ready")) messages.get("opendiscord:transcript-text-ready").workers.add( - new api.ODWorker("opendiscord:transcript-text-ready",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-text-ready",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,result} = params - instance.addEmbed(await embeds.getSafe("opendiscord:transcript-text-ready").build(source,{guild,channel,user,ticket,compiler,result})) - instance.addFile(await files.getSafe("opendiscord:text-transcript").build(source,{guild,channel,user,ticket,compiler,result})) + instance.addEmbed(await embeds.getSafe("opendiscord:transcript-text-ready").build(origin,{guild,channel,user,ticket,compiler,result})) + instance.addFile(await files.getSafe("opendiscord:text-transcript").build(origin,{guild,channel,user,ticket,compiler,result})) }) ) //TRANSCRIPT HTML READY messages.add(new api.ODMessage("opendiscord:transcript-html-ready")) messages.get("opendiscord:transcript-html-ready").workers.add( - new api.ODWorker("opendiscord:transcript-html-ready",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-html-ready",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,result} = params - instance.addEmbed(await embeds.getSafe("opendiscord:transcript-html-ready").build(source,{guild,channel,user,ticket,compiler,result})) - instance.addComponent(await buttons.getSafe("opendiscord:transcript-html-visit").build(source,{guild,channel,user,ticket,compiler,result})) + instance.addEmbed(await embeds.getSafe("opendiscord:transcript-html-ready").build(origin,{guild,channel,user,ticket,compiler,result})) + instance.addComponent(await buttons.getSafe("opendiscord:transcript-html-visit").build(origin,{guild,channel,user,ticket,compiler,result})) }) ) //TRANSCRIPT HTML PROGRESS messages.add(new api.ODMessage("opendiscord:transcript-html-progress")) messages.get("opendiscord:transcript-html-progress").workers.add( - new api.ODWorker("opendiscord:transcript-html-progress",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-html-progress",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,remaining} = params - instance.addEmbed(await embeds.getSafe("opendiscord:transcript-html-progress").build(source,{guild,channel,user,ticket,compiler,remaining})) + instance.addEmbed(await embeds.getSafe("opendiscord:transcript-html-progress").build(origin,{guild,channel,user,ticket,compiler,remaining})) }) ) //TRANSCRIPT ERROR messages.add(new api.ODMessage("opendiscord:transcript-error")) messages.get("opendiscord:transcript-error").workers.add( - new api.ODWorker("opendiscord:transcript-error",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transcript-error",0,async (instance,params,origin) => { const {guild,channel,user,ticket,compiler,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:transcript-error").build(source,{guild,channel,user,ticket,compiler,reason})) - instance.addComponent(await buttons.getSafe("opendiscord:transcript-error-retry").build(source,{guild,channel,user,ticket,compiler,reason})) - instance.addComponent(await buttons.getSafe("opendiscord:transcript-error-continue").build(source,{guild,channel,user,ticket,compiler,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:transcript-error").build(origin,{guild,channel,user,ticket,compiler,reason})) + instance.addComponent(await buttons.getSafe("opendiscord:transcript-error-retry").build(origin,{guild,channel,user,ticket,compiler,reason})) + instance.addComponent(await buttons.getSafe("opendiscord:transcript-error-continue").build(origin,{guild,channel,user,ticket,compiler,reason})) }) ) } @@ -959,9 +667,9 @@ const roleMessages = () => { //REACTION ROLE messages.add(new api.ODMessage("opendiscord:reaction-role")) messages.get("opendiscord:reaction-role").workers.add( - new api.ODWorker("opendiscord:reaction-role",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role",0,async (instance,params,origin) => { const {guild,user,role,result} = params - instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role").build(source,{guild,user,role,result})) + instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role").build(origin,{guild,user,role,result})) instance.setEphemeral(true) }) ) @@ -969,18 +677,18 @@ const roleMessages = () => { //REACTION ROLE DM messages.add(new api.ODMessage("opendiscord:reaction-role-dm")) messages.get("opendiscord:reaction-role-dm").workers.add( - new api.ODWorker("opendiscord:reaction-role-dm",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role-dm",0,async (instance,params,origin) => { const {guild,user,role,result} = params - instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role-dm").build(source,{guild,user,role,result})) + instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role-dm").build(origin,{guild,user,role,result})) }) ) //REACTION ROLE LOGS messages.add(new api.ODMessage("opendiscord:reaction-role-logs")) messages.get("opendiscord:reaction-role-logs").workers.add( - new api.ODWorker("opendiscord:reaction-role-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:reaction-role-logs",0,async (instance,params,origin) => { const {guild,user,role,result} = params - instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role-logs").build(source,{guild,user,role,result})) + instance.addEmbed(await embeds.getSafe("opendiscord:reaction-role-logs").build(origin,{guild,user,role,result})) }) ) } @@ -989,10 +697,10 @@ const clearMessages = () => { //CLEAR VERIFY MESSAGE messages.add(new api.ODMessage("opendiscord:clear-verify-message")) messages.get("opendiscord:clear-verify-message").workers.add( - new api.ODWorker("opendiscord:clear-verify-message",0,async (instance,params,source) => { - const {guild,channel,user,filter,list} = params - instance.addEmbed(await embeds.getSafe("opendiscord:clear-verify-message").build(source,{guild,channel,user,filter,list})) - instance.addComponent(await buttons.getSafe("opendiscord:clear-continue").build(source,{guild,channel,user,filter,list})) + new api.ODWorker("opendiscord:clear-verify-message",0,async (instance,params,origin) => { + const {guild,channel,user,filter,list,inProgress} = params + instance.addEmbed(await embeds.getSafe("opendiscord:clear-verify-message").build(origin,{guild,channel,user,filter,list,inProgress})) + instance.addComponent(await buttons.getSafe("opendiscord:clear-continue").build(origin,{guild,channel,user,filter,list,inProgress})) instance.setEphemeral(true) }) ) @@ -1000,9 +708,9 @@ const clearMessages = () => { //CLEAR MESSAGE messages.add(new api.ODMessage("opendiscord:clear-message")) messages.get("opendiscord:clear-message").workers.add( - new api.ODWorker("opendiscord:clear-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-message",0,async (instance,params,origin) => { const {guild,channel,user,filter,list} = params - instance.addEmbed(await embeds.getSafe("opendiscord:clear-message").build(source,{guild,channel,user,filter,list})) + instance.addEmbed(await embeds.getSafe("opendiscord:clear-message").build(origin,{guild,channel,user,filter,list})) instance.setEphemeral(true) }) ) @@ -1010,9 +718,9 @@ const clearMessages = () => { //CLEAR LOGS messages.add(new api.ODMessage("opendiscord:clear-logs")) messages.get("opendiscord:clear-logs").workers.add( - new api.ODWorker("opendiscord:clear-logs",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:clear-logs",0,async (instance,params,origin) => { const {guild,channel,user,filter,list} = params - instance.addEmbed(await embeds.getSafe("opendiscord:clear-logs").build(source,{guild,channel,user,filter,list})) + instance.addEmbed(await embeds.getSafe("opendiscord:clear-logs").build(origin,{guild,channel,user,filter,list})) }) ) } @@ -1021,56 +729,56 @@ const autoMessages = () => { //AUTOCLOSE MESSAGE messages.add(new api.ODMessage("opendiscord:autoclose-message")) messages.get("opendiscord:autoclose-message").workers.add( - new api.ODWorker("opendiscord:autoclose-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-message").build(source,{guild,channel,user,ticket})) - if (generalConfig.data.system.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:reopen-ticket").build("autoclose-message",{guild,channel,user,ticket})) - if (generalConfig.data.system.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("autoclose-message",{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-message").build(origin,{guild,channel,user,ticket})) + if (generalConfig.data.ticketSystem.enableTicketCloseButtons) instance.addComponent(await buttons.getSafe("opendiscord:reopen-ticket").build("autoclose-message",{guild,channel,user,ticket})) + if (generalConfig.data.ticketSystem.enableTicketDeleteButtons) instance.addComponent(await buttons.getSafe("opendiscord:delete-ticket").build("autoclose-message",{guild,channel,user,ticket})) }) ) //AUTODELETE MESSAGE messages.add(new api.ODMessage("opendiscord:autodelete-message")) messages.get("opendiscord:autodelete-message").workers.add( - new api.ODWorker("opendiscord:autodelete-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-message").build(source,{guild,channel,user,ticket})) + instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-message").build(origin,{guild,channel,user,ticket})) }) ) //AUTOCLOSE ENABLE messages.add(new api.ODMessage("opendiscord:autoclose-enable")) messages.get("opendiscord:autoclose-enable").workers.add( - new api.ODWorker("opendiscord:autoclose-enable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-enable",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,time} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-enable").build(source,{guild,channel,user,ticket,reason,time})) + instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-enable").build(origin,{guild,channel,user,ticket,reason,time})) }) ) //AUTODELETE ENABLE messages.add(new api.ODMessage("opendiscord:autodelete-enable")) messages.get("opendiscord:autodelete-enable").workers.add( - new api.ODWorker("opendiscord:autodelete-enable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-enable",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason,time} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-enable").build(source,{guild,channel,user,ticket,reason,time})) + instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-enable").build(origin,{guild,channel,user,ticket,reason,time})) }) ) //AUTOCLOSE DISABLE messages.add(new api.ODMessage("opendiscord:autoclose-disable")) messages.get("opendiscord:autoclose-disable").workers.add( - new api.ODWorker("opendiscord:autoclose-disable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autoclose-disable",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-disable").build(source,{guild,channel,user,ticket,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:autoclose-disable").build(origin,{guild,channel,user,ticket,reason})) }) ) //AUTODELETE DISABLE messages.add(new api.ODMessage("opendiscord:autodelete-disable")) messages.get("opendiscord:autodelete-disable").workers.add( - new api.ODWorker("opendiscord:autodelete-disable",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:autodelete-disable",0,async (instance,params,origin) => { const {guild,channel,user,ticket,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-disable").build(source,{guild,channel,user,ticket,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:autodelete-disable").build(origin,{guild,channel,user,ticket,reason})) }) ) } @@ -1079,36 +787,36 @@ const extraMessages = () => { //TOPIC SET messages.add(new api.ODMessage("opendiscord:topic-set")) messages.get("opendiscord:topic-set").workers.add( - new api.ODWorker("opendiscord:topic-set",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:topic-set",0,async (instance,params,origin) => { const {guild,channel,user,ticket,topic} = params - instance.addEmbed(await embeds.getSafe("opendiscord:topic-set").build(source,{guild,channel,user,ticket,topic})) + instance.addEmbed(await embeds.getSafe("opendiscord:topic-set").build(origin,{guild,channel,user,ticket,topic})) }) ) //PRIORITY SET messages.add(new api.ODMessage("opendiscord:priority-set")) messages.get("opendiscord:priority-set").workers.add( - new api.ODWorker("opendiscord:priority-set",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:priority-set",0,async (instance,params,origin) => { const {guild,channel,user,ticket,priority,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:priority-set").build(source,{guild,channel,user,ticket,priority,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:priority-set").build(origin,{guild,channel,user,ticket,priority,reason})) }) ) //PRIORITY GET messages.add(new api.ODMessage("opendiscord:priority-get")) messages.get("opendiscord:priority-get").workers.add( - new api.ODWorker("opendiscord:priority-get",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:priority-get",0,async (instance,params,origin) => { const {guild,channel,user,ticket,priority} = params - instance.addEmbed(await embeds.getSafe("opendiscord:priority-get").build(source,{guild,channel,user,ticket,priority})) + instance.addEmbed(await embeds.getSafe("opendiscord:priority-get").build(origin,{guild,channel,user,ticket,priority})) }) ) //TRANSFER MESSAGE messages.add(new api.ODMessage("opendiscord:transfer-message")) messages.get("opendiscord:transfer-message").workers.add( - new api.ODWorker("opendiscord:transfer-message",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:transfer-message",0,async (instance,params,origin) => { const {guild,channel,user,ticket,oldCreator,newCreator,reason} = params - instance.addEmbed(await embeds.getSafe("opendiscord:transfer-message").build(source,{guild,channel,user,ticket,oldCreator,newCreator,reason})) + instance.addEmbed(await embeds.getSafe("opendiscord:transfer-message").build(origin,{guild,channel,user,ticket,oldCreator,newCreator,reason})) }) ) } \ No newline at end of file diff --git a/src/builders/modals.ts b/src/builders/modals.ts index df7d222..281e93f 100644 --- a/src/builders/modals.ts +++ b/src/builders/modals.ts @@ -1,23 +1,23 @@ /////////////////////////////////////// //MODAL BUILDERS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" const modals = opendiscord.builders.modals const lang = opendiscord.languages -export const registerAllModals = async () => { +export async function registerAllModals(){ ticketModals() } const ticketModals = () => { //TICKET QUESTIONS modals.add(new api.ODModal("opendiscord:ticket-questions")) modals.get("opendiscord:ticket-questions").workers.add( - new api.ODWorker("opendiscord:ticket-questions",0,async (instance,params,source) => { + new api.ODWorker("opendiscord:ticket-questions",0,async (instance,params,origin) => { const {option} = params - instance.setCustomId("od:ticket-questions_"+option.id.value+"_"+source) + instance.setCustomId("od:ticket-questions_"+option.id.value+"_"+origin) instance.setTitle(option.exists("opendiscord:name") ? option.get("opendiscord:name").value : option.id.value) const questionIds = option.get("opendiscord:questions").value questionIds.forEach((id) => { @@ -30,7 +30,7 @@ const ticketModals = () => { required:question.get("opendiscord:required").value, placeholder:(question.get("opendiscord:placeholder").value) ? question.get("opendiscord:placeholder").value : undefined, minLength:(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-min").value : undefined, - maxLength:(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-max").value : undefined + maxLength:Math.min(1024-6,(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-max").value : (1024-6)) //embed field limit - 6x` characters }) else if (question instanceof api.ODParagraphQuestion) instance.addQuestion({ customId:question.id.value, @@ -39,7 +39,7 @@ const ticketModals = () => { required:question.get("opendiscord:required").value, placeholder:(question.get("opendiscord:placeholder").value) ? question.get("opendiscord:placeholder").value : undefined, minLength:(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-min").value : undefined, - maxLength:(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-max").value : undefined + maxLength:Math.min(1024-6,(question.get("opendiscord:length-enabled").value) ? question.get("opendiscord:length-max").value : (1024-6)) //embed field limit - 6x` characters }) }) }) @@ -48,10 +48,10 @@ const ticketModals = () => { //CLOSE TICKET REASON modals.add(new api.ODModal("opendiscord:close-ticket-reason")) modals.get("opendiscord:close-ticket-reason").workers.add( - new api.ODWorker("opendiscord:close-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params + new api.ODWorker("opendiscord:close-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params - instance.setCustomId("od:close-ticket-reason_"+ticket.id.value+"_"+source) + instance.setCustomId("od:close-ticket-reason|"+channel.id+"|"+message.id) instance.setTitle(lang.getTranslation("actions.buttons.close")) instance.addQuestion({ customId:"reason", @@ -66,10 +66,10 @@ const ticketModals = () => { //REOPEN TICKET REASON modals.add(new api.ODModal("opendiscord:reopen-ticket-reason")) modals.get("opendiscord:reopen-ticket-reason").workers.add( - new api.ODWorker("opendiscord:reopen-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params + new api.ODWorker("opendiscord:reopen-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params - instance.setCustomId("od:reopen-ticket-reason_"+ticket.id.value+"_"+source) + instance.setCustomId("od:reopen-ticket-reason|"+channel.id+"|"+message.id) instance.setTitle(lang.getTranslation("actions.buttons.reopen")) instance.addQuestion({ customId:"reason", @@ -84,10 +84,10 @@ const ticketModals = () => { //DELETE TICKET REASON modals.add(new api.ODModal("opendiscord:delete-ticket-reason")) modals.get("opendiscord:delete-ticket-reason").workers.add( - new api.ODWorker("opendiscord:delete-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params + new api.ODWorker("opendiscord:delete-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params - instance.setCustomId("od:delete-ticket-reason_"+ticket.id.value+"_"+source) + instance.setCustomId("od:delete-ticket-reason|"+channel.id+"|"+message.id) instance.setTitle(lang.getTranslation("actions.buttons.delete")) instance.addQuestion({ customId:"reason", @@ -102,10 +102,10 @@ const ticketModals = () => { //CLAIM TICKET REASON modals.add(new api.ODModal("opendiscord:claim-ticket-reason")) modals.get("opendiscord:claim-ticket-reason").workers.add( - new api.ODWorker("opendiscord:claim-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params + new api.ODWorker("opendiscord:claim-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params - instance.setCustomId("od:claim-ticket-reason_"+ticket.id.value+"_"+source) + instance.setCustomId("od:claim-ticket-reason|"+channel.id+"|"+message.id) instance.setTitle(lang.getTranslation("actions.buttons.claim")) instance.addQuestion({ customId:"reason", @@ -120,10 +120,10 @@ const ticketModals = () => { //UNCLAIM TICKET REASON modals.add(new api.ODModal("opendiscord:unclaim-ticket-reason")) modals.get("opendiscord:unclaim-ticket-reason").workers.add( - new api.ODWorker("opendiscord:unclaim-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params + new api.ODWorker("opendiscord:unclaim-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params - instance.setCustomId("od:unclaim-ticket-reason_"+ticket.id.value+"_"+source) + instance.setCustomId("od:unclaim-ticket-reason|"+channel.id+"|"+message.id) instance.setTitle(lang.getTranslation("actions.buttons.unclaim")) instance.addQuestion({ customId:"reason", @@ -138,10 +138,10 @@ const ticketModals = () => { //PIN TICKET REASON modals.add(new api.ODModal("opendiscord:pin-ticket-reason")) modals.get("opendiscord:pin-ticket-reason").workers.add( - new api.ODWorker("opendiscord:pin-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params + new api.ODWorker("opendiscord:pin-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params - instance.setCustomId("od:pin-ticket-reason_"+ticket.id.value+"_"+source) + instance.setCustomId("od:pin-ticket-reason|"+channel.id+"|"+message.id) instance.setTitle(lang.getTranslation("actions.buttons.pin")) instance.addQuestion({ customId:"reason", @@ -156,10 +156,10 @@ const ticketModals = () => { //UNPIN TICKET REASON modals.add(new api.ODModal("opendiscord:unpin-ticket-reason")) modals.get("opendiscord:unpin-ticket-reason").workers.add( - new api.ODWorker("opendiscord:unpin-ticket-reason",0,async (instance,params,source) => { - const {ticket} = params + new api.ODWorker("opendiscord:unpin-ticket-reason",0,async (instance,params,origin) => { + const {channel,message} = params - instance.setCustomId("od:unpin-ticket-reason_"+ticket.id.value+"_"+source) + instance.setCustomId("od:unpin-ticket-reason|"+channel.id+"|"+message.id) instance.setTitle(lang.getTranslation("actions.buttons.unpin")) instance.addQuestion({ customId:"reason", diff --git a/src/commands/add.ts b/src/commands/add.ts index 43d4b89..dd1dec6 100644 --- a/src/commands/add.ts +++ b/src/commands/add.ts @@ -1,45 +1,32 @@ /////////////////////////////////////// //ADD COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //ADD COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:add",generalConfig.data.prefix,"add")) opendiscord.responders.commands.get("opendiscord:add").workers.add([ - new api.ODWorker("opendiscord:add",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:add",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.add,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check if in guild/Server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"add") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data const data = instance.options.getUser("user",true) const reason = instance.options.getString("reason",false) @@ -52,15 +39,15 @@ export const registerCommandResponders = async () => { //start adding user to ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:add-ticket-user").run(source,{guild,channel,user,ticket,reason,sendMessage:false,data}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:add-message").build(source,{guild,channel,user,ticket,reason,data})) + await opendiscord.actions.get("opendiscord:add-ticket-user").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,data}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:add-message").build(origin,{guild,channel,user,ticket,reason,data})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'add' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/autoclose.ts b/src/commands/autoclose.ts index bdf5a8b..ba66cf7 100644 --- a/src/commands/autoclose.ts +++ b/src/commands/autoclose.ts @@ -1,50 +1,33 @@ /////////////////////////////////////// //AUTOCLOSE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //AUTOCLOSE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:autoclose",generalConfig.data.prefix,/^autoclose/)) opendiscord.responders.commands.get("opendiscord:autoclose").workers.add([ - new api.ODWorker("opendiscord:autoclose",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:autoclose",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.autoclose,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"autoclose") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } - - //return when already closed - if (ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.close"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() //subcommands const scope = instance.options.getSubCommand() @@ -54,40 +37,27 @@ export const registerCommandResponders = async () => { const reason = instance.options.getString("reason",false) ticket.get("opendiscord:autoclose-enabled").value = false ticket.get("opendiscord:autoclose-hours").value = 0 - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-disable").build(source,{guild,channel,user,ticket,reason})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-disable").build(origin,{guild,channel,user,ticket,reason})) }else if (scope == "enable"){ const time = instance.options.getNumber("time",true) const reason = instance.options.getString("reason",false) ticket.get("opendiscord:autoclose-enabled").value = true ticket.get("opendiscord:autoclose-hours").value = time - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-enable").build(source,{guild,channel,user,ticket,reason,time})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-enable").build(origin,{guild,channel,user,ticket,reason,time})) } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on autoclose "+scope+"!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { const scope = instance.options.getSubCommand() const reason = instance.options.getString("reason",false) opendiscord.log(instance.user.displayName+" used the 'autoclose "+scope+"' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"reason",value:reason ?? "/"}, ]) }) diff --git a/src/commands/autocomplete.ts b/src/commands/autocomplete.ts index e7cac2a..2dbd7f2 100644 --- a/src/commands/autocomplete.ts +++ b/src/commands/autocomplete.ts @@ -1,13 +1,13 @@ /////////////////////////////////////// //AUTOCOMPLETE COMMAND UTILS /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" -export const registerAutocompleteResponders = async () => { +export async function registerAutocompleteResponders(){ //PANEL ID AUTOCOMPLETE opendiscord.responders.autocomplete.add(new api.ODAutocompleteResponder("opendiscord:panel-id","panel","id")) - opendiscord.responders.autocomplete.get("opendiscord:panel-id").workers.add(new api.ODWorker("opendiscord:panel-id",0,async (instance,params,source,cancel) => { + opendiscord.responders.autocomplete.get("opendiscord:panel-id").workers.add(new api.ODWorker("opendiscord:panel-id",0,async (instance,params,origin,cancel) => { //create panel choices const panelChoices : {name:string, value:string}[] = [] opendiscord.configs.get("opendiscord:panels").data.forEach((panel) => { @@ -19,7 +19,7 @@ export const registerAutocompleteResponders = async () => { //OPTION ID AUTOCOMPLETE opendiscord.responders.autocomplete.add(new api.ODAutocompleteResponder("opendiscord:option-id",/ticket|move/,"id")) - opendiscord.responders.autocomplete.get("opendiscord:option-id").workers.add(new api.ODWorker("opendiscord:option-id",0,async (instance,params,source,cancel) => { + opendiscord.responders.autocomplete.get("opendiscord:option-id").workers.add(new api.ODWorker("opendiscord:option-id",0,async (instance,params,origin,cancel) => { //create ticket choices const ticketChoices : {name:string, value:string}[] = [] opendiscord.configs.get("opendiscord:options").data.forEach((option) => { diff --git a/src/commands/autodelete.ts b/src/commands/autodelete.ts index f912668..235c2cf 100644 --- a/src/commands/autodelete.ts +++ b/src/commands/autodelete.ts @@ -1,45 +1,30 @@ /////////////////////////////////////// //AUTODELETE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //AUTODELETE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:autodelete",generalConfig.data.prefix,/^autodelete/)) opendiscord.responders.commands.get("opendiscord:autodelete").workers.add([ - new api.ODWorker("opendiscord:autodelete",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:autodelete",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.autodelete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"autodelete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() //subcommands const scope = instance.options.getSubCommand() @@ -49,40 +34,27 @@ export const registerCommandResponders = async () => { const reason = instance.options.getString("reason",false) ticket.get("opendiscord:autodelete-enabled").value = false ticket.get("opendiscord:autodelete-days").value = 0 - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autodelete-disable").build(source,{guild,channel,user,ticket,reason})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autodelete-disable").build(origin,{guild,channel,user,ticket,reason})) }else if (scope == "enable"){ const time = instance.options.getNumber("time",true) const reason = instance.options.getString("reason",false) ticket.get("opendiscord:autodelete-enabled").value = true ticket.get("opendiscord:autodelete-days").value = time - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autodelete-enable").build(source,{guild,channel,user,ticket,reason,time})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:autodelete-enable").build(origin,{guild,channel,user,ticket,reason,time})) } - //update ticket message - const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) - if (ticketMessage){ - try{ - ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) - }catch(e){ - opendiscord.log("Unable to edit ticket message on autodelete "+scope+"!","error",[ - {key:"channel",value:"#"+channel.name}, - {key:"channelid",value:channel.id,hidden:true}, - {key:"messageid",value:ticketMessage.id}, - {key:"option",value:ticket.option.id.value} - ]) - opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) - } - } + //update ticket message (no await) + openticketUtils.updateTicketMessage(guild,channel,user,ticket) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { const scope = instance.options.getSubCommand() const reason = instance.options.getString("reason",false) opendiscord.log(instance.user.displayName+" used the 'autodelete "+scope+"' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"reason",value:reason ?? "/"}, ]) }) diff --git a/src/commands/blacklist.ts b/src/commands/blacklist.ts index 16e8548..10d0d18 100644 --- a/src/commands/blacklist.ts +++ b/src/commands/blacklist.ts @@ -1,29 +1,29 @@ /////////////////////////////////////// //BLACKLIST COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //BLACKLIST COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:blacklist",generalConfig.data.prefix,/^blacklist/)) opendiscord.responders.commands.get("opendiscord:blacklist").workers.add([ - new api.ODWorker("opendiscord:blacklist",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:blacklist",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.blacklist,"support",user,member,channel,guild) + const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.permissions.blacklist,"support",user,member,channel,guild) if (!permsResult.hasPerms){ if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) + else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(origin,{guild,channel,user,permissions:["support"]})) return cancel() } //check is in guild/server if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(origin,{channel:instance.channel,user:instance.user})) return cancel() } @@ -31,11 +31,11 @@ export const registerCommandResponders = async () => { const scope = instance.options.getSubCommand() if (!scope || (scope != "add" && scope != "get" && scope != "remove" && scope != "view")) return if (scope == "view"){ - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-view").build(source,{guild,channel,user})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-view").build(origin,{guild,channel,user})) }else if (scope == "get"){ const data = instance.options.getUser("user",true) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-get").build(source,{guild,channel,user,data})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-get").build(origin,{guild,channel,user,data})) }else if (scope == "add"){ const data = instance.options.getUser("user",true) @@ -46,15 +46,15 @@ export const registerCommandResponders = async () => { {key:"user",value:user.username}, {key:"userid",value:user.id,hidden:true}, {key:"channelid",value:channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"reason",value:reason ?? "/"} ]) //manage stats - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:users-blacklisted",1,"increase") - await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:users-blacklisted",user.id,1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:users-blacklisted",1,"increase") + await opendiscord.statistics.get("opendiscord:user").setStat("opendiscord:users-blacklisted",user.id,1,"increase") - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-add").build(source,{guild,channel,user,data,reason})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-add").build(origin,{guild,channel,user,data,reason})) }else if (scope == "remove"){ const data = instance.options.getUser("user",true) @@ -65,14 +65,14 @@ export const registerCommandResponders = async () => { {key:"user",value:user.username}, {key:"userid",value:user.id,hidden:true}, {key:"channelid",value:channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"reason",value:reason ?? "/"} ]) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-remove").build(source,{guild,channel,user,data,reason})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-remove").build(origin,{guild,channel,user,data,reason})) } }), - new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,origin,cancel) => { const {guild,channel,user} = instance if (!guild) return @@ -83,21 +83,21 @@ export const registerCommandResponders = async () => { const reason = instance.options.getString("reason",false) //to logs - if (generalConfig.data.system.logs.enabled && generalConfig.data.system.messages.blacklisting.logs){ + if (generalConfig.data.logs.enabled && generalConfig.data.logs.logMessages.blacklisting.logs){ const logChannel = opendiscord.posts.get("opendiscord:logs") - if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-logs").build(source,{guild,channel,user,mode:scope,data,reason})) + if (logChannel) logChannel.send(await opendiscord.builders.messages.getSafe("opendiscord:blacklist-logs").build(origin,{guild,channel,user,mode:scope,data,reason})) } //to dm - if (generalConfig.data.system.messages.blacklisting.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:blacklist-dm").build(source,{guild,channel,user,mode:scope,data,reason})) + if (generalConfig.data.logs.logMessages.blacklisting.dm) await opendiscord.client.sendUserDm(user,await opendiscord.builders.messages.getSafe("opendiscord:blacklist-dm").build(origin,{guild,channel,user,mode:scope,data,reason})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { const scope = instance.options.getSubCommand() opendiscord.log(instance.user.displayName+" used the 'blacklist "+scope+"' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/claim.ts b/src/commands/claim.ts index 72c2b6c..555e573 100644 --- a/src/commands/claim.ts +++ b/src/commands/claim.ts @@ -1,119 +1,235 @@ /////////////////////////////////////// //CLAIM COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //CLAIM COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:claim",generalConfig.data.prefix,"claim")) opendiscord.responders.commands.get("opendiscord:claim").workers.add([ - new api.ODWorker("opendiscord:claim",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:claim",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.claim,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when already claimed - if (ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.claim"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - const claimUser = instance.options.getUser("user",false) ?? user - const reason = instance.options.getString("reason",false) - + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"claim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnclaimed = await openticketUtils.replyTicketMustBeUnclaimed(instance,origin,ticket) + if (!isTicketUnclaimed) return cancel() + //start claiming ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:claim-ticket").run(source,{guild,channel,user:claimUser,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build(source,{guild,channel,user:claimUser,ticket,reason})) + const reason = instance.options.getString("reason",false) + const claimUser = instance.options.getUser("user",false) ?? user + await opendiscord.actions.get("opendiscord:claim-ticket").run(origin,{guild,channel,user:claimUser,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build(origin,{guild,channel,user:claimUser,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"claim-message", + messageOrigin:origin, + messageAuthor:claimUser.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'claim' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //CLAIM TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:claim-ticket",/^od:claim-ticket/)) opendiscord.responders.buttons.get("opendiscord:claim-ticket").workers.add( - new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"claim") + if (!hasPerms) return cancel() - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:claim-ticket-ticket-message").activate(instance) - else if (originalSource == "unclaim-message") await opendiscord.verifybars.get("opendiscord:claim-ticket-unclaim-message").activate(instance) - else await instance.defer("update",false) + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/claim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnclaimed = await openticketUtils.replyTicketMustBeUnclaimed(instance,origin,ticket) + if (!isTicketUnclaimed) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:claim-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:claim-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unclaim-message"){ + //unclaim message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:claim-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:unclaim-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } }) ) } -export const registerModalResponders = async () => { - //CLAIM WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:claim-ticket-reason",/^od:claim-ticket-reason_/)) - opendiscord.responders.modals.get("opendiscord:claim-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:claim-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } - - const originalSource = instance.interaction.customId.split("_")[2] as Exclude - const reason = instance.values.getTextField("reason",true) - - //claim with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:claim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "unclaim-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:claim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:claim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - } +export async function registerVerifyBars(){ + //CLAIM TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:claim-ticket")) + opendiscord.verifybars.get("opendiscord:claim-ticket").workers.add([ + new api.ODWorker("opendiscord:claim-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"claim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/claim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnclaimed = await openticketUtils.replyTicketMustBeUnclaimed(instance,origin,ticket) + if (!isTicketUnclaimed) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start claiming ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unclaim-message"){ + //unclaim message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } + }else if (params.selectedButtonId == "accept"){ + //CLAIM TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:claim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unclaim-message"){ + //converted to claim message + await opendiscord.actions.get("opendiscord:claim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"claim-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //CLAIM WITH REASON (MODAL) + instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:claim-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ + //CLAIM WITH REASON MODAL RESPONDER + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:claim-ticket-reason",/^od:claim-ticket-reason\|([^|]+)\|([^|]+)/)) + opendiscord.responders.modals.get("opendiscord:claim-ticket-reason").workers.add([ + new api.ODWorker("opendiscord:claim-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:claim-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"claim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/claim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnclaimed = await openticketUtils.replyTicketMustBeUnclaimed(instance,origin,ticket) + if (!isTicketUnclaimed) return cancel() + + //fetch state details + const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType + + //start claiming ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:claim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) + + }else if (originalMsgType == "unclaim-message"){ + //converted to claim message + await opendiscord.actions.get("opendiscord:claim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"claim-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) + } }) ]) } \ No newline at end of file diff --git a/src/commands/clear.ts b/src/commands/clear.ts index 1de6e25..06093d9 100644 --- a/src/commands/clear.ts +++ b/src/commands/clear.ts @@ -1,35 +1,31 @@ /////////////////////////////////////// //CLEAR COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const clearMsgState = opendiscord.states.get("opendiscord:clear-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //CLEAR COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:clear",generalConfig.data.prefix,"clear")) opendiscord.responders.commands.get("opendiscord:clear").workers.add([ - new api.ODWorker("opendiscord:clear",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:clear",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - + + //responder checks //check permissions (only allow global admins: ticket admins aren't allowed to clear tickets) - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.clear,"support",user,member,channel,guild,{allowChannelUserScope:false,allowChannelRoleScope:false}) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"clear",{allowChannelUserScope:false,allowChannelRoleScope:false}) + if (!hasPerms) return cancel() + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + //fetch data const tempFilter = instance.options.getString("filter",false) const filter = (tempFilter) ? tempFilter.toLowerCase() as api.ODTicketClearFilter : "all" - const list: string[] = [] + const channelNameList: string[] = [] const ticketList = opendiscord.tickets.getAll().filter((ticket) => { if (filter == "all") return true else if (filter == "open" && ticket.get("opendiscord:open").value) return true @@ -43,46 +39,70 @@ export const registerCommandResponders = async () => { }) for (const ticket of ticketList){ const ticketChannel = await opendiscord.tickets.getTicketChannel(ticket) - if (ticketChannel) list.push("#"+ticketChannel.name) + if (ticketChannel) channelNameList.push("#"+ticketChannel.name) } //reply with clear verify await instance.defer(true) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:clear-verify-message").build(source,{guild,channel,user,filter,list})) + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:clear-verify-message").build(origin,{guild,channel,user,filter,list:channelNameList,inProgress:false})) + if (sentMsg.success) await clearMsgState.setMsgState({channel,message:sentMsg.message,user},{ + messageOrigin:origin, + clearFilter:filter, + clearChannelNameList:channelNameList + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'clear' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //CLEAR CONTINUE BUTTON RESPONDER - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:clear-continue",/^od:clear-continue_/)) + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:clear-continue","od:clear-continue")) opendiscord.responders.buttons.get("opendiscord:clear-continue").workers.add( - new api.ODWorker("opendiscord:clear-continue",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!guild || channel.isDMBased()) return - const originalSource = instance.interaction.customId.split("_")[1] as api.ODActionManagerIds_Default["opendiscord:clear-tickets"]["source"] - const filter = instance.interaction.customId.split("_")[2] as api.ODTicketClearFilter + new api.ODWorker("opendiscord:clear-continue",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //check message state + const state = await clearMsgState.getMsgState({channel,message,user}) + if (!state){ + //TODO TRANSLATION!!! + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:"This interaction is no longer valid or has expired. Use the command `{0}` instead. It is normal to receive this error after a major Open Ticket update.".replace("{0}","/clear"),layout:"simple",customTitle:"Message State Expired"})) + return cancel() + } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"clear") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + //fetch state details + const originalUser = ((state.userId) ? await opendiscord.client.fetchUser(state.userId) : user) ?? user + const originalOrigin = state.data.messageOrigin + const originalClearFilter = state.data.clearFilter + const originalClearChannelNameList = state.data.clearChannelNameList //start ticket clear - await instance.defer("update",true) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:clear-verify-message").build(originalOrigin,{guild,channel,user:originalUser,filter:originalClearFilter,list:originalClearChannelNameList,inProgress:true})) + const list: string[] = [] const ticketList = opendiscord.tickets.getAll().filter((ticket) => { - if (filter == "all") return true - else if (filter == "open" && ticket.get("opendiscord:open").value) return true - else if (filter == "closed" && ticket.get("opendiscord:closed").value) return true - else if (filter == "claimed" && ticket.get("opendiscord:claimed").value) return true - else if (filter == "pinned" && ticket.get("opendiscord:pinned").value) return true - else if (filter == "unclaimed" && !ticket.get("opendiscord:claimed").value) return true - else if (filter == "unpinned" && !ticket.get("opendiscord:pinned").value) return true - else if (filter == "autoclosed" && ticket.get("opendiscord:closed").value) return true + if (originalClearFilter == "all") return true + else if (originalClearFilter == "open" && ticket.get("opendiscord:open").value) return true + else if (originalClearFilter == "closed" && ticket.get("opendiscord:closed").value) return true + else if (originalClearFilter == "claimed" && ticket.get("opendiscord:claimed").value) return true + else if (originalClearFilter == "pinned" && ticket.get("opendiscord:pinned").value) return true + else if (originalClearFilter == "unclaimed" && !ticket.get("opendiscord:claimed").value) return true + else if (originalClearFilter == "unpinned" && !ticket.get("opendiscord:pinned").value) return true + else if (originalClearFilter == "autoclosed" && ticket.get("opendiscord:closed").value) return true else return false }) for (const ticket of ticketList){ @@ -90,8 +110,8 @@ export const registerButtonResponders = async () => { if (ticketChannel) list.push("#"+ticketChannel.name) } - await opendiscord.actions.get("opendiscord:clear-tickets").run(originalSource,{guild,channel,user,filter,list:ticketList}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:clear-message").build(originalSource,{guild,channel,user,filter,list})) + await opendiscord.actions.get("opendiscord:clear-tickets").run(originalOrigin,{guild,channel,user,filter:originalClearFilter,list:ticketList}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:clear-message").build(originalOrigin,{guild,channel,user,filter:originalClearFilter,list})) }) ) } \ No newline at end of file diff --git a/src/commands/close.ts b/src/commands/close.ts index 8f2c01b..64f9595 100644 --- a/src/commands/close.ts +++ b/src/commands/close.ts @@ -1,131 +1,243 @@ /////////////////////////////////////// //CLOSE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //CLOSE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:close",generalConfig.data.prefix,"close")) opendiscord.responders.commands.get("opendiscord:close").workers.add([ - new api.ODWorker("opendiscord:close",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:close",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.close,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when already closed - if (ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.close"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"close") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() + + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"close") + if (!messagesHaveBeenSent) return cancel() //start closing ticket await instance.defer(false) const reason = instance.options.getString("reason",false) - await opendiscord.actions.get("opendiscord:close-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build(source,{guild,channel,user,ticket,reason})) + await opendiscord.actions.get("opendiscord:close-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build(origin,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"close-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'close' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //CLOSE TICKET BUTTON RESPONDER - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:close-ticket",/^od:close-ticket_/)) + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:close-ticket",/^od:close-ticket/)) opendiscord.responders.buttons.get("opendiscord:close-ticket").workers.add( - new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,message,user,member} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"close") + if (!hasPerms) return cancel() - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:close-ticket-ticket-message").activate(instance) - else if (originalSource == "reopen-message") await opendiscord.verifybars.get("opendiscord:close-ticket-reopen-message").activate(instance) - else await instance.defer("update",false) + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/close") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() + + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"close") + if (!messagesHaveBeenSent) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:close-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:close-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "reopen-message"){ + //reopen message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:close-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:reopen-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } }) ) } -export const registerModalResponders = async () => { - //CLOSE WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:close-ticket-reason",/^od:close-ticket-reason_/)) - opendiscord.responders.modals.get("opendiscord:close-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:close-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } +export async function registerVerifyBars(){ + //CLOSE TICKET VERIFYBAR + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:close-ticket")) + opendiscord.verifybars.get("opendiscord:close-ticket").workers.add([ + new api.ODWorker("opendiscord:close-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance - const originalSource = instance.interaction.customId.split("_")[2] as Exclude - const reason = instance.values.getTextField("reason",true) - - //close with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:close-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "reopen-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:close-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:close-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"close") + if (!hasPerms) return cancel() + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/close") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() + + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"close") + if (!messagesHaveBeenSent) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start closing ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "reopen-message"){ + //reopen message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } + }else if (params.selectedButtonId == "accept"){ + //CLOSE TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:close-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "reopen-message"){ + //converted to close message + await opendiscord.actions.get("opendiscord:close-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"close-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //CLOSE WITH REASON (MODAL) + instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:close-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ + //CLOSE WITH REASON MODAL RESPONDER + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:close-ticket-reason",/^od:close-ticket-reason\|([^|]+)\|([^|]+)/)) + opendiscord.responders.modals.get("opendiscord:close-ticket-reason").workers.add([ + new api.ODWorker("opendiscord:close-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:close-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"close") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/close") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() + + //fetch state details + const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType + + //start closing ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:close-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) + + }else if (originalMsgType == "reopen-message"){ + //converted to close message + await opendiscord.actions.get("opendiscord:close-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"close-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) + } }) ]) } \ No newline at end of file diff --git a/src/commands/delete.ts b/src/commands/delete.ts index ebe4c32..8c153a8 100644 --- a/src/commands/delete.ts +++ b/src/commands/delete.ts @@ -1,64 +1,42 @@ /////////////////////////////////////// //DELETE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //DELETE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:delete",generalConfig.data.prefix,"delete")) opendiscord.responders.commands.get("opendiscord:delete").workers.add([ - new api.ODWorker("opendiscord:delete",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:delete",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.delete,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() - //return when not allowed because of missing messages - if (!permsResult.isAdmin && (!generalConfig.data.system.allowCloseBeforeMessage || !generalConfig.data.system.allowCloseBeforeAdminMessage)){ - const analysis = await opendiscord.transcripts.collector.ticketUserMessagesAnalysis(ticket,guild,channel) - if (analysis && !generalConfig.data.system.allowCloseBeforeMessage && analysis.totalMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - if (analysis && !generalConfig.data.system.allowCloseBeforeAdminMessage && analysis.adminMessages < 1){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,layout:"simple",error:lang.getTranslation("errors.descriptions.closeBeforeAdminMessage"),customTitle:lang.getTranslation("errors.titles.noPermissions")})) - return cancel() - } - } + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketOpen = await openticketUtils.replyTicketMustBeOpen(instance,origin,ticket) + if (!isTicketOpen) return cancel() - const reason = instance.options.getString("reason",false) - const withoutTranscript = instance.options.getBoolean("notranscript",false) ?? false + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"delete") + if (!messagesHaveBeenSent) return cancel() //don't allow deleteWithoutTranscript to non-global-admins when enabled - if (withoutTranscript && generalConfig.data.system.adminOnlyDeleteWithoutTranscript){ + const withoutTranscript = instance.options.getBoolean("notranscript",false) ?? false + if (withoutTranscript && generalConfig.data.ticketSystem.adminOnlyDeleteWithoutTranscript){ if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) return cancel() @@ -67,84 +45,227 @@ export const registerCommandResponders = async () => { //start deleting ticket await instance.defer(false) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(source,{guild,channel,user,ticket,reason})) - await opendiscord.actions.get("opendiscord:delete-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript}) + const reason = instance.options.getString("reason",false) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(origin,{guild,channel,user,ticket,reason})) + await opendiscord.actions.get("opendiscord:delete-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript}) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'delete' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //DELETE TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:delete-ticket",/^od:delete-ticket/)) opendiscord.responders.buttons.get("opendiscord:delete-ticket").workers.add( - new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,message,user,member} = instance - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:delete-ticket-ticket-message").activate(instance) - else if (originalSource == "close-message") await opendiscord.verifybars.get("opendiscord:delete-ticket-close-message").activate(instance) - else if (originalSource == "reopen-message") await opendiscord.verifybars.get("opendiscord:delete-ticket-reopen-message").activate(instance) - else if (originalSource == "autoclose-message") await opendiscord.verifybars.get("opendiscord:delete-ticket-autoclose-message").activate(instance) - else await instance.defer("update",false) + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/delete") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"delete") + if (!messagesHaveBeenSent) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:delete-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:delete-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "close-message"){ + //close message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:delete-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:close-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "reopen-message"){ + //reopen message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:delete-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:reopen-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "autoclose-message"){ + //autoclose message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:delete-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:autoclose-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket})) + } }) ) } -export const registerModalResponders = async () => { - //REOPEN WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:delete-ticket-reason",/^od:delete-ticket-reason_/)) +export async function registerVerifyBars(){ + //DELETE TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:delete-ticket")) + opendiscord.verifybars.get("opendiscord:delete-ticket").workers.add([ + new api.ODWorker("opendiscord:delete-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/delete") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const messagesHaveBeenSent = await openticketUtils.replyMessageMustBeSentBeforeClose(instance,origin,ticket,"delete") + if (!messagesHaveBeenSent) return cancel() + + //don't allow deleteWithoutTranscript to non-global-admins when enabled + const withoutTranscript = (params.selectedButtonId == "accept-without-transcript") + if (withoutTranscript && generalConfig.data.ticketSystem.adminOnlyDeleteWithoutTranscript){ + if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild,{allowChannelRoleScope:false,allowChannelUserScope:false,allowGlobalRoleScope:true,allowGlobalUserScope:true}))){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) + return cancel() + } + } + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start deleting ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "close-message"){ + //close message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "reopen-message"){ + //reopen message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "autoclose-message"){ + //autoclose message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("verifybar",{guild,channel,user:originalUser,ticket})) + } + }else if (params.selectedButtonId == "accept" || params.selectedButtonId == "accept-without-transcript"){ + //DELETE TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true,withoutTranscript}) + ticket.get("opendiscord:for-deletion").value = true //disable ticket message buttons + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) + + }else if (originalMsgType == "close-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason:null})) + + }else if (originalMsgType == "reopen-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason:null})) + + }else if (originalMsgType == "autoclose-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false,withoutTranscript}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason:null})) + + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //DELETE WITH REASON (MODAL) + instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:delete-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ + //DELETE WITH REASON MODAL RESPONDER + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:delete-ticket-reason",/^od:delete-ticket-reason\|([^|]+)\|([^|]+)/)) opendiscord.responders.modals.get("opendiscord:delete-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:delete-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } + new api.ODWorker("opendiscord:delete-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance - const originalSource = instance.interaction.customId.split("_")[2] as Exclude + const match = /^od:delete-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"delete") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/delete") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + //fetch state details const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType - //delete with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true,withoutTranscript:false}) - //update ticket (for ticket message) => no-await doesn't wait for the action to set this variable - ticket.get("opendiscord:for-deletion").value = true + //start deleting ticket + //don't await DELETE action => else it will update the message after the channel has been deleted + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason,sendMessage:true,withoutTranscript:false}) + ticket.get("opendiscord:for-deletion").value = true //disable ticket message buttons await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "close-message"){ - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("other",{guild,channel,user,ticket,reason})) - }else if (originalSource == "reopen-message"){ - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("other",{guild,channel,user,ticket,reason})) - }else if (originalSource == "autoclose-message"){ - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - //don't await DELETE action => else it will update the message after the channel has been deleted - opendiscord.actions.get("opendiscord:delete-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true,withoutTranscript:false}) + + }else if (originalMsgType == "close-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + + }else if (originalMsgType == "reopen-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + + }else if (originalMsgType == "autoclose-message"){ + //converted to delete message + opendiscord.actions.get("opendiscord:delete-ticket").run(originalMsgType,{guild,channel,user,ticket,reason,sendMessage:false,withoutTranscript:false}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:delete-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + } }) ]) diff --git a/src/commands/help.ts b/src/commands/help.ts index a8eb799..1ad49a2 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,50 +1,46 @@ /////////////////////////////////////// //HELP COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //HELP COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:help",generalConfig.data.prefix,"help")) opendiscord.responders.commands.get("opendiscord:help").workers.add([ - new api.ODWorker("opendiscord:help",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:help",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.help,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"help") + if (!hasPerms) return cancel() //calculate slash/text mode for help menu let mode: "slash"|"text" - if (generalConfig.data.slashCommands && generalConfig.data.textCommands) mode = (generalConfig.data.system.preferSlashOverText) ? "slash" : "text" + if (generalConfig.data.slashCommands && generalConfig.data.textCommands) mode = (generalConfig.data.ticketSystem.preferSlashOverText) ? "slash" : "text" else if (!generalConfig.data.slashCommands) mode = "text" else mode = "slash" - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:help-menu").build(source,{mode,page:0})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:help-menu").build(origin,{mode,page:0})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'help' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //HELP MENU SWITCH BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:help-menu-switch",/^od:help-menu-switch_(slash|text)/)) opendiscord.responders.buttons.get("opendiscord:help-menu-switch").workers.add( - new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,origin,cancel) => { const mode = instance.interaction.customId.split("_")[1] as "slash"|"text" const pageButton = instance.getMessageComponent("button",/^od:help-menu-page_([0-9]+)/) const currentPage = (pageButton && pageButton.customId) ? Number(pageButton.customId.split("_")[1]) : 0 @@ -59,7 +55,7 @@ export const registerButtonResponders = async () => { //HELP MENU PREVIOUS BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:help-menu-previous",/^od:help-menu-previous/)) opendiscord.responders.buttons.get("opendiscord:help-menu-previous").workers.add( - new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,origin,cancel) => { const switchButton = instance.getMessageComponent("button",/^od:help-menu-switch_(slash|text)/) const pageButton = instance.getMessageComponent("button",/^od:help-menu-page_([0-9]+)/) const currentPage = (pageButton && pageButton.customId) ? Number(pageButton.customId.split("_")[1]) : 0 @@ -73,7 +69,7 @@ export const registerButtonResponders = async () => { //HELP MENU NEXT BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:help-menu-next",/^od:help-menu-next/)) opendiscord.responders.buttons.get("opendiscord:help-menu-next").workers.add( - new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:update-help-menu",0,async (instance,params,origin,cancel) => { const switchButton = instance.getMessageComponent("button",/^od:help-menu-switch_(slash|text)/) const pageButton = instance.getMessageComponent("button",/^od:help-menu-page_([0-9]+)/) const currentPage = (pageButton && pageButton.customId) ? Number(pageButton.customId.split("_")[1]) : 0 diff --git a/src/commands/move.ts b/src/commands/move.ts index 93ad8ed..3ec418f 100644 --- a/src/commands/move.ts +++ b/src/commands/move.ts @@ -1,48 +1,36 @@ /////////////////////////////////////// //MOVE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //MOVE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:move",generalConfig.data.prefix,"move")) opendiscord.responders.commands.get("opendiscord:move").workers.add([ - new api.ODWorker("opendiscord:move",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:move",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.move,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check if in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"move") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data const id = instance.options.getString("id",true) const reason = instance.options.getString("reason",false) - const option = opendiscord.options.get(id) + //return if unknown option if (!option || !(option instanceof api.ODTicketOption)){ instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.titles.unknownOption"),layout:"simple"})) @@ -56,15 +44,15 @@ export const registerCommandResponders = async () => { //start moving ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:move-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false,data:option}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:move-message").build(source,{guild,channel,user,ticket,reason,data:option})) + await opendiscord.actions.get("opendiscord:move-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,data:option}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:move-message").build(origin,{guild,channel,user,ticket,reason,data:option})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'move' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/panel.ts b/src/commands/panel.ts index cb84407..ff5356d 100644 --- a/src/commands/panel.ts +++ b/src/commands/panel.ts @@ -1,42 +1,41 @@ /////////////////////////////////////// //STATS COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //PANEL COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:panel",generalConfig.data.prefix,/^panel/)) opendiscord.responders.commands.get("opendiscord:panel").workers.add([ - new api.ODWorker("opendiscord:panel",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:panel",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.panel,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild || instance.channel.type == discord.ChannelType.GroupDM){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } - //get panel data + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"panel") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + //fetch data const id = instance.options.getString("id",true) const panel = opendiscord.panels.get(id) if (!panel){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-panel-unknown").build(source,{guild,channel,user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-panel-unknown").build(origin,{guild,channel,user})) return cancel() } - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:panel-ready").build(source,{guild,channel,user,panel})) - const panelMessage = await instance.channel.send((await opendiscord.builders.messages.getSafe("opendiscord:panel").build(source,{guild,channel,user,panel})).message) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:panel-ready").build(origin,{guild,channel,user,panel})) + const panelMessage = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:panel").build(origin,{guild,channel,user,panel})).message) //add panel to database (this way, the bot knows where all panels are located) const globalDatabase = opendiscord.databases.get("opendiscord:global") @@ -47,12 +46,12 @@ export const registerCommandResponders = async () => { await globalDatabase.set("opendiscord:panel-update",panelMessage.channel.id+"_"+panelMessage.id,panel.id.value) } }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'panel' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/pin.ts b/src/commands/pin.ts index f1a8499..8ca9fc0 100644 --- a/src/commands/pin.ts +++ b/src/commands/pin.ts @@ -1,151 +1,235 @@ /////////////////////////////////////// //PIN COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //PIN COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:pin",generalConfig.data.prefix,"pin")) opendiscord.responders.commands.get("opendiscord:pin").workers.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.pin - - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:pin",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:pin",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.pin,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when already pinned - if (ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.pin"),layout:"simple"})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"pin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnpinned = await openticketUtils.replyTicketMustBeUnpinned(instance,origin,ticket) + if (!isTicketUnpinned) return cancel() - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - const reason = instance.options.getString("reason",false) - //start pinning ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:pin-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build(source,{guild,channel,user,ticket,reason})) + const reason = instance.options.getString("reason",false) + await opendiscord.actions.get("opendiscord:pin-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build(origin,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"pin-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'pin' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //PIN TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:pin-ticket",/^od:pin-ticket/)) opendiscord.responders.buttons.get("opendiscord:pin-ticket").workers.add( - new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"pin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:pin-ticket-ticket-message").activate(instance) - else if (originalSource == "unpin-message") await opendiscord.verifybars.get("opendiscord:pin-ticket-unpin-message").activate(instance) - else await instance.defer("update",false) + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/pin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnpinned = await openticketUtils.replyTicketMustBeUnpinned(instance,origin,ticket) + if (!isTicketUnpinned) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:pin-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:pin-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unpin-message"){ + //unpin message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:pin-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:unpin-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } }) ) } -export const registerModalResponders = async () => { - //PIN WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:pin-ticket-reason",/^od:pin-ticket-reason_/)) - opendiscord.responders.modals.get("opendiscord:pin-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:pin-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } - const originalSource = instance.interaction.customId.split("_")[2] as Exclude - const reason = instance.values.getTextField("reason",true) - - //pin with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:pin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "unpin-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:pin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:pin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - } +export async function registerVerifyBars(){ + //PIN TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:pin-ticket")) + opendiscord.verifybars.get("opendiscord:pin-ticket").workers.add([ + new api.ODWorker("opendiscord:pin-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"pin") + if (!hasPerms) return cancel() + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/pin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnpinned = await openticketUtils.replyTicketMustBeUnpinned(instance,origin,ticket) + if (!isTicketUnpinned) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start pinning ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unpin-message"){ + //unpin message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } + }else if (params.selectedButtonId == "accept"){ + //PIN TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:pin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "unpin-message"){ + //converted to pin message + await opendiscord.actions.get("opendiscord:pin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"pin-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //PIN WITH REASON (MODAL) + instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:pin-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ + //PIN WITH REASON MODAL RESPONDER + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:pin-ticket-reason",/^od:pin-ticket-reason\|([^|]+)\|([^|]+)/)) + opendiscord.responders.modals.get("opendiscord:pin-ticket-reason").workers.add([ + new api.ODWorker("opendiscord:pin-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:pin-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"pin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/pin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketUnpinned = await openticketUtils.replyTicketMustBeUnpinned(instance,origin,ticket) + if (!isTicketUnpinned) return cancel() + + //fetch state details + const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType + + //start pinning ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:pin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) + + }else if (originalMsgType == "unpin-message"){ + //converted to pin message + await opendiscord.actions.get("opendiscord:pin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"pin-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) + } }) ]) } \ No newline at end of file diff --git a/src/commands/priority.ts b/src/commands/priority.ts index 39812ca..2125e41 100644 --- a/src/commands/priority.ts +++ b/src/commands/priority.ts @@ -1,44 +1,30 @@ /////////////////////////////////////// //PRIORITY COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //PRIORITY COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:priority",generalConfig.data.prefix,"priority")) opendiscord.responders.commands.get("opendiscord:priority").workers.add([ - new api.ODWorker("opendiscord:priority",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:priority",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.priority,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"priority") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() //subcommands const scope = instance.options.getSubCommand() @@ -56,21 +42,21 @@ export const registerCommandResponders = async () => { //start changing ticket priority await instance.defer(false) - await opendiscord.actions.get("opendiscord:update-ticket-priority").run(source,{guild,channel,user,ticket,newPriority:priority,sendMessage:false,reason}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:priority-set").build(source,{guild,channel,user,ticket,priority,reason})) + await opendiscord.actions.get("opendiscord:update-ticket-priority").run(origin,{guild,channel,user,ticket,newPriority:priority,sendMessage:false,reason}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:priority-set").build(origin,{guild,channel,user,ticket,priority,reason})) }else if (scope == "get"){ const priority = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:priority-get").build(source,{guild,channel,user,ticket,priority})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:priority-get").build(origin,{guild,channel,user,ticket,priority})) } }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { const scope = instance.options.getSubCommand() opendiscord.log(instance.user.displayName+" used the 'priority "+scope+"' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/remove.ts b/src/commands/remove.ts index c6ec472..554178d 100644 --- a/src/commands/remove.ts +++ b/src/commands/remove.ts @@ -1,45 +1,32 @@ /////////////////////////////////////// //REMOVE COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //REMOVE COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:remove",generalConfig.data.prefix,"remove")) opendiscord.responders.commands.get("opendiscord:remove").workers.add([ - new api.ODWorker("opendiscord:remove",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:remove",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.remove,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"remove") + if (!hasPerms) return cancel() - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data const data = instance.options.getUser("user",true) const reason = instance.options.getString("reason",false) @@ -52,15 +39,15 @@ export const registerCommandResponders = async () => { //start removing user from ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:remove-ticket-user").run(source,{guild,channel,user,ticket,reason,sendMessage:false,data}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:remove-message").build(source,{guild,channel,user,ticket,reason,data})) + await opendiscord.actions.get("opendiscord:remove-ticket-user").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,data}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:remove-message").build(origin,{guild,channel,user,ticket,reason,data})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'remove' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/rename.ts b/src/commands/rename.ts index 7bbee26..6b2dee6 100644 --- a/src/commands/rename.ts +++ b/src/commands/rename.ts @@ -1,59 +1,46 @@ /////////////////////////////////////// //RENAME COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //RENAME COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:rename",generalConfig.data.prefix,"rename")) opendiscord.responders.commands.get("opendiscord:rename").workers.add([ - new api.ODWorker("opendiscord:rename",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:rename",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.rename,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"rename") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data const name = instance.options.getString("name",true) const reason = instance.options.getString("reason",false) //start renaming ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:rename-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false,data:name}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:rename-message").build(source,{guild,channel,user,ticket,reason,data:name})) + await opendiscord.actions.get("opendiscord:rename-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,data:name}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:rename-message").build(origin,{guild,channel,user,ticket,reason,data:name})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'rename' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/reopen.ts b/src/commands/reopen.ts index fb86a30..2ba0772 100644 --- a/src/commands/reopen.ts +++ b/src/commands/reopen.ts @@ -1,123 +1,261 @@ /////////////////////////////////////// //REOPEN COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //REOPEN COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:reopen",generalConfig.data.prefix,"reopen")) opendiscord.responders.commands.get("opendiscord:reopen").workers.add([ - new api.ODWorker("opendiscord:reopen",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:reopen",0,async (instance,params,origin,cancel) => { const {guild,channel,user,member} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.reopen,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when not closed - if (!ticket.get("opendiscord:closed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.reopen"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - const reason = instance.options.getString("reason",false) + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"reopen") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClosed = await openticketUtils.replyTicketMustBeClosed(instance,origin,ticket) + if (!isTicketClosed) return cancel() //start reopening ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(source,{guild,channel,user,ticket,reason})) + const reason = instance.options.getString("reason",false) + await opendiscord.actions.get("opendiscord:reopen-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(origin,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"reopen-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'reopen' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //REOPEN TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:reopen-ticket",/^od:reopen-ticket/)) opendiscord.responders.buttons.get("opendiscord:reopen-ticket").workers.add( - new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,message,user,member} = instance - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:reopen-ticket-ticket-message").activate(instance) - else if (originalSource == "close-message") await opendiscord.verifybars.get("opendiscord:reopen-ticket-close-message").activate(instance) - else if (originalSource == "autoclose-message") await opendiscord.verifybars.get("opendiscord:reopen-ticket-autoclose-message").activate(instance) - else await instance.defer("update",false) + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"reopen") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/reopen") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClosed = await openticketUtils.replyTicketMustBeClosed(instance,origin,ticket) + if (!isTicketClosed) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:reopen-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:reopen-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "close-message"){ + //close message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:reopen-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:close-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "autoclose-message"){ + //autoclose message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:reopen-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:autoclose-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket})) + } }) ) } -export const registerModalResponders = async () => { - //REOPEN WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:reopen-ticket-reason",/^od:reopen-ticket-reason_/)) - opendiscord.responders.modals.get("opendiscord:reopen-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:reopen-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } - - const originalSource = instance.interaction.customId.split("_")[2] as Exclude - const reason = instance.values.getTextField("reason",true) - - //reopen with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "close-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("other",{guild,channel,user,ticket,reason})) - }else if (originalSource == "autoclose-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) - } +export async function registerVerifyBars(){ + //REOPEN TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:reopen-ticket")) + opendiscord.verifybars.get("opendiscord:reopen-ticket").workers.add([ + new api.ODWorker("opendiscord:reopen-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"reopen") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/reopen") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClosed = await openticketUtils.replyTicketMustBeClosed(instance,origin,ticket) + if (!isTicketClosed) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start reopening ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "close-message"){ + //close message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:close-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + }else if (originalMsgType == "autoclose-message"){ + //autoclose message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("verifybar",{guild,channel,user:originalUser,ticket})) + } + }else if (params.selectedButtonId == "accept"){ + //REOPEN TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "close-message"){ + //converted to reopen message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"reopen-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + }else if (originalMsgType == "autoclose-message"){ + //converted to reopen message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"reopen-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //REOPEN WITH REASON (MODAL) + instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:reopen-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ + //REOPEN WITH REASON MODAL RESPONDER + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:reopen-ticket-reason",/^od:reopen-ticket-reason\|([^|]+)\|([^|]+)/)) + opendiscord.responders.modals.get("opendiscord:reopen-ticket-reason").workers.add([ + new api.ODWorker("opendiscord:reopen-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:reopen-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"reopen") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/reopen") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClosed = await openticketUtils.replyTicketMustBeClosed(instance,origin,ticket) + if (!isTicketClosed) return cancel() + + //fetch state details + const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType + + //start reopening ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) + + }else if (originalMsgType == "close-message"){ + //converted to reopen message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"reopen-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) + }else if (originalMsgType == "autoclose-message"){ + //converted to reopen message + await opendiscord.actions.get("opendiscord:reopen-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:reopen-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"reopen-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) + } }) ]) } \ No newline at end of file diff --git a/src/commands/role.ts b/src/commands/role.ts index e9de633..58dbc17 100644 --- a/src/commands/role.ts +++ b/src/commands/role.ts @@ -1,30 +1,28 @@ /////////////////////////////////////// //ROLE BUTTON (not command) /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //ROLE OPTION BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:role-option",/^od:role-option_/)) opendiscord.responders.buttons.get("opendiscord:role-option").workers.add( - new api.ODWorker("opendiscord:role-option",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:role-option",0,async (instance,params,origin,cancel) => { const {guild,channel,user} = instance - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() //get option data const optionId = instance.interaction.customId.split("_")[2] const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODRoleOption)){ //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) return cancel() } @@ -33,10 +31,10 @@ export const registerButtonResponders = async () => { const res = await opendiscord.actions.get("opendiscord:reaction-role").run("panel-button",{guild,user,option,overwriteMode:null}) if (!res.result || !res.role){ //error - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild,channel:instance.channel,user,error:"Unable to receive role update data from worker!",layout:"advanced"})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user,error:"Unable to receive role update data from worker!",layout:"advanced"})) return cancel() } - if (generalConfig.data.system.replyOnReactionRole) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:reaction-role").build("panel-button",{guild,user,role:res.role,result:res.result})) + if (generalConfig.data.ticketSystem.replyOnReactionRole) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:reaction-role").build("panel-button",{guild,user,role:res.role,result:res.result})) }) ) } \ No newline at end of file diff --git a/src/commands/stats.ts b/src/commands/stats.ts index 46be1fb..d6d6da6 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -1,117 +1,58 @@ /////////////////////////////////////// //STATS COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //STATS COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:stats",generalConfig.data.prefix,/^stats/)) opendiscord.responders.commands.get("opendiscord:stats").workers.add([ - new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { - const permissionMode = generalConfig.data.system.permissions.stats - - //command is disabled - if (permissionMode == "none"){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - } - - //reset subcommand is owner/developer only - if (instance.options.getSubCommand() == "reset"){ - if (!opendiscord.permissions.hasPermissions("owner",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["owner","developer"]})) - return cancel() - }else return - } - - //permissions for normal scopes - if (permissionMode == "everyone") return - else if (permissionMode == "admin"){ - if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) - return cancel() - }else return - }else{ - if (!instance.guild || !instance.member){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) - return cancel() - } - const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) - if (!role){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) - return cancel() - } - if (!role.members.has(instance.member.id)){ - //no permissions - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - }else return - } - }), - new api.ODWorker("opendiscord:stats",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:stats",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance //check permissions - if (generalConfig.data.system.permissions.stats === "none"){ - //command is disabled - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) - return cancel() - - }else if (instance.options.getSubCommand() === "reset" && !opendiscord.permissions.hasPermissions("owner",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ + if (instance.options.getSubCommand() === "reset" && !opendiscord.permissions.hasPermissions("owner",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ //reset --> owner/developer role is required - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["owner","developer"]})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["owner","developer"]})) return cancel() - }else{ //default permissions check - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.stats,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"stats") + if (!hasPerms) return cancel() } - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() //subcommands const scope = instance.options.getSubCommand() if (!scope || (scope != "global" && scope != "ticket" && scope != "user" && scope != "reset")) return if (scope == "global"){ - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-global").build(source,{guild,channel,user})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-global").build(origin,{guild,channel,user})) }else if (scope == "ticket"){ const id = instance.options.getChannel("ticket",false)?.id ?? channel.id const ticket = opendiscord.tickets.get(id) - if (ticket) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-ticket").build(source,{guild,channel,user,scopeData:ticket})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-ticket-unknown").build(source,{guild,channel,user,id})) + if (ticket) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-ticket").build(origin,{guild,channel,user,scopeData:ticket})) + else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-ticket-unknown").build(origin,{guild,channel,user,id})) }else if (scope == "user"){ const statsUser = instance.options.getUser("user",false) ?? user - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-user").build(source,{guild,channel,user,scopeData:statsUser})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-user").build(origin,{guild,channel,user,scopeData:statsUser})) }else if (scope == "reset"){ const reason = instance.options.getString("reason",false) - opendiscord.stats.reset() - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-reset").build(source,{guild,channel,user,reason})) - + opendiscord.statistics.reset() + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:stats-reset").build(origin,{guild,channel,user,reason})) } }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { const scope = instance.options.getSubCommand() let data: string if (scope == "ticket"){ @@ -123,7 +64,7 @@ export const registerCommandResponders = async () => { {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source}, + {key:"method",value:origin}, {key:"data",value:data}, ]) }) diff --git a/src/commands/ticket.ts b/src/commands/ticket.ts index ce26854..92c1c86 100644 --- a/src/commands/ticket.ts +++ b/src/commands/ticket.ts @@ -1,173 +1,161 @@ /////////////////////////////////////// //TICKET COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages -async function checkTicketCreationPerms(instance:api.ODButtonResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance|api.ODCommandResponderInstance,source:api.ODActionManagerIds_Default["opendiscord:create-ticket-permissions"]["source"],guild:discord.Guild,user:discord.User,option:api.ODTicketOption){ +async function checkTicketCreationPerms(instance:api.ODButtonResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance|api.ODCommandResponderInstance,origin:api.ODActionManagerIdMappings["opendiscord:create-ticket-permissions"]["origin"],guild:discord.Guild,user:discord.User,option:api.ODTicketOption){ //check ticket permissions - const permsRes = await opendiscord.actions.get("opendiscord:create-ticket-permissions").run(source,{guild,user,option}) + const permsRes = await opendiscord.actions.get("opendiscord:create-ticket-permissions").run(origin,{guild,user,option}) if (!permsRes.valid && instance.channel){ //error - const newSource = (source === "slash" || source === "text") ? source : "other" - if (permsRes.reason == "blacklist") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-blacklisted").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user})) - else if (permsRes.reason == "cooldown") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-cooldown").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,until:permsRes.cooldownUntil})) - else if (permsRes.reason == "global-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global"})) - else if (permsRes.reason == "global-user-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global-user"})) - else if (permsRes.reason == "option-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option"})) - else if (permsRes.reason == "option-user-limit") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option-user"})) - else if (permsRes.reason == "custom") instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,layout:"simple",error:permsRes.customReason ?? lang.getTranslation("errors.descriptions.unableToCreateTicket")+" `Unknown invalid_permission_reason => no reason specified by plugin`",customTitle:lang.getTranslation("errors.titles.permissionError")})) - else instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newSource,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Unknown invalid_permission reason => calculation failed #1",layout:"advanced"})) + const newOrigin = (origin === "slash" || origin === "text") ? origin : "other" + if (permsRes.reason == "blacklist") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-blacklisted").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user})) + else if (permsRes.reason == "cooldown") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-cooldown").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,until:permsRes.cooldownUntil})) + else if (permsRes.reason == "global-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global"})) + else if (permsRes.reason == "global-user-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global-user"})) + else if (permsRes.reason == "option-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option"})) + else if (permsRes.reason == "option-user-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option-user"})) + else if (permsRes.reason == "custom") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,layout:"simple",error:permsRes.customReason ?? lang.getTranslation("errors.descriptions.unableToCreateTicket")+" `Unknown invalid_permission_reason => no reason specified by plugin`",customTitle:lang.getTranslation("errors.titles.permissionError")})) + else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Unknown invalid_permission reason => calculation failed #1",layout:"advanced"})) return false }else return true } -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //TICKET COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:ticket",generalConfig.data.prefix,/^ticket/)) opendiscord.responders.commands.get("opendiscord:ticket").workers.add([ - new api.ODWorker("opendiscord:ticket",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:ticket",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.ticket,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"ticket") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() //get option data const optionId = instance.options.getString("id",true) const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODTicketOption)){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) return cancel() } //start ticket creation if (option.exists("opendiscord:questions") && option.get("opendiscord:questions").value.length > 0){ - //send modal - instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:ticket-questions").build(source,{guild,channel,user,option})) + //SEND MODAL + instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:ticket-questions").build(origin,{guild,channel,user,option})) }else{ - //check ticket permissions - if (!(await checkTicketCreationPerms(instance,source,guild,user,option))) return cancel() - - //create ticket + //check ticket permissions (modals need check after submit) + if (!(await checkTicketCreationPerms(instance,origin,guild,user,option))) return cancel() + + //CREATE TICKET await instance.defer(true) - const res = await opendiscord.actions.get("opendiscord:create-ticket").run(source,{guild,user,answers:[],option}) + const res = await opendiscord.actions.get("opendiscord:create-ticket").run(origin,{guild,user,answers:[],option}) if (!res.channel || !res.ticket){ //error - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) return cancel() } - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build(source,{guild,channel:res.channel,user,ticket:res.ticket})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build(origin,{guild,channel:res.channel,user,ticket:res.ticket})) } }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'ticket' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //TICKET OPTION BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:ticket-option",/^od:ticket-option_/)) opendiscord.responders.buttons.get("opendiscord:ticket-option").workers.add( - new api.ODWorker("opendiscord:ticket-option",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:ticket-option",0,async (instance,params,origin,cancel) => { const {guild,channel,user} = instance - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } + + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() - //get option + //get option data const optionId = instance.interaction.customId.split("_")[2] const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODTicketOption)){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) return cancel() } //start ticket creation if (option.exists("opendiscord:questions") && option.get("opendiscord:questions").value.length > 0){ - //send modal + //SEND MODAL instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:ticket-questions").build("panel-button",{guild,channel,user,option})) }else{ - //check ticket permissions + //check ticket permissions (modals need check after submit) if (!(await checkTicketCreationPerms(instance,"panel-button",guild,user,option))) return cancel() - - //create ticket - await instance.defer((generalConfig.data.system.replyOnTicketCreation) ? "reply" : "update",true) + + //CREATE TICKET + await instance.defer((generalConfig.data.ticketSystem.replyOnTicketCreation) ? "reply" : "update",true) const res = await opendiscord.actions.get("opendiscord:create-ticket").run("panel-button",{guild,user,answers:[],option}) if (!res.channel || !res.ticket){ //error - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) return cancel() } - if (generalConfig.data.system.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build("panel-button",{guild,channel:res.channel,user,ticket:res.ticket})) + if (generalConfig.data.ticketSystem.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build("panel-button",{guild,channel:res.channel,user,ticket:res.ticket})) } }) ) } -export const registerDropdownResponders = async () => { +export async function registerDropdownResponders(){ //PANEL DROPDOWN TICKETS DROPDOWN RESPONDER opendiscord.responders.dropdowns.add(new api.ODDropdownResponder("opendiscord:panel-dropdown-tickets",/^od:panel-dropdown_/)) opendiscord.responders.dropdowns.get("opendiscord:panel-dropdown-tickets").workers.add( - new api.ODWorker("opendiscord:panel-dropdown-tickets",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:panel-dropdown-tickets",0,async (instance,params,origin,cancel) => { const {guild,channel,user} = instance - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel:instance.channel,user:instance.user})) - return cancel() - } + + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() - //get option + //get option data const optionId = instance.values.getStringValues()[0].split("_")[2] const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODTicketOption)){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) return cancel() } //start ticket creation if (option.exists("opendiscord:questions") && option.get("opendiscord:questions").value.length > 0){ - //send modal + //SEND MODAL instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:ticket-questions").build("panel-dropdown",{guild,channel,user,option})) }else{ - //check ticket permissions + //check ticket permissions (modals need check after submit) if (!(await checkTicketCreationPerms(instance,"panel-dropdown",guild,user,option))) return cancel() - - //create ticket - await instance.defer((generalConfig.data.system.replyOnTicketCreation) ? "reply" : "update",true) + + //CREATE TICKET + await instance.defer((generalConfig.data.ticketSystem.replyOnTicketCreation) ? "reply" : "update",true) const res = await opendiscord.actions.get("opendiscord:create-ticket").run("panel-dropdown",{guild,user,answers:[],option}) if (!res.channel || !res.ticket){ //error - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) return cancel() } - if (generalConfig.data.system.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build("panel-dropdown",{guild,channel:res.channel,user,ticket:res.ticket})) + if (generalConfig.data.ticketSystem.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build("panel-dropdown",{guild,channel:res.channel,user,ticket:res.ticket})) } //update panel after dropdown usage (reset panel choice) @@ -181,31 +169,29 @@ export const registerDropdownResponders = async () => { ) } -export const registerModalResponders = async () => { +export async function registerModalResponders(){ //TICKET QUESTIONS RESPONDER opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:ticket-questions",/^od:ticket-questions_/)) opendiscord.responders.modals.get("opendiscord:ticket-questions").workers.add([ - new api.ODWorker("opendiscord:ticket-questions",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:ticket-questions",0,async (instance,params,origin,cancel) => { const {guild,channel,user} = instance - await instance.defer((generalConfig.data.system.replyOnTicketCreation) ? "reply" : "update",true) - if (!channel) throw new api.ODSystemError("The 'Ticket Questions' modal requires a channel for responding!") - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } + + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() - const originalSource = instance.interaction.customId.split("_")[2] as ("panel-button"|"panel-dropdown"|"slash"|"text"|"other") - - //get option + //get option data const optionId = instance.interaction.customId.split("_")[1] const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODTicketOption)){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(source,{guild:instance.guild,channel,user:instance.user})) + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel,user:instance.user})) return cancel() } + //check ticket permissions (modals need check after submit) + const originalOrigin = instance.interaction.customId.split("_")[2] as ("panel-button"|"panel-dropdown"|"slash"|"text"|"other") + if (!(await checkTicketCreationPerms(instance,originalOrigin,guild,user,option))) return cancel() + //get answers const answers: {id:string,name:string,type:"short"|"paragraph",value:string|null}[] = [] option.get("opendiscord:questions").value.forEach((id) => { @@ -227,18 +213,17 @@ export const registerModalResponders = async () => { }) } }) + + await instance.defer((generalConfig.data.ticketSystem.replyOnTicketCreation) ? "reply" : "update",true) - //check ticket permissions - if (!(await checkTicketCreationPerms(instance,originalSource,guild,user,option))) return cancel() - - //create ticket - const res = await opendiscord.actions.get("opendiscord:create-ticket").run(originalSource,{guild,user,answers,option}) + //CREATE TICKET + const res = await opendiscord.actions.get("opendiscord:create-ticket").run(originalOrigin,{guild,user,answers,option}) if (!res.channel || !res.ticket){ //error - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild,channel,user,error:"Unable to receive ticket or channel from callback! #2",layout:"advanced"})) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:"Unable to receive ticket or channel from callback! #2",layout:"advanced"})) return cancel() } - if (generalConfig.data.system.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build(originalSource,{guild,channel:res.channel,user,ticket:res.ticket})) + if (generalConfig.data.ticketSystem.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build(originalOrigin,{guild,channel:res.channel,user,ticket:res.ticket})) }) ]) } \ No newline at end of file diff --git a/src/commands/topic.ts b/src/commands/topic.ts index 79992c2..b2b15ad 100644 --- a/src/commands/topic.ts +++ b/src/commands/topic.ts @@ -1,44 +1,29 @@ /////////////////////////////////////// //TOPIC COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //TOPIC COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:topic",generalConfig.data.prefix,"topic")) opendiscord.responders.commands.get("opendiscord:topic").workers.add([ - new api.ODWorker("opendiscord:topic",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:topic",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.topic,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/Server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"topic") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() //subcommands const scope = instance.options.getSubCommand() @@ -48,16 +33,16 @@ export const registerCommandResponders = async () => { const topic = instance.options.getString("topic",true) //start changing ticket topic await instance.defer(false) - await opendiscord.actions.get("opendiscord:update-ticket-topic").run(source,{guild,channel,user,ticket,newTopic:topic,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:topic-set").build(source,{guild,channel,user,ticket,topic})) + await opendiscord.actions.get("opendiscord:update-ticket-topic").run(origin,{guild,channel,user,ticket,newTopic:topic,sendMessage:false}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:topic-set").build(origin,{guild,channel,user,ticket,topic})) } }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'topic set' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/transfer.ts b/src/commands/transfer.ts index 5aa2298..7857ef8 100644 --- a/src/commands/transfer.ts +++ b/src/commands/transfer.ts @@ -1,60 +1,47 @@ /////////////////////////////////////// //TRANSFER COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //TRANSFER COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:transfer",generalConfig.data.prefix,"transfer")) opendiscord.responders.commands.get("opendiscord:transfer").workers.add([ - new api.ODWorker("opendiscord:transfer",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:transfer",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.transfer,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/Server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"transfer") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + //fetch data const oldCreator = await opendiscord.tickets.getTicketUser(ticket,"creator") ?? opendiscord.client.client.user const newCreator = instance.options.getUser("user",true) const reason = instance.options.getString("reason",false) //start transferring ticket ownership await instance.defer(false) - await opendiscord.actions.get("opendiscord:transfer-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false,newCreator}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:transfer-message").build(source,{guild,channel,user,ticket,oldCreator,newCreator,reason})) + await opendiscord.actions.get("opendiscord:transfer-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false,newCreator}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:transfer-message").build(origin,{guild,channel,user,ticket,oldCreator,newCreator,reason})) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'transfer' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) diff --git a/src/commands/unclaim.ts b/src/commands/unclaim.ts index 73cb608..6250061 100644 --- a/src/commands/unclaim.ts +++ b/src/commands/unclaim.ts @@ -1,116 +1,233 @@ /////////////////////////////////////// //UNCLAIM COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //UNCLAIM COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:unclaim",generalConfig.data.prefix,"unclaim")) opendiscord.responders.commands.get("opendiscord:unclaim").workers.add([ - new api.ODWorker("opendiscord:unclaim",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:unclaim",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.unclaim,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/Server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when not claimed - if (!ticket.get("opendiscord:claimed").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unclaim"),layout:"simple"})) - return cancel() - } - - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - const reason = instance.options.getString("reason",false) - + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unclaim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClaimed = await openticketUtils.replyTicketMustBeClaimed(instance,origin,ticket) + if (!isTicketClaimed) return cancel() + //start unclaiming ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build(source,{guild,channel,user,ticket,reason})) + const reason = instance.options.getString("reason",false) + await opendiscord.actions.get("opendiscord:unclaim-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build(origin,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unclaim-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'unclaim' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //UNCLAIM TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:unclaim-ticket",/^od:unclaim-ticket/)) opendiscord.responders.buttons.get("opendiscord:unclaim-ticket").workers.add( - new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:unclaim-ticket-ticket-message").activate(instance) - else if (originalSource == "claim-message") await opendiscord.verifybars.get("opendiscord:unclaim-ticket-claim-message").activate(instance) - else await instance.defer("update",false) + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unclaim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unclaim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClaimed = await openticketUtils.replyTicketMustBeClaimed(instance,origin,ticket) + if (!isTicketClaimed) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:unclaim-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:unclaim-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "claim-message"){ + //claim message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:unclaim-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:claim-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } }) ) } -export const registerModalResponders = async () => { +export async function registerVerifyBars(){ + //UNCLAIM TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unclaim-ticket")) + opendiscord.verifybars.get("opendiscord:unclaim-ticket").workers.add([ + new api.ODWorker("opendiscord:unclaim-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unclaim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unclaim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClaimed = await openticketUtils.replyTicketMustBeClaimed(instance,origin,ticket) + if (!isTicketClaimed) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start unclaiming ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "claim-message"){ + //claim message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:claim-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } + }else if (params.selectedButtonId == "accept"){ + //UNCLAIM TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "claim-message"){ + //converted to unclaim message + await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unclaim-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //UNCLAIM WITH REASON (MODAL) + instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:unclaim-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ //UNCLAIM WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:unclaim-ticket-reason",/^od:unclaim-ticket-reason_/)) + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:unclaim-ticket-reason",/^od:unclaim-ticket-reason\|([^|]+)\|([^|]+)/)) opendiscord.responders.modals.get("opendiscord:unclaim-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:unclaim-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } + new api.ODWorker("opendiscord:unclaim-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:unclaim-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unclaim") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() - const originalSource = instance.interaction.customId.split("_")[2] as Exclude + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unclaim") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketClaimed = await openticketUtils.replyTicketMustBeClaimed(instance,origin,ticket) + if (!isTicketClaimed) return cancel() + + //fetch state details const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType - //unclaim with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) + //start claiming ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "claim-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) + + }else if (originalMsgType == "claim-message"){ + //converted to unclaim message + await opendiscord.actions.get("opendiscord:unclaim-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unclaim-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unclaim-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) } }) ]) diff --git a/src/commands/unpin.ts b/src/commands/unpin.ts index 30a9579..a49d772 100644 --- a/src/commands/unpin.ts +++ b/src/commands/unpin.ts @@ -1,116 +1,233 @@ /////////////////////////////////////// //UNPIN COMMAND /////////////////////////////////////// -import {opendiscord, api, utilities} from "../index" +import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const lang = opendiscord.languages +const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") -export const registerCommandResponders = async () => { +export async function registerCommandResponders(){ //UNPIN COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:unpin",generalConfig.data.prefix,"unpin")) opendiscord.responders.commands.get("opendiscord:unpin").workers.add([ - new api.ODWorker("opendiscord:unpin",0,async (instance,params,source,cancel) => { + new api.ODWorker("opendiscord:unpin",0,async (instance,params,origin,cancel) => { const {user,member,channel,guild} = instance - - //check permissions - const permsResult = await opendiscord.permissions.checkCommandPerms(generalConfig.data.system.permissions.unpin,"support",user,member,channel,guild) - if (!permsResult.hasPerms){ - if (permsResult.reason == "not-in-server") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild,channel,user,permissions:["support"]})) - return cancel() - } - - //check is in guild/Server - if (!guild){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) - return cancel() - } - - //check if ticket exists - const ticket = opendiscord.tickets.get(channel.id) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return cancel() - } - - //return when not pinned yet - if (!ticket.get("opendiscord:pinned").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build("button",{guild,channel,user,error:opendiscord.languages.getTranslation("errors.actionInvalid.unpin"),layout:"simple"})) - return cancel() - } - //return when busy - if (ticket.get("opendiscord:busy").value){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) - return cancel() - } - - const reason = instance.options.getString("reason",false) - - //start unpinning ticket + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unpin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketPinned = await openticketUtils.replyTicketMustBePinned(instance,origin,ticket) + if (!isTicketPinned) return cancel() + + //start unpining ticket await instance.defer(false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build(source,{guild,channel,user,ticket,reason})) + const reason = instance.options.getString("reason",false) + await opendiscord.actions.get("opendiscord:unpin-ticket").run(origin,{guild,channel,user,ticket,reason,sendMessage:false}) + + //send message & set state + const sentMsg = await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build(origin,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unpin-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) }), - new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'unpin' command!","info",[ {key:"user",value:instance.user.username}, {key:"userid",value:instance.user.id,hidden:true}, {key:"channelid",value:instance.channel.id,hidden:true}, - {key:"method",value:source} + {key:"method",value:origin} ]) }) ]) } -export const registerButtonResponders = async () => { +export async function registerButtonResponders(){ //UNPIN TICKET BUTTON RESPONDER opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:unpin-ticket",/^od:unpin-ticket/)) opendiscord.responders.buttons.get("opendiscord:unpin-ticket").workers.add( - new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,source,cancel) => { - const originalSource = instance.interaction.customId.split("_")[1] as Exclude + new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unpin") + if (!hasPerms) return cancel() - if (originalSource == "ticket-message") await opendiscord.verifybars.get("opendiscord:unpin-ticket-ticket-message").activate(instance) - else if (originalSource == "pin-message") await opendiscord.verifybars.get("opendiscord:unpin-ticket-pin-message").activate(instance) - else await instance.defer("update",false) + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unpin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketPinned = await openticketUtils.replyTicketMustBePinned(instance,origin,ticket) + if (!isTicketPinned) return cancel() + + //fetch state details + const verifybar = opendiscord.verifybars.get("opendiscord:unpin-ticket") + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //send verifybar + if (generalConfig.data.ticketSystem.disableVerifyBars){ + //verifybar disabled, directly run response + await verifybar.activate(instance,"accept") + + }else if (originalMsgType == "ticket-message"){ + //ticket message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:unpin-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:ticket-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "pin-message"){ + //pin message verifybar + const modifiedMsg = opendiscord.components.modifiers.get("opendiscord:unpin-ticket-verifybar").modify(opendiscord.builders.messages.getSafe("opendiscord:pin-message"),originalMsgType,{guild,channel,user,verifybar}) + await instance.update(await modifiedMsg.build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } }) ) } -export const registerModalResponders = async () => { +export async function registerVerifyBars(){ + //UNPIN TICKET + opendiscord.verifybars.add(new api.ODVerifyBar("opendiscord:unpin-ticket")) + opendiscord.verifybars.get("opendiscord:unpin-ticket").workers.add([ + new api.ODWorker("opendiscord:unpin-ticket",0,async (instance,params,origin,cancel) => { + const {user,member,channel,guild,message} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unpin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unpin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketPinned = await openticketUtils.replyTicketMustBePinned(instance,origin,ticket) + if (!isTicketPinned) return cancel() + + //fetch state details + const originalUser = ((state.data.messageAuthor) ? await opendiscord.client.fetchUser(state.data.messageAuthor) : user) ?? user + const originalReason = state.data.messageReason ?? null + const originalMsgType = state.data.messageType + + //start unpinning ticket + if (params.selectedButtonId == "cancel"){ + //CANCEL + if (originalMsgType == "ticket-message"){ + //ticket message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "pin-message"){ + //pin message + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:pin-message").build("verifybar",{guild,channel,user:originalUser,ticket,reason:originalReason})) + } + }else if (params.selectedButtonId == "accept"){ + //UNPIN TICKET + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) + await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("verifybar",{guild,channel,user,ticket})) + }else if (originalMsgType == "pin-message"){ + //converted to unpin message + await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build("verifybar",{guild,channel,user,ticket,reason:null})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unpin-message", + messageOrigin:origin, + messageAuthor:user.id, + messageReason:null + },sentMsg.ephemeral) + } + }else if (params.selectedButtonId == "accept-with-reason"){ + //UNPIN WITH REASON (MODAL) + instance.modal(await opendiscord.builders.modals.getSafe("opendiscord:unpin-ticket-reason").build("other",{guild,channel,user,ticket,message})) + } + }) + ]) +} + +export async function registerModalResponders(){ //UNPIN WITH REASON MODAL RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:unpin-ticket-reason",/^od:unpin-ticket-reason_/)) + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:unpin-ticket-reason",/^od:unpin-ticket-reason\|([^|]+)\|([^|]+)/)) opendiscord.responders.modals.get("opendiscord:unpin-ticket-reason").workers.add([ - new api.ODWorker("opendiscord:unpin-ticket-reason",0,async (instance,params,source,cancel) => { - const {guild,channel,user} = instance - if (!channel) return - if (!guild){ - //error - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build(source,{channel,user:instance.user})) - return cancel() - } - const ticket = opendiscord.tickets.get(instance.interaction.customId.split("_")[1]) - if (!ticket || channel.isDMBased()){ - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) - return - } + new api.ODWorker("opendiscord:unpin-ticket-reason",0,async (instance,params,origin,cancel) => { + const {guild,user} = instance + + const match = /^od:unpin-ticket-reason\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const channel = await opendiscord.client.fetchTextChannel(match[1]) + const message = await opendiscord.client.fetchChannelMessage(match[1],match[2]) + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"unpin") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() - const originalSource = instance.interaction.customId.split("_")[2] as Exclude + const state = await openticketUtils.replyInteractiveMessageState(instance,origin,channel,message,"/unpin") + if (!state) return cancel() + + const ticket = await openticketUtils.replyIsTicket(instance,origin) + if (!ticket) return cancel() + + const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) + if (!isAvailable) return cancel() + + const isTicketPinned = await openticketUtils.replyTicketMustBePinned(instance,origin,ticket) + if (!isTicketPinned) return cancel() + + //fetch state details const reason = instance.values.getTextField("reason",true) + const originalMsgOrigin = state.data.messageOrigin + const originalMsgType = state.data.messageType - //unpin with reason - if (originalSource == "ticket-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) + //start pinning ticket + await instance.defer("update",false) + + if (originalMsgType == "ticket-message"){ + //ticket message + await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:true}) await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})) - }else if (originalSource == "pin-message"){ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:false}) - await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build("other",{guild,channel,user,ticket,reason})) - }else{ - await instance.defer("update",false) - await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalSource,{guild,channel,user,ticket,reason,sendMessage:true}) + + }else if (originalMsgType == "pin-message"){ + //converted to unpin message + await opendiscord.actions.get("opendiscord:unpin-ticket").run(originalMsgType,{guild,channel,user,ticket,reason:null,sendMessage:false}) + const sentMsg = await instance.update(await opendiscord.builders.messages.getSafe("opendiscord:unpin-message").build(originalMsgType,{guild,channel,user,ticket,reason})) + if (sentMsg.success) await interactiveMsgState.setMsgState({channel,message:sentMsg.message},{ + messageType:"unpin-message", + messageOrigin:originalMsgOrigin, + messageAuthor:user.id, + messageReason:reason + },sentMsg.ephemeral) } }) ]) diff --git a/src/components/_INFO.md b/src/components/_INFO.md new file mode 100644 index 0000000..ccf360e --- /dev/null +++ b/src/components/_INFO.md @@ -0,0 +1,13 @@ +Open Discord Components: +------------------------ + +The "Components" system will be the new way of creating message and modal templates in Open Discord with support for Discord components v2. + +Messages, embeds, modals, buttons, etc are currently constructed using the "Builders" system located in `./src/builders/...`. + +This will be replaced with the new component factories in `./src/components/...`. + +### Timing +Messages, modals & components will slowly be migrated to the new system during upcoming updates. The full migration will happen after `Open Ticket v4.3` and the HTML Transcripts v3 are ready for it. + +For more information, contact [DJj123dj](https://github.com/DJj123dj) on Discord, Github or via email \ No newline at end of file diff --git a/src/components/verifybarModifiers.ts b/src/components/verifybarModifiers.ts new file mode 100644 index 0000000..60d35ed --- /dev/null +++ b/src/components/verifybarModifiers.ts @@ -0,0 +1,138 @@ +/////////////////////////////////////// +//VERIFYBAR MESSAGE MODIFIERS +/////////////////////////////////////// +import {opendiscord, api, utilities} from "../index.js" +import * as discord from "discord.js" + +const modifiers = opendiscord.components.modifiers +const lang = opendiscord.languages +const generalConfig = opendiscord.configs.get("opendiscord:general") + +export async function addVerifyButton(instance:api.ODMessageInstance|api.ODComponentFactoryInstance,params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},defaultButtonType:"โœ…"|"โŒ",useDefaultLabels:boolean,verifyButtonId:string,customLabel?:string,customColor?:api.ODValidButtonColor,customEmoji?:string){ + const {guild,channel,user,verifybar} = params + if (instance instanceof api.ODMessageInstance){ + //message builders + instance.addComponent(await opendiscord.builders.buttons.getSafe("opendiscord:verifybar-button").build("verifybar",{ + guild,channel,user,verifybar, + defaultButtonType,useDefaultLabels,customColor,customEmoji,customLabel, + verifyButtonId + })) + }else{ + //message components + throw new api.ODSystemError("registerAllVerifyBarModifiers addVerifyButton() => verifybar doesn't support ODComponents v2 yet!") + //const message = instance.getComponent() + //if (!message) return + //const actionRow: api.ODActionRowComponent|null = message.getComponentsOfType("action-row")[0] + //if (actionRow) actionRow.addComponent(new api.ODButtonComponent("opendiscord:verifybar-button",{ + // //TODO + //}),"end") + } +} + +export async function registerAllVerifyBarModifiers(){ + //CLOSE TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:close-ticket-verifybar")) + modifiers.get("opendiscord:close-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:close-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or close with reason + await addVerifyButton(instance,params,"โŒ",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"โœ…",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.close")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"โœ…",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","โœ๏ธ") + })) + + //REOPEN TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:reopen-ticket-verifybar")) + modifiers.get("opendiscord:reopen-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:reopen-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or reopen with reason + await addVerifyButton(instance,params,"โŒ",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"โœ…",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.reopen")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"โœ…",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","โœ๏ธ") + })) + + //DELETE TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:delete-ticket-verifybar")) + modifiers.get("opendiscord:delete-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:delete-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or delete with reason or without transcript + await addVerifyButton(instance,params,"โŒ",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"โœ…",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.delete")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"โœ…",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","โœ๏ธ") + if (generalConfig.data.ticketSystem.enableDeleteWithoutTranscript) await addVerifyButton(instance,params,"โœ…",false,api.ODVerifyButtonId.AcceptWithoutTranscript,lang.getTranslation("actions.buttons.withoutTranscript"),"red","๐Ÿ“„") + })) + + //CLAIM TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:claim-ticket-verifybar")) + modifiers.get("opendiscord:claim-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:claim-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or claim with reason + await addVerifyButton(instance,params,"โŒ",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"โœ…",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.claim")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"โœ…",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","โœ๏ธ") + })) + + //UNCLAIM TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:unclaim-ticket-verifybar")) + modifiers.get("opendiscord:unclaim-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:unclaim-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or unclaim with reason + await addVerifyButton(instance,params,"โŒ",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"โœ…",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.unclaim")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"โœ…",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","โœ๏ธ") + })) + + //PIN TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:pin-ticket-verifybar")) + modifiers.get("opendiscord:pin-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:pin-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or pin with reason + await addVerifyButton(instance,params,"โŒ",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"โœ…",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.pin")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"โœ…",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","โœ๏ธ") + })) + + //UNPIN TICKET VERIFYBAR + modifiers.add(new api.ODMessageComponentModifier("opendiscord:unpin-ticket-verifybar")) + modifiers.get("opendiscord:unpin-ticket-verifybar").workers.add(new api.ODWorker("opendiscord:unpin-ticket-verifybar",100,async (instance,params,origin,cancel) => { + if (instance instanceof api.ODMessageInstance){ + instance.data.components = [] //clear actionrow + }else{ + throw new api.ODSystemError("registerAllVerifyBarModifiers()... => verifybars don't support ODComponents v2 yet!") + } + + //cancel, accept or unpin with reason + await addVerifyButton(instance,params,"โŒ",true,api.ODVerifyButtonId.Cancel) + await addVerifyButton(instance,params,"โœ…",true,api.ODVerifyButtonId.Accept,lang.getTranslation("actions.buttons.unpin")) + if (generalConfig.data.ticketSystem.enableTicketActionWithReason) await addVerifyButton(instance,params,"โœ…",false,api.ODVerifyButtonId.AcceptWithReason,lang.getTranslation("actions.buttons.withReason"),"blue","โœ๏ธ") + })) +} \ No newline at end of file diff --git a/src/core/api.ts b/src/core/api.ts new file mode 100644 index 0000000..d9df578 --- /dev/null +++ b/src/core/api.ts @@ -0,0 +1,43 @@ +//EXPORT FRAMEWORK +export * from "@open-discord-bots/framework/api" + +//EXPORT OPEN TICKET MAPPINGS +export * from "./mappings/action.js" +export * from "./mappings/base.js" +export * from "./mappings/builder.js" +export * from "./mappings/checker.js" +export * from "./mappings/client.js" +export * from "./mappings/code.js" +export * from "./mappings/component.js" +export * from "./mappings/config.js" +export * from "./mappings/console.js" +export * from "./mappings/cooldown.js" +export * from "./mappings/database.js" +export * from "./mappings/event.js" +export * from "./mappings/flag.js" +export * from "./mappings/fuse.js" +export * from "./mappings/helpmenu.js" +export * from "./mappings/language.js" +export * from "./mappings/permission.js" +export * from "./mappings/plugin.js" +export * from "./mappings/post.js" +export * from "./mappings/progressbar.js" +export * from "./mappings/responder.js" +export * from "./mappings/session.js" +export * from "./mappings/startscreen.js" +export * from "./mappings/state.js" +export * from "./mappings/statistic.js" +export * from "./mappings/verifybar.js" + +//EXPORT OPENTICKET MODULES +export * from "./api/blacklist.js" +export * from "./api/option.js" +export * from "./api/panel.js" +export * from "./api/priority.js" +export * from "./api/question.js" +export * from "./api/role.js" +export * from "./api/ticket.js" +export * from "./api/transcript.js" + +//EXPORT MAIN MODULE +export { ODOpenTicketMain } from "./main.js" \ No newline at end of file diff --git a/src/core/api/api.ts b/src/core/api/api.ts deleted file mode 100644 index 7286d8f..0000000 --- a/src/core/api/api.ts +++ /dev/null @@ -1,64 +0,0 @@ -//MAIN MODULE -export * from "./main" - -//BASE MODULES -export * from "./modules/base" -export * from "./modules/event" -export * from "./modules/config" -export * from "./modules/database" -export * from "./modules/language" -export * from "./modules/flag" -export * from "./modules/console" -export * from "./modules/defaults" -export * from "./modules/plugin" -export * from "./modules/checker" -export * from "./modules/client" -export * from "./modules/worker" -export * from "./modules/builder" -export * from "./modules/responder" -export * from "./modules/action" -export * from "./modules/permission" -export * from "./modules/helpmenu" -export * from "./modules/session" -export * from "./modules/stat" -export * from "./modules/code" -export * from "./modules/cooldown" -export * from "./modules/post" -export * from "./modules/verifybar" -export * from "./modules/progressbar" -export * from "./modules/startscreen" - -//OPENTICKET DEFAULT MODULES -export * from "./defaults/base" -export * from "./defaults/event" -export * from "./defaults/config" -export * from "./defaults/database" -export * from "./defaults/plugin" -export * from "./defaults/checker" -export * from "./defaults/client" -export * from "./defaults/language" -export * from "./defaults/builder" -export * from "./defaults/responder" -export * from "./defaults/action" -export * from "./defaults/flag" -export * from "./defaults/permission" -export * from "./defaults/helpmenu" -export * from "./defaults/session" -export * from "./defaults/stat" -export * from "./defaults/worker" -export * from "./defaults/code" -export * from "./defaults/cooldown" -export * from "./defaults/post" -export * from "./defaults/progressbar" -export * from "./defaults/startscreen" -export * from "./defaults/console" - -//OPENTICKET MODULES -export * from "./openticket/question" -export * from "./openticket/option" -export * from "./openticket/panel" -export * from "./openticket/ticket" -export * from "./openticket/blacklist" -export * from "./openticket/transcript" -export * from "./openticket/role" -export * from "./openticket/priority" \ No newline at end of file diff --git a/src/core/api/openticket/blacklist.ts b/src/core/api/blacklist.ts similarity index 69% rename from src/core/api/openticket/blacklist.ts rename to src/core/api/blacklist.ts index d7dedcd..391f526 100644 --- a/src/core/api/openticket/blacklist.ts +++ b/src/core/api/blacklist.ts @@ -1,8 +1,7 @@ /////////////////////////////////////// //OPENTICKET BLACKLIST MODULE /////////////////////////////////////// -import { ODManager, ODManagerData, ODValidId } from "../modules/base" -import { ODDebugger } from "../modules/console" +import * as api from "@open-discord-bots/framework/api" /**## ODBlacklist `class` * This is an Open Ticket blacklisted user. @@ -11,22 +10,22 @@ import { ODDebugger } from "../modules/console" * * Create this class & add it to the `ODBlacklistManager` to blacklist someone! */ -export class ODBlacklist extends ODManagerData { +export class ODBlacklist extends api.ODManagerData { /**The reason why this user got blacklisted. (optional) */ - #reason: string|null + private rawReason: string|null - constructor(id:ODValidId,reason:string|null){ + constructor(id:api.ODValidId,reason:string|null){ super(id) - this.#reason = reason + this.rawReason = reason } /**The reason why this user got blacklisted. (optional) */ set reason(reason:string|null) { - this.#reason = reason + this.rawReason = reason this._change() } get reason(){ - return this.#reason + return this.rawReason } } @@ -37,8 +36,8 @@ export class ODBlacklist extends ODManagerData { * * All `ODBlacklist`'s added, removed & edited in this list will be synced automatically with the database. */ -export class ODBlacklistManager extends ODManager { - constructor(debug:ODDebugger){ +export class ODBlacklistManager extends api.ODManager { + constructor(debug:api.ODDebugger){ super(debug,"blacklist") } } \ No newline at end of file diff --git a/src/core/api/defaults/base.ts b/src/core/api/defaults/base.ts deleted file mode 100644 index 2cfa279..0000000 --- a/src/core/api/defaults/base.ts +++ /dev/null @@ -1,45 +0,0 @@ -/////////////////////////////////////// -//BASE MODULE -/////////////////////////////////////// -import { ODVersion, ODVersionManager, ODValidId } from "../modules/base" - -/**## ODVersionManagerIds_Default `interface` - * This interface is a list of ids available in the `ODVersionManager` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODVersionManagerIds_Default { - "opendiscord:version":ODVersion, - "opendiscord:last-version":ODVersion, - "opendiscord:api":ODVersion, - "opendiscord:transcripts":ODVersion, - "opendiscord:livestatus":ODVersion -} - -/**## ODFlagManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODFlagManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.flags`! - */ -export class ODVersionManager_Default extends ODVersionManager { - get(id:VersionId): ODVersionManagerIds_Default[VersionId] - get(id:ODValidId): ODVersion|null - - get(id:ODValidId): ODVersion|null { - return super.get(id) - } - - remove(id:VersionId): ODVersionManagerIds_Default[VersionId] - remove(id:ODValidId): ODVersion|null - - remove(id:ODValidId): ODVersion|null { - return super.remove(id) - } - - exists(id:keyof ODVersionManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/builder.ts b/src/core/api/defaults/builder.ts deleted file mode 100644 index 96c652b..0000000 --- a/src/core/api/defaults/builder.ts +++ /dev/null @@ -1,549 +0,0 @@ -/////////////////////////////////////// -//DEFAULT BUILDER MODULE -/////////////////////////////////////// -import { ODValidButtonColor, ODValidId } from "../modules/base" -import { ODBuilderManager, ODButton, ODButtonInstance, ODButtonManager, ODDropdown, ODDropdownInstance, ODDropdownManager, ODEmbed, ODEmbedInstance, ODEmbedManager, ODFile, ODFileInstance, ODFileManager, ODMessage, ODMessageInstance, ODMessageManager, ODModal, ODModalInstance, ODModalManager } from "../modules/builder" -import { ODWorkerManager_Default } from "./worker" -import { ODTicket, ODTicketClearFilter } from "../openticket/ticket" -import { ODPermissionEmbedType } from "../defaults/permission" -import { ODTextCommandErrorInvalidOption, ODTextCommandErrorMissingOption, ODTextCommandErrorUnknownCommand } from "../modules/client" -import { ODPanel } from "../openticket/panel" -import { ODRoleOption, ODTicketOption, ODWebsiteOption } from "../openticket/option" -import { ODVerifyBar } from "../modules/verifybar" -import * as discord from "discord.js" -import { ODTranscriptCompiler, ODTranscriptCompilerCompileResult } from "../openticket/transcript" -import { ODRole, ODRoleUpdateResult } from "../openticket/role" -import { ODPriorityLevel } from "../openticket/priority" - -/**## ODBuilderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODBuilderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders`! - */ -export class ODBuilderManager_Default extends ODBuilderManager { - declare buttons: ODButtonManager_Default - declare dropdowns: ODDropdownManager_Default - declare files: ODFileManager_Default - declare embeds: ODEmbedManager_Default - declare messages: ODMessageManager_Default - declare modals: ODModalManager_Default -} - -/**## ODButtonManagerIds_Default `interface` - * This interface is a list of ids available in the `ODButtonManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODButtonManagerIds_Default { - "opendiscord:verifybar-success":{source:"verifybar"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,customData?:string,customColor?:ODValidButtonColor,customLabel?:string,customEmoji?:string},workers:"opendiscord:verifybar-success"}, - "opendiscord:verifybar-failure":{source:"verifybar"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,customData?:string,customColor?:ODValidButtonColor,customLabel?:string,customEmoji?:string},workers:"opendiscord:verifybar-failure"}, - - "opendiscord:error-ticket-deprecated-transcript":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{},workers:"opendiscord:error-ticket-deprecated-transcript"}, - - "opendiscord:help-menu-previous":{source:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-previous"}, - "opendiscord:help-menu-next":{source:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-next"}, - "opendiscord:help-menu-page":{source:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-page"} - "opendiscord:help-menu-switch":{source:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-switch"}, - - "opendiscord:ticket-option":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODTicketOption},workers:"opendiscord:ticket-option"}, - "opendiscord:website-option":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODWebsiteOption},workers:"opendiscord:website-option"}, - "opendiscord:role-option":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODRoleOption},workers:"opendiscord:role-option"} - - "opendiscord:visit-ticket":{source:"ticket-created"|"dm"|"logs"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:visit-ticket"}, - - "opendiscord:close-ticket":{source:"ticket-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:close-ticket"}, - "opendiscord:delete-ticket":{source:"ticket-message"|"close-message"|"autoclose-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:delete-ticket"}, - "opendiscord:reopen-ticket":{source:"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:reopen-ticket"}, - "opendiscord:claim-ticket":{source:"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:claim-ticket"}, - "opendiscord:unclaim-ticket":{source:"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unclaim-ticket"}, - "opendiscord:pin-ticket":{source:"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:pin-ticket"}, - "opendiscord:unpin-ticket":{source:"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unpin-ticket"}, - - "opendiscord:transcript-html-visit":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-visit"}, - "opendiscord:transcript-error-retry":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error-retry"}, - "opendiscord:transcript-error-continue":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error-continue"}, - - "opendiscord:clear-continue":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-continue"}, -} - -/**## ODButtonManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODButtonManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.buttons`! - */ -export class ODButtonManager_Default extends ODButtonManager { - get(id:ButtonId): ODButton_Default - get(id:ODValidId): ODButton|null - - get(id:ODValidId): ODButton|null { - return super.get(id) - } - - remove(id:ButtonId): ODButton_Default - remove(id:ODValidId): ODButton|null - - remove(id:ODValidId): ODButton|null { - return super.remove(id) - } - - exists(id:keyof ODButtonManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:ButtonId): ODButton_Default - getSafe(id:ODValidId): ODButton - - getSafe(id:ODValidId): ODButton { - return super.getSafe(id) - } -} - -/**## ODButton_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODButton class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODButton`'s! - */ -export class ODButton_Default extends ODButton { - declare workers: ODWorkerManager_Default -} - -/**## ODDropdownManagerIds_Default `interface` - * This interface is a list of ids available in the `ODDropdownManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODDropdownManagerIds_Default { - "opendiscord:panel-dropdown-tickets":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,options:ODTicketOption[]},workers:"opendiscord:panel-dropdown-tickets"} -} - -/**## ODDropdownManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODDropdownManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.dropdowns`! - */ -export class ODDropdownManager_Default extends ODDropdownManager { - get(id:DropdownId): ODDropdown_Default - get(id:ODValidId): ODDropdown|null - - get(id:ODValidId): ODDropdown|null { - return super.get(id) - } - - remove(id:DropdownId): ODDropdown_Default - remove(id:ODValidId): ODDropdown|null - - remove(id:ODValidId): ODDropdown|null { - return super.remove(id) - } - - exists(id:keyof ODDropdownManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:DropdownId): ODDropdown_Default - getSafe(id:ODValidId): ODDropdown - - getSafe(id:ODValidId): ODDropdown { - return super.getSafe(id) - } -} - -/**## ODDropdown_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODDropdown class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODDropdown`'s! - */ -export class ODDropdown_Default extends ODDropdown { - declare workers: ODWorkerManager_Default -} - -/**## ODFileManagerIds_Default `interface` - * This interface is a list of ids available in the `ODFileManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFileManagerIds_Default { - "opendiscord:text-transcript":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,result:ODTranscriptCompilerCompileResult},workers:"opendiscord:text-transcript"} -} - -/**## ODFileManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODFileManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.files`! - */ -export class ODFileManager_Default extends ODFileManager { - get(id:FileId): ODFile_Default - get(id:ODValidId): ODFile|null - - get(id:ODValidId): ODFile|null { - return super.get(id) - } - - remove(id:FileId): ODFile_Default - remove(id:ODValidId): ODFile|null - - remove(id:ODValidId): ODFile|null { - return super.remove(id) - } - - exists(id:keyof ODFileManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:FileId): ODFile_Default - getSafe(id:ODValidId): ODFile - - getSafe(id:ODValidId): ODFile { - return super.getSafe(id) - } -} - -/**## ODFile_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODFile class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODFile`'s! - */ -export class ODFile_Default extends ODFile { - declare workers: ODWorkerManager_Default -} - -/**## ODEmbedManagerIds_Default `interface` - * This interface is a list of ids available in the `ODEmbedManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODEmbedManagerIds_Default { - "opendiscord:error":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:string,layout:"simple"|"advanced",customTitle?:string},workers:"opendiscord:error"}, - "opendiscord:error-option-missing":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorMissingOption},workers:"opendiscord:error-option-missing"}, - "opendiscord:error-option-invalid":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorInvalidOption},workers:"opendiscord:error-option-invalid"}, - "opendiscord:error-unknown-command":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorUnknownCommand},workers:"opendiscord:error-unknown-command"}, - "opendiscord:error-no-permissions":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,permissions:ODPermissionEmbedType[]},workers:"opendiscord:error-no-permissions"}, - "opendiscord:error-no-permissions-cooldown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,until?:Date},workers:"opendiscord:error-no-permissions-cooldown"}, - "opendiscord:error-no-permissions-blacklisted":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-no-permissions-blacklisted"}, - "opendiscord:error-no-permissions-limits":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,limit:"global"|"global-user"|"option"|"option-user"},workers:"opendiscord:error-no-permissions-limits"}, - "opendiscord:error-responder-timeout":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-responder-timeout"}, - "opendiscord:error-ticket-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-unknown"}, - "opendiscord:error-ticket-deprecated":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-deprecated"}, - "opendiscord:error-option-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-option-unknown"}, - "opendiscord:error-panel-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-panel-unknown"}, - "opendiscord:error-not-in-guild":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-not-in-guild"}, - "opendiscord:error-channel-rename":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-rename"|"ticket-move"|"ticket-priority"|"ticket-transfer"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, - "opendiscord:error-ticket-busy":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-busy"}, - - "opendiscord:help-menu":{source:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu"}, - - "opendiscord:stats-global":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:stats-global"}, - "opendiscord:stats-ticket":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:ODTicket},workers:"opendiscord:stats-ticket"}, - "opendiscord:stats-user":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:discord.User},workers:"opendiscord:stats-user"|"opendiscord:easter-egg"}, - "opendiscord:stats-reset":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,reason:string|null},workers:"opendiscord:stats-reset"}, - "opendiscord:stats-ticket-unknown":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,id:string},workers:"opendiscord:stats-ticket-unknown"}, - - "opendiscord:panel":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel"}, - "opendiscord:ticket-created":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created"}, - "opendiscord:ticket-created-dm":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-dm"}, - "opendiscord:ticket-created-logs":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-logs"}, - "opendiscord:ticket-message":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-message"}, - "opendiscord:close-message":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:close-message"}, - "opendiscord:reopen-message":{source:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:reopen-message"}, - "opendiscord:delete-message":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:delete-message"}, - "opendiscord:claim-message":{source:"slash"|"text"|"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:claim-message"}, - "opendiscord:unclaim-message":{source:"slash"|"text"|"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unclaim-message"}, - "opendiscord:pin-message":{source:"slash"|"text"|"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:pin-message"}, - "opendiscord:unpin-message":{source:"slash"|"text"|"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unpin-message"}, - "opendiscord:rename-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:string},workers:"opendiscord:rename-message"}, - "opendiscord:move-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:ODTicketOption},workers:"opendiscord:move-message"}, - "opendiscord:add-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:add-message"}, - "opendiscord:remove-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:remove-message"}, - "opendiscord:ticket-action-dm":{source:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-dm"}, - "opendiscord:ticket-action-logs":{source:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-logs"}, - - "opendiscord:blacklist-view":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:blacklist-view"}, - "opendiscord:blacklist-get":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User},workers:"opendiscord:blacklist-get"}, - "opendiscord:blacklist-add":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-add"}, - "opendiscord:blacklist-remove":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-remove"} - "opendiscord:blacklist-dm":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-dm"}, - "opendiscord:blacklist-logs":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-logs"}, - - "opendiscord:transcript-text-ready":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{contents:string},null>,result:ODTranscriptCompilerCompileResult<{contents:string}>},workers:"opendiscord:transcript-text-ready"}, - "opendiscord:transcript-html-ready":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"}, - "opendiscord:transcript-html-progress":{source:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"}, - "opendiscord:transcript-error":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error"}, - - "opendiscord:reaction-role":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"}, - "opendiscord:reaction-role-dm":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"}, - "opendiscord:reaction-role-logs":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-logs"}, - - "opendiscord:clear-verify-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-verify-message"}, - "opendiscord:clear-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-message"}, - "opendiscord:clear-logs":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-logs"}, - - "opendiscord:autoclose-message":{source:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autoclose-message"}, - "opendiscord:autodelete-message":{source:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autodelete-message"}, - "opendiscord:autoclose-enable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autoclose-enable"}, - "opendiscord:autodelete-enable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autodelete-enable"}, - "opendiscord:autoclose-disable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autoclose-disable"}, - "opendiscord:autodelete-disable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autodelete-disable"}, - - "opendiscord:topic-set":{source:"slash"|"text"|"ticket-action"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,topic:string},workers:"opendiscord:topic-set"}, - "opendiscord:priority-set":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel,reason:string|null},workers:"opendiscord:priority-set"}, - "opendiscord:priority-get":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel},workers:"opendiscord:priority-get"}, - "opendiscord:transfer-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,oldCreator:discord.User,newCreator:discord.User,reason:string|null},workers:"opendiscord:transfer-message"}, -} - -/**## ODEmbedManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODEmbedManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.embeds`! - */ -export class ODEmbedManager_Default extends ODEmbedManager { - get(id:EmbedId): ODEmbed_Default - get(id:ODValidId): ODEmbed|null - - get(id:ODValidId): ODEmbed|null { - return super.get(id) - } - - remove(id:EmbedId): ODEmbed_Default - remove(id:ODValidId): ODEmbed|null - - remove(id:ODValidId): ODEmbed|null { - return super.remove(id) - } - - exists(id:keyof ODEmbedManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:EmbedId): ODEmbed_Default - getSafe(id:ODValidId): ODEmbed - - getSafe(id:ODValidId): ODEmbed { - return super.getSafe(id) - } -} - -/**## ODEmbed_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODEmbed class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODEmbed`'s! - */ -export class ODEmbed_Default extends ODEmbed { - declare workers: ODWorkerManager_Default -} - -/**## ODMessageManagerIds_Default `interface` - * This interface is a list of ids available in the `ODMessageManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODMessageManagerIds_Default { - "opendiscord:verifybar-ticket-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-ticket-message"}, - "opendiscord:verifybar-close-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-close-message"}, - "opendiscord:verifybar-reopen-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-reopen-message"}, - "opendiscord:verifybar-claim-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-claim-message"}, - "opendiscord:verifybar-unclaim-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-unclaim-message"}, - "opendiscord:verifybar-pin-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-pin-message"}, - "opendiscord:verifybar-unpin-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-unpin-message"} - "opendiscord:verifybar-autoclose-message":{source:"verifybar",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message},workers:"opendiscord:verifybar-autoclose-message"} - - "opendiscord:error":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:string,layout:"simple"|"advanced",customTitle?:string},workers:"opendiscord:error"}, - "opendiscord:error-option-missing":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorMissingOption},workers:"opendiscord:error-option-missing"}, - "opendiscord:error-option-invalid":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorInvalidOption},workers:"opendiscord:error-option-invalid"}, - "opendiscord:error-unknown-command":{source:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:ODTextCommandErrorUnknownCommand},workers:"opendiscord:error-unknown-command"}, - "opendiscord:error-no-permissions":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,permissions:ODPermissionEmbedType[]},workers:"opendiscord:error-no-permissions"}, - "opendiscord:error-no-permissions-cooldown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,until?:Date},workers:"opendiscord:error-no-permissions-cooldown"}, - "opendiscord:error-no-permissions-blacklisted":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-no-permissions-blacklisted"}, - "opendiscord:error-no-permissions-limits":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,limit:"global"|"global-user"|"option"|"option-user"},workers:"opendiscord:error-no-permissions-limits"}, - "opendiscord:error-responder-timeout":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-responder-timeout"}, - "opendiscord:error-ticket-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-unknown"}, - "opendiscord:error-ticket-deprecated":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-deprecated"}, - "opendiscord:error-option-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-option-unknown"}, - "opendiscord:error-panel-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-panel-unknown"}, - "opendiscord:error-not-in-guild":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-not-in-guild"}, - "opendiscord:error-channel-rename":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-rename"|"ticket-move"|"ticket-priority"|"ticket-transfer"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, - "opendiscord:error-ticket-busy":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-busy"}, - - "opendiscord:help-menu":{source:"slash"|"text"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu"}, - - "opendiscord:stats-global":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:stats-global"}, - "opendiscord:stats-ticket":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:ODTicket},workers:"opendiscord:stats-ticket"}, - "opendiscord:stats-user":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:discord.User},workers:"opendiscord:stats-user"|"opendiscord:easter-egg"}, - "opendiscord:stats-reset":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,reason:string|null},workers:"opendiscord:stats-reset"}, - "opendiscord:stats-ticket-unknown":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,id:string},workers:"opendiscord:stats-ticket-unknown"}, - - "opendiscord:panel":{source:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel-layout"|"opendiscord:panel-components"}, - "opendiscord:panel-ready":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel-ready"}, - - "opendiscord:ticket-created":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created"}, - "opendiscord:ticket-created-dm":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-dm"}, - "opendiscord:ticket-created-logs":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-logs"}, - "opendiscord:ticket-message":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-message-layout"|"opendiscord:ticket-message-components"|"opendiscord:ticket-message-disable-components"}, - "opendiscord:close-message":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:close-message"}, - "opendiscord:reopen-message":{source:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:reopen-message"}, - "opendiscord:delete-message":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:delete-message"}, - "opendiscord:claim-message":{source:"slash"|"text"|"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:claim-message"}, - "opendiscord:unclaim-message":{source:"slash"|"text"|"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unclaim-message"}, - "opendiscord:pin-message":{source:"slash"|"text"|"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:pin-message"}, - "opendiscord:unpin-message":{source:"slash"|"text"|"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unpin-message"}, - "opendiscord:rename-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:string},workers:"opendiscord:rename-message"}, - "opendiscord:move-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:ODTicketOption},workers:"opendiscord:move-message"}, - "opendiscord:add-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:add-message"}, - "opendiscord:remove-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:remove-message"}, - "opendiscord:ticket-action-dm":{source:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-dm"}, - "opendiscord:ticket-action-logs":{source:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-logs"}, - - "opendiscord:blacklist-view":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:blacklist-view"}, - "opendiscord:blacklist-get":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User},workers:"opendiscord:blacklist-get"}, - "opendiscord:blacklist-add":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-add"}, - "opendiscord:blacklist-remove":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-remove"}, - "opendiscord:blacklist-dm":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-dm"}, - "opendiscord:blacklist-logs":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-logs"}, - - "opendiscord:transcript-text-ready":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{contents:string},null>,result:ODTranscriptCompilerCompileResult<{contents:string}>},workers:"opendiscord:transcript-text-ready"}, - "opendiscord:transcript-html-ready":{source:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"}, - "opendiscord:transcript-html-progress":{source:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"}, - "opendiscord:transcript-error":{source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error"}, - - "opendiscord:reaction-role":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"}, - "opendiscord:reaction-role-dm":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"}, - "opendiscord:reaction-role-logs":{source:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-logs"}, - - "opendiscord:clear-verify-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-verify-message"}, - "opendiscord:clear-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-message"}, - "opendiscord:clear-logs":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-logs"}, - - "opendiscord:autoclose-message":{source:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autoclose-message"}, - "opendiscord:autodelete-message":{source:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autodelete-message"}, - "opendiscord:autoclose-enable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autoclose-enable"}, - "opendiscord:autodelete-enable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autodelete-enable"}, - "opendiscord:autoclose-disable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autoclose-disable"}, - "opendiscord:autodelete-disable":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autodelete-disable"}, - - "opendiscord:topic-set":{source:"slash"|"text"|"ticket-action"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,topic:string},workers:"opendiscord:topic-set"}, - "opendiscord:priority-set":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel,reason:string|null},workers:"opendiscord:priority-set"}, - "opendiscord:priority-get":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel},workers:"opendiscord:priority-get"}, - "opendiscord:transfer-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,oldCreator:discord.User,newCreator:discord.User,reason:string|null},workers:"opendiscord:transfer-message"}, -} - -/**## ODMessageManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODMessageManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.messages`! - */ -export class ODMessageManager_Default extends ODMessageManager { - get(id:MessageId): ODMessage_Default - get(id:ODValidId): ODMessage|null - - get(id:ODValidId): ODMessage|null { - return super.get(id) - } - - remove(id:MessageId): ODMessage_Default - remove(id:ODValidId): ODMessage|null - - remove(id:ODValidId): ODMessage|null { - return super.remove(id) - } - - exists(id:keyof ODMessageManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:MessageId): ODMessage_Default - getSafe(id:ODValidId): ODMessage - - getSafe(id:ODValidId): ODMessage { - return super.getSafe(id) - } -} - -/**## ODMessage_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODMessage class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODMessage`'s! - */ -export class ODMessage_Default extends ODMessage { - declare workers: ODWorkerManager_Default -} - -/**## ODModalManagerIds_Default `interface` - * This interface is a list of ids available in the `ODModalManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODModalManagerIds_Default { - "opendiscord:ticket-questions":{source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,option:ODTicketOption},workers:"opendiscord:ticket-questions"} - "opendiscord:close-ticket-reason":{source:"ticket-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:close-ticket-reason"} - "opendiscord:reopen-ticket-reason":{source:"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:reopen-ticket-reason"} - "opendiscord:delete-ticket-reason":{source:"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:delete-ticket-reason"} - "opendiscord:claim-ticket-reason":{source:"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:claim-ticket-reason"} - "opendiscord:unclaim-ticket-reason":{source:"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unclaim-ticket-reason"} - "opendiscord:pin-ticket-reason":{source:"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:pin-ticket-reason"} - "opendiscord:unpin-ticket-reason":{source:"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unpin-ticket-reason"} -} - -/**## ODModalManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODModalManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.builders.modals`! - */ -export class ODModalManager_Default extends ODModalManager { - get(id:ModalId): ODModal_Default - get(id:ODValidId): ODModal|null - - get(id:ODValidId): ODModal|null { - return super.get(id) - } - - remove(id:ModalId): ODModal_Default - remove(id:ODValidId): ODModal|null - - remove(id:ODValidId): ODModal|null { - return super.remove(id) - } - - exists(id:keyof ODModalManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getSafe(id:ModalId): ODModal_Default - getSafe(id:ODValidId): ODModal - - getSafe(id:ODValidId): ODModal { - return super.getSafe(id) - } -} - -/**## ODModal_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODModal class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODModal`'s! - */ -export class ODModal_Default extends ODModal { - declare workers: ODWorkerManager_Default -} \ No newline at end of file diff --git a/src/core/api/defaults/checker.ts b/src/core/api/defaults/checker.ts deleted file mode 100644 index 9cc654c..0000000 --- a/src/core/api/defaults/checker.ts +++ /dev/null @@ -1,385 +0,0 @@ -/////////////////////////////////////// -//DEFAULT CONFIG CHECKER MODULE -/////////////////////////////////////// -import { ODLanguageManager_Default } from "../api" -import { ODValidId } from "../modules/base" -import { ODCheckerManager, ODChecker, ODCheckerTranslationRegister, ODCheckerRenderer, ODCheckerFunctionManager, ODCheckerResult, ODCheckerFunction } from "../modules/checker" -import ansis from "ansis" - -/**## ODCheckerManagerIds_Default `interface` - * This interface is a list of ids available in the `ODCheckerManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODCheckerManagerIds_Default { - "opendiscord:general":ODChecker, - "opendiscord:questions":ODChecker, - "opendiscord:options":ODChecker, - "opendiscord:panels":ODChecker, - "opendiscord:transcripts":ODChecker -} - -/**## ODCheckerManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCheckerManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.checkers`! - */ -export class ODCheckerManager_Default extends ODCheckerManager { - declare translation: ODCheckerTranslationRegister_Default - declare renderer: ODCheckerRenderer_Default - declare functions: ODCheckerFunctionManager_Default - - get(id:CheckerId): ODCheckerManagerIds_Default[CheckerId] - get(id:ODValidId): ODChecker|null - - get(id:ODValidId): ODChecker|null { - return super.get(id) - } - - remove(id:CheckerId): ODCheckerManagerIds_Default[CheckerId] - remove(id:ODValidId): ODChecker|null - - remove(id:ODValidId): ODChecker|null { - return super.remove(id) - } - - exists(id:keyof ODCheckerManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODCheckerRenderer_Default `default_class` - * This is a special class that adds type definitions & features to the ODCheckerRenderer class. - * It contains the code that renders the default config checker. - * - * This default class is made for the global variable `opendiscord.checkers.renderer`! - */ -export class ODCheckerRenderer_Default extends ODCheckerRenderer { - extraHeaderText: string[] = [] - extraFooterText: string[] = [] - extraTopText: string[] = [] - extraBottomText: string[] = [] - - horizontalFiller: string = "=" - verticalFiller: string = "|" - descriptionSeparator: string = " => " - headerSeparator: string = " => " - footerTipPrefix: string = "=> " - - disableHeader: boolean = false - disableFooter: boolean = false - - getComponents(compact:boolean, renderEmpty:boolean, translation:ODCheckerTranslationRegister_Default, data:ODCheckerResult): string[] { - const tm = translation - const t = { - headerOpenticket:tm.get("other","opendiscord:header-openticket") ?? "OPEN TICKET", - headerConfigchecker:tm.get("other","opendiscord:header-configchecker") ?? "CONFIG CHECKER", - headerDescription:tm.get("other","opendiscord:header-description") ?? "check for errors in your config files!", - footerError:tm.get("other","opendiscord:footer-error") ?? "the bot won't start until all {0}'s are fixed!", - footerWarning:tm.get("other","opendiscord:footer-warning") ?? "it's recommended to fix all {0}'s before starting!", - footerSupport:tm.get("other","opendiscord:footer-support") ?? "SUPPORT: {0} - DOCS: {1}", - error:tm.get("other","opendiscord:type-error") ?? "[ERROR]", - warning:tm.get("other","opendiscord:type-warning") ?? "[WARNING]", - info:tm.get("other","opendiscord:type-info") ?? "[INFO]", - compactInfo:tm.get("other","opendiscord:compact-information") ?? "use {0} for more information!", - dataPath:tm.get("other","opendiscord:data-path") ?? "path", - dataDocs:tm.get("other","opendiscord:data-docs") ?? "docs", - dataMessage:tm.get("other","opendiscord:data-message") ?? "message" - } - const hasErrors = data.messages.filter((m) => m.type == "error").length > 0 - const hasWarnings = data.messages.filter((m) => m.type == "warning").length > 0 - const hasInfo = data.messages.filter((m) => m.type == "info").length > 0 - - if (!renderEmpty && !hasErrors && !hasWarnings && (!hasInfo || compact)) return [] - - const headerText = ansis.bold.hex("#f8ba00")(t.headerOpenticket)+" "+t.headerConfigchecker+this.headerSeparator+ansis.hex("#f8ba00")(t.headerDescription) - const footerErrorText = (hasErrors) ? this.footerTipPrefix+ansis.gray(tm.insertTranslationParams(t.footerError,[ansis.bold.red(t.error)])) : "" - const footerWarningText = (hasWarnings) ? this.footerTipPrefix+ansis.gray(tm.insertTranslationParams(t.footerWarning,[ansis.bold.yellow(t.warning)])) : "" - const footerSupportText = tm.insertTranslationParams(t.footerSupport,[ansis.green("https://discord.dj-dj.be"),ansis.green("https://otdocs.dj-dj.be")]) - const bottomCompactInfo = (compact) ? ansis.gray(tm.insertTranslationParams(t.compactInfo,[ansis.bold.green("npm start -- --checker")])) : "" - - const finalHeader = [headerText,...this.extraHeaderText] - const finalFooter = [footerErrorText,footerWarningText,footerSupportText,...this.extraFooterText] - const finalTop = [...this.extraTopText] - const finalBottom = [bottomCompactInfo,...this.extraBottomText] - const borderLength = this.#getLongestLength([...finalHeader,...finalFooter]) - - const finalComponents: string[] = [] - - //header - if (!this.disableHeader){ - finalHeader.forEach((text) => { - if (text.length < 1) return - finalComponents.push(this.#createBlockFromText(text,borderLength)) - }) - } - finalComponents.push(this.#getHorizontalDivider(borderLength+4)) - - //top - finalTop.forEach((text) => { - if (text.length < 1) return - finalComponents.push(this.verticalFiller+" "+text) - }) - finalComponents.push(this.verticalFiller) - - //messages - if (compact){ - //use compact messages - data.messages.forEach((msg,index) => { - //compact mode doesn't render info - if (msg.type == "info") return - - //check if translation available & use it if possible - const rawTranslation = tm.get("message",msg.messageId.value) - const translatedMessage = (rawTranslation) ? tm.insertTranslationParams(rawTranslation,msg.translationParams) : msg.message - - if (msg.type == "error") finalComponents.push(this.verticalFiller+" "+ansis.bold.red(`${t.error} ${translatedMessage}`)) - else if (msg.type == "warning") finalComponents.push(this.verticalFiller+" "+ansis.bold.yellow(`${t.warning} ${translatedMessage}`)) - - const pathSplitter = msg.path ? ":" : "" - finalComponents.push(this.verticalFiller+ansis.bold(this.descriptionSeparator)+ansis.cyan(`${ansis.magenta(msg.filepath+pathSplitter)} ${msg.path}`)) - if (index != data.messages.length-1) finalComponents.push(this.verticalFiller) - }) - }else{ - //use full messages - data.messages.forEach((msg,index) => { - //check if translation available & use it if possible - const rawTranslation = tm.get("message",msg.messageId.value) - const translatedMessage = (rawTranslation) ? tm.insertTranslationParams(rawTranslation,msg.translationParams) : msg.message - - if (msg.type == "error") finalComponents.push(this.verticalFiller+" "+ansis.bold.red(`${t.error} ${translatedMessage}`)) - else if (msg.type == "warning") finalComponents.push(this.verticalFiller+" "+ansis.bold.yellow(`${t.warning} ${translatedMessage}`)) - else if (msg.type == "info") finalComponents.push(this.verticalFiller+" "+ansis.bold.blue(`${t.info} ${translatedMessage}`)) - - const pathSplitter = msg.path ? ":" : "" - finalComponents.push(this.verticalFiller+" "+ansis.bold((t.dataPath)+this.descriptionSeparator)+ansis.cyan(`${ansis.magenta(msg.filepath+pathSplitter)} ${msg.path}`)) - if (msg.locationDocs) finalComponents.push(this.verticalFiller+" "+ansis.bold(t.dataDocs+this.descriptionSeparator)+ansis.italic.gray(msg.locationDocs)) - if (msg.messageDocs) finalComponents.push(this.verticalFiller+" "+ansis.bold(t.dataMessage+this.descriptionSeparator)+ansis.italic.gray(msg.messageDocs)) - if (index != data.messages.length-1) finalComponents.push(this.verticalFiller) - }) - } - - //bottom - finalComponents.push(this.verticalFiller) - finalBottom.forEach((text) => { - if (text.length < 1) return - finalComponents.push(this.verticalFiller+" "+text) - }) - - //footer - finalComponents.push(this.#getHorizontalDivider(borderLength+4)) - if (!this.disableFooter){ - finalFooter.forEach((text) => { - if (text.length < 1) return - finalComponents.push(this.#createBlockFromText(text,borderLength)) - }) - finalComponents.push(this.#getHorizontalDivider(borderLength+4)) - } - - //return all components - return finalComponents - } - /**Get the length of the longest string in the array. */ - #getLongestLength(texts:string[]): number { - return Math.max(...texts.map((t) => ansis.strip(t).length)) - } - /**Get a horizontal divider used between different parts of the config checker result. */ - #getHorizontalDivider(width:number): string { - if (width > 2) width = width-2 - else return this.verticalFiller+this.verticalFiller - let divider = this.verticalFiller + this.horizontalFiller.repeat(width) + this.verticalFiller - return divider - } - /**Create a block of text with a vertical divider on the left & right side. */ - #createBlockFromText(text:string,width:number): string { - if (width < 3) return this.verticalFiller+this.verticalFiller - let newWidth = width-ansis.strip(text).length+1 - let final = this.verticalFiller+" "+text+" ".repeat(newWidth)+this.verticalFiller - return final - } -} - -/**## ODCheckerTranslationRegisterOtherIds_Default `interface` - * This interface is a list of ids available in the `ODCheckerTranslationRegister_Default` class. - * It's used to generate typescript declarations for this class. - */ -export type ODCheckerTranslationRegisterOtherIds_Default = ( - "opendiscord:header-openticket"| - "opendiscord:header-configchecker"| - "opendiscord:header-description"| - "opendiscord:type-error"| - "opendiscord:type-warning"| - "opendiscord:type-info"| - "opendiscord:data-path"| - "opendiscord:data-docs"| - "opendiscord:data-message"| - "opendiscord:compact-information"| - "opendiscord:footer-error"| - "opendiscord:footer-warning"| - "opendiscord:footer-support" -) - -/**## ODCheckerTranslationRegisterMessageIds_Default `interface` - * This interface is a list of ids available in the `ODCheckerTranslationRegister_Default` class. - * It's used to generate typescript declarations for this class. - */ -export type ODCheckerTranslationRegisterMessageIds_Default = ( - "opendiscord:invalid-type"| - "opendiscord:property-missing"| - "opendiscord:property-optional"| - "opendiscord:object-disabled"| - "opendiscord:null-invalid"| - "opendiscord:switch-invalid-type"| - "opendiscord:object-switch-invalid-type"| - - "opendiscord:string-too-short"| - "opendiscord:string-too-long"| - "opendiscord:string-length-invalid"| - "opendiscord:string-starts-with"| - "opendiscord:string-ends-with"| - "opendiscord:string-contains"| - "opendiscord:string-inverted-contains"| - "opendiscord:string-choices"| - "opendiscord:string-lowercase"| - "opendiscord:string-uppercase"| - "opendiscord:string-special-characters"| - "opendiscord:string-no-spaces"| - "opendiscord:string-regex"| - "opendiscord:string-capital-word"| - "opendiscord:string-capital-sentence"| - "opendiscord:string-punctuation"| - - "opendiscord:number-nan"| - "opendiscord:number-too-short"| - "opendiscord:number-too-long"| - "opendiscord:number-length-invalid"| - "opendiscord:number-too-small"| - "opendiscord:number-too-large"| - "opendiscord:number-not-equal"| - "opendiscord:number-step"| - "opendiscord:number-step-offset"| - "opendiscord:number-starts-with"| - "opendiscord:number-ends-with"| - "opendiscord:number-contains"| - "opendiscord:number-inverted-contains"| - "opendiscord:number-choices"| - "opendiscord:number-float"| - "opendiscord:number-negative"| - "opendiscord:number-positive"| - "opendiscord:number-zero"| - - "opendiscord:boolean-true"| - "opendiscord:boolean-false"| - - "opendiscord:array-empty-disabled"| - "opendiscord:array-empty-required"| - "opendiscord:array-too-short"| - "opendiscord:array-too-long"| - "opendiscord:array-length-invalid"| - "opendiscord:array-invalid-types"| - "opendiscord:array-double"| - - "opendiscord:discord-invalid-id"| - "opendiscord:discord-invalid-id-options"| - "opendiscord:discord-invalid-token"| - "opendiscord:color-invalid"| - "opendiscord:emoji-too-short"| - "opendiscord:emoji-too-long"| - "opendiscord:emoji-custom"| - "opendiscord:emoji-invalid"| - "opendiscord:url-invalid"| - "opendiscord:url-invalid-http"| - "opendiscord:url-invalid-protocol"| - "opendiscord:url-invalid-hostname"| - "opendiscord:url-invalid-extension"| - "opendiscord:url-invalid-path"| - "opendiscord:id-not-unique"| - "opendiscord:id-non-existent"| - - "opendiscord:invalid-language"| - "opendiscord:invalid-button"| - "opendiscord:unused-option"| - "opendiscord:unused-question"| - "opendiscord:dropdown-option" -) - -/**## ODCheckerTranslationRegister_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCheckerTranslationRegister class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.checkers.translation`! - */ -export class ODCheckerTranslationRegister_Default extends ODCheckerTranslationRegister { - get(type:"other", id:ODCheckerTranslationRegisterOtherIds_Default): string - get(type:"message", id:ODCheckerTranslationRegisterMessageIds_Default): string - get(type:"message"|"other", id:string): string|null - - get(type:"message"|"other", id:string): string|null { - return super.get(type,id) - } - - set(type:"other", id:ODCheckerTranslationRegisterOtherIds_Default, translation:string): boolean - set(type:"message", id:ODCheckerTranslationRegisterMessageIds_Default, translation:string): boolean - set(type:"message"|"other", id:string, translation:string): boolean - - set(type:"message"|"other", id:string, translation:string): boolean { - return super.set(type,id,translation) - } - - delete(type:"other", id:ODCheckerTranslationRegisterOtherIds_Default): boolean - delete(type:"message", id:ODCheckerTranslationRegisterMessageIds_Default): boolean - delete(type:"message"|"other", id:string): boolean - - delete(type:"message"|"other", id:string): boolean { - return super.delete(type,id) - } - - quickTranslate(manager:ODLanguageManager_Default, translationId:string, type:"other"|"message", id:ODCheckerTranslationRegisterOtherIds_Default|ODCheckerTranslationRegisterMessageIds_Default) - quickTranslate(manager:ODLanguageManager_Default, translationId:string, type:"other"|"message", id:string) - - quickTranslate(manager:ODLanguageManager_Default, translationId:string, type:"other"|"message", id:ODCheckerTranslationRegisterOtherIds_Default|ODCheckerTranslationRegisterMessageIds_Default|string){ - super.quickTranslate(manager,translationId,type,id) - } -} - -/**## ODCheckerFunctionManagerIds_Default `interface` - * This interface is a list of ids available in the `ODCheckerFunctionManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODCheckerFunctionManagerIds_Default { - "opendiscord:unused-options":ODCheckerFunction, - "opendiscord:unused-questions":ODCheckerFunction, - "opendiscord:dropdown-options":ODCheckerFunction -} - -/**## ODCheckerFunctionManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCheckerFunctionManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.checkers.functions`! - */ -export class ODCheckerFunctionManager_Default extends ODCheckerFunctionManager { - get(id:CheckerFunctionId): ODCheckerFunctionManagerIds_Default[CheckerFunctionId] - get(id:ODValidId): ODCheckerFunction|null - - get(id:ODValidId): ODCheckerFunction|null { - return super.get(id) - } - - remove(id:CheckerFunctionId): ODCheckerFunctionManagerIds_Default[CheckerFunctionId] - remove(id:ODValidId): ODCheckerFunction|null - - remove(id:ODValidId): ODCheckerFunction|null { - return super.remove(id) - } - - exists(id:keyof ODCheckerFunctionManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/client.ts b/src/core/api/defaults/client.ts deleted file mode 100644 index 7b5a540..0000000 --- a/src/core/api/defaults/client.ts +++ /dev/null @@ -1,207 +0,0 @@ -/////////////////////////////////////// -//DEFAULT CLIENT MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODClientManager, ODSlashCommand, ODTextCommand, ODSlashCommandManager, ODTextCommandManager, ODSlashCommandInteractionCallback, ODTextCommandInteractionCallback, ODContextMenu, ODContextMenuManager, ODContextMenuInteractionCallback } from "../modules/client" - -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW COMMANDS? - * - Register the command in loadAllSlashCommands() & loadAllTextCommands() in (./src/data/framework/commandLoader.ts) - * - Add autocomplete for the command in OD(Slash/Text)CommandManagerIds_Default in (./src/core/api/defaults/client.ts) - * - Add the command to the help menu in (./src/data/framework/helpMenuLoader.ts) - * - If required, new config variables should be added (incl. logs, dm-logs & permissions). - * - Update the Open Ticket Documentation. - * - If the command contains complex logic or can be executed from a button/dropdown, it should be placed inside an `ODAction`. - * - Check all files, test the bot carefully & try a lot of different scenario's with different settings. - */ - -/**## ODClientManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODClientManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.client`! - */ -export class ODClientManager_Default extends ODClientManager { - declare slashCommands: ODSlashCommandManager_Default - declare textCommands: ODTextCommandManager_Default - declare contextMenus: ODContextMenuManager_Default -} - -/**## ODSlashCommandManagerIds_Default `interface` - * This interface is a list of ids available in the `ODSlashCommandManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODSlashCommandManagerIds_Default { - "opendiscord:help":ODSlashCommand, - "opendiscord:panel":ODSlashCommand, - "opendiscord:ticket":ODSlashCommand, - "opendiscord:close":ODSlashCommand, - "opendiscord:delete":ODSlashCommand, - "opendiscord:reopen":ODSlashCommand, - "opendiscord:claim":ODSlashCommand, - "opendiscord:unclaim":ODSlashCommand, - "opendiscord:pin":ODSlashCommand, - "opendiscord:unpin":ODSlashCommand, - "opendiscord:move":ODSlashCommand, - "opendiscord:rename":ODSlashCommand, - "opendiscord:add":ODSlashCommand, - "opendiscord:remove":ODSlashCommand, - "opendiscord:blacklist":ODSlashCommand, - "opendiscord:stats":ODSlashCommand, - "opendiscord:clear":ODSlashCommand, - "opendiscord:autoclose":ODSlashCommand, - "opendiscord:autodelete":ODSlashCommand, - "opendiscord:topic":ODSlashCommand, - "opendiscord:priority":ODSlashCommand, - "opendiscord:transfer":ODSlashCommand, -} - -/**## ODSlashCommandManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODSlashCommandManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.client.slashCommands`! - */ -export class ODSlashCommandManager_Default extends ODSlashCommandManager { - get(id:SlashCommandId): ODSlashCommandManagerIds_Default[SlashCommandId] - get(id:ODValidId): ODSlashCommand|null - - get(id:ODValidId): ODSlashCommand|null { - return super.get(id) - } - - remove(id:SlashCommandId): ODSlashCommandManagerIds_Default[SlashCommandId] - remove(id:ODValidId): ODSlashCommand|null - - remove(id:ODValidId): ODSlashCommand|null { - return super.remove(id) - } - - exists(id:keyof ODSlashCommandManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - onInteraction(commandName:keyof ODSlashCommandManagerIds_Default, callback:ODSlashCommandInteractionCallback): void - onInteraction(commandName:string|RegExp, callback:ODSlashCommandInteractionCallback): void - - onInteraction(commandName:string|RegExp, callback:ODSlashCommandInteractionCallback): void { - return super.onInteraction(commandName,callback) - } -} - -/**## ODTextCommandManagerIds_Default `interface` - * This interface is a list of ids available in the `ODTextCommandManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODTextCommandManagerIds_Default { - "opendiscord:dump":ODTextCommand, - "opendiscord:help":ODTextCommand, - "opendiscord:panel":ODTextCommand, - "opendiscord:close":ODTextCommand, - "opendiscord:delete":ODTextCommand, - "opendiscord:reopen":ODTextCommand, - "opendiscord:claim":ODTextCommand, - "opendiscord:unclaim":ODTextCommand, - "opendiscord:pin":ODTextCommand, - "opendiscord:unpin":ODTextCommand, - "opendiscord:move":ODTextCommand, - "opendiscord:rename":ODTextCommand, - "opendiscord:add":ODTextCommand, - "opendiscord:remove":ODTextCommand, - "opendiscord:blacklist-view":ODTextCommand, - "opendiscord:blacklist-add":ODTextCommand, - "opendiscord:blacklist-remove":ODTextCommand, - "opendiscord:blacklist-get":ODTextCommand, - "opendiscord:stats-global":ODTextCommand, - "opendiscord:stats-reset":ODTextCommand, - "opendiscord:stats-ticket":ODTextCommand, - "opendiscord:stats-user":ODTextCommand, - "opendiscord:clear":ODTextCommand, - "opendiscord:autoclose-disable":ODTextCommand, - "opendiscord:autoclose-enable":ODTextCommand, - "opendiscord:autodelete-disable":ODTextCommand, - "opendiscord:autodelete-enable":ODTextCommand, - "opendiscord:topic-set":ODTextCommand, - "opendiscord:priority-set":ODTextCommand, - "opendiscord:priority-get":ODTextCommand, - "opendiscord:transfer":ODTextCommand, -} - -/**## ODTextCommandManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODTextCommandManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.client.textCommands`! - */ -export class ODTextCommandManager_Default extends ODTextCommandManager { - get(id:TextCommandId): ODTextCommandManagerIds_Default[TextCommandId] - get(id:ODValidId): ODTextCommand|null - - get(id:ODValidId): ODTextCommand|null { - return super.get(id) - } - - remove(id:TextCommandId): ODTextCommandManagerIds_Default[TextCommandId] - remove(id:ODValidId): ODTextCommand|null - - remove(id:ODValidId): ODTextCommand|null { - return super.remove(id) - } - - exists(id:keyof ODTextCommandManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - onInteraction(commandPrefix:string, commandName:string|RegExp, callback:ODTextCommandInteractionCallback): void { - return super.onInteraction(commandPrefix,commandName,callback) - } -} - -/**## ODContextMenuManagerIds_Default `interface` - * This interface is a list of ids available in the `ODContextMenuManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODContextMenuManagerIds_Default { - //"opendiscord:test-menu":ODContextMenu -} - -/**## ODContextMenuManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODContextMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.client.contextMenus`! - */ -export class ODContextMenuManager_Default extends ODContextMenuManager { - get(id:ContextMenuId): ODContextMenuManagerIds_Default[ContextMenuId] - get(id:ODValidId): ODContextMenu|null - - get(id:ODValidId): ODContextMenu|null { - return super.get(id) - } - - remove(id:ContextMenuId): ODContextMenuManagerIds_Default[ContextMenuId] - remove(id:ODValidId): ODContextMenu|null - - remove(id:ODValidId): ODContextMenu|null { - return super.remove(id) - } - - exists(id:keyof ODContextMenuManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - onInteraction(menuName:keyof ODContextMenuManagerIds_Default, callback:ODContextMenuInteractionCallback): void - onInteraction(menuName:string|RegExp, callback:ODContextMenuInteractionCallback): void - - onInteraction(menuName:string|RegExp, callback:ODContextMenuInteractionCallback): void { - return super.onInteraction(menuName,callback) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/code.ts b/src/core/api/defaults/code.ts deleted file mode 100644 index 7ef19a8..0000000 --- a/src/core/api/defaults/code.ts +++ /dev/null @@ -1,57 +0,0 @@ -/////////////////////////////////////// -//DEFAULT CODE MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODCode, ODCodeManager } from "../modules/code" - -/**## ODCodeManagerIds_Default `interface` - * This interface is a list of ids available in the `ODCodeManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODCodeManagerIds_Default { - "opendiscord:command-error-handling":ODCode, - "opendiscord:start-listening-interactions":ODCode, - "opendiscord:panel-database-cleaner":ODCode, - "opendiscord:suffix-database-cleaner":ODCode, - "opendiscord:option-database-cleaner":ODCode, - "opendiscord:user-database-cleaner":ODCode, - "opendiscord:ticket-database-cleaner":ODCode, - "opendiscord:panel-auto-update":ODCode, - "opendiscord:ticket-saver":ODCode, - "opendiscord:blacklist-saver":ODCode, - "opendiscord:auto-role-on-join":ODCode, - "opendiscord:autoclose-timeout":ODCode, - "opendiscord:autoclose-leave":ODCode, - "opendiscord:autodelete-timeout":ODCode, - "opendiscord:autodelete-leave":ODCode, - "opendiscord:ticket-anti-busy":ODCode, -} - -/**## ODCodeManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCodeManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.code`! - */ -export class ODCodeManager_Default extends ODCodeManager { - get(id:CodeId): ODCodeManagerIds_Default[CodeId] - get(id:ODValidId): ODCode|null - - get(id:ODValidId): ODCode|null { - return super.get(id) - } - - remove(id:CodeId): ODCodeManagerIds_Default[CodeId] - remove(id:ODValidId): ODCode|null - - remove(id:ODValidId): ODCode|null { - return super.remove(id) - } - - exists(id:keyof ODCodeManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/console.ts b/src/core/api/defaults/console.ts deleted file mode 100644 index 6b4d0da..0000000 --- a/src/core/api/defaults/console.ts +++ /dev/null @@ -1,42 +0,0 @@ -/////////////////////////////////////// -//DEFAULT CONSOLE MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODLiveStatusUrlSource, ODLiveStatusManager, ODLiveStatusSource } from "../modules/console" - -/**## ODLiveStatusManagerIds_Default `interface` - * This interface is a list of ids available in the `ODLiveStatusManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODLiveStatusManagerIds_Default { - "opendiscord:default-djdj-dev":ODLiveStatusUrlSource -} - -/**## ODLiveStatusManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODLiveStatusManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.livestatus`! - */ -export class ODLiveStatusManager_Default extends ODLiveStatusManager { - get(id:LiveStatusId): ODLiveStatusManagerIds_Default[LiveStatusId] - get(id:ODValidId): ODLiveStatusSource|null - - get(id:ODValidId): ODLiveStatusSource|null { - return super.get(id) - } - - remove(id:LiveStatusId): ODLiveStatusManagerIds_Default[LiveStatusId] - remove(id:ODValidId): ODLiveStatusSource|null - - remove(id:ODValidId): ODLiveStatusSource|null { - return super.remove(id) - } - - exists(id:keyof ODLiveStatusManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/cooldown.ts b/src/core/api/defaults/cooldown.ts deleted file mode 100644 index 75a53e7..0000000 --- a/src/core/api/defaults/cooldown.ts +++ /dev/null @@ -1,42 +0,0 @@ -/////////////////////////////////////// -//DEFAULT COOLDOWN MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODCooldown, ODCooldownManager } from "../modules/cooldown" - -/**## ODCooldownManagerIds_Default `interface` - * This interface is a list of ids available in the `ODCooldownManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODCooldownManagerIds_Default { - -} - -/**## ODCooldownManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCooldownManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.cooldowns`! - */ -export class ODCooldownManager_Default extends ODCooldownManager { - get(id:CooldownId): ODCooldownManagerIds_Default[CooldownId] - get(id:ODValidId): ODCooldown|null - - get(id:ODValidId): ODCooldown|null { - return super.get(id) - } - - remove(id:CooldownId): ODCooldownManagerIds_Default[CooldownId] - remove(id:ODValidId): ODCooldown|null - - remove(id:ODValidId): ODCooldown|null { - return super.remove(id) - } - - exists(id:keyof ODCooldownManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/database.ts b/src/core/api/defaults/database.ts deleted file mode 100644 index 8e73a10..0000000 --- a/src/core/api/defaults/database.ts +++ /dev/null @@ -1,257 +0,0 @@ -/////////////////////////////////////// -//DEFAULT DATABASE MODULE -/////////////////////////////////////// -import { ODOptionalPromise, ODValidId, ODValidJsonType } from "../modules/base" -import { ODDatabaseManager, ODDatabase, ODFormattedJsonDatabase } from "../modules/database" -import { ODTicketJson } from "../openticket/ticket" -import { ODOptionJson } from "../openticket/option" - -/**## ODDatabaseManagerIds_Default `interface` - * This interface is a list of ids available in the `ODDatabaseManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODDatabaseManagerIds_Default { - "opendiscord:global":ODFormattedJsonDatabase_DefaultGlobal, - "opendiscord:stats":ODFormattedJsonDatabase, - "opendiscord:tickets":ODFormattedJsonDatabase_DefaultTickets, - "opendiscord:users":ODFormattedJsonDatabase_DefaultUsers, - "opendiscord:options":ODFormattedJsonDatabase_DefaultOptions, -} - -/**## ODDatabaseManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODDatabaseManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.databases`! - */ -export class ODDatabaseManager_Default extends ODDatabaseManager { - get(id:DatabaseId): ODDatabaseManagerIds_Default[DatabaseId] - get(id:ODValidId): ODDatabase|null - - get(id:ODValidId): ODDatabase|null { - return super.get(id) - } - - remove(id:DatabaseId): ODDatabaseManagerIds_Default[DatabaseId] - remove(id:ODValidId): ODDatabase|null - - remove(id:ODValidId): ODDatabase|null { - return super.remove(id) - } - - exists(id:keyof ODDatabaseManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODFormattedJsonDatabaseIds_DefaultGlobal `type` - * This interface is a list of ids available in the `ODFormattedJsonDatabase_DefaultGlobal` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFormattedJsonDatabaseIds_DefaultGlobal { - "opendiscord:panel-message":string, - "opendiscord:panel-update":string, - "opendiscord:option-suffix-counter":number, - "opendiscord:option-suffix-history":string[], - "opendiscord:last-version":string -} - -/**## ODFormattedJsonDatabase_DefaultGlobal `default_class` - * This is a special class that adds type definitions & typescript to the ODFormattedJsonDatabase class. - * It doesn't add any extra features! - * - * This default class is made for the `global.json` database! - */ -export class ODFormattedJsonDatabase_DefaultGlobal extends ODFormattedJsonDatabase { - set(category:CategoryId, key:string, value:ODFormattedJsonDatabaseIds_DefaultGlobal[CategoryId]): ODOptionalPromise - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise - - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - return super.set(category,key,value) - } - - get(category:CategoryId, key:string): ODOptionalPromise - get(category:string, key:string): ODOptionalPromise - - get(category:string, key:string): ODOptionalPromise { - return super.get(category,key) - } - - delete(category:CategoryId, key:string): ODOptionalPromise - delete(category:string, key:string): ODOptionalPromise - - delete(category:string, key:string): ODOptionalPromise { - return super.delete(category,key) - } - - exists(category:keyof ODFormattedJsonDatabaseIds_DefaultGlobal, key:string): ODOptionalPromise - exists(category:string, key:string): ODOptionalPromise - - exists(category:string, key:string): ODOptionalPromise { - return super.exists(category,key) - } - - getCategory(category:CategoryId): ODOptionalPromise<{key:string, value:ODFormattedJsonDatabaseIds_DefaultGlobal[CategoryId]}[]|undefined> - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> - - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - return super.getCategory(category) - } -} - -/**## ODFormattedJsonDatabaseIds_DefaultTickets `type` - * This interface is a list of ids available in the `ODDatabaseManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFormattedJsonDatabaseIds_DefaultTickets { - "opendiscord:ticket":ODTicketJson -} - -/**## ODFormattedJsonDatabase_DefaultTickets `default_class` - * This is a special class that adds type definitions & typescript to the ODFormattedJsonDatabase class. - * It doesn't add any extra features! - * - * This default class is made for the `tickets.json` database! - */ -export class ODFormattedJsonDatabase_DefaultTickets extends ODFormattedJsonDatabase { - set(category:CategoryId, key:string, value:ODFormattedJsonDatabaseIds_DefaultTickets[CategoryId]): ODOptionalPromise - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise - - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - return super.set(category,key,value) - } - - get(category:CategoryId, key:string): ODOptionalPromise - get(category:string, key:string): ODOptionalPromise - - get(category:string, key:string): ODOptionalPromise { - return super.get(category,key) - } - - delete(category:CategoryId, key:string): ODOptionalPromise - delete(category:string, key:string): ODOptionalPromise - - delete(category:string, key:string): ODOptionalPromise { - return super.delete(category,key) - } - - exists(category:keyof ODFormattedJsonDatabaseIds_DefaultTickets, key:string): ODOptionalPromise - exists(category:string, key:string): ODOptionalPromise - - exists(category:string, key:string): ODOptionalPromise { - return super.exists(category,key) - } - - getCategory(category:CategoryId): ODOptionalPromise<{key:string, value:ODFormattedJsonDatabaseIds_DefaultTickets[CategoryId]}[]|undefined> - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> - - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - return super.getCategory(category) - } -} - -/**## ODFormattedJsonDatabaseIds_DefaultUsers `type` - * This interface is a list of ids available in the `ODDatabaseManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFormattedJsonDatabaseIds_DefaultUsers { - "opendiscord:blacklist":ODTicketJson -} - -/**## ODFormattedJsonDatabase_DefaultUsers `default_class` - * This is a special class that adds type definitions & typescript to the ODFormattedJsonDatabase class. - * It doesn't add any extra features! - * - * This default class is made for the `users.json` database! - */ -export class ODFormattedJsonDatabase_DefaultUsers extends ODFormattedJsonDatabase { - set(category:CategoryId, key:string, value:ODFormattedJsonDatabaseIds_DefaultUsers[CategoryId]): ODOptionalPromise - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise - - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - return super.set(category,key,value) - } - - get(category:CategoryId, key:string): ODOptionalPromise - get(category:string, key:string): ODOptionalPromise - - get(category:string, key:string): ODOptionalPromise { - return super.get(category,key) - } - - delete(category:CategoryId, key:string): ODOptionalPromise - delete(category:string, key:string): ODOptionalPromise - - delete(category:string, key:string): ODOptionalPromise { - return super.delete(category,key) - } - - exists(category:keyof ODFormattedJsonDatabaseIds_DefaultUsers, key:string): ODOptionalPromise - exists(category:string, key:string): ODOptionalPromise - - exists(category:string, key:string): ODOptionalPromise { - return super.exists(category,key) - } - - getCategory(category:CategoryId): ODOptionalPromise<{key:string, value:ODFormattedJsonDatabaseIds_DefaultUsers[CategoryId]}[]|undefined> - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> - - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - return super.getCategory(category) - } -} - - -/**## ODFormattedJsonDatabaseIds_DefaultOptions `type` - * This interface is a list of ids available in the `ODDatabaseManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFormattedJsonDatabaseIds_DefaultOptions { - "opendiscord:used-option":ODOptionJson -} - -/**## ODFormattedJsonDatabase_DefaultOptions `default_class` - * This is a special class that adds type definitions & typescript to the ODFormattedJsonDatabase class. - * It doesn't add any extra features! - * - * This default class is made for the `options.json` database! - */ -export class ODFormattedJsonDatabase_DefaultOptions extends ODFormattedJsonDatabase { - set(category:CategoryId, key:string, value:ODFormattedJsonDatabaseIds_DefaultOptions[CategoryId]): ODOptionalPromise - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise - - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - return super.set(category,key,value) - } - - get(category:CategoryId, key:string): ODOptionalPromise - get(category:string, key:string): ODOptionalPromise - - get(category:string, key:string): ODOptionalPromise { - return super.get(category,key) - } - - delete(category:CategoryId, key:string): ODOptionalPromise - delete(category:string, key:string): ODOptionalPromise - - delete(category:string, key:string): ODOptionalPromise { - return super.delete(category,key) - } - - exists(category:keyof ODFormattedJsonDatabaseIds_DefaultOptions, key:string): ODOptionalPromise - exists(category:string, key:string): ODOptionalPromise - - exists(category:string, key:string): ODOptionalPromise { - return super.exists(category,key) - } - - getCategory(category:CategoryId): ODOptionalPromise<{key:string, value:ODFormattedJsonDatabaseIds_DefaultOptions[CategoryId]}[]|undefined> - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> - - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - return super.getCategory(category) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/event.ts b/src/core/api/defaults/event.ts deleted file mode 100644 index 86496fc..0000000 --- a/src/core/api/defaults/event.ts +++ /dev/null @@ -1,390 +0,0 @@ -/////////////////////////////////////// -//DEFAULT EVENT MODULE -/////////////////////////////////////// -//BASE MODULES -import { ODPromiseVoid, ODValidId } from "../modules/base" -import { ODConsoleManager, ODError } from "../modules/console" -import { ODCheckerResult, ODCheckerStorage } from "../modules/checker" -import { ODDefaultsManager } from "../modules/defaults" -import { ODLanguage } from "../modules/language" -import { ODClientActivityManager } from "../modules/client" -import { ODEvent, ODEventManager } from "../modules/event" -import * as discord from "discord.js" - -//DEFAULT MODULES -import { ODPluginClassManager_Default, ODPluginManager_Default } from "./plugin" -import { ODConfigManager_Default} from "./config" -import { ODDatabaseManager_Default } from "./database" -import { ODFlagManager_Default } from "./flag" -import { ODSessionManager_Default } from "./session" -import { ODLanguageManager_Default } from "./language" -import { ODCheckerFunctionManager_Default, ODCheckerManager_Default, ODCheckerRenderer_Default, ODCheckerTranslationRegister_Default } from "./checker" -import { ODClientManager_Default, ODContextMenuManager_Default, ODSlashCommandManager_Default, ODTextCommandManager_Default } from "./client" -import { ODBuilderManager_Default, ODButtonManager_Default, ODDropdownManager_Default, ODEmbedManager_Default, ODFileManager_Default, ODMessageManager_Default, ODModalManager_Default } from "./builder" -import { ODAutocompleteResponderManager_Default, ODButtonResponderManager_Default, ODCommandResponderManager_Default, ODContextMenuResponderManager_Default, ODDropdownResponderManager_Default, ODModalResponderManager_Default, ODResponderManager_Default } from "./responder" -import { ODActionManager_Default } from "./action" -import { ODPermissionManager_Default } from "./permission" -import { ODHelpMenuManager_Default } from "./helpmenu" -import { ODStatsManager_Default } from "./stat" -import { ODCodeManager_Default } from "./code" -import { ODCooldownManager_Default } from "./cooldown" -import { ODPostManager_Default } from "./post" -import { ODVerifyBarManager_Default } from "./verifybar" -import { ODStartScreenManager_Default } from "./startscreen" -import { ODLiveStatusManager_Default } from "./console" -import { ODProgressBarManager_Default, ODProgressBarRendererManager_Default } from "./progressbar" - -//OPEN TICKET MODULES -import { ODOptionManager, ODTicketOption } from "../openticket/option" -import { ODPanel, ODPanelManager } from "../openticket/panel" -import { ODTicket, ODTicketClearFilter, ODTicketManager } from "../openticket/ticket" -import { ODQuestionManager } from "../openticket/question" -import { ODBlacklistManager } from "../openticket/blacklist" -import { ODTranscriptManager_Default } from "../openticket/transcript" -import { ODRole, ODRoleManager } from "../openticket/role" -import { ODPriorityLevel, ODPriorityManager_Default } from "../openticket/priority" - -/**## ODEventIds_Default `interface` - * This interface is a list of ids available in the `ODEvent_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODEventIds_Default { - //error handling - "onErrorHandling": ODEvent_Default<(error:Error, origin:NodeJS.UncaughtExceptionOrigin) => ODPromiseVoid> - "afterErrorHandling": ODEvent_Default<(error:Error, origin:NodeJS.UncaughtExceptionOrigin, message:ODError) => ODPromiseVoid> - - //plugins - "afterPluginsLoaded": ODEvent_Default<(plugins:ODPluginManager_Default) => ODPromiseVoid> - "onPluginClassLoad": ODEvent_Default<(classes:ODPluginClassManager_Default, plugins:ODPluginManager_Default) => ODPromiseVoid> - "afterPluginClassesLoaded": ODEvent_Default<(classes:ODPluginClassManager_Default, plugins:ODPluginManager_Default) => ODPromiseVoid> - - //flags - "onFlagLoad": ODEvent_Default<(flags:ODFlagManager_Default) => ODPromiseVoid> - "afterFlagsLoaded": ODEvent_Default<(flags:ODFlagManager_Default) => ODPromiseVoid> - "onFlagInit": ODEvent_Default<(flags:ODFlagManager_Default) => ODPromiseVoid> - "afterFlagsInitiated": ODEvent_Default<(flags:ODFlagManager_Default) => ODPromiseVoid> - - //progress bars - "onProgressBarRendererLoad": ODEvent_Default<(renderers:ODProgressBarRendererManager_Default) => ODPromiseVoid> - "afterProgressBarRenderersLoaded": ODEvent_Default<(renderers:ODProgressBarRendererManager_Default) => ODPromiseVoid> - "onProgressBarLoad": ODEvent_Default<(progressbars:ODProgressBarManager_Default) => ODPromiseVoid> - "afterProgressBarsLoaded": ODEvent_Default<(progressbars:ODProgressBarManager_Default) => ODPromiseVoid> - - //configs - "onConfigLoad": ODEvent_Default<(configs:ODConfigManager_Default) => ODPromiseVoid> - "afterConfigsLoaded": ODEvent_Default<(configs:ODConfigManager_Default) => ODPromiseVoid> - "onConfigInit": ODEvent_Default<(configs:ODConfigManager_Default) => ODPromiseVoid> - "afterConfigsInitiated": ODEvent_Default<(configs:ODConfigManager_Default) => ODPromiseVoid> - - //databases - "onDatabaseLoad": ODEvent_Default<(databases:ODDatabaseManager_Default) => ODPromiseVoid> - "afterDatabasesLoaded": ODEvent_Default<(databases:ODDatabaseManager_Default) => ODPromiseVoid> - "onDatabaseInit": ODEvent_Default<(databases:ODDatabaseManager_Default) => ODPromiseVoid> - "afterDatabasesInitiated": ODEvent_Default<(databases:ODDatabaseManager_Default) => ODPromiseVoid> - - //languages - "onLanguageLoad": ODEvent_Default<(languages:ODLanguageManager_Default) => ODPromiseVoid> - "afterLanguagesLoaded": ODEvent_Default<(languages:ODLanguageManager_Default) => ODPromiseVoid> - "onLanguageInit": ODEvent_Default<(languages:ODLanguageManager_Default) => ODPromiseVoid> - "afterLanguagesInitiated": ODEvent_Default<(languages:ODLanguageManager_Default) => ODPromiseVoid> - "onLanguageSelect": ODEvent_Default<(languages:ODLanguageManager_Default) => ODPromiseVoid> - "afterLanguagesSelected": ODEvent_Default<(main:ODLanguage|null, backup:ODLanguage|null, languages:ODLanguageManager_Default) => ODPromiseVoid> - - //sessions - "onSessionLoad": ODEvent_Default<(languages:ODSessionManager_Default) => ODPromiseVoid> - "afterSessionsLoaded": ODEvent_Default<(languages:ODSessionManager_Default) => ODPromiseVoid> - - //config checkers - "onCheckerLoad": ODEvent_Default<(checkers:ODCheckerManager_Default) => ODPromiseVoid> - "afterCheckersLoaded": ODEvent_Default<(checkers:ODCheckerManager_Default) => ODPromiseVoid> - "onCheckerFunctionLoad": ODEvent_Default<(functions:ODCheckerFunctionManager_Default, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "afterCheckerFunctionsLoaded": ODEvent_Default<(functions:ODCheckerFunctionManager_Default, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "onCheckerExecute": ODEvent_Default<(checkers:ODCheckerManager_Default) => ODPromiseVoid> - "afterCheckersExecuted": ODEvent_Default<(result:ODCheckerResult, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "onCheckerTranslationLoad": ODEvent_Default<(translations:ODCheckerTranslationRegister_Default, enabled:boolean, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "afterCheckerTranslationsLoaded": ODEvent_Default<(translations:ODCheckerTranslationRegister_Default, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "onCheckerRender": ODEvent_Default<(renderer:ODCheckerRenderer_Default, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "afterCheckersRendered": ODEvent_Default<(renderer:ODCheckerRenderer_Default, checkers:ODCheckerManager_Default) => ODPromiseVoid> - "onCheckerQuit": ODEvent_Default<(checkers:ODCheckerManager_Default) => ODPromiseVoid> - - //plugin loading before client - "onPluginBeforeClientLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeClientLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //client configuration - "onClientLoad": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "afterClientLoaded": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "onClientInit": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "afterClientInitiated": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "onClientReady": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "afterClientReady": ODEvent_Default<(client:ODClientManager_Default) => ODPromiseVoid> - "onClientActivityLoad": ODEvent_Default<(activity:ODClientActivityManager, client:ODClientManager_Default) => ODPromiseVoid> - "afterClientActivityLoaded": ODEvent_Default<(activity:ODClientActivityManager, client:ODClientManager_Default) => ODPromiseVoid> - "onClientActivityInit": ODEvent_Default<(activity:ODClientActivityManager, client:ODClientManager_Default) => ODPromiseVoid> - "afterClientActivityInitiated": ODEvent_Default<(activity:ODClientActivityManager, client:ODClientManager_Default) => ODPromiseVoid> - - //priority levels - "onPriorityLoad": ODEvent_Default<(priorities:ODPriorityManager_Default) => ODPromiseVoid> - "afterPrioritiesLoaded": ODEvent_Default<(priorities:ODPriorityManager_Default) => ODPromiseVoid> - - //client slash commands - "onSlashCommandLoad": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "afterSlashCommandsLoaded": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "onSlashCommandRegister": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "afterSlashCommandsRegistered": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - - //client context menus - "onContextMenuLoad": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "afterContextMenusLoaded": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "onContextMenuRegister": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - "afterContextMenusRegistered": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - - //client text commands - "onTextCommandLoad": ODEvent_Default<(text:ODTextCommandManager_Default, client:ODClientManager_Default,) => ODPromiseVoid> - "afterTextCommandsLoaded": ODEvent_Default<(text:ODTextCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid> - - //plugin loading before managers - "onPluginBeforeManagerLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeManagerLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //questions - "onQuestionLoad": ODEvent_Default<(questions:ODQuestionManager) => ODPromiseVoid> - "afterQuestionsLoaded": ODEvent_Default<(questions:ODQuestionManager) => ODPromiseVoid> - - //options - "onOptionLoad": ODEvent_Default<(options:ODOptionManager) => ODPromiseVoid> - "afterOptionsLoaded": ODEvent_Default<(options:ODOptionManager) => ODPromiseVoid> - - //panels - "onPanelLoad": ODEvent_Default<(panels:ODPanelManager) => ODPromiseVoid> - "afterPanelsLoaded": ODEvent_Default<(panels:ODPanelManager) => ODPromiseVoid> - "onPanelSpawn": ODEvent_Default<(panel:ODPanel) => ODPromiseVoid> - "afterPanelSpawned": ODEvent_Default<(panel:ODPanel) => ODPromiseVoid> - - //tickets - "onTicketLoad": ODEvent_Default<(tickets:ODTicketManager) => ODPromiseVoid> - "afterTicketsLoaded": ODEvent_Default<(tickets:ODTicketManager) => ODPromiseVoid> - - //ticket creation - "onTicketChannelCreation": ODEvent_Default<(option:ODTicketOption, user:discord.User) => ODPromiseVoid> - "afterTicketChannelCreated": ODEvent_Default<(option:ODTicketOption, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - "onTicketChannelDeletion": ODEvent_Default<(ticket:ODTicket, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - "afterTicketChannelDeleted": ODEvent_Default<(ticket:ODTicket, user:discord.User) => ODPromiseVoid> - "onTicketPermissionsCreated": ODEvent_Default<(option:ODTicketOption, permissions:ODPermissionManager_Default, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - "afterTicketPermissionsCreated": ODEvent_Default<(option:ODTicketOption, permissions:ODPermissionManager_Default, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - "onTicketMainMessageCreated": ODEvent_Default<(ticket:ODTicket, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - "afterTicketMainMessageCreated": ODEvent_Default<(ticket:ODTicket, message:discord.Message, channel:discord.GuildTextBasedChannel, user:discord.User) => ODPromiseVoid> - - //ticket actions - "onTicketCreate": ODEvent_Default<(creator:discord.User) => ODPromiseVoid> - "afterTicketCreated": ODEvent_Default<(ticket:ODTicket, creator:discord.User, channel:discord.GuildTextBasedChannel) => ODPromiseVoid> - "onTicketClose": ODEvent_Default<(ticket:ODTicket, closer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketClosed": ODEvent_Default<(ticket:ODTicket, closer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketReopen": ODEvent_Default<(ticket:ODTicket, reopener:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketReopened": ODEvent_Default<(ticket:ODTicket, reopener:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketDelete": ODEvent_Default<(ticket:ODTicket, deleter:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketDeleted": ODEvent_Default<(ticket:ODTicket, deleter:discord.User, reason:string|null) => ODPromiseVoid> - "onTicketMove": ODEvent_Default<(ticket:ODTicket, mover:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketMoved": ODEvent_Default<(ticket:ODTicket, mover:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketClaim": ODEvent_Default<(ticket:ODTicket, claimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketClaimed": ODEvent_Default<(ticket:ODTicket, claimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketUnclaim": ODEvent_Default<(ticket:ODTicket, unclaimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketUnclaimed": ODEvent_Default<(ticket:ODTicket, unclaimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketPin": ODEvent_Default<(ticket:ODTicket, pinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketPinned": ODEvent_Default<(ticket:ODTicket, pinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketUnpin": ODEvent_Default<(ticket:ODTicket, unpinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketUnpinned": ODEvent_Default<(ticket:ODTicket, unpinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketUserAdd": ODEvent_Default<(ticket:ODTicket, adder:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketUserAdded": ODEvent_Default<(ticket:ODTicket, adder:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketUserRemove": ODEvent_Default<(ticket:ODTicket, remover:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketUserRemoved": ODEvent_Default<(ticket:ODTicket, remover:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketRename": ODEvent_Default<(ticket:ODTicket, renamer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "afterTicketRenamed": ODEvent_Default<(ticket:ODTicket, renamer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => ODPromiseVoid> - "onTicketsClear": ODEvent_Default<(tickets:ODTicket[], clearer:discord.User, channel:discord.GuildTextBasedChannel, filter:ODTicketClearFilter) => ODPromiseVoid> - "afterTicketsCleared": ODEvent_Default<(tickets:ODTicket[], clearer:discord.User, channel:discord.GuildTextBasedChannel, filter:ODTicketClearFilter) => ODPromiseVoid> - "onTicketTopicChange": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldTopic:string, newTopic:string) => ODPromiseVoid> - "afterTicketTopicChanged": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldTopic:string, newTopic:string) => ODPromiseVoid> - "onTicketPriorityChange": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel, reason:string|null) => ODPromiseVoid> - "afterTicketPriorityChanged": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel, reason:string|null) => ODPromiseVoid> - "onTicketTransfer": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldCreator:discord.User, newCreator:discord.User, reason:string|null) => ODPromiseVoid> - "afterTicketTransferred": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldCreator:discord.User, newCreator:discord.User, reason:string|null) => ODPromiseVoid> - - //roles - "onRoleLoad": ODEvent_Default<(roles:ODRoleManager) => ODPromiseVoid> - "afterRolesLoaded": ODEvent_Default<(roles:ODRoleManager) => ODPromiseVoid> - "onRoleUpdate": ODEvent_Default<(user:discord.User,role:ODRole) => ODPromiseVoid> - "afterRolesUpdated": ODEvent_Default<(user:discord.User,role:ODRole) => ODPromiseVoid> - - //blacklist - "onBlacklistLoad": ODEvent_Default<(blacklist:ODBlacklistManager) => ODPromiseVoid> - "afterBlacklistLoaded": ODEvent_Default<(blacklist:ODBlacklistManager) => ODPromiseVoid> - - //transcripts - "onTranscriptCompilerLoad": ODEvent_Default<(transcripts:ODTranscriptManager_Default) => ODPromiseVoid> - "afterTranscriptCompilersLoaded": ODEvent_Default<(transcripts:ODTranscriptManager_Default) => ODPromiseVoid> - "onTranscriptHistoryLoad": ODEvent_Default<(transcripts:ODTranscriptManager_Default) => ODPromiseVoid> - "afterTranscriptHistoryLoaded": ODEvent_Default<(transcripts:ODTranscriptManager_Default) => ODPromiseVoid> - - //transcript creation - "onTranscriptCreate": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "afterTranscriptCreated": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "onTranscriptInit": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "afterTranscriptInitiated": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "onTranscriptCompile": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "afterTranscriptCompiled": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "onTranscriptReady": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - "afterTranscriptReady": ODEvent_Default<(transcripts:ODTranscriptManager_Default,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => ODPromiseVoid> - - //plugin loading before builders - "onPluginBeforeBuilderLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeBuilderLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //builders - "onButtonBuilderLoad": ODEvent_Default<(buttons:ODButtonManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterButtonBuildersLoaded": ODEvent_Default<(buttons:ODButtonManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onDropdownBuilderLoad": ODEvent_Default<(dropdowns:ODDropdownManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterDropdownBuildersLoaded": ODEvent_Default<(dropdowns:ODDropdownManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onFileBuilderLoad": ODEvent_Default<(files:ODFileManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterFileBuildersLoaded": ODEvent_Default<(files:ODFileManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onEmbedBuilderLoad": ODEvent_Default<(embeds:ODEmbedManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterEmbedBuildersLoaded": ODEvent_Default<(embeds:ODEmbedManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onMessageBuilderLoad": ODEvent_Default<(messages:ODMessageManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterMessageBuildersLoaded": ODEvent_Default<(messages:ODMessageManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onModalBuilderLoad": ODEvent_Default<(modals:ODModalManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterModalBuildersLoaded": ODEvent_Default<(modals:ODModalManager_Default, builders:ODBuilderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - - //plugin loading before responders - "onPluginBeforeResponderLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeResponderLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //responders - "onCommandResponderLoad": ODEvent_Default<(commands:ODCommandResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterCommandRespondersLoaded": ODEvent_Default<(commands:ODCommandResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onButtonResponderLoad": ODEvent_Default<(buttons:ODButtonResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterButtonRespondersLoaded": ODEvent_Default<(buttons:ODButtonResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onDropdownResponderLoad": ODEvent_Default<(dropdowns:ODDropdownResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterDropdownRespondersLoaded": ODEvent_Default<(dropdowns:ODDropdownResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onModalResponderLoad": ODEvent_Default<(modals:ODModalResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterModalRespondersLoaded": ODEvent_Default<(modals:ODModalResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onContextMenuResponderLoad": ODEvent_Default<(menus:ODContextMenuResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterContextMenuRespondersLoaded": ODEvent_Default<(menus:ODContextMenuResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "onAutocompleteResponderLoad": ODEvent_Default<(autocomplete:ODAutocompleteResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - "afterAutocompleteRespondersLoaded": ODEvent_Default<(autocomplete:ODAutocompleteResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> - - //plugin loading before finalizations - "onPluginBeforeFinalizationLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeFinalizationLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //actions - "onActionLoad": ODEvent_Default<(actions:ODActionManager_Default) => ODPromiseVoid> - "afterActionsLoaded": ODEvent_Default<(actions:ODActionManager_Default) => ODPromiseVoid> - - //verifybars - "onVerifyBarLoad": ODEvent_Default<(verifybars:ODVerifyBarManager_Default) => ODPromiseVoid> - "afterVerifyBarsLoaded": ODEvent_Default<(verifybars:ODVerifyBarManager_Default) => ODPromiseVoid> - - //permissions - "onPermissionLoad": ODEvent_Default<(permissions:ODPermissionManager_Default) => ODPromiseVoid> - "afterPermissionsLoaded": ODEvent_Default<(permissions:ODPermissionManager_Default) => ODPromiseVoid> - - //posts - "onPostLoad": ODEvent_Default<(posts:ODPostManager_Default) => ODPromiseVoid> - "afterPostsLoaded": ODEvent_Default<(posts:ODPostManager_Default) => ODPromiseVoid> - "onPostInit": ODEvent_Default<(posts:ODPostManager_Default) => ODPromiseVoid> - "afterPostsInitiated": ODEvent_Default<(posts:ODPostManager_Default) => ODPromiseVoid> - - //cooldowns - "onCooldownLoad": ODEvent_Default<(cooldowns:ODCooldownManager_Default) => ODPromiseVoid> - "afterCooldownsLoaded": ODEvent_Default<(cooldowns:ODCooldownManager_Default) => ODPromiseVoid> - "onCooldownInit": ODEvent_Default<(cooldowns:ODCooldownManager_Default) => ODPromiseVoid> - "afterCooldownsInitiated": ODEvent_Default<(cooldowns:ODCooldownManager_Default) => ODPromiseVoid> - - //help menu - "onHelpMenuCategoryLoad": ODEvent_Default<(menu:ODHelpMenuManager_Default) => ODPromiseVoid> - "afterHelpMenuCategoriesLoaded": ODEvent_Default<(menu:ODHelpMenuManager_Default) => ODPromiseVoid> - "onHelpMenuComponentLoad": ODEvent_Default<(menu:ODHelpMenuManager_Default) => ODPromiseVoid> - "afterHelpMenuComponentsLoaded": ODEvent_Default<(menu:ODHelpMenuManager_Default) => ODPromiseVoid> - - //stats - "onStatScopeLoad": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - "afterStatScopesLoaded": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - "onStatLoad": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - "afterStatsLoaded": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - "onStatInit": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - "afterStatsInitiated": ODEvent_Default<(stats:ODStatsManager_Default) => ODPromiseVoid> - - //plugin loading before code - "onPluginBeforeCodeLoad": ODEvent_Default<() => ODPromiseVoid>, - "afterPluginBeforeCodeLoaded": ODEvent_Default<() => ODPromiseVoid>, - - //code - "onCodeLoad": ODEvent_Default<(code:ODCodeManager_Default) => ODPromiseVoid> - "afterCodeLoaded": ODEvent_Default<(code:ODCodeManager_Default) => ODPromiseVoid> - "onCodeExecute": ODEvent_Default<(code:ODCodeManager_Default) => ODPromiseVoid> - "afterCodeExecuted": ODEvent_Default<(code:ODCodeManager_Default) => ODPromiseVoid> - - //livestatus - "onLiveStatusSourceLoad": ODEvent_Default<(livestatus:ODLiveStatusManager_Default) => ODPromiseVoid> - "afterLiveStatusSourcesLoaded": ODEvent_Default<(livestatus:ODLiveStatusManager_Default) => ODPromiseVoid> - - //startscreen - "onStartScreenLoad": ODEvent_Default<(startscreen:ODStartScreenManager_Default) => ODPromiseVoid> - "afterStartScreensLoaded": ODEvent_Default<(startscreen:ODStartScreenManager_Default) => ODPromiseVoid> - "onStartScreenRender": ODEvent_Default<(startscreen:ODStartScreenManager_Default) => ODPromiseVoid> - "afterStartScreensRendered": ODEvent_Default<(startscreen:ODStartScreenManager_Default) => ODPromiseVoid> - - //ready - "beforeReadyForUsage": ODEvent_Default<() => ODPromiseVoid> - "onReadyForUsage": ODEvent_Default<() => ODPromiseVoid> -} - -/**## ODEventManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODEvent class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.events`! - */ -export class ODEventManager_Default extends ODEventManager { - get(id:StartScreenId): ODEventIds_Default[StartScreenId] - get(id:ODValidId): ODEvent|null - - get(id:ODValidId): ODEvent|null { - return super.get(id) - } - - remove(id:StartScreenId): ODEventIds_Default[StartScreenId] - remove(id:ODValidId): ODEvent|null - - remove(id:ODValidId): ODEvent|null { - return super.remove(id) - } - - exists(id:keyof ODEventIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODEventManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODEvent class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.events`! - */ -export class ODEvent_Default ODPromiseVoid)> extends ODEvent { - listen(callback:Callback): void { - return super.listen(callback) - } - listenOnce(callback:Callback): void { - return super.listenOnce(callback) - } - wait(): Promise> - wait(): Promise { - return super.wait() - } - emit(params:Parameters): Promise { - return super.emit(params) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/flag.ts b/src/core/api/defaults/flag.ts deleted file mode 100644 index 8830cc2..0000000 --- a/src/core/api/defaults/flag.ts +++ /dev/null @@ -1,57 +0,0 @@ -/////////////////////////////////////// -//DEFAULT PROCESS MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODFlagManager, ODFlag } from "../modules/flag" - -/**## ODFlagManagerIds_Default `interface` - * This interface is a list of ids available in the `ODFlagManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODFlagManagerIds_Default { - "opendiscord:no-migration":ODFlag, - "opendiscord:dev-config":ODFlag, - "opendiscord:dev-database":ODFlag, - "opendiscord:debug":ODFlag, - "opendiscord:crash":ODFlag, - "opendiscord:no-transcripts":ODFlag, - "opendiscord:no-checker":ODFlag, - "opendiscord:checker":ODFlag, - "opendiscord:no-easter":ODFlag, - "opendiscord:no-plugins":ODFlag, - "opendiscord:soft-plugins":ODFlag, - "opendiscord:force-slash-update":ODFlag, - "opendiscord:no-compile":ODFlag, - "opendiscord:compile-only":ODFlag, - "opendiscord:silent":ODFlag, - "opendiscord:cli":ODFlag, -} - -/**## ODFlagManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODFlagManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.flags`! - */ -export class ODFlagManager_Default extends ODFlagManager { - get(id:FlagId): ODFlagManagerIds_Default[FlagId] - get(id:ODValidId): ODFlag|null - - get(id:ODValidId): ODFlag|null { - return super.get(id) - } - - remove(id:FlagId): ODFlagManagerIds_Default[FlagId] - remove(id:ODValidId): ODFlag|null - - remove(id:ODValidId): ODFlag|null { - return super.remove(id) - } - - exists(id:keyof ODFlagManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/helpmenu.ts b/src/core/api/defaults/helpmenu.ts deleted file mode 100644 index 9864f18..0000000 --- a/src/core/api/defaults/helpmenu.ts +++ /dev/null @@ -1,338 +0,0 @@ -/////////////////////////////////////// -//DEFAULT HELP MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODHelpMenuCategory, ODHelpMenuCommandComponent, ODHelpMenuComponent, ODHelpMenuManager } from "../modules/helpmenu" - -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW COMMANDS? - * - Register the command in loadAllSlashCommands() & loadAllTextCommands() in (./src/data/framework/commandLoader.ts) - * - Add autocomplete for the command in OD(Slash/Text)CommandManagerIds_Default in (./src/core/api/defaults/client.ts) - * - Add the command to the help menu in (./src/data/framework/helpMenuLoader.ts) - * - If required, new config variables should be added (incl. logs, dm-logs & permissions). - * - Update the Open Ticket Documentation. - * - If the command contains complex logic or can be executed from a button/dropdown, it should be placed inside an `ODAction`. - * - Check all files, test the bot carefully & try a lot of different scenario's with different settings. - */ - -/**## ODHelpMenuManagerIds_Default `interface` - * This interface is a list of ids available in the `ODHelpMenuManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerIds_Default { - "opendiscord:general":ODHelpMenuCategory_DefaultGeneral, - "opendiscord:ticket-basic":ODHelpMenuCategory_DefaultTicketBasic, - "opendiscord:ticket-advanced":ODHelpMenuCategory_DefaultTicketAdvanced, - "opendiscord:ticket-user":ODHelpMenuCategory_DefaultTicketUser, - "opendiscord:admin":ODHelpMenuCategory_DefaultAdmin, - "opendiscord:advanced":ODHelpMenuCategory_DefaultAdvanced, - "opendiscord:extra":ODHelpMenuCategory_DefaultExtra -} - -/**## ODHelpMenuManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.helpmenu`! - */ -export class ODHelpMenuManager_Default extends ODHelpMenuManager { - get(id:HelpMenuCategoryId): ODHelpMenuManagerIds_Default[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuCategory|null - - get(id:ODValidId): ODHelpMenuCategory|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerIds_Default[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuCategory|null - - remove(id:ODValidId): ODHelpMenuCategory|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultGeneral `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultGeneral` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultGeneral { - "opendiscord:help":ODHelpMenuCommandComponent, - "opendiscord:ticket":ODHelpMenuCommandComponent|null -} - -/**## ODHelpMenuCategory_DefaultGeneral `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:general` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultGeneral extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultGeneral[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultGeneral[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultGeneral): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultTicketBasic `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultTicketBasic` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultTicketBasic { - "opendiscord:close":ODHelpMenuCommandComponent, - "opendiscord:delete":ODHelpMenuCommandComponent, - "opendiscord:reopen":ODHelpMenuCommandComponent -} - -/**## ODHelpMenuCategory_DefaultTicketBasic `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:ticket` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultTicketBasic extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketBasic[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketBasic[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultTicketBasic): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultTicketAdvanced `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultTicketAdvanced` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultTicketAdvanced { - "opendiscord:pin":ODHelpMenuCommandComponent, - "opendiscord:unpin":ODHelpMenuCommandComponent, - "opendiscord:move":ODHelpMenuCommandComponent, - "opendiscord:rename":ODHelpMenuCommandComponent -} - -/**## ODHelpMenuCategory_DefaultTicketAdvanced `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:ticket` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultTicketAdvanced extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketAdvanced[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketAdvanced[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultTicketAdvanced): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultTicketUser `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultTicketUser` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultTicketUser { - "opendiscord:claim":ODHelpMenuCommandComponent, - "opendiscord:unclaim":ODHelpMenuCommandComponent, - "opendiscord:add":ODHelpMenuCommandComponent, - "opendiscord:remove":ODHelpMenuCommandComponent, - "opendiscord:transfer":ODHelpMenuCommandComponent, -} - -/**## ODHelpMenuCategory_DefaultTicketUser `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:ticket` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultTicketUser extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketUser[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultTicketUser[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultTicketUser): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultAdmin `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultAdmin` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultAdmin { - "opendiscord:panel":ODHelpMenuCommandComponent, - "opendiscord:blacklist-view":ODHelpMenuCommandComponent, - "opendiscord:blacklist-add":ODHelpMenuCommandComponent, - "opendiscord:blacklist-remove":ODHelpMenuCommandComponent, - "opendiscord:blacklist-get":ODHelpMenuCommandComponent -} - -/**## ODHelpMenuCategory_DefaultAdmin `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:admin` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultAdmin extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultAdmin[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultAdmin[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultAdmin): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultAdvanced `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultAdvanced` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultAdvanced { - "opendiscord:stats-global":ODHelpMenuCommandComponent, - "opendiscord:stats-reset":ODHelpMenuCommandComponent, - "opendiscord:stats-ticket":ODHelpMenuCommandComponent, - "opendiscord:stats-user":ODHelpMenuCommandComponent, - "opendiscord:autoclose-disable":ODHelpMenuCommandComponent, - "opendiscord:autoclose-enable":ODHelpMenuCommandComponent, - "opendiscord:autodelete-disable":ODHelpMenuCommandComponent, - "opendiscord:autodelete-enable":ODHelpMenuCommandComponent, - "opendiscord:topic-set":ODHelpMenuCommandComponent, - "opendiscord:priority-set":ODHelpMenuCommandComponent, -} - -/**## ODHelpMenuCategory_DefaultAdvanced `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:advanced` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultAdvanced extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultAdvanced[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultAdvanced[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultAdvanced): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODHelpMenuManagerCategoryIds_DefaultExtra `type` - * This interface is a list of ids available in the `ODHelpMenuCategory_DefaultExtra` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODHelpMenuManagerCategoryIds_DefaultExtra {} - -/**## ODHelpMenuCategory_DefaultExtra `default_class` - * This is a special class that adds type definitions & typescript to the ODHelpMenuManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:general` category in `opendiscord.helpmenu`! - */ -export class ODHelpMenuCategory_DefaultExtra extends ODHelpMenuCategory { - get(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultExtra[HelpMenuCategoryId] - get(id:ODValidId): ODHelpMenuComponent|null - - get(id:ODValidId): ODHelpMenuComponent|null { - return super.get(id) - } - - remove(id:HelpMenuCategoryId): ODHelpMenuManagerCategoryIds_DefaultExtra[HelpMenuCategoryId] - remove(id:ODValidId): ODHelpMenuComponent|null - - remove(id:ODValidId): ODHelpMenuComponent|null { - return super.remove(id) - } - - exists(id:keyof ODHelpMenuManagerCategoryIds_DefaultExtra): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/permission.ts b/src/core/api/defaults/permission.ts deleted file mode 100644 index 2a67d3e..0000000 --- a/src/core/api/defaults/permission.ts +++ /dev/null @@ -1,31 +0,0 @@ -/////////////////////////////////////// -//DEFAULT PERMISSION MODULE -/////////////////////////////////////// -import { ODDebugger } from "../modules/console" -import { ODPermissionManager } from "../modules/permission" -import { ODClientManager_Default } from "./client" - -/**## ODPermissionManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODPermissionManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.permissions`! - */ -export class ODPermissionManager_Default extends ODPermissionManager { - constructor(debug:ODDebugger,client:ODClientManager_Default){ - super(debug,client,true) - } -} - -/**## ODPermissionEmbedType `type` - * This type contains all types available in the `opendiscord:no-permissions` embed. - */ -export type ODPermissionEmbedType = ( - "developer"| - "owner"| - "admin"| - "moderator"| - "support"| - "member"| - "discord-administrator" -) \ No newline at end of file diff --git a/src/core/api/defaults/plugin.ts b/src/core/api/defaults/plugin.ts deleted file mode 100644 index b9644b4..0000000 --- a/src/core/api/defaults/plugin.ts +++ /dev/null @@ -1,77 +0,0 @@ -/////////////////////////////////////// -//DEFAULT POST MODULE -/////////////////////////////////////// -import { ODValidId, ODManagerData } from "../modules/base" -import { ODPlugin, ODPluginClassManager, ODPluginManager } from "../modules/plugin" - -/**## ODPluginManagerIds_Default `interface` - * This interface is a list of ids available in the `ODPluginManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODPluginManagerIds_Default {} - -/**## ODPluginManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODPluginManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.plugins`! - */ -export class ODPluginManager_Default extends ODPluginManager { - declare classes: ODPluginClassManager_Default - - get(id:PluginId): ODPluginManagerIds_Default[PluginId] - get(id:ODValidId): ODPlugin|null - - get(id:ODValidId): ODPlugin|null { - return super.get(id) - } - - remove(id:PluginId): ODPluginManagerIds_Default[PluginId] - remove(id:ODValidId): ODPlugin|null - - remove(id:ODValidId): ODPlugin|null { - return super.remove(id) - } - - exists(id:keyof ODPluginManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODPluginClassManagerIds_Default `interface` - * This interface is a list of ids available in the `ODPluginClassManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODPluginClassManagerIds_Default {} - -/**## ODPluginClassManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODPluginClassManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.plugins.classes`! - */ -export class ODPluginClassManager_Default extends ODPluginClassManager { - get(id:PluginClassId): ODPluginClassManagerIds_Default[PluginClassId] - get(id:ODValidId): ODManagerData|null - - get(id:ODValidId): ODManagerData|null { - return super.get(id) - } - - remove(id:PluginClassId): ODPluginClassManagerIds_Default[PluginClassId] - remove(id:ODValidId): ODManagerData|null - - remove(id:ODValidId): ODManagerData|null { - return super.remove(id) - } - - exists(id:keyof ODPluginClassManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/post.ts b/src/core/api/defaults/post.ts deleted file mode 100644 index bdc64db..0000000 --- a/src/core/api/defaults/post.ts +++ /dev/null @@ -1,44 +0,0 @@ -/////////////////////////////////////// -//DEFAULT POST MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODPost, ODPostManager } from "../modules/post" -import * as discord from "discord.js" - -/**## ODPostManagerIds_Default `interface` - * This interface is a list of ids available in the `ODPostManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODPostManagerIds_Default { - "opendiscord:logs":ODPost|null, - "opendiscord:transcripts":ODPost|null -} - -/**## ODPostManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODPostManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.code`! - */ -export class ODPostManager_Default extends ODPostManager { - get(id:PostId): ODPostManagerIds_Default[PostId] - get(id:ODValidId): ODPost|null - - get(id:ODValidId): ODPost|null { - return super.get(id) - } - - remove(id:PostId): ODPostManagerIds_Default[PostId] - remove(id:ODValidId): ODPost|null - - remove(id:ODValidId): ODPost|null { - return super.remove(id) - } - - exists(id:keyof ODPostManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/progressbar.ts b/src/core/api/defaults/progressbar.ts deleted file mode 100644 index f353472..0000000 --- a/src/core/api/defaults/progressbar.ts +++ /dev/null @@ -1,171 +0,0 @@ -/////////////////////////////////////// -//DEFAULT PROGRESS BAR MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODValidConsoleColor } from "../modules/console" -import { ODManualProgressBar, ODProgressBar, ODProgressBarManager, ODProgressBarRenderer, ODProgressBarRendererManager } from "../modules/progressbar" -import ansis from "ansis" - -/**## ODProgressBarRenderer_DefaultSettingsLabel `type` - * All available label types for the default progress bar renderer - */ -export type ODProgressBarRenderer_DefaultSettingsLabel = "value"|"percentage"|"fraction"|"time-ms"|"time-sec"|"time-min" - -/**## ODProgressBarRenderer_DefaultSettings `interface` - * This interface contains the settings for the default progress bar renderer. - */ -export interface ODProgressBarRenderer_DefaultSettings { - /**The color of the progress bar border. */ - borderColor:ODValidConsoleColor|"openticket", - /**The color of the progress bar (filled side). */ - filledBarColor:ODValidConsoleColor|"openticket", - /**The color of the progress bar (empty side). */ - emptyBarColor:ODValidConsoleColor|"openticket", - /**The color of the text before the progress bar. */ - prefixColor:ODValidConsoleColor|"openticket", - /**The color of the text after the progress bar. */ - suffixColor:ODValidConsoleColor|"openticket", - /**The color of the progress bar label. */ - labelColor:ODValidConsoleColor|"openticket", - - /**The character used in the left border. */ - leftBorderChar:string, - /**The character used in the right border. */ - rightBorderChar:string, - /**The character used in the filled side of the progress bar. */ - filledBarChar:string, - /**The character used in the empty side of the progress bar. */ - emptyBarChar:string, - /**The label type. (will show a number related to the progress) */ - labelType:ODProgressBarRenderer_DefaultSettingsLabel, - /**The position of the label. */ - labelPosition:"start"|"end", - /**The width of the bar. (50 characters by default) */ - barWidth:number, - - /**Show the bar. */ - showBar:boolean, - /**Show the label. */ - showLabel:boolean, - /**Show the border. */ - showBorder:boolean, -} - -export class ODProgressBarRenderer_Default extends ODProgressBarRenderer { - constructor(id:ODValidId,settings:ODProgressBarRenderer_DefaultSettings){ - super(id,(settings,min,max,value,rawPrefix,rawSuffix) => { - const percentage = (value-min)/(max-min) - const barLevel = Math.round(percentage*settings.barWidth) - - const borderAnsis = (settings.borderColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.borderColor] - const filledBarAnsis = (settings.filledBarColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.filledBarColor] - const emptyBarAnsis = (settings.emptyBarColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.emptyBarColor] - const labelAnsis = (settings.labelColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.labelColor] - const prefixAnsis = (settings.prefixColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.prefixColor] - const suffixAnsis = (settings.suffixColor == "openticket") ? ansis.hex("#f8ba00") : ansis[settings.suffixColor] - - const leftBorder = (settings.showBorder) ? borderAnsis(settings.leftBorderChar) : "" - const rightBorder = (settings.showBorder) ? borderAnsis(settings.rightBorderChar) : "" - const bar = (settings.showBar) ? filledBarAnsis(settings.filledBarChar.repeat(barLevel))+emptyBarAnsis(settings.emptyBarChar.repeat(settings.barWidth-barLevel)) : "" - const prefix = (rawPrefix) ? prefixAnsis(rawPrefix)+" " : "" - const suffix = (rawSuffix) ? " "+suffixAnsis(rawSuffix) : "" - let label: string - if (!settings.showLabel) label = "" - if (settings.labelType == "fraction") label = labelAnsis(value+"/"+max) - else if (settings.labelType == "percentage") label = labelAnsis(Math.round(percentage*100)+"%") - else if (settings.labelType == "time-ms") label = labelAnsis(value+"ms") - else if (settings.labelType == "time-sec") label = labelAnsis(Math.round(value*10)/10+"sec") - else if (settings.labelType == "time-min") label = labelAnsis(Math.round(value*10)/10+"min") - else label = labelAnsis(value.toString()) - - const labelWithPrefixAndSuffix = prefix+label+suffix - return (settings.labelPosition == "start") ? labelWithPrefixAndSuffix+" "+leftBorder+bar+rightBorder : leftBorder+bar+rightBorder+" "+labelWithPrefixAndSuffix - },settings) - } -} - -/**## ODProgressBarRendererManagerIds_Default `interface` - * This interface is a list of ids available in the `ODProgressBarRendererManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODProgressBarRendererManagerIds_Default { - "opendiscord:value-renderer":ODProgressBarRenderer_Default, - "opendiscord:fraction-renderer":ODProgressBarRenderer_Default, - "opendiscord:percentage-renderer":ODProgressBarRenderer_Default, - "opendiscord:time-ms-renderer":ODProgressBarRenderer_Default, - "opendiscord:time-sec-renderer":ODProgressBarRenderer_Default, - "opendiscord:time-min-renderer":ODProgressBarRenderer_Default, -} - -/**## ODProgressBarRendererManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODProgressBarRendererManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.progressbars.renderers`! - */ -export class ODProgressBarRendererManager_Default extends ODProgressBarRendererManager { - get(id:ProgressBarId): ODProgressBarRendererManagerIds_Default[ProgressBarId] - get(id:ODValidId): ODProgressBarRenderer<{}>|null - - get(id:ODValidId): ODProgressBarRenderer<{}>|null { - return super.get(id) - } - - remove(id:ProgressBarId): ODProgressBarRendererManagerIds_Default[ProgressBarId] - remove(id:ODValidId): ODProgressBarRenderer<{}>|null - - remove(id:ODValidId): ODProgressBarRenderer<{}>|null { - return super.remove(id) - } - - exists(id:keyof ODProgressBarRendererManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODProgressBarManagerIds_Default `interface` - * This interface is a list of ids available in the `ODProgressBarManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODProgressBarManagerIds_Default { - "opendiscord:slash-command-remove":ODManualProgressBar, - "opendiscord:slash-command-create":ODManualProgressBar, - "opendiscord:slash-command-update":ODManualProgressBar, - "opendiscord:context-menu-remove":ODManualProgressBar, - "opendiscord:context-menu-create":ODManualProgressBar, - "opendiscord:context-menu-update":ODManualProgressBar, -} - -/**## ODProgressBarManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODProgressBarManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.progressbars`! - */ -export class ODProgressBarManager_Default extends ODProgressBarManager { - declare renderers: ODProgressBarRendererManager_Default - - get(id:ProgressBarId): ODProgressBarManagerIds_Default[ProgressBarId] - get(id:ODValidId): ODProgressBar|null - - get(id:ODValidId): ODProgressBar|null { - return super.get(id) - } - - remove(id:ProgressBarId): ODProgressBarManagerIds_Default[ProgressBarId] - remove(id:ODValidId): ODProgressBar|null - - remove(id:ODValidId): ODProgressBar|null { - return super.remove(id) - } - - exists(id:keyof ODProgressBarManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/responder.ts b/src/core/api/defaults/responder.ts deleted file mode 100644 index 8d7b317..0000000 --- a/src/core/api/defaults/responder.ts +++ /dev/null @@ -1,355 +0,0 @@ -/////////////////////////////////////// -//DEFAULT RESPONDER MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODAutocompleteResponder, ODAutocompleteResponderInstance, ODAutocompleteResponderManager, ODButtonResponder, ODButtonResponderInstance, ODButtonResponderManager, ODCommandResponder, ODCommandResponderInstance, ODCommandResponderManager, ODContextMenuResponder, ODContextMenuResponderInstance, ODContextMenuResponderManager, ODDropdownResponder, ODDropdownResponderInstance, ODDropdownResponderManager, ODModalResponder, ODModalResponderInstance, ODModalResponderManager, ODResponderManager } from "../modules/responder" -import { ODWorkerManager_Default } from "./worker" - -/**## ODResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders`! - */ -export class ODResponderManager_Default extends ODResponderManager { - declare commands: ODCommandResponderManager_Default - declare buttons: ODButtonResponderManager_Default - declare dropdowns: ODDropdownResponderManager_Default - declare modals: ODModalResponderManager_Default - declare contextMenus: ODContextMenuResponderManager_Default - declare autocomplete: ODAutocompleteResponderManager_Default -} - -/**## ODCommandResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODCommandResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODCommandResponderManagerIds_Default { - "opendiscord:help":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:help"|"opendiscord:logs"}, - "opendiscord:stats":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:stats"|"opendiscord:logs"}, - "opendiscord:panel":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:panel"|"opendiscord:logs"}, - "opendiscord:ticket":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:ticket"|"opendiscord:logs"}, - "opendiscord:blacklist":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:blacklist"|"opendiscord:discord-logs"|"opendiscord:logs"}, - - "opendiscord:close":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:close"|"opendiscord:logs"}, - "opendiscord:reopen":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:reopen"|"opendiscord:logs"}, - "opendiscord:delete":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:delete"|"opendiscord:logs"}, - "opendiscord:claim":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:claim"|"opendiscord:logs"}, - "opendiscord:unclaim":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:unclaim"|"opendiscord:logs"}, - "opendiscord:pin":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:pin"|"opendiscord:logs"}, - "opendiscord:unpin":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:unpin"|"opendiscord:logs"}, - - "opendiscord:rename":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:rename"|"opendiscord:logs"}, - "opendiscord:move":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:move"|"opendiscord:logs"}, - "opendiscord:add":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:add"|"opendiscord:logs"}, - "opendiscord:remove":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:remove"|"opendiscord:logs"}, - "opendiscord:clear":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:clear"|"opendiscord:logs"}, - "opendiscord:topic":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:topic"|"opendiscord:logs"}, - "opendiscord:priority":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:priority"|"opendiscord:logs"}, - "opendiscord:transfer":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:transfer"|"opendiscord:logs"}, - - "opendiscord:autoclose":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:autoclose"|"opendiscord:logs"}, - "opendiscord:autodelete":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:autodelete"|"opendiscord:logs"}, -} - -/**## ODCommandResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCommandResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.commands`! - */ -export class ODCommandResponderManager_Default extends ODCommandResponderManager { - get(id:CommandResponderId): ODCommandResponder_Default - get(id:ODValidId): ODCommandResponder<"slash"|"text",any>|null - - get(id:ODValidId): ODCommandResponder<"slash"|"text",any>|null { - return super.get(id) - } - - remove(id:CommandResponderId): ODCommandResponder_Default - remove(id:ODValidId): ODCommandResponder<"slash"|"text",any>|null - - remove(id:ODValidId): ODCommandResponder<"slash"|"text",any>|null { - return super.remove(id) - } - - exists(id:keyof ODCommandResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODCommandResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODCommandResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODCommandResponder`'s! - */ -export class ODCommandResponder_Default extends ODCommandResponder { - declare workers: ODWorkerManager_Default -} - -/**## ODButtonResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODButtonResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODButtonResponderManagerIds_Default { - "opendiscord:verifybar-success":{source:"button",params:{},workers:"opendiscord:handle-verifybar"}, - "opendiscord:verifybar-failure":{source:"button",params:{},workers:"opendiscord:handle-verifybar"}, - - "opendiscord:help-menu-switch":{source:"button",params:{},workers:"opendiscord:update-help-menu"}, - "opendiscord:help-menu-previous":{source:"button",params:{},workers:"opendiscord:update-help-menu"}, - "opendiscord:help-menu-next":{source:"button",params:{},workers:"opendiscord:update-help-menu"}, - - "opendiscord:ticket-option":{source:"button",params:{},workers:"opendiscord:ticket-option"}, - "opendiscord:role-option":{source:"button",params:{},workers:"opendiscord:role-option"}, - - "opendiscord:claim-ticket":{source:"button",params:{},workers:"opendiscord:claim-ticket"}, - "opendiscord:unclaim-ticket":{source:"button",params:{},workers:"opendiscord:unclaim-ticket"}, - "opendiscord:pin-ticket":{source:"button",params:{},workers:"opendiscord:pin-ticket"}, - "opendiscord:unpin-ticket":{source:"button",params:{},workers:"opendiscord:unpin-ticket"}, - "opendiscord:close-ticket":{source:"button",params:{},workers:"opendiscord:close-ticket"}, - "opendiscord:reopen-ticket":{source:"button",params:{},workers:"opendiscord:reopen-ticket"}, - "opendiscord:delete-ticket":{source:"button",params:{},workers:"opendiscord:delete-ticket"}, - - "opendiscord:transcript-error-retry":{source:"button",params:{},workers:"opendiscord:permissions"|"opendiscord:delete-ticket"|"opendiscord:logs"}, - "opendiscord:transcript-error-continue":{source:"button",params:{},workers:"opendiscord:permissions"|"opendiscord:delete-ticket"|"opendiscord:logs"}, - "opendiscord:clear-continue":{source:"button",params:{},workers:"opendiscord:clear-continue"}, -} - -/**## ODButtonResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODButtonResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.buttons`! - */ -export class ODButtonResponderManager_Default extends ODButtonResponderManager { - get(id:ButtonResponderId): ODButtonResponder_Default - get(id:ODValidId): ODButtonResponder<"button",any>|null - - get(id:ODValidId): ODButtonResponder<"button",any>|null { - return super.get(id) - } - - remove(id:ButtonResponderId): ODButtonResponder_Default - remove(id:ODValidId): ODButtonResponder<"button",any>|null - - remove(id:ODValidId): ODButtonResponder<"button",any>|null { - return super.remove(id) - } - - exists(id:keyof ODButtonResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODButtonResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODButtonResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODButtonResponder`'s! - */ -export class ODButtonResponder_Default extends ODButtonResponder { - declare workers: ODWorkerManager_Default -} - -/**## ODDropdownResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODDropdownResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODDropdownResponderManagerIds_Default { - "opendiscord:panel-dropdown-tickets":{source:"dropdown",params:{},workers:"opendiscord:panel-dropdown-tickets"}, -} - -/**## ODDropdownResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODDropdownResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.dropdowns`! - */ -export class ODDropdownResponderManager_Default extends ODDropdownResponderManager { - get(id:DropdownResponderId): ODDropdownResponder_Default - get(id:ODValidId): ODDropdownResponder<"dropdown",any>|null - - get(id:ODValidId): ODDropdownResponder<"dropdown",any>|null { - return super.get(id) - } - - remove(id:DropdownResponderId): ODDropdownResponder_Default - remove(id:ODValidId): ODDropdownResponder<"dropdown",any>|null - - remove(id:ODValidId): ODDropdownResponder<"dropdown",any>|null { - return super.remove(id) - } - - exists(id:keyof ODDropdownResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODDropdownResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODDropdownResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODDropdownResponder`'s! - */ -export class ODDropdownResponder_Default extends ODDropdownResponder { - declare workers: ODWorkerManager_Default -} - -/**## ODModalResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODModalResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODModalResponderManagerIds_Default { - "opendiscord:ticket-questions":{source:"modal",params:{},workers:"opendiscord:ticket-questions"}, - "opendiscord:close-ticket-reason":{source:"modal",params:{},workers:"opendiscord:close-ticket-reason"}, - "opendiscord:reopen-ticket-reason":{source:"modal",params:{},workers:"opendiscord:reopen-ticket-reason"}, - "opendiscord:delete-ticket-reason":{source:"modal",params:{},workers:"opendiscord:delete-ticket-reason"}, - "opendiscord:claim-ticket-reason":{source:"modal",params:{},workers:"opendiscord:claim-ticket-reason"}, - "opendiscord:unclaim-ticket-reason":{source:"modal",params:{},workers:"opendiscord:unclaim-ticket-reason"}, - "opendiscord:pin-ticket-reason":{source:"modal",params:{},workers:"opendiscord:pin-ticket-reason"}, - "opendiscord:unpin-ticket-reason":{source:"modal",params:{},workers:"opendiscord:unpin-ticket-reason"}, -} - -/**## ODModalResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODModalResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.dropdowns`! - */ -export class ODModalResponderManager_Default extends ODModalResponderManager { - get(id:ModalResponderId): ODModalResponder_Default - get(id:ODValidId): ODModalResponder<"modal",any>|null - - get(id:ODValidId): ODModalResponder<"modal",any>|null { - return super.get(id) - } - - remove(id:ModalResponderId): ODModalResponder_Default - remove(id:ODValidId): ODModalResponder<"modal",any>|null - - remove(id:ODValidId): ODModalResponder<"modal",any>|null { - return super.remove(id) - } - - exists(id:keyof ODModalResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODModalResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODModalResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODModalResponder`'s! - */ -export class ODModalResponder_Default extends ODModalResponder { - declare workers: ODWorkerManager_Default -} - -/**## ODContextMenuResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODContextMenuResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODContextMenuResponderManagerIds_Default { - //"opendiscord:example":{source:"context-menu",params:{},workers:"opendiscord:example"}, -} - -/**## ODContextMenuResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODContextMenuResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.contextMenus`! - */ -export class ODContextMenuResponderManager_Default extends ODContextMenuResponderManager { - get(id:ModalResponderId): ODContextMenuResponder_Default - get(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null - - get(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null { - return super.get(id) - } - - remove(id:ModalResponderId): ODContextMenuResponder_Default - remove(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null - - remove(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null { - return super.remove(id) - } - - exists(id:keyof ODContextMenuResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODContextMenuResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODContextMenuResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODContextMenuResponder`'s! - */ -export class ODContextMenuResponder_Default extends ODContextMenuResponder { - declare workers: ODWorkerManager_Default -} - -/**## ODAutocompleteResponderManagerIds_Default `interface` - * This interface is a list of ids available in the `ODAutocompleteResponderManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODAutocompleteResponderManagerIds_Default { - "opendiscord:panel-id":{source:"autocomplete",params:{},workers:"opendiscord:panel-id"}, - "opendiscord:option-id":{source:"autocomplete",params:{},workers:"opendiscord:option-id"} -} - -/**## ODAutocompleteResponderManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODAutocompleteResponderManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.responders.autocomplete`! - */ -export class ODAutocompleteResponderManager_Default extends ODAutocompleteResponderManager { - get(id:ModalResponderId): ODAutocompleteResponder_Default - get(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null - - get(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null { - return super.get(id) - } - - remove(id:ModalResponderId): ODAutocompleteResponder_Default - remove(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null - - remove(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null { - return super.remove(id) - } - - exists(id:keyof ODAutocompleteResponderManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODAutocompleteResponder_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODAutocompleteResponder class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODAutocompleteResponder`'s! - */ -export class ODAutocompleteResponder_Default extends ODAutocompleteResponder { - declare workers: ODWorkerManager_Default -} \ No newline at end of file diff --git a/src/core/api/defaults/session.ts b/src/core/api/defaults/session.ts deleted file mode 100644 index 8eef65b..0000000 --- a/src/core/api/defaults/session.ts +++ /dev/null @@ -1,42 +0,0 @@ -/////////////////////////////////////// -//DEFAULT SESSION MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODSession, ODSessionManager } from "../modules/session" - -/**## ODSessionManagerIds_Default `interface` - * This interface is a list of ids available in the `ODSessionManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODSessionManagerIds_Default { - //"test-session":ODSession -} - -/**## ODSessionManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODSessionManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.sessions`! - */ -export class ODSessionManager_Default extends ODSessionManager { - get(id:SessionId): ODSessionManagerIds_Default[SessionId] - get(id:ODValidId): ODSession|null - - get(id:ODValidId): ODSession|null { - return super.get(id) - } - - remove(id:SessionId): ODSessionManagerIds_Default[SessionId] - remove(id:ODValidId): ODSession|null - - remove(id:ODValidId): ODSession|null { - return super.remove(id) - } - - exists(id:keyof ODSessionManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/startscreen.ts b/src/core/api/defaults/startscreen.ts deleted file mode 100644 index b3da322..0000000 --- a/src/core/api/defaults/startscreen.ts +++ /dev/null @@ -1,48 +0,0 @@ -/////////////////////////////////////// -//DEFAULT STARTSCREEN MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODStartScreenCategoryComponent, ODStartScreenComponent, ODStartScreenFlagsCategoryComponent, ODStartScreenHeaderComponent, ODStartScreenLiveStatusCategoryComponent, ODStartScreenLogoComponent, ODStartScreenManager, ODStartScreenPluginsCategoryComponent, ODStartScreenPropertiesCategoryComponent } from "../modules/startscreen" - -/**## ODStartScreenManagerIds_Default `interface` - * This interface is a list of ids available in the `ODStartScreenManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStartScreenManagerIds_Default { - "opendiscord:logo":ODStartScreenLogoComponent, - "opendiscord:header":ODStartScreenHeaderComponent, - "opendiscord:flags":ODStartScreenFlagsCategoryComponent, - "opendiscord:plugins":ODStartScreenPluginsCategoryComponent, - "opendiscord:stats":ODStartScreenPropertiesCategoryComponent, - "opendiscord:livestatus":ODStartScreenLiveStatusCategoryComponent, - "opendiscord:logs":ODStartScreenCategoryComponent -} - -/**## ODStartScreenManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODStartScreenManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.startscreen`! - */ -export class ODStartScreenManager_Default extends ODStartScreenManager { - get(id:StartScreenId): ODStartScreenManagerIds_Default[StartScreenId] - get(id:ODValidId): ODStartScreenComponent|null - - get(id:ODValidId): ODStartScreenComponent|null { - return super.get(id) - } - - remove(id:StartScreenId): ODStartScreenManagerIds_Default[StartScreenId] - remove(id:ODValidId): ODStartScreenComponent|null - - remove(id:ODValidId): ODStartScreenComponent|null { - return super.remove(id) - } - - exists(id:keyof ODStartScreenManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/stat.ts b/src/core/api/defaults/stat.ts deleted file mode 100644 index dd8486e..0000000 --- a/src/core/api/defaults/stat.ts +++ /dev/null @@ -1,472 +0,0 @@ -/////////////////////////////////////// -//DEFAULT SESSION MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODStatScope, ODStatGlobalScope, ODStatsManager, ODStat, ODBasicStat, ODDynamicStat, ODValidStatValue, ODStatScopeSetMode } from "../modules/stat" - -/**## ODStatsManagerIds_Default `interface` - * This interface is a list of ids available in the `ODStatsManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatsManagerIds_Default { - "opendiscord:global":ODStatGlobalScope_DefaultGlobal, - "opendiscord:system":ODStatGlobalScope_DefaultSystem, - "opendiscord:user":ODStatScope_DefaultUser, - "opendiscord:ticket":ODStatScope_DefaultTicket, - "opendiscord:participants":ODStatScope_DefaultParticipants, - "opendiscord:messages":ODStatScope_DefaultMessages, -} - -/**## ODStatsManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.stats`! - */ -export class ODStatsManager_Default extends ODStatsManager { - get(id:StatsId): ODStatsManagerIds_Default[StatsId] - get(id:ODValidId): ODStatScope|null - - get(id:ODValidId): ODStatScope|null { - return super.get(id) - } - - remove(id:StatsId): ODStatsManagerIds_Default[StatsId] - remove(id:ODValidId): ODStatScope|null - - remove(id:ODValidId): ODStatScope|null { - return super.remove(id) - } - - exists(id:keyof ODStatsManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODStatGlobalScopeIds_DefaultGlobal `type` - * This interface is a list of ids available in the `ODStatGlobalScope_DefaultGlobal` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatGlobalScopeIds_DefaultGlobal { - "opendiscord:tickets-created":ODBasicStat, - "opendiscord:tickets-closed":ODBasicStat, - "opendiscord:tickets-deleted":ODBasicStat, - "opendiscord:tickets-reopened":ODBasicStat, - "opendiscord:tickets-autoclosed":ODBasicStat, - "opendiscord:tickets-autodeleted":ODBasicStat, - "opendiscord:tickets-claimed":ODBasicStat, - "opendiscord:tickets-pinned":ODBasicStat, - "opendiscord:tickets-moved":ODBasicStat, - "opendiscord:tickets-transferred":ODBasicStat, - "opendiscord:users-blacklisted":ODBasicStat, - "opendiscord:transcripts-created":ODBasicStat, - "opendiscord:ticket-volume":ODDynamicStat, - "opendiscord:average-tickets":ODDynamicStat, -} - -/**## ODStatGlobalScope_DefaultGlobal `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:global` category in `opendiscord.stats`! - */ -export class ODStatGlobalScope_DefaultGlobal extends ODStatGlobalScope { - get(id:StatsId): ODStatGlobalScopeIds_DefaultGlobal[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatGlobalScopeIds_DefaultGlobal[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatGlobalScopeIds_DefaultGlobal): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId): Promise - getStat(id:ODValidId): Promise - - getStat(id:ODValidId): Promise { - return super.getStat(id) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,value,mode) - } - - resetStat(id:ODValidId): Promise - resetStat(id:ODValidId): Promise - - resetStat(id:ODValidId): Promise { - return super.resetStat(id) - } -} - -/**## ODStatGlobalScopeIds_DefaultSystem `type` - * This interface is a list of ids available in the `ODStatScope_DefaultSystem` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatGlobalScopeIds_DefaultSystem { - "opendiscord:startup-date":ODDynamicStat, - "opendiscord:system-uptime":ODDynamicStat, - "opendiscord:version":ODDynamicStat -} - -/**## ODStatGlobalScope_DefaultSystem `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:system` category in `opendiscord.stats`! - */ -export class ODStatGlobalScope_DefaultSystem extends ODStatGlobalScope { - get(id:StatsId): ODStatGlobalScopeIds_DefaultSystem[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatGlobalScopeIds_DefaultSystem[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatGlobalScopeIds_DefaultSystem): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId): Promise - getStat(id:ODValidId): Promise - - getStat(id:ODValidId): Promise { - return super.getStat(id) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,value,mode) - } - - resetStat(id:ODValidId): Promise - resetStat(id:ODValidId): Promise - - resetStat(id:ODValidId): Promise { - return super.resetStat(id) - } -} - -/**## ODStatScopeIds_DefaultUser `type` - * This interface is a list of ids available in the `ODStatScope_DefaultUser` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatScopeIds_DefaultUser { - "opendiscord:name":ODDynamicStat, - "opendiscord:role":ODDynamicStat, - "opendiscord:tickets-created":ODBasicStat, - "opendiscord:tickets-closed":ODBasicStat, - "opendiscord:tickets-deleted":ODBasicStat, - "opendiscord:tickets-reopened":ODBasicStat, - "opendiscord:tickets-claimed":ODBasicStat, - "opendiscord:tickets-pinned":ODBasicStat, - "opendiscord:tickets-moved":ODBasicStat, - "opendiscord:tickets-transferred":ODBasicStat, - "opendiscord:users-blacklisted":ODBasicStat, - "opendiscord:transcripts-created":ODBasicStat, - "opendiscord:current-tickets":ODDynamicStat, -} - -/**## ODStatScope_DefaultUser `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:user` category in `opendiscord.stats`! - */ -export class ODStatScope_DefaultUser extends ODStatScope { - get(id:StatsId): ODStatScopeIds_DefaultUser[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatScopeIds_DefaultUser[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatScopeIds_DefaultUser): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId, scopeId:string): Promise - getStat(id:ODValidId, scopeId:string): Promise - - getStat(id:ODValidId, scopeId:string): Promise { - return super.getStat(id,scopeId) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,scopeId,value,mode) - } - - resetStat(id:ODValidId, scopeId:string): Promise - resetStat(id:ODValidId, scopeId:string): Promise - - resetStat(id:ODValidId, scopeId:string): Promise { - return super.resetStat(id,scopeId) - } -} - -/**## ODStatScopeIds_DefaultTicket `type` - * This interface is a list of ids available in the `ODStatScope_DefaultTicket` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatScopeIds_DefaultTicket { - "opendiscord:name":ODDynamicStat, - "opendiscord:status":ODDynamicStat, - "opendiscord:claimed":ODDynamicStat, - "opendiscord:pinned":ODDynamicStat, - "opendiscord:creation-date":ODDynamicStat, - "opendiscord:creator":ODDynamicStat, - "opendiscord:ticket-age":ODDynamicStat, - "opendiscord:response-time":ODDynamicStat, - "opendiscord:resolution-time":ODDynamicStat, -} - -/**## ODStatScope_DefaultTicket `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:ticket` category in `opendiscord.stats`! - */ -export class ODStatScope_DefaultTicket extends ODStatScope { - get(id:StatsId): ODStatScopeIds_DefaultTicket[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatScopeIds_DefaultTicket[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatScopeIds_DefaultTicket): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId, scopeId:string): Promise - getStat(id:ODValidId, scopeId:string): Promise - - getStat(id:ODValidId, scopeId:string): Promise { - return super.getStat(id,scopeId) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,scopeId,value,mode) - } - - resetStat(id:ODValidId, scopeId:string): Promise - resetStat(id:ODValidId, scopeId:string): Promise - - resetStat(id:ODValidId, scopeId:string): Promise { - return super.resetStat(id,scopeId) - } -} - -/**## ODStatScopeIds_DefaultParticipants `type` - * This interface is a list of ids available in the `ODStatScope_DefaultParticipants` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatScopeIds_DefaultParticipants { - "opendiscord:participants":ODDynamicStat -} - -/**## ODStatScope_DefaultParticipants `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:participants` category in `opendiscord.stats`! - */ -export class ODStatScope_DefaultParticipants extends ODStatScope { - get(id:StatsId): ODStatScopeIds_DefaultParticipants[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatScopeIds_DefaultParticipants[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatScopeIds_DefaultParticipants): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId, scopeId:string): Promise - getStat(id:ODValidId, scopeId:string): Promise - - getStat(id:ODValidId, scopeId:string): Promise { - return super.getStat(id,scopeId) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,scopeId,value,mode) - } - - resetStat(id:ODValidId, scopeId:string): Promise - resetStat(id:ODValidId, scopeId:string): Promise - - resetStat(id:ODValidId, scopeId:string): Promise { - return super.resetStat(id,scopeId) - } -} - -/**## ODStatScopeIds_DefaultMessages `type` - * This interface is a list of ids available in the `ODStatScope_DefaultMessages` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODStatScopeIds_DefaultMessages { - "opendiscord:count":ODDynamicStat -} - -/**## ODStatScope_DefaultMessages `default_class` - * This is a special class that adds type definitions & typescript to the ODStatsManager class. - * It doesn't add any extra features! - * - * This default class is made for the `opendiscord:participants` category in `opendiscord.stats`! - */ -export class ODStatScope_DefaultMessages extends ODStatScope { - get(id:StatsId): ODStatScopeIds_DefaultMessages[StatsId] - get(id:ODValidId): ODStat|null - - get(id:ODValidId): ODStat|null { - return super.get(id) - } - - remove(id:StatsId): ODStatScopeIds_DefaultMessages[StatsId] - remove(id:ODValidId): ODStat|null - - remove(id:ODValidId): ODStat|null { - return super.remove(id) - } - - exists(id:keyof ODStatScopeIds_DefaultMessages): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getStat(id:StatsId, scopeId:string): Promise - getStat(id:ODValidId, scopeId:string): Promise - - getStat(id:ODValidId, scopeId:string): Promise { - return super.getStat(id,scopeId) - } - - getAllStats(id:StatsId): Promise<{id:string,value:ODValidStatValue}[]> - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> - - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - - setStat(id:StatsId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise - - setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,scopeId,value,mode) - } - - resetStat(id:ODValidId, scopeId:string): Promise - resetStat(id:ODValidId, scopeId:string): Promise - - resetStat(id:ODValidId, scopeId:string): Promise { - return super.resetStat(id,scopeId) - } -} \ No newline at end of file diff --git a/src/core/api/defaults/verifybar.ts b/src/core/api/defaults/verifybar.ts deleted file mode 100644 index 7ee623b..0000000 --- a/src/core/api/defaults/verifybar.ts +++ /dev/null @@ -1,72 +0,0 @@ -/////////////////////////////////////// -//DEFAULT VERIFYBAR MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODButtonResponderInstance } from "../modules/responder" -import { ODWorkerManager_Default } from "../defaults/worker" -import { ODVerifyBarManager, ODVerifyBar } from "../modules/verifybar" -import * as discord from "discord.js" - -/**## ODVerifyBarManagerIds_Default `interface` - * This interface is a list of ids available in the `ODVerifyBarManager_Default` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODVerifyBarManagerIds_Default { - "opendiscord:claim-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:claim-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:claim-ticket-unclaim-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:claim-ticket",failureWorkerIds:"opendiscord:back-to-unclaim-message"}, - "opendiscord:unclaim-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:unclaim-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:unclaim-ticket-claim-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:unclaim-ticket",failureWorkerIds:"opendiscord:back-to-claim-message"}, - "opendiscord:pin-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:pin-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:pin-ticket-unpin-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:pin-ticket",failureWorkerIds:"opendiscord:back-to-unpin-message"}, - "opendiscord:unpin-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:unpin-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:unpin-ticket-pin-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:unpin-ticket",failureWorkerIds:"opendiscord:back-to-pin-message"}, - "opendiscord:close-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:close-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:close-ticket-reopen-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:close-ticket",failureWorkerIds:"opendiscord:back-to-reopen-message"}, - "opendiscord:reopen-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:reopen-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"}, - "opendiscord:reopen-ticket-close-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:reopen-ticket",failureWorkerIds:"opendiscord:back-to-close-message"}, - "opendiscord:reopen-ticket-autoclose-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:reopen-ticket",failureWorkerIds:"opendiscord:back-to-autoclose-message"}, - "opendiscord:delete-ticket-ticket-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:delete-ticket",failureWorkerIds:"opendiscord:back-to-ticket-message"} - "opendiscord:delete-ticket-close-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:delete-ticket",failureWorkerIds:"opendiscord:back-to-close-message"} - "opendiscord:delete-ticket-reopen-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:delete-ticket",failureWorkerIds:"opendiscord:back-to-reopen-message"} - "opendiscord:delete-ticket-autoclose-message":{successWorkerIds:"opendiscord:permissions"|"opendiscord:delete-ticket",failureWorkerIds:"opendiscord:back-to-autoclose-message"} -} - -/**## ODVerifyBarManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODVerifyBarManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.verifybars`! - */ -export class ODVerifyBarManager_Default extends ODVerifyBarManager { - get(id:VerifyBarId): ODVerifyBar_Default - get(id:ODValidId): ODVerifyBar|null - - get(id:ODValidId): ODVerifyBar|null { - return super.get(id) - } - - remove(id:VerifyBarId): ODVerifyBar_Default - remove(id:ODValidId): ODVerifyBar|null - - remove(id:ODValidId): ODVerifyBar|null { - return super.remove(id) - } - - exists(id:keyof ODVerifyBarManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODVerifyBar_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODVerifyBar class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODVerifyBar`'s! - */ -export class ODVerifyBar_Default extends ODVerifyBar { - declare success: ODWorkerManager_Default|null},SuccessWorkerIds> - declare failure: ODWorkerManager_Default|null},FailureWorkerIds> -} \ No newline at end of file diff --git a/src/core/api/defaults/worker.ts b/src/core/api/defaults/worker.ts deleted file mode 100644 index 3642aee..0000000 --- a/src/core/api/defaults/worker.ts +++ /dev/null @@ -1,35 +0,0 @@ -/////////////////////////////////////// -//DEFAULT WORKER MODULE -/////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODWorker, ODWorkerManager } from "../modules/worker" - - -/**## ODWorkerManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODWorkerManager class. - * It doesn't add any extra features! - * - * This default class is made for the worker manager in actions, builders & responders! - */ -export class ODWorkerManager_Default extends ODWorkerManager { - get(id:WorkerIds): ODWorker - get(id:ODValidId): ODWorker|null - - get(id:ODValidId): ODWorker|null { - return super.get(id) - } - - remove(id:WorkerIds): ODWorker - remove(id:ODValidId): ODWorker|null - - remove(id:ODValidId): ODWorker|null { - return super.remove(id) - } - - exists(id:WorkerIds): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} \ No newline at end of file diff --git a/src/core/api/main.ts b/src/core/api/main.ts deleted file mode 100644 index f5e282a..0000000 --- a/src/core/api/main.ts +++ /dev/null @@ -1,193 +0,0 @@ -//BASE MODULES -import { ODEnvHelper, ODVersion } from "./modules/base" -import { ODConsoleManager, ODConsoleMessage, ODConsoleMessageParam, ODConsoleMessageTypes, ODDebugFileManager, ODDebugger, ODError } from "./modules/console" -import { ODCheckerStorage } from "./modules/checker" -import { ODDefaultsManager } from "./modules/defaults" - -//DEFAULT MODULES -import { ODVersionManager_Default } from "./defaults/base" -import { ODPluginManager_Default } from "./defaults/plugin" -import { ODEventManager_Default } from "./defaults/event" -import { ODConfigManager_Default} from "./defaults/config" -import { ODDatabaseManager_Default } from "./defaults/database" -import { ODFlagManager_Default } from "./defaults/flag" -import { ODSessionManager_Default } from "./defaults/session" -import { ODLanguageManager_Default } from "./defaults/language" -import { ODCheckerFunctionManager_Default, ODCheckerManager_Default, ODCheckerRenderer_Default, ODCheckerTranslationRegister_Default } from "./defaults/checker" -import { ODClientManager_Default } from "./defaults/client" -import { ODBuilderManager_Default } from "./defaults/builder" -import { ODResponderManager_Default } from "./defaults/responder" -import { ODActionManager_Default } from "./defaults/action" -import { ODPermissionManager_Default } from "./defaults/permission" -import { ODHelpMenuManager_Default } from "./defaults/helpmenu" -import { ODStatsManager_Default } from "./defaults/stat" -import { ODCodeManager_Default } from "./defaults/code" -import { ODCooldownManager_Default } from "./defaults/cooldown" -import { ODPostManager_Default } from "./defaults/post" -import { ODVerifyBarManager_Default } from "./defaults/verifybar" -import { ODProgressBarManager_Default } from "./defaults/progressbar" -import { ODStartScreenManager_Default } from "./defaults/startscreen" -import { ODLiveStatusManager_Default } from "./defaults/console" - -//OPEN TICKET MODULES -import { ODOptionManager } from "./openticket/option" -import { ODPanelManager } from "./openticket/panel" -import { ODTicketManager } from "./openticket/ticket" -import { ODQuestionManager } from "./openticket/question" -import { ODBlacklistManager } from "./openticket/blacklist" -import { ODTranscriptManager_Default } from "./openticket/transcript" -import { ODRoleManager } from "./openticket/role" -import { ODPriorityManager_Default } from "./openticket/priority" - -/**## ODMain `class` - * This is the main Open Ticket class. - * It contains all managers from the entire bot & has shortcuts to the event & logging system. - * - * This class can't be overwritten or extended & is available as the global variable `openticket`! - */ -export class ODMain { - /**The manager that handles all versions in the bot. */ - versions: ODVersionManager_Default - - /**The timestamp that the (node.js) process of the bot started. */ - processStartupDate: Date = new Date() - /**The timestamp that the bot finished loading and is ready for usage. */ - readyStartupDate: Date|null = null - - /**The manager responsible for the debug file. (`otdebug.txt`) */ - debugfile: ODDebugFileManager - /**The manager responsible for the console system. (logs, errors, etc) */ - console: ODConsoleManager - /**The manager responsible for sending debug logs to the debug file. (`otdebug.txt`) */ - debug: ODDebugger - /**The manager containing all Open Ticket events. */ - events: ODEventManager_Default - - /**The manager that handles & executes all plugins in the bot. */ - plugins: ODPluginManager_Default - /**The manager that manages & checks all the console flags of the bot. (like `--debug`) */ - flags: ODFlagManager_Default - /**The manager responsible for progress bars in the console. */ - progressbars: ODProgressBarManager_Default - /**The manager that manages & contains all the config files of the bot. (like `config/general.json`) */ - configs: ODConfigManager_Default - /**The manager that manages & contains all the databases of the bot. (like `database/global.json`) */ - databases: ODDatabaseManager_Default - /**The manager that manages all the data sessions of the bot. (it's a temporary database) */ - sessions: ODSessionManager_Default - /**The manager that manages all languages & translations of the bot. (but not for plugins) */ - languages: ODLanguageManager_Default - - /**The manager that handles & executes all config checkers in the bot. (the code that checks if you have something wrong in your config) */ - checkers: ODCheckerManager_Default - /**The manager that manages all builders in the bot. (e.g. buttons, dropdowns, messages, modals, etc) */ - builders: ODBuilderManager_Default - /**The manager that manages all responders in the bot. (e.g. commands, buttons, dropdowns, modals) */ - responders: ODResponderManager_Default - /**The manager that manages all actions or procedures in the bot. (e.g. ticket-creation, ticket-deletion, ticket-claiming, etc) */ - actions: ODActionManager_Default - /**The manager that manages all verify bars in the bot. (the โœ… โŒ buttons) */ - verifybars: ODVerifyBarManager_Default - /**The manager that contains all permissions for commands & actions in the bot. (use it to check if someone has admin perms or not) */ - permissions: ODPermissionManager_Default - /**The manager that contains all cooldowns of the bot. (e.g. ticket-cooldowns) */ - cooldowns: ODCooldownManager_Default - /**The manager that manages & renders the Open Ticket help menu. (not the embed, but the text) */ - helpmenu: ODHelpMenuManager_Default - /**The manager that manages, saves & renders the Open Ticket statistics. (not the embed, but the text & database) */ - stats: ODStatsManager_Default - /**This manager is a place where you can put code that executes when the bot almost finishes the setup. (can be used for less important stuff that doesn't require an exact time-order) */ - code: ODCodeManager_Default - /**The manager that manages all posts (static discord channels) in the bot. (e.g. (transcript) logs, etc) */ - posts: ODPostManager_Default - - /**The manager responsible for everything related to the client. (e.g. status, login, slash & text commands, etc) */ - client: ODClientManager_Default - /**This manager contains A LOD of booleans. With these switches, you can turn off "default behaviours" from the bot. This is used if you want to replace the default Open Ticket code. */ - defaults: ODDefaultsManager - /**This manager manages all the variables in the ENV. It reads from both the `.env` file & the `process.env`. (these 2 will be combined) */ - env: ODEnvHelper - - /**The manager responsible for the livestatus system. (remote console logs) */ - livestatus: ODLiveStatusManager_Default - /**The manager responsible for the livestatus system. (remote console logs) */ - startscreen: ODStartScreenManager_Default - - //OPEN TICKET - /**The manager that manages all the data of questions in the bot. (these are used in options & tickets) */ - questions: ODQuestionManager - /**The manager that manages all the data of options in the bot. (these are used for panels, ticket creation, reaction roles) */ - options: ODOptionManager - /**The manager that manages all the data of panels in the bot. (panels contain the options) */ - panels: ODPanelManager - /**The manager that manages all tickets in the bot. (here, you can get & edit a lot of data from tickets) */ - tickets: ODTicketManager - /**The manager that manages the ticket blacklist. (people who are blacklisted can't create a ticket) */ - blacklist: ODBlacklistManager - /**The manager that manages the ticket transcripts. (both the history & compilers) */ - transcripts: ODTranscriptManager_Default - /**The manager that manages all reaction roles in the bot. (here, you can add additional data to roles) */ - roles: ODRoleManager - /**The manager that manages all priority levels in the bot. (register/edit ticket priority levels) */ - priorities: ODPriorityManager_Default - - constructor(){ - this.versions = new ODVersionManager_Default() - this.versions.add(ODVersion.fromString("opendiscord:version","v4.1.3")) - this.versions.add(ODVersion.fromString("opendiscord:api","v1.0.0")) - this.versions.add(ODVersion.fromString("opendiscord:transcripts","v2.1.0")) - this.versions.add(ODVersion.fromString("opendiscord:livestatus","v2.0.0")) - - this.debugfile = new ODDebugFileManager("./","otdebug.txt",5000,this.versions.get("opendiscord:version")) - this.console = new ODConsoleManager(100,this.debugfile) - this.debug = new ODDebugger(this.console) - this.events = new ODEventManager_Default(this.debug) - - this.plugins = new ODPluginManager_Default(this.debug) - this.flags = new ODFlagManager_Default(this.debug) - this.progressbars = new ODProgressBarManager_Default(this.debug) - this.configs = new ODConfigManager_Default(this.debug) - this.databases = new ODDatabaseManager_Default(this.debug) - this.sessions = new ODSessionManager_Default(this.debug) - this.languages = new ODLanguageManager_Default(this.debug,false) - - this.checkers = new ODCheckerManager_Default(this.debug,new ODCheckerStorage(),new ODCheckerRenderer_Default(),new ODCheckerTranslationRegister_Default(),new ODCheckerFunctionManager_Default(this.debug)) - this.builders = new ODBuilderManager_Default(this.debug) - this.client = new ODClientManager_Default(this.debug) - this.responders = new ODResponderManager_Default(this.debug,this.client) - this.actions = new ODActionManager_Default(this.debug) - this.verifybars = new ODVerifyBarManager_Default(this.debug) - this.permissions = new ODPermissionManager_Default(this.debug,this.client) - this.cooldowns = new ODCooldownManager_Default(this.debug) - this.helpmenu = new ODHelpMenuManager_Default(this.debug) - this.stats = new ODStatsManager_Default(this.debug) - this.code = new ODCodeManager_Default(this.debug) - this.posts = new ODPostManager_Default(this.debug) - - this.defaults = new ODDefaultsManager() - this.env = new ODEnvHelper() - - this.livestatus = new ODLiveStatusManager_Default(this.debug,this) - this.startscreen = new ODStartScreenManager_Default(this.debug,this.livestatus) - - //OPEN TICKET - this.questions = new ODQuestionManager(this.debug) - this.options = new ODOptionManager(this.debug) - this.panels = new ODPanelManager(this.debug) - this.tickets = new ODTicketManager(this.debug,this.client) - this.blacklist = new ODBlacklistManager(this.debug) - this.transcripts = new ODTranscriptManager_Default(this.debug,this.tickets,this.client,this.permissions) - this.roles = new ODRoleManager(this.debug) - this.priorities = new ODPriorityManager_Default(this.debug) - } - - /**Log a message to the console. But in the Open Ticket style :) */ - log(message:ODConsoleMessage): void - log(message:ODError): void - log(message:string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]): void - log(message:ODConsoleMessage|ODError|string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]){ - if (message instanceof ODConsoleMessage) this.console.log(message) - else if (message instanceof ODError) this.console.log(message) - else if (["string","number","boolean","object"].includes(typeof message)) this.console.log(message,type,params) - } -} \ No newline at end of file diff --git a/src/core/api/modules/action.ts b/src/core/api/modules/action.ts deleted file mode 100644 index 92a5993..0000000 --- a/src/core/api/modules/action.ts +++ /dev/null @@ -1,58 +0,0 @@ -/////////////////////////////////////// -//ACTION MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODValidId, ODSystemError, ODManagerData } from "./base" -import { ODWorkerManager, ODWorkerCallback, ODWorker } from "./worker" -import { ODDebugger } from "./console" - -/**## ODActionImplementation `class` - * This is an Open Ticket action implementation. - * - * It is a basic implementation of the `ODWorkerManager` used by all `ODAction` classes. - * - * This class can't be used stand-alone & needs to be extended from! - */ -export class ODActionImplementation extends ODManagerData { - /**The manager that has all workers of this implementation */ - workers: ODWorkerManager - - constructor(id:ODValidId, callback?:ODWorkerCallback, priority?:number, callbackId?:ODValidId){ - super(id) - this.workers = new ODWorkerManager("descending") - if (callback) this.workers.add(new ODWorker(callbackId ? callbackId : id,priority ?? 0,callback)) - } - /**Execute all workers & return the result. */ - async run(source:Source, params:Params): Promise> { - throw new ODSystemError("Tried to build an unimplemented ODResponderImplementation") - } -} - -/**## ODActionManager `class` - * This is an Open Ticket action manager. - * - * It contains all Open Ticket actions. You can compare actions with some sort of "procedure". - * It's a complicated task that is divided into multiple functions. - * - * Some examples are `ticket-creation`, `ticket-closing`, `ticket-claiming`, ... - * - * It's recommended to use this system in combination with Open Ticket responders! - */ -export class ODActionManager extends ODManager> { - constructor(debug:ODDebugger){ - super(debug,"action") - } -} - -export class ODAction extends ODActionImplementation { - /**Run this action */ - async run(source:Source, params:Params): Promise> { - //create instance - const instance = {} - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //return data generated by workers - return instance - } -} \ No newline at end of file diff --git a/src/core/api/modules/base.ts b/src/core/api/modules/base.ts deleted file mode 100644 index ef70065..0000000 --- a/src/core/api/modules/base.ts +++ /dev/null @@ -1,763 +0,0 @@ -/////////////////////////////////////// -//BASE MODULE -/////////////////////////////////////// -import * as fs from "fs" -import { ODConsoleWarningMessage, ODDebugger } from "./console" - -/**## ODPromiseVoid `type` - * This is a simple type to represent a callback return value that could be a promise or not. - */ -export type ODPromiseVoid = void|Promise - -/**## ODOptionalPromise `type` - * This is a simple type to represent a type as normal value or a promise value. - */ -export type ODOptionalPromise = T|Promise - - -/**## ODValidButtonColor `type` - * This is a collection of all the possible button colors. - */ -export type ODValidButtonColor = "gray"|"red"|"green"|"blue" - -/**## ODValidId `type` - * This is a valid Open Ticket identifier. It can be an `ODId` or `string`! - * - * You will see this type in many functions from Open Ticket. - */ -export type ODValidId = string|ODId - -/**## ODValidJsonType `type` - * This is a collection of all types that can be stored in a JSON file! - * - * list: `string`, `number`, `boolean`, `array`, `object`, `null` - */ -export type ODValidJsonType = string|number|boolean|object|ODValidJsonType[]|null - - -/**## ODInterfaceWithPartialProperty `type` - * This is a utility type to create an interface where some properties are optional! - */ -export type ODInterfaceWithPartialProperty = Omit & Partial> - -/**## ODDiscordIdType `type` - * A list of all available discord ID types. Used in the config checker. - */ -export type ODDiscordIdType = "role"|"server"|"channel"|"category"|"user"|"member"|"interaction"|"message" - -/**## ODId `class` - * This is an Open Ticket identifier. - * - * It can only contain the following characters: `a-z`, `A-Z`, `0-9`, `:`, `-` & `_` - * - * You can use this class to assign a unique id when creating configs, databases, languages & more! - */ -export class ODId { - /**The full value of this `ODId` as a `string`. */ - #value: string - /**The full value of this `ODId` as a `string`. */ - set value(id:string){ - this._change(this.#value,id) - this.#value = id - } - get value(){ - return this.#value - } - /**The change listener for the parent `ODManager` of this `ODId`. */ - #change: ((oldId:string,newId:string) => void)|null = null - - constructor(id:ODValidId){ - if (typeof id != "string" && !(id instanceof ODId)) throw new ODSystemError("Invalid constructor parameter => id:ODValidId") - - if (typeof id == "string"){ - //id is string - const result: string[] = [] - const charregex = /[a-zA-Z0-9รฉรจรงร รชรขรดรปรฎ\:\-\_]/ - - id.split("").forEach((char) => { - if (charregex.test(char)){ - result.push(char) - } - }) - - if (result.length > 0) this.#value = result.join("") - else throw new ODSystemError("invalid ID at 'new ODID(id: "+id+")'") - }else{ - //id is ODId - this.#value = id.#value - } - } - - /**Returns a string representation of this id. (same as `this.value`) */ - toString(){ - return this.#value - } - /**The namespace of the id before `:`. (e.g. `openticket` for `openticket:autoclose-enabled`) */ - getNamespace(){ - const splitted = this.#value.split(":") - if (splitted.length > 1) return splitted[0] - else return "" - } - /**The identifier of the id after `:`. (e.g. `autoclose-enabled` for `openticket:autoclose-enabled`) */ - getIdentifier(){ - const splitted = this.#value.split(":") - if (splitted.length > 1){ - splitted.shift() - return splitted.join(":") - }else return this.#value - } - /**Trigger an `onChange()` event in the parent `ODManager` of this class. */ - protected _change(oldId:string,newId:string){ - if (this.#change){ - try{ - this.#change(oldId,newId) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to execute _change() callback!") - } - } - } - /****(โŒ SYSTEM ONLY!!)** Set the callback executed when a value inside this class changes. */ - changed(callback:((oldId:string,newId:string) => void)|null){ - this.#change = callback - } -} - -/**## ODManagerChangeHelper `class` - * This is an Open Ticket manager change helper. - * - * It is used to let the "onChange" event in the `ODManager` class work. - * You can use this class when extending your own `ODManager` - */ -export class ODManagerChangeHelper { - #change: (() => void)|null = null - - /**Trigger an `onChange()` event in the parent `ODManager` of this class. */ - protected _change(){ - if (this.#change){ - try{ - this.#change() - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to execute _change() callback!") - } - } - } - /****(โŒ SYSTEM ONLY!!)** Set the callback executed when a value inside this class changes. */ - changed(callback:(() => void)|null){ - this.#change = callback - } -} - -/**## ODManagerData `class` - * This is Open Ticket manager data. - * - * It provides a template for all classes that are used in the `ODManager`. - * - * There is an `id:ODId` property & also some events used in the manager. - */ -export class ODManagerData extends ODManagerChangeHelper { - /**The id of this data. */ - id: ODId - - constructor(id:ODValidId){ - if (typeof id != "string" && !(id instanceof ODId)) throw new ODSystemError("Invalid constructor parameter => id:ODValidId") - super() - this.id = new ODId(id) - } -} - -/**## ODManagerCallback `type` - * This is a callback for the `onChange` and `onRemove` events in the `ODManager` - */ -export type ODManagerCallback = (data:DataType) => void -/**## ODManagerAddCallback `type` - * This is a callback for the `onAdd` event in the `ODManager` - */ -export type ODManagerAddCallback = (data:DataType, overwritten:boolean) => void - -/**## ODManager `class` - * This is an Open Ticket manager. - * - * It can be used to store & manage classes based on their `ODId`. - * It is somewhat the same as the default JS `Map()`. - * You can extend this class when creating your own classes & managers. - * - * This class has many useful functions based on `ODId` (add, get, remove, getAll, getFiltered, exists, loopAll, ...) - */ -export class ODManager extends ODManagerChangeHelper { - /**Alias to Open Ticket debugger. */ - #debug?: ODDebugger - /**The message to send when debugging this manager. */ - #debugname?: string - /**The map storing all data classes in this manager. */ - #data: Map = new Map() - /**An array storing all listeners when data is added. */ - #addListeners: ODManagerAddCallback[] = [] - /**An array storing all listeners when data has changed. */ - #changeListeners: ODManagerCallback[] = [] - /**An array storing all listeners when data is removed. */ - #removeListeners: ODManagerCallback[] = [] - - constructor(debug?:ODDebugger, debugname?:string){ - super() - this.#debug = debug - this.#debugname = debugname - } - - /**Add data to the manager. The `ODId` in the data class will be used as identifier! You can optionally select to overwrite existing data!*/ - add(data:DataType|DataType[], overwrite?:boolean): boolean { - //repeat same command when data is an array - if (Array.isArray(data)){ - data.forEach((arrayData) => { - this.add(arrayData,overwrite) - }) - return false - } - - //add listener for data id change => transfer data within manager - data.id.changed((oldId,newId) => { - this.#data.delete(oldId) - this.#data.set(newId,data) - }) - - //add data - let didOverwrite: boolean - if (this.#data.has(data.id.value)){ - if (!overwrite) throw new ODSystemError("Id '"+data.id.value+"' already exists in "+this.#debugname+" manager. Use 'overwrite:true' to allow overwriting!") - this.#data.set(data.id.value,data) - didOverwrite = true - if (this.#debug) this.#debug.debug("Added new "+this.#debugname+" to manager",[{key:"id",value:data.id.value},{key:"overwrite",value:"true"}]) - - }else{ - this.#data.set(data.id.value,data) - didOverwrite = false - if (this.#debug) this.#debug.debug("Added new "+this.#debugname+" to manager",[{key:"id",value:data.id.value},{key:"overwrite",value:"false"}]) - - } - - //emit change listeners - data.changed(() => { - //notify change in upper-manager (because data in this manager changed) - this._change() - this.#changeListeners.forEach((cb) => { - try{ - cb(data) - }catch(err){ - throw new ODSystemError("Failed to run manager onChange() listener.\n"+err) - } - }) - }) - - //emit add listeners - this.#addListeners.forEach((cb) => { - try{ - cb(data,didOverwrite) - }catch(err){ - throw new ODSystemError("Failed to run manager onAdd() listener.\n"+err) - } - }) - - //notify change in upper-manager (because data added) - this._change() - - return didOverwrite - } - /**Get data that matches the `ODId`. Returns the found data.*/ - get(id:ODValidId): DataType|null { - const newId = new ODId(id) - const data = this.#data.get(newId.value) - if (data) return data - else return null - } - /**Remove data that matches the `ODId`. Returns the removed data. */ - remove(id:ODValidId): DataType|null { - const newId = new ODId(id) - const data = this.#data.get(newId.value) - - if (!data){ - if (this.#debug) this.#debug.debug("Removed "+this.#debugname+" from manager",[{key:"id",value:newId.value},{key:"found",value:"false"}]) - return null - }else{ - this.#data.delete(newId.value) - if (this.#debug) this.#debug.debug("Removed "+this.#debugname+" from manager",[{key:"id",value:newId.value},{key:"found",value:"true"}]) - } - - //remove all listeners - data.id.changed(null) - data.changed(null) - - //emit remove listeners - this.#removeListeners.forEach((cb) => { - try{ - cb(data) - }catch(err){ - throw new ODSystemError("Failed to run manager onRemove() listener.\n"+err) - } - }) - - //notify change in upper-manager (because data removed) - this._change() - - return data - } - /**Check if data that matches the `ODId` exists. Returns a boolean. */ - exists(id:ODValidId): boolean { - const newId = new ODId(id) - if (this.#data.has(newId.value)) return true - else return false - } - /**Get all data inside this manager*/ - getAll(): DataType[] { - return Array.from(this.#data.values()) - } - /**Get all data that matches inside the filter function*/ - getFiltered(predicate:(value:DataType, index:number, array:DataType[]) => unknown): DataType[] { - return Array.from(this.#data.values()).filter(predicate) - } - /**Get all data where the `ODId` matches the provided RegExp. */ - getRegex(regex:RegExp): DataType[] { - return Array.from(this.#data.values()).filter((data) => regex.test(data.id.value)) - } - /**Get the length/size/amount of the data inside this manager. */ - getLength(){ - return this.#data.size - } - /**Get a list of all the ids inside this manager*/ - getIds(): ODId[] { - const ids = Array.from(this.#data.keys()) - return ids.map((id) => new ODId(id)) - } - /**Run an iterator over all data in this manager. This method also supports async-await behaviour!*/ - async loopAll(cb:(data:DataType,id:ODId) => ODPromiseVoid): Promise { - for (const data of this.getAll()){ - await cb(data,data.id) - } - } - /**Use the Open Ticket debugger in this manager for logs*/ - useDebug(debug?:ODDebugger, debugname?:string){ - this.#debug = debug - this.#debugname = debugname - } - /**Listen for when data is added to this manager. */ - onAdd(callback:ODManagerAddCallback){ - this.#addListeners.push(callback) - } - /**Listen for when data is changed in this manager. */ - onChange(callback:ODManagerCallback){ - this.#changeListeners.push(callback) - } - /**Listen for when data is removed from this manager. */ - onRemove(callback:ODManagerCallback){ - this.#removeListeners.push(callback) - } -} - -/**## ODManagerWithSafety `class` - * This is an Open Ticket safe manager. - * - * It functions exactly the same as a normal `ODManager`, but it has 1 function extra! - * The `getSafe()` function will always return data, because when it doesn't find an id, it returns pre-configured backup data. - */ -export class ODManagerWithSafety extends ODManager { - /**The function that creates backup data returned in `getSafe()` when an id is missing in this manager. */ - #backupCreator: () => DataType - /** Temporary storage for manager debug name. */ - #debugname: string - - constructor(backupCreator:() => DataType, debug?:ODDebugger, debugname?:string){ - super(debug,debugname) - this.#backupCreator = backupCreator - this.#debugname = debugname ?? "unknown" - } - - /**Get data that matches the `ODId`. Returns the backup data when not found. - * - * ### โš ๏ธ This should only be used when the data doesn't need to be written/edited - */ - getSafe(id:ODValidId): DataType { - const data = super.get(id) - if (!data){ - process.emit("uncaughtException",new ODSystemError("ODManagerWithSafety:getSafe(\""+id+"\") => Unknown Id => Used backup data ("+this.#debugname+" manager)")) - return this.#backupCreator() - } - else return data - } -} - -/**## ODVersionManager `class` - * A Open Ticket version manager. - * - * It is used to manage different `ODVersion`'s from the bot. You will use it to check which version of the bot is used. - */ -export class ODVersionManager extends ODManager { - constructor(){ - super() - } -} - -/**## ODVersion `class` - * This is an Open Ticket version. - * - * It has many features like comparing versions & checking if they are compatible. - * - * You can use it in your own plugin, but most of the time you will use it to check the Open Ticket version! - */ -export class ODVersion extends ODManagerData { - /**The first number of the version (example: `v1.2.3` => `1`) */ - primary: number - /**The second number of the version (example: `v1.2.3` => `2`) */ - secondary: number - /**The third number of the version (example: `v1.2.3` => `3`) */ - tertiary: number - - constructor(id:ODValidId, primary:number, secondary:number, tertiary:number){ - super(id) - if (typeof primary != "number") throw new ODSystemError("Invalid constructor parameter => primary:number") - if (typeof secondary != "number") throw new ODSystemError("Invalid constructor parameter => secondary:number") - if (typeof tertiary != "number") throw new ODSystemError("Invalid constructor parameter => tertiary:number") - - this.primary = primary - this.secondary = secondary - this.tertiary = tertiary - } - - /**Get the version from a string (also possible with `v` prefix) - * @example const version = api.ODVersion.fromString("id","v1.2.3") //creates version 1.2.3 - */ - static fromString(id:ODValidId, version:string){ - if (typeof id != "string" && !(id instanceof ODId)) throw new ODSystemError("Invalid function parameter => id:ODValidId") - if (typeof version != "string") throw new ODSystemError("Invalid function parameter => version:string") - - const versionCheck = (version.startsWith("v")) ? version.substring(1) : version - const splittedVersion = versionCheck.split(".") - - return new this(id,Number(splittedVersion[0]),Number(splittedVersion[1]),Number(splittedVersion[2])) - } - /**Get the version as a string (`noprefix:true` => with `v` prefix) - * @example - * new api.ODVersion(1,0,0).toString(false) //returns "v1.0.0" - * new api.ODVersion(1,0,0).toString(true) //returns "1.0.0" - */ - toString(noprefix?:boolean){ - const prefix = noprefix ? "" : "v" - return prefix+[this.primary,this.secondary,this.tertiary].join(".") - } - /**Compare this version with another version and returns the result: `higher`, `lower` or `equal` - * @example - * new api.ODVersion(1,0,0).compare(new api.ODVersion(1,2,0)) //returns "lower" - * new api.ODVersion(1,3,0).compare(new api.ODVersion(1,2,0)) //returns "higher" - * new api.ODVersion(1,2,0).compare(new api.ODVersion(1,2,0)) //returns "equal" - */ - compare(comparator:ODVersion): "higher"|"lower"|"equal" { - if (!(comparator instanceof ODVersion)) throw new ODSystemError("Invalid function parameter => comparator:ODVersion") - - if (this.primary < comparator.primary) return "lower" - else if (this.primary > comparator.primary) return "higher" - else { - if (this.secondary < comparator.secondary) return "lower" - else if (this.secondary > comparator.secondary) return "higher" - else { - if (this.tertiary < comparator.tertiary) return "lower" - else if (this.tertiary > comparator.tertiary) return "higher" - else return "equal" - } - } - } - /**Check if this version is included in the list - * @example - * const list = [ - * new api.ODVersion(1,0,0), - * new api.ODVersion(1,0,1), - * new api.ODVersion(1,0,2) - * ] - * new api.ODVersion(1,0,0).compatible(list) //returns true - * new api.ODVersion(1,0,1).compatible(list) //returns true - * new api.ODVersion(1,0,3).compatible(list) //returns false - */ - compatible(list:ODVersion[]): boolean { - if (!Array.isArray(list)) throw new ODSystemError("Invalid function parameter => list:ODVersion[]") - if (!list.every((v) => (v instanceof ODVersion))) throw new ODSystemError("Invalid function parameter => list:ODVersion[]") - - return list.some((v) => { - return (v.toString() === this.toString()) - }) - } - /**Check if this version is higher or equal to the provided `requirement`. */ - min(requirement:string|ODVersion){ - if (typeof requirement == "string") requirement = ODVersion.fromString("temp",requirement) - - //skip when primary version is higher or lower than current one. - if (this.primary < requirement.primary) return false - else if (this.primary > requirement.primary) return true - - //skip when secondary version is higher or lower than current one. - if (this.secondary < requirement.secondary) return false - else if (this.secondary > requirement.secondary) return true - - //skip when tertiary version is higher or lower than current one. - if (this.tertiary < requirement.tertiary) return false - else if (this.tertiary > requirement.tertiary) return true - - return true - } - /**Check if this version is lower or equal to the provided `requirement`. */ - max(requirement:string|ODVersion){ - if (typeof requirement == "string") requirement = ODVersion.fromString("temp",requirement) - - //skip when primary version is higher or lower than current one. - if (this.primary < requirement.primary) return true - else if (this.primary > requirement.primary) return false - - //skip when secondary version is higher or lower than current one. - if (this.secondary < requirement.secondary) return true - else if (this.secondary > requirement.secondary) return false - - //skip when tertiary version is higher or lower than current one. - if (this.tertiary < requirement.tertiary) return true - else if (this.tertiary > requirement.tertiary) return false - - return true - } - /**Check if this version is matches the major version (`vX.X`) of the provided `requirement`. */ - major(requirement:string|ODVersion){ - if (typeof requirement == "string") requirement = ODVersion.fromString("temp",requirement) - return (this.primary == requirement.primary && this.secondary == requirement.secondary) - } - /**Check if this version is matches the minor version (`vX.X.X`) of the provided `requirement`. */ - minor(requirement:string|ODVersion){ - if (typeof requirement == "string") requirement = ODVersion.fromString("temp",requirement) - return (this.primary == requirement.primary && this.secondary == requirement.secondary && this.tertiary == requirement.tertiary) - } -} - -/**## ODHTTPGetRequest `class` - * This is a class that can help you with creating simple HTTP GET requests. - * - * It works using the native node.js fetch() method. You can configure all options in the constructor! - * @example - * const request = new api.ODHTTPGetRequest("https://www.example.com/abc.txt",false,{}) - * - * const result = await request.run() - * result.body //the response body (string) - * result.status //the response code (number) - * result.response //the full response (object) - */ -export class ODHTTPGetRequest { - /**The url used in the request */ - url: string - /**The request config for additional options */ - config: RequestInit - /**Throw on error OR return http code 500 */ - throwOnError: boolean - - constructor(url:string,throwOnError:boolean,config?:RequestInit){ - if (typeof url != "string") throw new ODSystemError("Invalid constructor parameter => url:string") - if (typeof throwOnError != "boolean") throw new ODSystemError("Invalid constructor parameter => throwOnError:boolean") - if (typeof config != "undefined" && typeof config != "object") throw new ODSystemError("Invalid constructor parameter => config?:RequestInit") - - this.url = url - this.throwOnError = throwOnError - const newConfig = config ?? {} - newConfig.method = "GET" - if (newConfig.headers) Object.assign(newConfig.headers,{"User-Agent":"OpenDiscordBots-OpenTicket/4.1.3"}) - else newConfig.headers = {"User-Agent":"OpenDiscordBots-OpenTicket/4.1.3"} - this.config = newConfig - } - - /**Execute the GET request.*/ - run(): Promise<{status:number, body:string, response?:Response}> { - return new Promise(async (resolve,reject) => { - try{ - const response = await fetch(this.url,this.config) - resolve({ - status:response.status, - body:(await response.text()), - response:response - }) - }catch(err){ - if (this.throwOnError) return reject("[OPENTICKET ERROR]: ODHTTPGetRequest => Unknown fetch() error: "+err) - else return resolve({ - status:500, - body:"Open Ticket Error: Unknown fetch() error: "+err, - }) - } - }) - } -} - -/**## ODHTTPPostRequest `class` - * This is a class that can help you with creating simple HTTP POST requests. - * - * It works using the native node.js fetch() method. You can configure all options in the constructor! - * @example - * const request = new api.ODHTTPPostRequest("https://www.example.com/abc.txt",false,{}) - * - * const result = await request.run() - * result.body //the response body (string) - * result.status //the response code (number) - * result.response //the full response (object) - */ -export class ODHTTPPostRequest { - /**The url used in the request */ - url: string - /**The request config for additional options */ - config: RequestInit - /**Throw on error OR return http code 500 */ - throwOnError: boolean - - constructor(url:string,throwOnError:boolean,config?:RequestInit){ - if (typeof url != "string") throw new ODSystemError("Invalid constructor parameter => url:string") - if (typeof throwOnError != "boolean") throw new ODSystemError("Invalid constructor parameter => throwOnError:boolean") - if (typeof config != "undefined" && typeof config != "object") throw new ODSystemError("Invalid constructor parameter => config?:RequestInit") - - this.url = url - this.throwOnError = throwOnError - const newConfig = config ?? {} - newConfig.method = "POST" - if (newConfig.headers) Object.assign(newConfig.headers,{"User-Agent":"OpenDiscordBots-OpenTicket/4.1.3"}) - else newConfig.headers = {"User-Agent":"OpenDiscordBots-OpenTicket/4.1.3"} - this.config = newConfig - } - - /**Execute the POST request.*/ - run(): Promise<{status:number, body:string, response?:Response}> { - return new Promise(async (resolve,reject) => { - try{ - const response = await fetch(this.url,this.config) - resolve({ - status:response.status, - body:(await response.text()), - response:response - }) - }catch(err){ - if (this.throwOnError) return reject("[OPENTICKET ERROR]: ODHTTPPostRequest => Unknown fetch() error: "+err) - else return resolve({ - status:500, - body:"Open Ticket Error: Unknown fetch() error!", - }) - } - }) - } -} - -/**## ODEnvHelper `class` - * This is a utility class that helps you with reading the ENV. - * - * It has support for the built-in `process.env` & `.env` file - * @example - * const envHelper = new api.ODEnvHelper() - * - * const variableA = envHelper.getVariable("value-a") - * const variableB = envHelper.getVariable("value-b","dotenv") //only get from .env - * const variableA = envHelper.getVariable("value-c","env") //only get from process.env - */ -export class ODEnvHelper { - /**All variables found in the `.env` file */ - dotenv: object - /**All variables found in `process.env` */ - env: object - - constructor(customEnvPath?:string){ - if (typeof customEnvPath != "undefined" && typeof customEnvPath != "string") throw new ODSystemError("Invalid constructor parameter => customEnvPath?:string") - - const path = customEnvPath ? customEnvPath : ".env" - this.dotenv = fs.existsSync(path) ? this.#readDotEnv(fs.readFileSync(path)) : {} - this.env = process.env - } - - /**Get a variable from the env */ - getVariable(name:string,source?:"dotenv"|"env"): any|undefined { - if (typeof name != "string") throw new ODSystemError("Invalid function parameter => name:string") - if ((typeof source != "undefined" && typeof source != "string") || (source && !["env","dotenv"].includes(source))) throw new ODSystemError("Invalid function parameter => source:'dotenv'|'env'") - - if (source == "dotenv"){ - return this.dotenv[name] - }else if (source == "env"){ - return this.env[name] - }else{ - //when no source specified => .env has priority over process.env - if (this.dotenv[name]) return this.dotenv[name] - else return this.env[name] - } - } - - //THIS CODE IS COPIED FROM THE DODENV-LIB - //Repo: https://github.com/motdotla/dotenv - //Source: https://github.com/motdotla/dotenv/blob/master/lib/main.js#L12 - #readDotEnv(src:Buffer){ - const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg - const obj = {} - - // Convert buffer to string - let lines = src.toString() - - // Convert line breaks to same format - lines = lines.replace(/\r\n?/mg, '\n') - - let match - while ((match = LINE.exec(lines)) != null) { - const key = match[1] - - // Default undefined or null to empty string - let value = (match[2] || '') - - // Remove whitespace - value = value.trim() - - // Check if double quoted - const maybeQuote = value[0] - - // Remove surrounding quotes - value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2') - - // Expand newlines if double quoted - if (maybeQuote === '"') { - value = value.replace(/\\n/g, '\n') - value = value.replace(/\\r/g, '\r') - } - - // Add to object - obj[key] = value - } - return obj - } -} - -/**## ODSystemError `class` - * A wrapper for the node.js `Error` class that makes the error look better in the console! - * - * This wrapper is made for Open Ticket system errors! **It can only be used by Open Ticket itself!** - */ -export class ODSystemError extends Error { - /**This variable gets detected by the error handling system to know how to render it */ - _ODErrorType = "system" - - /**Create an `ODSystemError` directly from an `Error` class */ - static fromError(err:Error){ - err["_ODErrorType"] = "system" - return err as ODSystemError - } -} - -/**## ODPluginError `class` - * A wrapper for the node.js `Error` class that makes the error look better in the console! - * - * This wrapper is made for Open Ticket plugin errors! **It can only be used by plugins!** - */ -export class ODPluginError extends Error { - /**This variable gets detected by the error handling system to know how to render it */ - _ODErrorType = "plugin" - - /**Create an `ODPluginError` directly from an `Error` class */ - static fromError(err:Error){ - err["_ODErrorType"] = "plugin" - return err as ODPluginError - } -} - -/**Oh, what could this be `ยฏ\_(ใƒ„)_/ยฏ` */ -export interface ODEasterEggs { - creator:string, - translators:string[] -} \ No newline at end of file diff --git a/src/core/api/modules/builder.ts b/src/core/api/modules/builder.ts deleted file mode 100644 index f864f2a..0000000 --- a/src/core/api/modules/builder.ts +++ /dev/null @@ -1,1554 +0,0 @@ -/////////////////////////////////////// -//BUILDER MODULE -/////////////////////////////////////// -import { ODId, ODValidButtonColor, ODValidId, ODSystemError, ODInterfaceWithPartialProperty, ODManagerWithSafety, ODManagerData } from "./base" -import * as discord from "discord.js" -import { ODWorkerManager, ODWorkerCallback, ODWorker } from "./worker" -import { ODDebugger } from "./console" - -/**## ODBuilderImplementation `class` - * This is an Open Ticket builder implementation. - * - * It is a basic implementation of the `ODWorkerManager` used by all `ODBuilder` classes. - * - * This class can't be used stand-alone & needs to be extended from! - */ -export class ODBuilderImplementation extends ODManagerData { - /**The manager that has all workers of this implementation */ - workers: ODWorkerManager - /**Cache a build or create it every time from scratch when this.build() gets executed. */ - allowCache: boolean = false - /**Did the build already got created/cached? */ - didCache: boolean = false - /**The cache of this build. */ - cache:BuildType|null = null - - constructor(id:ODValidId, callback?:ODWorkerCallback, priority?:number, callbackId?:ODValidId){ - super(id) - this.workers = new ODWorkerManager("ascending") - if (callback) this.workers.add(new ODWorker(callbackId ? callbackId : id,priority ?? 0,callback)) - } - - /**Set if caching is allowed */ - setCacheMode(allowed:boolean){ - this.allowCache = allowed - this.resetCache() - return this - } - /**Reset the current cache */ - resetCache(){ - this.cache = null - this.didCache = false - return this - } - /**Execute all workers & return the result. */ - async build(source:Source, params:Params): Promise { - throw new ODSystemError("Tried to build an unimplemented ODBuilderImplementation") - } -} - -/**## ODBuilderManager `class` - * This is an Open Ticket builder manager. - * - * It contains all Open Ticket builders. You can find messages, embeds, files & dropdowns, buttons & modals all here! - * - * Using the Open Ticket builder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit messages - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - cache frequently used objects - * - get to know the source of the build request for a specific message, button, etc - * - And so much more! - */ -export class ODBuilderManager { - /**The manager for all button builders */ - buttons: ODButtonManager - /**The manager for all dropdown builders */ - dropdowns: ODDropdownManager - /**The manager for all file/attachment builders */ - files: ODFileManager - /**The manager for all embed builders */ - embeds: ODEmbedManager - /**The manager for all message builders */ - messages: ODMessageManager - /**The manager for all modal builders */ - modals: ODModalManager - - constructor(debug:ODDebugger){ - this.buttons = new ODButtonManager(debug) - this.dropdowns = new ODDropdownManager(debug) - this.files = new ODFileManager(debug) - this.embeds = new ODEmbedManager(debug) - this.messages = new ODMessageManager(debug) - this.modals = new ODModalManager(debug) - } -} - -/**## ODComponentBuildResult `interface` - * This interface contains the result from a built component (button/dropdown). This can be used in the `ODMessage` builder! - */ -export interface ODComponentBuildResult { - /**The id of this component (button or dropdown) */ - id:ODId, - /**The discord component or `\n` when it is a spacer between action rows */ - component:discord.MessageActionRowComponentBuilder|"\n"|null -} - -/**## ODButtonManager `class` - * This is an Open Ticket button manager. - * - * It contains all Open Ticket button builders. Here, you can add your own buttons or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODButtonManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODButton("opendiscord:unknown-button",(instance,params,source,cancel) => { - instance.setCustomId("od:unknown-button") - instance.setMode("button") - instance.setColor("red") - instance.setLabel("") - instance.setEmoji("โœ–") - instance.setDisabled(true) - cancel() - }) - },debug,"button") - } - - /**Get a newline component for buttons & dropdowns! */ - getNewLine(id:ODValidId): ODComponentBuildResult { - return { - id:new ODId(id), - component:"\n" - } - } -} - -/**## ODButtonData `interface` - * This interface contains the data to build a button. - */ -export interface ODButtonData { - /**The custom id of this button */ - customId:string, - /**The mode of this button */ - mode:"button"|"url", - /**The url for when the mode is set to "url" */ - url:string|null, - /**The button color */ - color:ODValidButtonColor|null, - /**The button label */ - label:string|null, - /**The button emoji */ - emoji:string|null, - /**Is the button disabled? */ - disabled:boolean -} - -/**## ODButtonInstance `class` - * This is an Open Ticket button instance. - * - * It contains all properties & functions to build a button! - */ -export class ODButtonInstance { - /**The current data of this button */ - data: ODButtonData = { - customId:"", - mode:"button", - url:null, - color:null, - label:null, - emoji:null, - disabled:false - } - - /**Set the custom id of this button */ - setCustomId(id:ODButtonData["customId"]){ - this.data.customId = id - return this - } - /**Set the mode of this button */ - setMode(mode:ODButtonData["mode"]){ - this.data.mode = mode - return this - } - /**Set the url of this button */ - setUrl(url:ODButtonData["url"]){ - this.data.url = url - return this - } - /**Set the color of this button */ - setColor(color:ODButtonData["color"]){ - this.data.color = color - return this - } - /**Set the label of this button */ - setLabel(label:ODButtonData["label"]){ - this.data.label = label - return this - } - /**Set the emoji of this button */ - setEmoji(emoji:ODButtonData["emoji"]){ - this.data.emoji = emoji - return this - } - /**Disable this button */ - setDisabled(disabled:ODButtonData["disabled"]){ - this.data.disabled = disabled - return this - } -} - -/**## ODButton `class` - * This is an Open Ticket button builder. - * - * With this class, you can create a button to use in a message. - * The only difference with normal buttons is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODButton extends ODBuilderImplementation { - /**Build this button & compile it for discord.js */ - async build(source:Source, params:Params): Promise { - if (this.didCache && this.cache && this.allowCache) return this.cache - - try { - //create instance - const instance = new ODButtonInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js button - const button = new discord.ButtonBuilder() - if (instance.data.mode == "button") button.setCustomId(instance.data.customId) - if (instance.data.mode == "url") button.setStyle(discord.ButtonStyle.Link) - else if (instance.data.color == "gray") button.setStyle(discord.ButtonStyle.Secondary) - else if (instance.data.color == "blue") button.setStyle(discord.ButtonStyle.Primary) - else if (instance.data.color == "green") button.setStyle(discord.ButtonStyle.Success) - else if (instance.data.color == "red") button.setStyle(discord.ButtonStyle.Danger) - if (instance.data.url) button.setURL(instance.data.url) - if (instance.data.label) button.setLabel(instance.data.label) - if (instance.data.emoji) button.setEmoji(instance.data.emoji) - if (instance.data.disabled) button.setDisabled(instance.data.disabled) - if (!instance.data.emoji && !instance.data.label) button.setLabel(instance.data.customId) - - this.cache = {id:this.id,component:button} - this.didCache = true - return {id:this.id,component:button} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODButton:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,component:null} - } - } -} - -/**## ODQuickButton `class` - * This is an Open Ticket quick button builder. - * - * With this class, you can quickly create a button to use in a message. - * This quick button can be used by Open Ticket plugins instead of the normal builders to speed up the process! - * - * Because of the quick functionality, these buttons are less customisable by other plugins. - */ -export class ODQuickButton { - /**The id of this button. */ - id: ODId - /**The current data of this button */ - data: Partial - - constructor(id:ODValidId,data:Partial){ - this.id = new ODId(id) - this.data = data - } - - /**Build this button & compile it for discord.js */ - async build(): Promise { - try { - //create the discord.js button - const button = new discord.ButtonBuilder() - if (this.data.mode == "button" || (!this.data.mode && this.data.customId)) button.setCustomId(this.data.customId ?? "od:unknown-button") - if (this.data.mode == "url") button.setStyle(discord.ButtonStyle.Link) - else if (this.data.color == "gray") button.setStyle(discord.ButtonStyle.Secondary) - else if (this.data.color == "blue") button.setStyle(discord.ButtonStyle.Primary) - else if (this.data.color == "green") button.setStyle(discord.ButtonStyle.Success) - else if (this.data.color == "red") button.setStyle(discord.ButtonStyle.Danger) - else button.setStyle(discord.ButtonStyle.Secondary) - if (this.data.url) button.setURL(this.data.url) - if (this.data.label) button.setLabel(this.data.label) - if (this.data.emoji) button.setEmoji(this.data.emoji) - if (this.data.disabled) button.setDisabled(this.data.disabled) - if (!this.data.emoji && !this.data.label) button.setLabel(this.data.customId ?? "od:unknown-button") - - return {id:this.id,component:button} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODQuickButton:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,component:null} - } - } -} - -/**## ODDropdownManager `class` - * This is an Open Ticket dropdown manager. - * - * It contains all Open Ticket dropdown builders. Here, you can add your own dropdowns or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODDropdownManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODDropdown("opendiscord:unknown-dropdown",(instance,params,source,cancel) => { - instance.setCustomId("od:unknown-dropdown") - instance.setType("string") - instance.setPlaceholder("โŒ ") - instance.setDisabled(true) - instance.setOptions([ - {emoji:"โŒ",label:"",value:"error"} - ]) - cancel() - }) - },debug,"dropdown") - } - - /**Get a newline component for buttons & dropdowns! */ - getNewLine(id:ODValidId): ODComponentBuildResult { - return { - id:new ODId(id), - component:"\n" - } - } -} - -/**## ODDropdownData `interface` - * This interface contains the data to build a dropdown. - */ -export interface ODDropdownData { - /**The custom id of this dropdown */ - customId:string, - /**The type of this dropdown */ - type:"string"|"role"|"channel"|"user"|"mentionable", - /**The placeholder of this dropdown */ - placeholder:string|null, - /**The minimum amount of items to be selected in this dropdown */ - minValues:number|null, - /**The maximum amount of items to be selected in this dropdown */ - maxValues:number|null, - /**Is this dropdown disabled? */ - disabled:boolean, - /**Allowed channel types when the type is "channel" */ - channelTypes:discord.ChannelType[] - - /**The options when the type is "string" */ - options:discord.SelectMenuComponentOptionData[], - /**The options when the type is "user" */ - users:discord.User[], - /**The options when the type is "role" */ - roles:discord.Role[], - /**The options when the type is "channel" */ - channels:discord.Channel[], - /**The options when the type is "mentionable" */ - mentionables:(discord.User|discord.Role)[], -} - -/**## ODDropdownInstance `class` - * This is an Open Ticket dropdown instance. - * - * It contains all properties & functions to build a dropdown! - */ -export class ODDropdownInstance { - /**The current data of this dropdown */ - data: ODDropdownData = { - customId:"", - type:"string", - placeholder:null, - minValues:null, - maxValues:null, - disabled:false, - channelTypes:[], - - options:[], - users:[], - roles:[], - channels:[], - mentionables:[] - } - - /**Set the custom id of this dropdown */ - setCustomId(id:ODDropdownData["customId"]){ - this.data.customId = id - return this - } - /**Set the type of this dropdown */ - setType(type:ODDropdownData["type"]){ - this.data.type = type - return this - } - /**Set the placeholder of this dropdown */ - setPlaceholder(placeholder:ODDropdownData["placeholder"]){ - this.data.placeholder = placeholder - return this - } - /**Set the minimum amount of values in this dropdown */ - setMinValues(minValues:ODDropdownData["minValues"]){ - this.data.minValues = minValues - return this - } - /**Set the maximum amount of values ax this dropdown */ - setMaxValues(maxValues:ODDropdownData["maxValues"]){ - this.data.maxValues = maxValues - return this - } - /**Set the disabled of this dropdown */ - setDisabled(disabled:ODDropdownData["disabled"]){ - this.data.disabled = disabled - return this - } - /**Set the channel types of this dropdown */ - setChannelTypes(channelTypes:ODDropdownData["channelTypes"]){ - this.data.channelTypes = channelTypes - return this - } - /**Set the options of this dropdown (when `type == "string"`) */ - setOptions(options:ODDropdownData["options"]){ - this.data.options = options - return this - } - /**Set the users of this dropdown (when `type == "user"`) */ - setUsers(users:ODDropdownData["users"]){ - this.data.users = users - return this - } - /**Set the roles of this dropdown (when `type == "role"`) */ - setRoles(roles:ODDropdownData["roles"]){ - this.data.roles = roles - return this - } - /**Set the channels of this dropdown (when `type == "channel"`) */ - setChannels(channels:ODDropdownData["channels"]){ - this.data.channels = channels - return this - } - /**Set the mentionables of this dropdown (when `type == "mentionable"`) */ - setMentionables(mentionables:ODDropdownData["mentionables"]){ - this.data.mentionables = mentionables - return this - } -} - -/**## ODDropdown `class` - * This is an Open Ticket dropdown builder. - * - * With this class, you can create a dropdown to use in a message. - * The only difference with normal dropdowns is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODDropdown extends ODBuilderImplementation { - /**Build this dropdown & compile it for discord.js */ - async build(source:Source, params:Params): Promise { - if (this.didCache && this.cache && this.allowCache) return this.cache - - try{ - //create instance - const instance = new ODDropdownInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js dropdown - if (instance.data.type == "string"){ - const dropdown = new discord.StringSelectMenuBuilder() - dropdown.setCustomId(instance.data.customId) - dropdown.setOptions(...instance.data.options) - if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder) - if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues) - if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues) - if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled) - - this.cache = {id:this.id,component:dropdown} - this.didCache = true - return {id:this.id,component:dropdown} - - }else if (instance.data.type == "user"){ - const dropdown = new discord.UserSelectMenuBuilder() - dropdown.setCustomId(instance.data.customId) - if (instance.data.users.length > 0) dropdown.setDefaultUsers(...instance.data.users.map((u) => u.id)) - if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder) - if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues) - if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues) - if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled) - - this.cache = {id:this.id,component:dropdown} - this.didCache = true - return {id:this.id,component:dropdown} - - }else if (instance.data.type == "role"){ - const dropdown = new discord.RoleSelectMenuBuilder() - dropdown.setCustomId(instance.data.customId) - if (instance.data.roles.length > 0) dropdown.setDefaultRoles(...instance.data.roles.map((r) => r.id)) - if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder) - if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues) - if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues) - if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled) - - this.cache = {id:this.id,component:dropdown} - this.didCache = true - return {id:this.id,component:dropdown} - - }else if (instance.data.type == "channel"){ - const dropdown = new discord.ChannelSelectMenuBuilder() - dropdown.setCustomId(instance.data.customId) - if (instance.data.channels.length > 0) dropdown.setDefaultChannels(...instance.data.channels.map((c) => c.id)) - if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder) - if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues) - if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues) - if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled) - - this.cache = {id:this.id,component:dropdown} - this.didCache = true - return {id:this.id,component:dropdown} - - }else if (instance.data.type == "mentionable"){ - const dropdown = new discord.MentionableSelectMenuBuilder() - - const values: ({type:discord.SelectMenuDefaultValueType.User,id:string}|{type:discord.SelectMenuDefaultValueType.Role,id:string})[] = [] - instance.data.mentionables.forEach((m) => { - if (m instanceof discord.User){ - values.push({type:discord.SelectMenuDefaultValueType.User,id:m.id}) - }else{ - values.push({type:discord.SelectMenuDefaultValueType.Role,id:m.id}) - } - }) - - dropdown.setCustomId(instance.data.customId) - if (instance.data.mentionables.length > 0) dropdown.setDefaultValues(...values) - if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder) - if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues) - if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues) - if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled) - - this.cache = {id:this.id,component:dropdown} - this.didCache = true - return {id:this.id,component:dropdown} - }else{ - throw new Error("Tried to build an ODDropdown with unknown type!") - } - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODDropdown:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,component:null} - } - } -} - -/**## ODQuickDropdown `class` - * This is an Open Ticket quick dropdown builder. - * - * With this class, you can quickly create a dropdown to use in a message. - * This quick dropdown can be used by Open Ticket plugins instead of the normal builders to speed up the process! - * - * Because of the quick functionality, these dropdowns are less customisable by other plugins. - */ -export class ODQuickDropdown { - /**The id of this dropdown. */ - id: ODId - /**The current data of this dropdown */ - data: Partial - - constructor(id:ODValidId,data:Partial){ - this.id = new ODId(id) - this.data = data - } - - /**Build this dropdown & compile it for discord.js */ - async build(): Promise { - try{ - //create the discord.js dropdown - if (this.data.type == "string"){ - if (!this.data.options) throw new ODSystemError("ODQuickDropdown:build(): "+this.id.value+" => Dropdown requires at least 1 option to be present.") - const dropdown = new discord.StringSelectMenuBuilder() - dropdown.setCustomId(this.data.customId ?? "od:unknown-dropdown") - dropdown.setOptions(...this.data.options) - if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder) - if (this.data.minValues) dropdown.setMinValues(this.data.minValues) - if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues) - if (this.data.disabled) dropdown.setDisabled(this.data.disabled) - - return {id:this.id,component:dropdown} - - }else if (this.data.type == "user"){ - if (!this.data.users) throw new ODSystemError("ODQuickDropdown:build(): "+this.id.value+" => Dropdown requires at least 1 user option to be present.") - const dropdown = new discord.UserSelectMenuBuilder() - dropdown.setCustomId(this.data.customId ?? "od:unknown-dropdown") - if (this.data.users.length > 0) dropdown.setDefaultUsers(...this.data.users.map((u) => u.id)) - if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder) - if (this.data.minValues) dropdown.setMinValues(this.data.minValues) - if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues) - if (this.data.disabled) dropdown.setDisabled(this.data.disabled) - - return {id:this.id,component:dropdown} - - }else if (this.data.type == "role"){ - if (!this.data.roles) throw new ODSystemError("ODQuickDropdown:build(): "+this.id.value+" => Dropdown requires at least 1 role option to be present.") - const dropdown = new discord.RoleSelectMenuBuilder() - dropdown.setCustomId(this.data.customId ?? "od:unknown-dropdown") - if (this.data.roles.length > 0) dropdown.setDefaultRoles(...this.data.roles.map((r) => r.id)) - if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder) - if (this.data.minValues) dropdown.setMinValues(this.data.minValues) - if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues) - if (this.data.disabled) dropdown.setDisabled(this.data.disabled) - - return {id:this.id,component:dropdown} - - }else if (this.data.type == "channel"){ - if (!this.data.channels) throw new ODSystemError("ODQuickDropdown:build(): "+this.id.value+" => Dropdown requires at least 1 channel option to be present.") - const dropdown = new discord.ChannelSelectMenuBuilder() - dropdown.setCustomId(this.data.customId ?? "od:unknown-dropdown") - if (this.data.channels.length > 0) dropdown.setDefaultChannels(...this.data.channels.map((c) => c.id)) - if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder) - if (this.data.minValues) dropdown.setMinValues(this.data.minValues) - if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues) - if (this.data.disabled) dropdown.setDisabled(this.data.disabled) - - return {id:this.id,component:dropdown} - - }else if (this.data.type == "mentionable"){ - if (!this.data.mentionables) throw new ODSystemError("ODQuickDropdown:build(): "+this.id.value+" => Dropdown requires at least 1 mentionable option to be present.") - const dropdown = new discord.MentionableSelectMenuBuilder() - - const values: ({type:discord.SelectMenuDefaultValueType.User,id:string}|{type:discord.SelectMenuDefaultValueType.Role,id:string})[] = [] - this.data.mentionables.forEach((m) => { - if (m instanceof discord.User){ - values.push({type:discord.SelectMenuDefaultValueType.User,id:m.id}) - }else{ - values.push({type:discord.SelectMenuDefaultValueType.Role,id:m.id}) - } - }) - - dropdown.setCustomId(this.data.customId ?? "od:unknown-dropdown") - if (this.data.mentionables.length > 0) dropdown.setDefaultValues(...values) - if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder) - if (this.data.minValues) dropdown.setMinValues(this.data.minValues) - if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues) - if (this.data.disabled) dropdown.setDisabled(this.data.disabled) - - return {id:this.id,component:dropdown} - }else{ - throw new Error("Tried to build an ODQuickDropdown with unknown type!") - } - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODQuickDropdown:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,component:null} - } - } -} - -/**## ODFileManager `class` - * This is an Open Ticket file manager. - * - * It contains all Open Ticket file builders. Here, you can add your own files or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODFileManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODFile("opendiscord:unknown-file",(instance,params,source,cancel) => { - instance.setName("openticket_unknown-file.txt") - instance.setDescription("โŒ ") - instance.setContents("Couldn't find file in registery `opendiscord.builders.files`") - cancel() - }) - },debug,"file") - } -} - -/**## ODFileData `interface` - * This interface contains the data to build a file. - */ -export interface ODFileData { - /**The file buffer, string or raw data */ - file:discord.BufferResolvable - /**The name of the file */ - name:string, - /**The description of the file */ - description:string|null, - /**Set the file to be a spoiler */ - spoiler:boolean -} - -/**## ODFileBuildResult `interface` - * This interface contains the result from a built file (attachment). This can be used in the `ODMessage` builder! - */ -export interface ODFileBuildResult { - /**The id of this file */ - id:ODId, - /**The discord file */ - file:discord.AttachmentBuilder|null -} - -/**## ODFileInstance `class` - * This is an Open Ticket file instance. - * - * It contains all properties & functions to build a file! - */ -export class ODFileInstance { - /**The current data of this file */ - data: ODFileData = { - file:"", - name:"file.txt", - description:null, - spoiler:false - } - - /**Set the file path of this attachment */ - setFile(file:string){ - this.data.file = file - return this - } - /**Set the file contents of this attachment */ - setContents(contents:string|Buffer){ - this.data.file = (typeof contents == "string") ? Buffer.from(contents) : contents - return this - } - /**Set the name of this attachment */ - setName(name:ODFileData["name"]){ - this.data.name = name - return this - } - /**Set the description of this attachment */ - setDescription(description:ODFileData["description"]){ - this.data.description = description - return this - } - /**Set this attachment to show as a spoiler */ - setSpoiler(spoiler:ODFileData["spoiler"]){ - this.data.spoiler = spoiler - return this - } -} - -/**## ODFile `class` - * This is an Open Ticket file builder. - * - * With this class, you can create a file to use in a message. - * The only difference with normal files is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODFile extends ODBuilderImplementation { - /**Build this attachment & compile it for discord.js */ - async build(source:Source, params:Params): Promise { - if (this.didCache && this.cache && this.allowCache) return this.cache - - try{ - //create instance - const instance = new ODFileInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js attachment - const file = new discord.AttachmentBuilder(instance.data.file) - file.setName(instance.data.name ? instance.data.name : "file.txt") - if (instance.data.description) file.setDescription(instance.data.description) - if (instance.data.spoiler) file.setSpoiler(instance.data.spoiler) - - - this.cache = {id:this.id,file} - this.didCache = true - return {id:this.id,file} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODFile:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,file:null} - } - } -} - -/**## ODQuickFile `class` - * This is an Open Ticket quick file builder. - * - * With this class, you can quickly create a file to use in a message. - * This quick file can be used by Open Ticket plugins instead of the normal builders to speed up the process! - * - * Because of the quick functionality, these files are less customisable by other plugins. - */ -export class ODQuickFile { - /**The id of this file. */ - id: ODId - /**The current data of this file */ - data: Partial - - constructor(id:ODValidId,data:Partial){ - this.id = new ODId(id) - this.data = data - } - - /**Build this attachment & compile it for discord.js */ - async build(): Promise { - try{ - //create the discord.js attachment - const file = new discord.AttachmentBuilder(this.data.file ?? "") - file.setName(this.data.name ? this.data.name : "file.txt") - if (this.data.description) file.setDescription(this.data.description) - if (this.data.spoiler) file.setSpoiler(this.data.spoiler) - - return {id:this.id,file} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODQuickFile:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,file:null} - } - } -} - -/**## ODEmbedManager `class` - * This is an Open Ticket embed manager. - * - * It contains all Open Ticket embed builders. Here, you can add your own embeds or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODEmbedManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODEmbed("opendiscord:unknown-embed",(instance,params,source,cancel) => { - instance.setFooter("opendiscord:unknown-embed") - instance.setColor("#ff0000") - instance.setTitle("โŒ ") - instance.setDescription("Couldn't find embed in registery `opendiscord.builders.embeds`") - cancel() - }) - },debug,"embed") - } -} - -/**## ODEmbedData `interface` - * This interface contains the data to build an embed. - */ -export interface ODEmbedData { - /**The title of the embed */ - title:string|null, - /**The color of the embed */ - color:discord.ColorResolvable|string|null, - /**The url of the embed */ - url:string|null, - /**The description of the embed */ - description:string|null, - /**The author text of the embed */ - authorText:string|null, - /**The author image of the embed */ - authorImage:string|null, - /**The author url of the embed */ - authorUrl:string|null, - /**The footer text of the embed */ - footerText:string|null, - /**The footer image of the embed */ - footerImage:string|null, - /**The image of the embed */ - image:string|null, - /**The thumbnail of the embed */ - thumbnail:string|null, - /**The fields of the embed */ - fields:ODInterfaceWithPartialProperty[], - /**The timestamp of the embed */ - timestamp:number|Date|null -} - -/**## ODEmbedBuildResult `interface` - * This interface contains the result from a built embed. This can be used in the `ODMessage` builder! - */ -export interface ODEmbedBuildResult { - /**The id of this embed */ - id:ODId, - /**The discord embed */ - embed:discord.EmbedBuilder|null -} - -/**## ODEmbedInstance `class` - * This is an Open Ticket embed instance. - * - * It contains all properties & functions to build an embed! - */ -export class ODEmbedInstance { - /**The current data of this embed */ - data: ODEmbedData = { - title:null, - color:null, - url:null, - description:null, - authorText:null, - authorImage:null, - authorUrl:null, - footerText:null, - footerImage:null, - image:null, - thumbnail:null, - fields:[], - timestamp:null - } - - /**Set the title of this embed */ - setTitle(title:ODEmbedData["title"]){ - this.data.title = title - return this - } - /**Set the color of this embed */ - setColor(color:ODEmbedData["color"]){ - this.data.color = color - return this - } - /**Set the url of this embed */ - setUrl(url:ODEmbedData["url"]){ - this.data.url = url - return this - } - /**Set the description of this embed */ - setDescription(description:ODEmbedData["description"]){ - this.data.description = description - return this - } - /**Set the author of this embed */ - setAuthor(text:ODEmbedData["authorText"], image?:ODEmbedData["authorImage"], url?:ODEmbedData["authorUrl"]){ - this.data.authorText = text - this.data.authorImage = image ?? null - this.data.authorUrl = url ?? null - return this - } - /**Set the footer of this embed */ - setFooter(text:ODEmbedData["footerText"], image?:ODEmbedData["footerImage"]){ - this.data.footerText = text - this.data.footerImage = image ?? null - return this - } - /**Set the image of this embed */ - setImage(image:ODEmbedData["image"]){ - this.data.image = image - return this - } - /**Set the thumbnail of this embed */ - setThumbnail(thumbnail:ODEmbedData["thumbnail"]){ - this.data.thumbnail = thumbnail - return this - } - /**Set the fields of this embed */ - setFields(fields:ODEmbedData["fields"]){ - //TEMP CHECKS - fields.forEach((field,index) => { - if (field.value.length >= 1024) throw new ODSystemError("ODEmbed:setFields() => field "+index+" reached 1024 character limit!") - if (field.name.length >= 256) throw new ODSystemError("ODEmbed:setFields() => field "+index+" reached 256 name character limit!") - }) - - this.data.fields = fields - return this - } - /**Add fields to this embed */ - addFields(...fields:ODEmbedData["fields"]){ - //TEMP CHECKS - fields.forEach((field,index) => { - if (field.value.length >= 1024) throw new ODSystemError("ODEmbed:addFields() => field "+index+" reached 1024 character limit!") - if (field.name.length >= 256) throw new ODSystemError("ODEmbed:addFields() => field "+index+" reached 256 name character limit!") - }) - - this.data.fields.push(...fields) - return this - } - /**Clear all fields from this embed */ - clearFields(){ - this.data.fields = [] - return this - } - /**Set the timestamp of this embed */ - setTimestamp(timestamp:ODEmbedData["timestamp"]){ - this.data.timestamp = timestamp - return this - } -} - -/**## ODEmbed `class` - * This is an Open Ticket embed builder. - * - * With this class, you can create a embed to use in a message. - * The only difference with normal embeds is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODEmbed extends ODBuilderImplementation { - /**Build this embed & compile it for discord.js */ - async build(source:Source, params:Params): Promise { - if (this.didCache && this.cache && this.allowCache) return this.cache - - try{ - //create instance - const instance = new ODEmbedInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js embed - const embed = new discord.EmbedBuilder() - if (instance.data.title) embed.setTitle(instance.data.title) - if (instance.data.color) embed.setColor(instance.data.color as discord.ColorResolvable) - if (instance.data.url) embed.setURL(instance.data.url) - if (instance.data.description) embed.setDescription(instance.data.description) - if (instance.data.authorText) embed.setAuthor({ - name:instance.data.authorText, - iconURL:instance.data.authorImage ?? undefined, - url:instance.data.authorUrl ?? undefined - }) - if (instance.data.footerText) embed.setFooter({ - text:instance.data.footerText, - iconURL:instance.data.footerImage ?? undefined, - }) - if (instance.data.image) embed.setImage(instance.data.image) - if (instance.data.thumbnail) embed.setThumbnail(instance.data.thumbnail) - if (instance.data.timestamp) embed.setTimestamp(instance.data.timestamp) - if (instance.data.fields.length > 0) embed.setFields(instance.data.fields) - - this.cache = {id:this.id,embed} - this.didCache = true - return {id:this.id,embed} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODEmbed:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,embed:null} - } - } -} - -/**## ODQuickEmbed `class` - * This is an Open Ticket quick embed builder. - * - * With this class, you can quickly create a embed to use in a message. - * This quick embed can be used by Open Ticket plugins instead of the normal builders to speed up the process! - * - * Because of the quick functionality, these embeds are less customisable by other plugins. - */ -export class ODQuickEmbed { - /**The id of this embed. */ - id: ODId - /**The current data of this embed */ - data: Partial - - constructor(id:ODValidId,data:Partial){ - this.id = new ODId(id) - this.data = data - } - - /**Build this embed & compile it for discord.js */ - async build(): Promise { - try{ - //create the discord.js embed - const embed = new discord.EmbedBuilder() - if (this.data.title) embed.setTitle(this.data.title) - if (this.data.color) embed.setColor(this.data.color as discord.ColorResolvable) - if (this.data.url) embed.setURL(this.data.url) - if (this.data.description) embed.setDescription(this.data.description) - if (this.data.authorText) embed.setAuthor({ - name:this.data.authorText, - iconURL:this.data.authorImage ?? undefined, - url:this.data.authorUrl ?? undefined - }) - if (this.data.footerText) embed.setFooter({ - text:this.data.footerText, - iconURL:this.data.footerImage ?? undefined, - }) - if (this.data.image) embed.setImage(this.data.image) - if (this.data.thumbnail) embed.setThumbnail(this.data.thumbnail) - if (this.data.timestamp) embed.setTimestamp(this.data.timestamp) - if (this.data.fields && this.data.fields.length > 0) embed.setFields(this.data.fields) - - return {id:this.id,embed} - }catch(err){ - process.emit("uncaughtException",new ODSystemError("ODQuickEmbed:build(\""+this.id.value+"\") => Major Error (see next error)")) - process.emit("uncaughtException",err) - return {id:this.id,embed:null} - } - } -} - -/**## ODMessageManager `class` - * This is an Open Ticket message manager. - * - * It contains all Open Ticket message builders. Here, you can add your own messages or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODMessageManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODMessage("opendiscord:unknown-message",(instance,params,source,cancel) => { - instance.setContent("**โŒ **\nCouldn't find message in registery `opendiscord.builders.messages`") - cancel() - }) - },debug,"message") - } -} - -/**## ODMessageData `interface` - * This interface contains the data to build a message. - */ -export interface ODMessageData { - /**The content of this message. `null` when no content */ - content:string|null, - /**Poll data for this message */ - poll:discord.PollData|null, - /**Try to make this message ephemeral when available */ - ephemeral:boolean, - - /**Embeds from this message */ - embeds:ODEmbedBuildResult[], - /**Components from this message */ - components:ODComponentBuildResult[], - /**Files from this message */ - files:ODFileBuildResult[], - - /**Additional options that aren't covered by the Open Ticket api!*/ - additionalOptions:Omit -} - -/**## ODMessageBuildResult `interface` - * This interface contains the result from a built message. This can be sent in a discord channel! - */ -export interface ODMessageBuildResult { - /**The id of this message */ - id:ODId, - /**The discord message */ - message:Omit, - /**When enabled, the bot will try to send this as an ephemeral message */ - ephemeral:boolean -} - -/**## ODMessageBuildSentResult `interface` - * This interface contains the result from a sent built message. This can be used to edit, view & save the message that got created. - */ -export interface ODMessageBuildSentResult { - /**Did the message get sent successfully? */ - success:boolean, - /**The message that got sent. */ - message:discord.Message|null -} - -/**## ODMessageInstance `class` - * This is an Open Ticket message instance. - * - * It contains all properties & functions to build a message! - */ -export class ODMessageInstance { - /**The current data of this message */ - data: ODMessageData = { - content:null, - poll:null, - ephemeral:false, - embeds:[], - components:[], - files:[], - additionalOptions:{} - } - - /**Set the content of this message */ - setContent(content:ODMessageData["content"]){ - this.data.content = content - return this - } - /**Set the poll of this message */ - setPoll(poll:ODMessageData["poll"]){ - this.data.poll = poll - return this - } - /**Make this message ephemeral when possible */ - setEphemeral(ephemeral:ODMessageData["ephemeral"]){ - this.data.ephemeral = ephemeral - return this - } - /**Set the embeds of this message */ - setEmbeds(...embeds:ODEmbedBuildResult[]){ - this.data.embeds = embeds - return this - } - /**Add an embed to this message! */ - addEmbed(embed:ODEmbedBuildResult){ - this.data.embeds.push(embed) - return this - } - /**Remove an embed from this message */ - removeEmbed(id:ODValidId){ - const index = this.data.embeds.findIndex((embed) => embed.id.value === new ODId(id).value) - if (index > -1) this.data.embeds.splice(index,1) - return this - } - /**Get an embed from this message */ - getEmbed(id:ODValidId){ - const embed = this.data.embeds.find((embed) => embed.id.value === new ODId(id).value) - if (embed) return embed.embed - else return null - } - /**Set the components of this message */ - setComponents(...components:ODComponentBuildResult[]){ - this.data.components = components - return this - } - /**Add a component to this message! */ - addComponent(component:ODComponentBuildResult){ - this.data.components.push(component) - return this - } - /**Remove a component from this message */ - removeComponent(id:ODValidId){ - const index = this.data.components.findIndex((component) => component.id.value === new ODId(id).value) - if (index > -1) this.data.components.splice(index,1) - return this - } - /**Get a component from this message */ - getComponent(id:ODValidId){ - const component = this.data.components.find((component) => component.id.value === new ODId(id).value) - if (component) return component.component - else return null - } - /**Set the files of this message */ - setFiles(...files:ODFileBuildResult[]){ - this.data.files = files - return this - } - /**Add a file to this message! */ - addFile(file:ODFileBuildResult){ - this.data.files.push(file) - return this - } - /**Remove a file from this message */ - removeFile(id:ODValidId){ - const index = this.data.files.findIndex((file) => file.id.value === new ODId(id).value) - if (index > -1) this.data.files.splice(index,1) - return this - } - /**Get a file from this message */ - getFile(id:ODValidId){ - const file = this.data.files.find((file) => file.id.value === new ODId(id).value) - if (file) return file.file - else return null - } -} - -/**## ODMessage `class` - * This is an Open Ticket message builder. - * - * With this class, you can create a message to send in a discord channel. - * The only difference with normal messages is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODMessage extends ODBuilderImplementation { - /**Build this message & compile it for discord.js */ - async build(source:Source, params:Params){ - if (this.didCache && this.cache && this.allowCache) return this.cache - - //create instance - const instance = new ODMessageInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js message - const componentArray: discord.ActionRowBuilder[] = [] - let currentRow: discord.ActionRowBuilder = new discord.ActionRowBuilder() - instance.data.components.forEach((c) => { - //return when component crashed - if (c.component == null) return - else if (c.component == "\n"){ - //create new current row when required - if (currentRow.components.length > 0){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - }else if (c.component instanceof discord.BaseSelectMenuBuilder){ - //push current row when not empty - if (currentRow.components.length > 0){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - currentRow.addComponents(c.component) - //create new current row after dropdown - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - }else{ - //push button to current row - currentRow.addComponents(c.component) - } - - //create new row when 5 rows in length - if (currentRow.components.length == 5){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - }) - //push final row to array - if (currentRow.components.length > 0) componentArray.push(currentRow) - - const filteredEmbeds = instance.data.embeds.map((e) => e.embed).filter((e) => e instanceof discord.EmbedBuilder) as discord.EmbedBuilder[] - const filteredFiles = instance.data.files.map((f) => f.file).filter((f) => f instanceof discord.AttachmentBuilder) as discord.AttachmentBuilder[] - - const message : discord.MessageCreateOptions = { - content:instance.data.content ?? "", - poll:instance.data.poll ?? undefined, - embeds:filteredEmbeds, - components:componentArray, - files:filteredFiles - } - - let result = {id:this.id,message,ephemeral:instance.data.ephemeral} - - Object.assign(result.message,instance.data.additionalOptions) - - this.cache = result - this.didCache = true - return result - } -} - -/**## ODQuickMessage `class` - * This is an Open Ticket quick message builder. - * - * With this class, you can quickly create a message to send in a discord channel. - * This quick message can be used by Open Ticket plugins instead of the normal builders to speed up the process! - * - * Because of the quick functionality, these messages are less customisable by other plugins. - */ -export class ODQuickMessage { - /**The id of this message. */ - id: ODId - /**The current data of this message. */ - data: Partial - - constructor(id:ODValidId,data:Partial){ - this.id = new ODId(id) - this.data = data - } - - /**Build this message & compile it for discord.js */ - async build(): Promise { - //create the discord.js message - const componentArray: discord.ActionRowBuilder[] = [] - let currentRow: discord.ActionRowBuilder = new discord.ActionRowBuilder() - this.data.components?.forEach((c) => { - //return when component crashed - if (c.component == null) return - else if (c.component == "\n"){ - //create new current row when required - if (currentRow.components.length > 0){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - }else if (c.component instanceof discord.BaseSelectMenuBuilder){ - //push current row when not empty - if (currentRow.components.length > 0){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - currentRow.addComponents(c.component) - //create new current row after dropdown - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - }else{ - //push button to current row - currentRow.addComponents(c.component) - } - - //create new row when 5 rows in length - if (currentRow.components.length == 5){ - componentArray.push(currentRow) - currentRow = new discord.ActionRowBuilder() - } - }) - //push final row to array - if (currentRow.components.length > 0) componentArray.push(currentRow) - - const filteredEmbeds = (this.data.embeds?.map((e) => e.embed).filter((e) => e instanceof discord.EmbedBuilder) as discord.EmbedBuilder[]) ?? [] - const filteredFiles = (this.data.files?.map((f) => f.file).filter((f) => f instanceof discord.AttachmentBuilder) as discord.AttachmentBuilder[]) ?? [] - - const message : discord.MessageCreateOptions = { - content:this.data.content ?? "", - poll:this.data.poll ?? undefined, - embeds:filteredEmbeds, - components:componentArray, - files:filteredFiles - } - - let result = {id:this.id,message,ephemeral:this.data.ephemeral ?? false} - - Object.assign(result.message,this.data.additionalOptions) - return result - } -} - -/**## ODModalManager `class` - * This is an Open Ticket modal manager. - * - * It contains all Open Ticket modal builders. Here, you can add your own modals or edit existing ones! - * - * It's recommended to use this system in combination with all the other Open Ticket builders! - */ -export class ODModalManager extends ODManagerWithSafety> { - constructor(debug:ODDebugger){ - super(() => { - return new ODModal("opendiscord:unknown-modal",(instance,params,source,cancel) => { - instance.setCustomId("od:unknown-modal") - instance.setTitle("โŒ ") - instance.setQuestions( - { - style:"short", - customId:"error", - label:"error", - placeholder:"Contact the bot creator for more info!" - } - ) - cancel() - }) - },debug,"modal") - } -} - -/**## ODModalDataQuestion `interface` - * This interface contains the data to build a modal question. - */ -export interface ODModalDataQuestion { - /**The style of this modal question */ - style:"short"|"paragraph", - /**The custom id of this modal question */ - customId:string - /**The label of this modal question */ - label?:string, - /**The min length of this modal question */ - minLength?:number, - /**The max length of this modal question */ - maxLength?:number, - /**Is this modal question required? */ - required?:boolean, - /**The placeholder of this modal question */ - placeholder?:string, - /**The initial value of this modal question */ - value?:string -} - -/**## ODModalData `interface` - * This interface contains the data to build a modal. - */ -export interface ODModalData { - /**The custom id of this modal */ - customId:string, - /**The title of this modal */ - title:string|null, - /**The collection of questions in this modal */ - questions:ODModalDataQuestion[], -} - -/**## ODModalBuildResult `interface` - * This interface contains the result from a built modal (form). This can be used in the `ODMessage` builder! - */ -export interface ODModalBuildResult { - /**The id of this modal */ - id:ODId, - /**The discord modal */ - modal:discord.ModalBuilder -} - -/**## ODModalInstance `class` - * This is an Open Ticket modal instance. - * - * It contains all properties & functions to build a modal! - */ -export class ODModalInstance { - /**The current data of this modal */ - data: ODModalData = { - customId:"", - title:null, - questions:[] - } - - /**Set the custom id of this modal */ - setCustomId(customId:ODModalData["customId"]){ - this.data.customId = customId - return this - } - /**Set the title of this modal */ - setTitle(title:ODModalData["title"]){ - this.data.title = title - return this - } - /**Set the questions of this modal */ - setQuestions(...questions:ODModalData["questions"]){ - this.data.questions = questions - return this - } - /**Add a question to this modal! */ - addQuestion(question:ODModalDataQuestion){ - this.data.questions.push(question) - return this - } - /**Remove a question from this modal */ - removeQuestion(customId:string){ - const index = this.data.questions.findIndex((question) => question.customId === customId) - if (index > -1) this.data.questions.splice(index,1) - return this - } - /**Get a question from this modal */ - getQuestion(customId:string){ - const question = this.data.questions.find((question) => question.customId === customId) - if (question) return question - else return null - } -} - -/**## ODModal `class` - * This is an Open Ticket modal builder. - * - * With this class, you can create a modal to use as response in interactions. - * The only difference with normal modals is that this one can be edited by Open Ticket plugins! - * - * This is possible by using "workers" or multiple functions that will be executed in priority order! - */ -export class ODModal extends ODBuilderImplementation { - /**Build this modal & compile it for discord.js */ - async build(source:Source, params:Params){ - if (this.didCache && this.cache && this.allowCache) return this.cache - - //create instance - const instance = new ODModalInstance() - - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - - //create the discord.js modal - const modal = new discord.ModalBuilder() - modal.setCustomId(instance.data.customId) - if (instance.data.title) modal.setTitle(instance.data.title) - else modal.setTitle(instance.data.customId) - - instance.data.questions.forEach((question) => { - const input = new discord.TextInputBuilder() - .setStyle(question.style == "paragraph" ? discord.TextInputStyle.Paragraph : discord.TextInputStyle.Short) - .setCustomId(question.customId) - .setLabel(question.label ? question.label : question.customId) - .setRequired(question.required ? true : false) - - if (question.minLength) input.setMinLength(question.minLength) - if (question.maxLength) input.setMaxLength(question.maxLength) - if (question.value) input.setValue(question.value) - if (question.placeholder) input.setPlaceholder(question.placeholder) - - modal.addComponents( - new discord.ActionRowBuilder() - .addComponents(input) - ) - }) - - this.cache = {id:this.id,modal} - this.didCache = true - return {id:this.id,modal} - } -} \ No newline at end of file diff --git a/src/core/api/modules/checker.ts b/src/core/api/modules/checker.ts deleted file mode 100644 index ceb2e84..0000000 --- a/src/core/api/modules/checker.ts +++ /dev/null @@ -1,1549 +0,0 @@ -/////////////////////////////////////// -//CONFIG CHECKER MODULE -/////////////////////////////////////// -import { ODDiscordIdType, ODId, ODManager, ODManagerData, ODValidId, ODValidJsonType } from "./base" -import { ODConfig } from "./config" -import { ODLanguageManager } from "./language" -import { ODDebugger } from "./console" - -/**## ODCheckerResult `interface` - * This interface is the result from a config checker check() function. - */ -export interface ODCheckerResult { - valid:boolean - messages:ODCheckerMessage[] -} - -/**## ODCheckerManager `class` - * This is an Open Ticket checker manager. - * - * It manages all config checkers in the bot and allows plugins to access config checkers from Open Ticket & other plugins! - * - * You can use this class to get/add a config checker (`ODChecker`) in your plugin! - */ -export class ODCheckerManager extends ODManager { - /**The global temporary storage shared between all config checkers. */ - storage: ODCheckerStorage - /**The class responsible for rendering the config checker report. */ - renderer: ODCheckerRenderer - /**The class responsible for translating the config checker report. */ - translation: ODCheckerTranslationRegister - /**Final functions are global functions executed just before the report is created. */ - functions: ODCheckerFunctionManager - /**A variable containing the last result returned from `checkAll()` */ - lastResult: ODCheckerResult|null = null - - constructor(debug:ODDebugger, storage:ODCheckerStorage, renderer:ODCheckerRenderer, translation:ODCheckerTranslationRegister, functions:ODCheckerFunctionManager){ - super(debug,"config checker") - this.storage = storage - this.renderer = renderer - this.translation = translation - this.functions = functions - } - /**Check all config checkers registered in this manager.*/ - checkAll(sort:boolean): ODCheckerResult { - this.storage.reset() - - let isValid = true - const final: ODCheckerMessage[] = [] - - const checkers = this.getAll() - checkers.sort((a,b) => b.priority-a.priority) - - checkers.forEach((checker) => { - const res = checker.check() - final.push(...res.messages) - - if (!res.valid) isValid = false - }) - - this.functions.getAll().forEach((func) => { - const res = func.func(this,this.functions) - final.push(...res.messages) - - if (!res.valid) isValid = false - }) - - //sort messages => (info, warning, error) - if (sort) final.sort((a,b) => { - const typeA = (a.type == "error") ? 2 : (a.type == "warning") ? 1 : 0 - const typeB = (b.type == "error") ? 2 : (b.type == "warning") ? 1 : 0 - - return typeA-typeB - }) - - this.lastResult = { - valid:isValid, - messages:final - } - - return { - valid:isValid, - messages:final - } - } - /**Create temporary and unlisted `ODConfig`, `ODChecker` & `ODCheckerStorage` classes. This will help you use a `ODCheckerStructure` validator without officially registering it in `opendiscord.checkers`. */ - createTemporaryCheckerEnvironment(){ - return new ODChecker("opendiscord:temporary-environment",new ODCheckerStorage(),0,new ODConfig("opendiscord:temporary-environment",{}),new ODCheckerStructure("opendiscord:temporary-environment",{})) - } -} - -/**## ODCheckerStorage `class` - * This is an Open Ticket checker storage. - * - * It stores temporary data to share between config checkers! - * (e.g. The `messages.json` needs to access the `"id"` from `options.json`) - * - * - * You can use this class when you create your own config checker implementation! (not required for using the built-in config checker) - */ -export class ODCheckerStorage { - /**This is the array that stores all the data. โŒ **(don't edit unless really needed!)***/ - storage: {source:ODId, key:string, value:any}[] = [] - - /**Get data from the database (`source` => id of `ODChecker`) */ - get(source:ODValidId, key:string): any|null { - const result = this.storage.find(d => (d.source.value == new ODId(source).value) && (d.key == key)) - return (result) ? result.value : null - } - /**Add data to the database (`source` => id of `ODChecker`). This function also overwrites existing data!*/ - set(source:ODValidId, key:string, value:any){ - const index = this.storage.findIndex(d => (d.source.value == new ODId(source).value) && (d.key == key)) - if (index > -1){ - //overwrite - this.storage[index] = { - source:new ODId(source), - key,value - } - return true - }else{ - this.storage.push({ - source:new ODId(source), - key,value - }) - return false - } - } - /**Delete data from the database (`source` => id of `ODChecker`) */ - delete(source:ODValidId, key:string){ - const index = this.storage.findIndex(d => (d.source.value == new ODId(source).value) && (d.key == key)) - if (index > -1){ - //delete - this.storage.splice(index,1) - return true - }else return false - } - - /**Reset the entire database */ - reset(){ - this.storage = [] - } -} - -/**## ODCheckerRenderer `class` - * This is an Open Ticket checker renderer. - * - * It's responsible for rendering the config checker result in the console. - * This class doesn't provide any components! You need to create them by extending this class - * - * You can use this class if you want to change how the config checker looks! - */ -export class ODCheckerRenderer { - /**Get all components */ - getComponents(compact:boolean, renderEmpty:boolean, translation:ODCheckerTranslationRegister, data:ODCheckerResult): string[] { - return [] - } - /**Render all components */ - render(components:string[]){ - if (components.length < 1) return - console.log("\n") - components.forEach((c) => { - console.log(c) - }) - console.log("\n") - } -} - -/**## ODCheckerTranslationRegister `class` - * This is an Open Ticket checker translation register. - * - * It's used to store & manage the translation for each message from the config checker! - * Most translations are stored by message id, but there are some exceptions like the additional text on the checker report. - * - * You can use this class if you want to translate your config checker messages! **This is optional & isn't required for the checker to work!** - */ -export class ODCheckerTranslationRegister { - /**This is the array that stores all the data. โŒ **(don't edit unless really needed!)***/ - #translations: {type:"message"|"other", id:string, translation:string}[] = [] - - /**Get the translation from a config checker message/sentence */ - get(type:"message"|"other", id:string): string|null { - const result = this.#translations.find(d => (d.id == id) && (d.type == type)) - return (result) ? result.translation : null - } - /**Set the translation for a config checker message/sentence. This function also overwrites existing translations!*/ - set(type:"message"|"other", id:string, translation:string){ - const index = this.#translations.findIndex(d => (d.id == id) && (d.type == type)) - if (index > -1){ - //overwrite - this.#translations[index] = {type,id,translation} - return true - }else{ - this.#translations.push({type,id,translation}) - return false - } - } - /**Delete the translation for a config checker message/sentence. */ - delete(type:"message"|"other", id:string){ - const index = this.#translations.findIndex(d => (d.id == id) && (d.type == type)) - if (index > -1){ - //delete - this.#translations.splice(index,1) - return true - }else return false - } - - /**Get all translations */ - getAll(){ - return this.#translations - } - - /**Insert the translation params into the text. */ - insertTranslationParams(text:string, translationParams:string[]){ - translationParams.forEach((value,index) => { - text = text.replace(`{${index}}`,value) - }) - return text - } - /**A shortcut to copy translations from the `ODLanguageManager` to `ODCheckerTranslationRegister` */ - quickTranslate(manager:ODLanguageManager, translationId:string, type:"other"|"message", id:string){ - const translation = manager.getTranslation(translationId) - if (translation) this.set(type,id,translation) - } -} - -/**## ODCheckerFunctionCallback `type` - * This is the function used in the `ODCheckerFunction` class. - */ -export type ODCheckerFunctionCallback = (manager:ODCheckerManager, functions:ODCheckerFunctionManager) => ODCheckerResult - -/**## ODCheckerFunction `class` - * This is an Open Ticket config checker function. - * - * It is a global function that will be executed after all config checkers. It can do additional checks for invalid/missing configurations. - * It's mostly used for things that need to be checked globally! - */ -export class ODCheckerFunction extends ODManagerData { - /**The function which will be executed globally after all config checkers. */ - func: ODCheckerFunctionCallback - - constructor(id:ODValidId, func:ODCheckerFunctionCallback){ - super(id) - this.func = func - } -} - -/**## ODCheckerFunctionManager `class` - * This is an Open Ticket config checker function manager. - * - * It manages all `ODCheckerFunction`'s and it has some extra shortcuts for frequently used methods. - */ -export class ODCheckerFunctionManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"config checker function") - } - - /**A shortcut to create a warning, info or error message */ - createMessage(checkerId:ODValidId, id:ODValidId, filepath:string, type:"info"|"warning"|"error", message:string, locationTrace:ODCheckerLocationTrace, docs:string|null, translationParams:string[], locationId:ODId, locationDocs:string|null): ODCheckerMessage { - return { - checkerId:new ODId(checkerId), - messageId:new ODId(id), - locationId, - - type,message, - path:this.locationTraceToString(locationTrace), - filepath, - translationParams, - - messageDocs:docs, - locationDocs - } - } - /**Create a string from the location trace (path)*/ - locationTraceToString(trace:ODCheckerLocationTrace){ - const final: ODCheckerLocationTrace = [] - trace.forEach((t) => { - if (typeof t == "number"){ - final.push(`:${t}`) - }else{ - final.push(`."${t}"`) - } - }) - return final.join("").substring(1) - } - /**De-reference the locationTrace array. Use this before adding a value to the array*/ - locationTraceDeref(trace:ODCheckerLocationTrace): ODCheckerLocationTrace { - return JSON.parse(JSON.stringify(trace)) - } -} - -/**## ODCheckerLocationTrace `type` - * This type is an array of strings & numbers which represents the location trace from the config checker. - * It's used to generate a path to the error (e.g. `"abc"."efg".1."something"`) - */ -export type ODCheckerLocationTrace = (string|number)[] - -/**## ODCheckerOptions `interface` - * This interface contains all optional properties to customise in the `ODChecker` class. - */ -export interface ODCheckerOptions { - /**The name of this config in the Interactive Setup CLI. */ - cliDisplayName?:string - /**The description of this config in the Interactive Setup CLI. */ - cliDisplayDescription?:string -} - -/**## ODChecker `class` - * This is an Open Ticket config checker. - * - * It checks a specific config file for invalid/missing configurations. This data can then be used to show to the user what's wrong! - * You can check for example if a string is longer/shorter than a certain amount of characters & more! - * - * You can use this class when you create your own custom config file & you want to check it for syntax errors. - */ -export class ODChecker extends ODManagerData { - /**The storage of this checker (reference for `ODCheckerManager.storage`) */ - storage: ODCheckerStorage - /**The higher the priority, the faster it gets checked! */ - priority: number - /**The config file that needs to be checked */ - config: ODConfig - /**The structure of the config file */ - structure: ODCheckerStructure - /**Temporary storage for all error messages from the check() method (not recommended to use) */ - messages: ODCheckerMessage[] = [] - /**Temporary storage for the quit status from the check() method (not recommended to use) */ - quit: boolean = false - /**All additional properties of this config checker. */ - options: ODCheckerOptions - - constructor(id:ODValidId, storage: ODCheckerStorage, priority:number, config:ODConfig, structure:ODCheckerStructure, options?:ODCheckerOptions){ - super(id) - this.storage = storage - this.priority = priority - this.config = config - this.structure = structure - this.options = options ?? {} - } - - /**Get a human-readable number string. */ - #ordinalNumber(num:number){ - const i = Math.abs(Math.round(num)) - const cent = i % 100 - if (cent >= 10 && cent <= 20) return i+'th' - const dec = i % 10 - if (dec === 1) return i+'st' - if (dec === 2) return i+'nd' - if (dec === 3) return i+'rd' - return i+'th' - } - /**Run this checker. Returns all errors*/ - check(): ODCheckerResult { - this.messages = [] - this.quit = false - - this.structure.check(this,this.config.data,[]) - return { - valid:!this.quit, - messages:this.messages - } - } - /**Create a string from the location trace/path in a human readable format. */ - locationTraceToString(trace:ODCheckerLocationTrace){ - const final: ODCheckerLocationTrace = [] - trace.forEach((t) => { - if (typeof t == "number"){ - final.push(`:(${this.#ordinalNumber(t+1)})`) - }else{ - final.push(`."${t}"`) - } - }) - return final.join("").substring(1) - } - /**De-reference the locationTrace array. Use this before adding a value to the array*/ - locationTraceDeref(trace:ODCheckerLocationTrace): ODCheckerLocationTrace { - return JSON.parse(JSON.stringify(trace)) - } - - /**A shortcut to create a warning, info or error message */ - createMessage(id:ODValidId, type:"info"|"warning"|"error", message:string, locationTrace:ODCheckerLocationTrace, docs:string|null, translationParams:string[], locationId:ODId, locationDocs:string|null){ - if (type == "error") this.quit = true - this.messages.push({ - checkerId:this.id, - messageId:new ODId(id), - locationId, - - type,message, - path:this.locationTraceToString(locationTrace), - filepath:this.config.path, - translationParams, - - messageDocs:docs, - locationDocs - }) - } -} - -/**## ODCheckerMessage `interface` - * This interface is an object which has all variables required for a config checker message! - */ -export interface ODCheckerMessage { - checkerId:ODId, - messageId:ODId, - locationId:ODId, - - type:"info"|"warning"|"error", - message:string, - path:string, - filepath:string, - translationParams:string[], - - messageDocs:string|null, - locationDocs:string|null -} - -/**## ODCheckerStructureOptions `interface` - * This interface has the basic options for the `ODCheckerStructure`! - */ -export interface ODCheckerStructureOptions { - /**Add a custom checker function. Returns `true` when valid. */ - custom?:(checker:ODChecker, value:ODValidJsonType, locationTrace:ODCheckerLocationTrace, locationId:ODId, locationDocs:string|null) => boolean, - /**Set the url to the documentation of this variable. */ - docs?:string, - /**The name of this config in the Interactive Setup CLI. */ - cliDisplayName?:string - /**The description of this config in the Interactive Setup CLI. */ - cliDisplayDescription?:string - /**Hide the description of this config in the Interactive Setup CLI parent view/list. */ - cliHideDescriptionInParent?:boolean - /**The default value of this variable when creating it in the Interactive Setup CLI. When not specified, the user will be asked to insert a value. */ - cliInitDefaultValue?:ODValidJsonType -} - -/**## ODCheckerStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for a single variable in a config file, customise it in the settings! - * If you want prebuilt checkers (for strings, booleans, numbers, ...), check the other `ODCheckerStructure`'s! - * - * **Not recommended to use!** It's recommended to extend from another `ODConfigCheckerStructure` class! - */ -export class ODCheckerStructure { - /**The id of this checker structure */ - id: ODId - /**The options for this checker structure */ - options: ODCheckerStructureOptions - - constructor(id:ODValidId, options:ODCheckerStructureOptions){ - this.id = new ODId(id) - this.options = options - } - - /**Check a variable if it matches all settings in this checker. This function is automatically executed by Open Ticket! */ - check(checker:ODChecker, value:ODValidJsonType, locationTrace:ODCheckerLocationTrace): boolean { - if (typeof this.options.custom != "undefined"){ - return this.options.custom(checker,value,locationTrace,this.id,(this.options.docs ?? null)) - }else return true - } -} - -/**## ODCheckerObjectStructureOptions `interface` - * This interface has the options for `ODCheckerObjectStructure`! - */ -export interface ODCheckerObjectStructureOptions extends ODCheckerStructureOptions { - /**Add a checker for a property in an object (can also be optional) */ - children:{key:string, priority?:number, optional?:boolean, cliHideInEditMode?:boolean, checker:ODCheckerStructure}[], - /**A list of keys to skip when creating this object with the Interactive Setup CLI. The default value of these properties will be used instead. */ - cliInitSkipKeys?:string[], - /**The key of a (primitive) property in this object to show the value of in the Interactive Setup CLI when listed in an array. */ - cliDisplayKeyInParentArray?:string, - /**A list of additional (primitive) property keys in this object to show the value of in the Interactive Setup CLI when listed in an array. */ - cliDisplayAdditionalKeysInParentArray?:string[] -} - -/**## ODCheckerObjectStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for an object variable in a config file, customise it in the settings! - * A checker for the children can be set in the settings. - */ -export class ODCheckerObjectStructure extends ODCheckerStructure { - declare options: ODCheckerObjectStructureOptions - - constructor(id:ODValidId, options:ODCheckerObjectStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:object, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - //check type & options - if (typeof value != "object"){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: object!",lt,null,["object"],this.id,(this.options.docs ?? null)) - return false - } - - //sort children - if (typeof this.options.children == "undefined") return super.check(checker,value,locationTrace) - const sortedChildren = this.options.children.sort((a,b) => { - if ((a.priority ?? 0) < (b.priority ?? 0)) return -1 - else if ((a.priority ?? 0) > (b.priority ?? 0)) return 1 - else return 0 - }) - - //check children - let localQuit = false - sortedChildren.forEach((child) => { - const localLt = checker.locationTraceDeref(lt) - localLt.push(child.key) - - if (typeof value[child.key] == "undefined"){ - if (!child.optional){ - localQuit = true - checker.createMessage("opendiscord:property-missing","error",`The property "${child.key}" is mising from this object!`,lt,null,[`"${child.key}"`],this.id,(this.options.docs ?? null)) - }else{ - checker.createMessage("opendiscord:property-optional","info",`The property "${child.key}" is optional in this object!`,lt,null,[`"${child.key}"`],this.id,(this.options.docs ?? null)) - } - }else if (!child.checker.check(checker,value[child.key],localLt)) localQuit = true - }) - - //do local quit or check custom function - if (localQuit) return false - else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerStringStructureOptions `interface` - * This interface has the options for `ODCheckerStringStructure`! - */ -export interface ODCheckerStringStructureOptions extends ODCheckerStructureOptions { - /**The minimum length of this string */ - minLength?:number, - /**The maximum length of this string */ - maxLength?:number, - /**Set the required length of this string */ - length?:number, - /**This string needs to start with ... */ - startsWith?:string, - /**This string needs to end with ... */ - endsWith?:string, - /**This string needs to contain ... */ - contains?:string, - /**This string is not allowed to contain ... */ - invertedContains?:string, - /**You need to choose between ... */ - choices?:string[], - /**This string needs to be in lowercase. */ - lowercaseOnly?:boolean, - /**This string needs to be in uppercase. */ - uppercaseOnly?:boolean, - /**This string shouldn't contain any special characters (allowed: A-Z, a-z, 0-9, space, a few punctuation marks, ...). */ - noSpecialCharacters?:boolean, - /**Do not allow any spaces in this string. */ - withoutSpaces?:boolean, - /**Give a warning when a sentence doesn't start with a capital letter. Or require every word to start with a capital letter. (Ignores numbers, unicode characters, ...) */ - capitalLetterWarning?:false|"sentence"|"word" - /**Give a warning when a sentence doesn't end with a punctuation letter (.,?!) */ - punctuationWarning?:boolean - /**The string needs to match this regex */ - regex?:RegExp, - /**Provide an optional list for autocomplete when using the Interactive Setup CLI. Defaults to the `choices` option. */ - cliAutocompleteList?:string[], - /**Dynamically provide a list for autocomplete items when using the Interactive Setup CLI. */ - cliAutocompleteFunc?:() => Promise -} - -/**## ODCheckerStringStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for a string variable in a config file, customise it in the settings! - */ -export class ODCheckerStringStructure extends ODCheckerStructure { - declare options: ODCheckerStringStructureOptions - - constructor(id:ODValidId, options:ODCheckerStringStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:string, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - //check type & options - if (typeof value != "string"){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: string!",lt,null,["string"],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.minLength != "undefined" && value.length < this.options.minLength){ - checker.createMessage("opendiscord:string-too-short","error",`This string can't be shorter than ${this.options.minLength} characters!`,lt,null,[this.options.minLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.maxLength != "undefined" && value.length > this.options.maxLength){ - checker.createMessage("opendiscord:string-too-long","error",`This string can't be longer than ${this.options.maxLength} characters!`,lt,null,[this.options.maxLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.length != "undefined" && value.length !== this.options.length){ - checker.createMessage("opendiscord:string-length-invalid","error",`This string needs to be ${this.options.length} characters long!`,lt,null,[this.options.length.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.startsWith != "undefined" && !value.startsWith(this.options.startsWith)){ - checker.createMessage("opendiscord:string-starts-with","error",`This string needs to start with "${this.options.startsWith}"!`,lt,null,[`"${this.options.startsWith}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.endsWith != "undefined" && !value.endsWith(this.options.endsWith)){ - checker.createMessage("opendiscord:string-ends-with","error",`This string needs to end with "${this.options.endsWith}"!`,lt,null,[`"${this.options.endsWith}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.contains != "undefined" && !value.includes(this.options.contains)){ - checker.createMessage("opendiscord:string-contains","error",`This string needs to contain "${this.options.contains}"!`,lt,null,[`"${this.options.contains}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.invertedContains != "undefined" && value.includes(this.options.invertedContains)){ - checker.createMessage("opendiscord:string-inverted-contains","error",`This string is not allowed to contain "${this.options.invertedContains}"!`,lt,null,[`"${this.options.invertedContains}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.choices != "undefined" && !this.options.choices.includes(value)){ - checker.createMessage("opendiscord:string-choices","error",`This string can only be one of the following values: "${this.options.choices.join(`", "`)}"!`,lt,null,[`"${this.options.choices.join(`", "`)}"`],this.id,(this.options.docs ?? null)) - return false - }else if (this.options.lowercaseOnly && value !== value.toLowerCase()){ - checker.createMessage("opendiscord:string-lowercase","error",`This string must be written in lowercase only!`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (this.options.uppercaseOnly && value !== value.toUpperCase()){ - checker.createMessage("opendiscord:string-uppercase","error",`This string must be written in uppercase only!`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (this.options.noSpecialCharacters && !/^[A-Za-z0-9 ]*$/.test(value)){ - checker.createMessage("opendiscord:string-special-characters","error",`This string is not allowed to contain any special characters! (a-z, 0-9 & space only)`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (this.options.withoutSpaces && value.includes(" ")){ - checker.createMessage("opendiscord:string-no-spaces","error",`This string is not allowed to contain spaces!`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.regex != "undefined" && !this.options.regex.test(value)){ - checker.createMessage("opendiscord:string-regex","error","This string is invalid!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else{ - //warnings - if ((this.options.capitalLetterWarning == "word" && !value.split(" ").every((word) => word.length == 0 || /^[^a-z].*/.test(word)))) checker.createMessage("opendiscord:string-capital-word","warning",`It's recommended that each word in this string starts with a capital letter!`,lt,null,[],this.id,(this.options.docs ?? null)) - if ((this.options.capitalLetterWarning == "sentence" && !value.split(/ *[.?!] */).every((sentence) => sentence.length == 0 || /^[^a-z].*/.test(sentence)))) checker.createMessage("opendiscord:string-capital-sentence","warning",`It looks like some sentences in this string don't start with a capital letter!`,lt,null,[],this.id,(this.options.docs ?? null)) - if (this.options.punctuationWarning && value.length > 0 && (!value.endsWith(".") && !value.endsWith("?") && !value.endsWith("!") && !value.endsWith("'") && !value.endsWith('"') && !value.endsWith(",") && !value.endsWith(";") && !value.endsWith(":") && !value.endsWith("="))) checker.createMessage("opendiscord:string-punctuation","warning",`It looks like the sentence in this string doesn't end with a punctuation mark!`,lt,null,[],this.id,(this.options.docs ?? null)) - - return super.check(checker,value,locationTrace) - } - } -} - -/**## ODCheckerNumberStructureOptions `interface` - * This interface has the options for `ODCheckerNumberStructure`! - */ -export interface ODCheckerNumberStructureOptions extends ODCheckerStructureOptions { - /**Is `NaN` (not a number) allowed? (`false` by default) */ - nanAllowed?:boolean - /**The minimum length of this number */ - minLength?:number, - /**The maximum length of this number */ - maxLength?:number, - /**Set the required length of this number */ - length?:number, - /**The minimum value of this number */ - min?:number, - /**The maximum value of this number */ - max?:number, - /**This number is required to match the value */ - is?:number, - /**Only allow a multiple of ... starting at `this.offset` or 0 */ - step?:number, - /**The offset for the step function. */ - offset?:number, - /**This number needs to start with ... */ - startsWith?:string, - /**This number needs to end with ... */ - endsWith?:string, - /**This number needs to contain ... */ - contains?:string, - /**This number is not allowed to contain ... */ - invertedContains?:string, - /**You need to choose between ... */ - choices?:number[], - /**Are numbers with a decimal value allowed? */ - floatAllowed?:boolean, - /**Are negative numbers allowed (without zero) */ - negativeAllowed?:boolean, - /**Are positive numers allowed (without zero) */ - positiveAllowed?:boolean, - /**Is zero allowed? */ - zeroAllowed?:boolean -} - -/**## ODCheckerNumberStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for a number variable in a config file, customise it in the settings! - */ -export class ODCheckerNumberStructure extends ODCheckerStructure { - declare options: ODCheckerNumberStructureOptions - - constructor(id:ODValidId, options:ODCheckerNumberStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:number, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - //offset for step - const stepOffset = (typeof this.options.offset != "undefined") ? this.options.offset : 0 - - //check type & options - if (typeof value != "number"){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: number!",lt,null,["number"],this.id,(this.options.docs ?? null)) - return false - }else if (!this.options.nanAllowed && isNaN(value)){ - checker.createMessage("opendiscord:number-nan","error",`This number can't be NaN (Not A Number)!`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.minLength != "undefined" && value.toString().length < this.options.minLength){ - checker.createMessage("opendiscord:number-too-short","error",`This number can't be shorter than ${this.options.minLength} characters!`,lt,null,[this.options.minLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.maxLength != "undefined" && value.toString().length > this.options.maxLength){ - checker.createMessage("opendiscord:number-too-long","error",`This number can't be longer than ${this.options.maxLength} characters!`,lt,null,[this.options.maxLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.length != "undefined" && value.toString().length !== this.options.length){ - checker.createMessage("opendiscord:number-length-invalid","error",`This number needs to be ${this.options.length} characters long!`,lt,null,[this.options.length.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.min != "undefined" && value < this.options.min){ - checker.createMessage("opendiscord:number-too-small","error",`This number needs to be at least ${this.options.min}!`,lt,null,[this.options.min.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.max != "undefined" && value > this.options.max){ - checker.createMessage("opendiscord:number-too-large","error",`This number needs to be at most ${this.options.max}!`,lt,null,[this.options.max.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.is != "undefined" && value == this.options.is){ - checker.createMessage("opendiscord:number-not-equal","error",`This number needs to be ${this.options.is}!`,lt,null,[this.options.is.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.step != "undefined" && ((value - stepOffset) % this.options.step) !== 0){ - if (stepOffset > 0) checker.createMessage("opendiscord:number-step-offset","error",`This number needs to be a multiple of ${this.options.step} starting with ${stepOffset}!`,lt,null,[this.options.step.toString(),stepOffset.toString()],this.id,(this.options.docs ?? null)) - else checker.createMessage("opendiscord:number-step","error",`This number needs to be a multiple of ${this.options.step}!`,lt,null,[this.options.step.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.startsWith != "undefined" && !value.toString().startsWith(this.options.startsWith)){ - checker.createMessage("opendiscord:number-starts-with","error",`This number needs to start with "${this.options.startsWith}"!`,lt,null,[`"${this.options.startsWith}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.endsWith != "undefined" && !value.toString().endsWith(this.options.endsWith)){ - checker.createMessage("opendiscord:number-ends-with","error",`This number needs to end with "${this.options.endsWith}"!`,lt,null,[`"${this.options.endsWith}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.contains != "undefined" && !value.toString().includes(this.options.contains)){ - checker.createMessage("opendiscord:number-contains","error",`This number needs to contain "${this.options.contains}"!`,lt,null,[`"${this.options.contains}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.invertedContains != "undefined" && value.toString().includes(this.options.invertedContains)){ - checker.createMessage("opendiscord:number-inverted-contains","error",`This number is not allowed to contain "${this.options.invertedContains}"!`,lt,null,[`"${this.options.invertedContains}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.choices != "undefined" && !this.options.choices.includes(value)){ - checker.createMessage("opendiscord:number-choices","error",`This number can only be one of the following values: "${this.options.choices.join(`", "`)}"!`,lt,null,[`"${this.options.choices.join(`", "`)}"`],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.floatAllowed != "undefined" && !this.options.floatAllowed && (value % 1) !== 0){ - checker.createMessage("opendiscord:number-float","error","This number can't be a decimal!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.negativeAllowed != "undefined" && !this.options.negativeAllowed && value < 0){ - checker.createMessage("opendiscord:number-negative","error","This number can't be negative!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.positiveAllowed != "undefined" && !this.options.positiveAllowed && value > 0){ - checker.createMessage("opendiscord:number-positive","error","This number can't be positive!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.zeroAllowed != "undefined" && !this.options.zeroAllowed && value === 0){ - checker.createMessage("opendiscord:number-zero","error","This number can't be zero!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerBooleanStructureOptions `interface` - * This interface has the options for `ODCheckerBooleanStructure`! - */ -export interface ODCheckerBooleanStructureOptions extends ODCheckerStructureOptions { - /**Is `true` allowed? */ - trueAllowed?:boolean, - /**Is `false` allowed? */ - falseAllowed?:boolean -} - -/**## ODCheckerBooleanStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for a boolean variable in a config file, customise it in the settings! - */ -export class ODCheckerBooleanStructure extends ODCheckerStructure { - declare options: ODCheckerBooleanStructureOptions - - constructor(id:ODValidId, options:ODCheckerBooleanStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:boolean, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - //check type & options - if (typeof value != "boolean"){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: boolean!",lt,null,["boolean"],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.trueAllowed != "undefined" && !this.options.trueAllowed && value == true){ - checker.createMessage("opendiscord:boolean-true","error","This boolean can't be true!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.falseAllowed != "undefined" && !this.options.falseAllowed && value == false){ - checker.createMessage("opendiscord:boolean-false","error","This boolean can't be false!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerArrayStructureOptions `interface` - * This interface has the options for `ODCheckerArrayStructure`! - */ -export interface ODCheckerArrayStructureOptions extends ODCheckerStructureOptions { - /**The checker for all the properties in this array */ - propertyChecker?:ODCheckerStructure, - /**Don't allow this array to be empty */ - disableEmpty?:boolean, - /**This array is required to be empty */ - emptyRequired?:boolean, - /**The minimum length of this array */ - minLength?:number, - /**The maximum length of this array */ - maxLength?:number, - /**The length of the array needs to be the same as this value */ - length?:number, - /**Allow double values (only for `string`, `number` & `boolean`) */ - allowDoubles?:boolean - /**Only allow these types in the array (for multi-type propertyCheckers) */ - allowedTypes?:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[], - /**The name of the properties inside this array. Used in the GUI of the Interactive Setup CLI. */ - cliDisplayPropertyName?:string -} - -/**## ODCheckerArrayStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for an array variable in a config file, customise it in the settings! - */ -export class ODCheckerArrayStructure extends ODCheckerStructure { - declare options: ODCheckerArrayStructureOptions - - constructor(id:ODValidId, options:ODCheckerArrayStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:Array, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - if (!Array.isArray(value)){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: array!",lt,null,["array"],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.disableEmpty != "undefined" && this.options.disableEmpty && value.length == 0){ - checker.createMessage("opendiscord:array-empty-disabled","error","This array isn't allowed to be empty!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.emptyRequired != "undefined" && this.options.emptyRequired && value.length != 0){ - checker.createMessage("opendiscord:array-empty-required","error","This array is required to be empty!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.minLength != "undefined" && value.length < this.options.minLength){ - checker.createMessage("opendiscord:array-too-short","error",`This array needs to have a length of at least ${this.options.minLength}!`,lt,null,[this.options.minLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.maxLength != "undefined" && value.length > this.options.maxLength){ - checker.createMessage("opendiscord:array-too-long","error",`This array needs to have a length of at most ${this.options.maxLength}!`,lt,null,[this.options.maxLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.length != "undefined" && value.length == this.options.length){ - checker.createMessage("opendiscord:array-length-invalid","error",`This array needs to have a length of ${this.options.length}!`,lt,null,[this.options.length.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.allowedTypes != "undefined" && !this.#arrayAllowedTypesCheck(value,this.options.allowedTypes)){ - checker.createMessage("opendiscord:array-invalid-types","error",`This array can only contain the following types: ${this.options.allowedTypes.join(", ")}!`,lt,null,[this.options.allowedTypes.join(", ").toString()],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.options.allowDoubles != "undefined" && !this.options.allowDoubles && this.#arrayHasDoubles(value)){ - checker.createMessage("opendiscord:array-double","error","This array doesn't allow the same value twice!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else{ - //check all properties - let localQuit = false - if (this.options.propertyChecker) value.forEach((property,index) => { - if (!this.options.propertyChecker) return - - const localLt = checker.locationTraceDeref(lt) - localLt.push(index) - - if (!this.options.propertyChecker.check(checker,property,localLt)) localQuit = true - }) - - //return false if invalid properties - if (localQuit){ - checker.quit = true - return false - }else return super.check(checker,value,locationTrace) - } - } - - /**Check this array for the allowed types */ - #arrayAllowedTypesCheck(array:any[],allowedTypes:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[]): boolean { - //return TRUE if ALL values are valid - return !array.some((value) => { - if (allowedTypes.includes("string") && typeof value == "string"){ - return false //this value is valid - }else if (allowedTypes.includes("number") && typeof value == "number"){ - return false //this value is valid - }else if (allowedTypes.includes("boolean") && typeof value == "boolean"){ - return false //this value is valid - }else if (allowedTypes.includes("object") && typeof value == "object"){ - return false //this value is valid - }else if (allowedTypes.includes("array") && Array.isArray(value)){ - return false //this value is valid - }else if (allowedTypes.includes("null") && value === null){ - return false //this value is valid - }else if (allowedTypes.includes("other")){ - return false //this value is valid - }else{ - return true //this value is invalid - } - }) - } - /**Check this array for doubles */ - #arrayHasDoubles(array:any[]): boolean { - const alreadyFound: string[] = [] - let hasDoubles = false - array.forEach((value) => { - if (alreadyFound.includes(value)) hasDoubles = true - else alreadyFound.push(value) - }) - - return hasDoubles - } -} - -/**## ODCheckerNullStructureOptions `interface` - * This interface has the options for `ODCheckerNullStructure`! - */ -export interface ODCheckerNullStructureOptions extends ODCheckerStructureOptions { - /**Is the value allowed to be null */ - nullAllowed?:boolean -} - -/**## ODCheckerNullStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will check for a null variable in a config file, customise it in the settings! - */ -export class ODCheckerNullStructure extends ODCheckerStructure { - declare options: ODCheckerNullStructureOptions - - constructor(id:ODValidId, options:ODCheckerNullStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:null, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - //check type & options - if (typeof this.options.nullAllowed != "undefined" && !this.options.nullAllowed && value == null){ - checker.createMessage("opendiscord:null-invalid","error","This property can't be null!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (value !== null){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: null!",lt,null,["null"],this.id,(this.options.docs ?? null)) - return false - }else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerTypeSwitchStructureOptions `interface` - * This interface has the options for `ODCheckerTypeSwitchStructure`! - */ -export interface ODCheckerTypeSwitchStructureOptions extends ODCheckerStructureOptions { - /**A checker that will always run (replaces all other checkers) */ - all?:ODCheckerStructure, - /**A checker when the property is a string */ - string?:ODCheckerStringStructure, - /**A checker when the property is a number */ - number?:ODCheckerNumberStructure, - /**A checker when the property is a boolean */ - boolean?:ODCheckerBooleanStructure, - /**A checker when the property is null */ - null?:ODCheckerNullStructure, - /**A checker when the property is an array */ - array?:ODCheckerArrayStructure, - /**A checker when the property is an object */ - object?:ODCheckerObjectStructure, - /**A checker when the property is something else */ - other?:ODCheckerStructure, - /**A list of allowed types */ - allowedTypes:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[] -} - -/**## ODCheckerTypeSwitchStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will switch checkers based on the type of the variable in a config file, customise it in the settings! - */ -export class ODCheckerTypeSwitchStructure extends ODCheckerStructure { - declare options: ODCheckerTypeSwitchStructureOptions - - constructor(id:ODValidId, options:ODCheckerTypeSwitchStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:any, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - if (this.options.all){ - return this.options.all.check(checker,value,lt) - - }else if (this.options.string && typeof value == "string"){ - return this.options.string.check(checker,value,lt) - - }else if (this.options.number && typeof value == "number"){ - return this.options.number.check(checker,value,lt) - - }else if (this.options.boolean && typeof value == "boolean"){ - return this.options.boolean.check(checker,value,lt) - - }else if (this.options.array && Array.isArray(value)){ - return this.options.array.check(checker,value,lt) - - }else if (this.options.null && value === null){ - return this.options.null.check(checker,value,lt) - - }else if (this.options.object && typeof value == "object"){ - return this.options.object.check(checker,value,lt) - - }else if (this.options.other){ - return this.options.other.check(checker,value,lt) - - }else if (this.options.allowedTypes && this.options.allowedTypes.length > 0){ - checker.createMessage("opendiscord:switch-invalid-type","error",`This needs to be one of the following types: ${this.options.allowedTypes.join(", ")}!`,lt,null,[this.options.allowedTypes.join(", ")],this.id,(this.options.docs ?? null)) - return false - }else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerObjectSwitchStructureOptions `interface` - * This interface has the options for `ODCheckerObjectSwitchStructure`! - */ -export interface ODCheckerObjectSwitchStructureOptions extends ODCheckerStructureOptions { - /**An array of object checkers with their name, properties & priority. */ - objects:{ - /**The properties to match for this checker to be used. */ - properties:{key:string, value:boolean|string|number}[], - /**The name for this object type (used in rendering) */ - name:string, - /**The higher the priority, the earlier this checker will be tested. */ - priority:number, - /**The object checker used once the properties have been matched. */ - checker:ODCheckerObjectStructure - }[] -} - -/**## ODCheckerObjectSwitchStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will switch object checkers based on a variable match in one of the objects, customise it in the settings! - */ -export class ODCheckerObjectSwitchStructure extends ODCheckerStructure { - declare options: ODCheckerObjectSwitchStructureOptions - - constructor(id:ODValidId, options:ODCheckerObjectSwitchStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:object, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - if (this.options.objects){ - //check type & options - if (typeof value != "object"){ - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: object!",lt,null,["object"],this.id,(this.options.docs ?? null)) - return false - } - - //sort objects - const sortedObjects = this.options.objects.sort((a,b) => { - if (a.priority < b.priority) return -1 - else if (a.priority > b.priority) return 1 - else return 0 - }) - - - //check objects - let localQuit = false - let didSelectObject = false - sortedObjects.forEach((obj) => { - if (!obj.properties.some((p) => value[p.key] !== p.value)){ - didSelectObject = true - if (!obj.checker.check(checker,value,lt)) localQuit = true - } - }) - - //do local quit or check custom function - if (!didSelectObject){ - checker.createMessage("opendiscord:object-switch-invalid-type","error",`This object needs to be one of the following types: ${this.options.objects.map((obj) => obj.name).join(", ")}!`,lt,null,[this.options.objects.map((obj) => obj.name).join(", ")],this.id,(this.options.docs ?? null)) - return false - }else if (localQuit){ - return false - }else return super.check(checker,value,locationTrace) - }else return super.check(checker,value,locationTrace) - } -} - -/**## ODCheckerEnabledObjectStructureOptions `interface` - * This interface has the options for `ODCheckerEnabledObjectStructure`! - */ -export interface ODCheckerEnabledObjectStructureOptions extends ODCheckerStructureOptions { - /**The name of the property to match the `enabledValue`. */ - property:string, - /**The value of the property to be enabled. (e.g. `true`) */ - enabledValue:boolean|string|number, - /**The object checker to use once the property has been matched. */ - checker:ODCheckerObjectStructure -} - -/**## ODCheckerEnabledObjectStructure `class` - * This is an Open Ticket config checker structure. - * - * This class will enable an object checker based on a variable match in the object, customise it in the settings! - */ -export class ODCheckerEnabledObjectStructure extends ODCheckerStructure { - declare options: ODCheckerEnabledObjectStructureOptions - - constructor(id:ODValidId, options:ODCheckerEnabledObjectStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:object, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "object"){ - //value isn't an object - checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: object!",lt,null,["object"],this.id,(this.options.docs ?? null)) - return false - - }else if (this.options.property && typeof value[this.options.property] == "undefined"){ - //property doesn't exist - checker.createMessage("opendiscord:property-missing","error",`The property "${this.options.property}" is mising from this object!`,lt,null,[`"${this.options.property}"`],this.id,(this.options.docs ?? null)) - return false - - }else if (this.options.property && value[this.options.property] === (typeof this.options.enabledValue == "undefined" ? true : this.options.enabledValue)){ - //this object is enabled - if (this.options.checker) return this.options.checker.check(checker,value,lt) - else return super.check(checker,value,locationTrace) - - }else{ - //this object is disabled - if (this.options.property) checker.createMessage("opendiscord:object-disabled","info",`This object is disabled, enable it using "${this.options.property}"!`,lt,null,[`"${this.options.property}"`],this.id,(this.options.docs ?? null)) - return super.check(checker,value,locationTrace) - } - } -} - -/**## ODCheckerCustomStructure_DiscordId `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for discord ids (channel, user, role, ...)** - */ -export class ODCheckerCustomStructure_DiscordId extends ODCheckerStringStructure { - /**The type of id (used in rendering) */ - readonly type: ODDiscordIdType - /**Is this id allowed to be empty */ - readonly emptyAllowed: boolean - /**Extra matches (value will also be valid when one of these options match) */ - readonly extraOptions: string[] - - constructor(id:ODValidId, type:ODDiscordIdType, emptyAllowed:boolean, extraOptions:string[], options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string") return false - else if ((!emptyAllowed && value.length < 15) || value.length > 50 || !/^[0-9]*$/.test(value)){ - if (!(extraOptions.length > 0 && extraOptions.some((opt) => opt == value))){ - //value is not an id & not one of the extra options - if (extraOptions.length > 0) checker.createMessage("opendiscord:discord-invalid-id-options","error",`This is an invalid discord ${type} id! You can also use one of these: ${extraOptions.join(", ")}!`,lt,null,[type,extraOptions.join(", ")],this.id,(this.options.docs ?? null)) - else checker.createMessage("opendiscord:discord-invalid-id","error",`This is an invalid discord ${type} id!`,lt,null,[type],this.id,(this.options.docs ?? null)) - return false - }else return true - } - return true - } - super(id,newOptions) - this.type = type - this.emptyAllowed = emptyAllowed - this.extraOptions = extraOptions - } -} - -/**## ODCheckerCustomStructure_DiscordIdArray `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for discord id arrays (channel, user, role, ...)** - */ -export class ODCheckerCustomStructure_DiscordIdArray extends ODCheckerArrayStructure { - /**The type of id (used in rendering) */ - readonly type: ODDiscordIdType - /**Extra matches (value will also be valid when one of these options match) */ - readonly extraOptions: string[] - - constructor(id:ODValidId, type:ODDiscordIdType, extraOptions:string[], options?:ODCheckerArrayStructureOptions, idOptions?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.propertyChecker = new ODCheckerCustomStructure_DiscordId(id,type,false,extraOptions,idOptions) - super(id,newOptions) - this.type = type - this.extraOptions = extraOptions - } -} - -/**## ODCheckerCustomStructure_DiscordToken `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for a discord (auth) token** - */ -export class ODCheckerCustomStructure_DiscordToken extends ODCheckerStringStructure { - constructor(id:ODValidId, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string" || !/^[A-Za-z0-9-_\.]+$/.test(value)){ - checker.createMessage("opendiscord:discord-invalid-token","error","This is an invalid discord token (syntactically)!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - } - return true - } - super(id,newOptions) - } -} - -/**## ODCheckerCustomStructure_DiscordToken `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for a hex color** - */ -export class ODCheckerCustomStructure_HexColor extends ODCheckerStringStructure { - /**When enabled, you are also allowed to use `#fff` instead of `#ffffff` */ - readonly allowShortForm: boolean - /**Allow this hex color to be empty. */ - readonly emptyAllowed: boolean - - constructor(id:ODValidId, allowShortForm:boolean, emptyAllowed:boolean, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string") return false - else if (emptyAllowed && value.length == 0){ - return true - }else if ((!allowShortForm && !/^#[a-fA-F0-9]{6}$/.test(value)) || (allowShortForm && !/^#[a-fA-F0-9]{6}$/.test(value) && !/^#[a-fA-F0-9]{3}$/.test(value))){ - checker.createMessage("opendiscord:color-invalid","error","This is an invalid hex color!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else return true - } - super(id,newOptions) - this.allowShortForm = allowShortForm - this.emptyAllowed = emptyAllowed - } -} - -/**## ODCheckerCustomStructure_EmojiString `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for an emoji (string)** - */ -export class ODCheckerCustomStructure_EmojiString extends ODCheckerStringStructure { - /**The minimum amount of emojis required (0 to allow empty) */ - readonly minLength: number - /**The maximum amount of emojis allowed */ - readonly maxLength: number - /**Allow custom discord emoji ids (`<:12345678910:emoji_name>`) */ - readonly allowCustomDiscordEmoji: boolean - - constructor(id:ODValidId, minLength:number, maxLength:number, allowCustomDiscordEmoji:boolean, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - if (typeof value != "string") return false - - const discordEmojiSplitter = /(?:)/g - const splitted = value.split(discordEmojiSplitter) - const discordEmojiAmount = splitted.length-1 - const unicodeEmojiAmount = [...new Intl.Segmenter().segment(splitted.join(""))].length - const emojiAmount = discordEmojiAmount+unicodeEmojiAmount - - if (emojiAmount < minLength){ - checker.createMessage("opendiscord:emoji-too-short","error",`This string needs to have at least ${minLength} emoji's!`,lt,null,[maxLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (emojiAmount > maxLength){ - checker.createMessage("opendiscord:emoji-too-long","error",`This string needs to have at most ${maxLength} emoji's!`,lt,null,[maxLength.toString()],this.id,(this.options.docs ?? null)) - return false - }else if (!allowCustomDiscordEmoji && //.test(value)){ - checker.createMessage("opendiscord:emoji-custom","error",`This emoji can't be a custom discord emoji!`,lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (!/^(?:\p{Emoji}|\p{Emoji_Component}|(?:))*$/u.test(value)){ - checker.createMessage("opendiscord:emoji-invalid","error","This is an invalid emoji!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - } - return true - } - super(id,newOptions) - this.minLength = minLength - this.maxLength = maxLength - this.allowCustomDiscordEmoji = allowCustomDiscordEmoji - } -} - -/**## ODCheckerCustomStructureOptions_UrlString `interface` - * This interface has the options for `ODCheckerCustomStructure_UrlString`! - */ -export interface ODCheckerCustomStructureOptions_UrlString { - /**Allow urls with `http://` instead of `https://` */ - allowHttp?:boolean - /**Allowed hostnames (string or regex) => will match domain + subdomain */ - allowedHostnames?: (string|RegExp)[] - /**Allowed extentions (string) => will match the end of the url (`.png`,`.svg`,...) */ - allowedExtensions?: string[] - /**Allowed paths (string or regex) => will match path + extension (not domain + subdomain) */ - allowedPaths?: (string|RegExp)[], - /**A regex that will be executed on the entire url (including search params, protcol, domain, ...) */ - regex?:RegExp -} - -/**## ODCheckerCustomStructure_UrlString `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for a URL (string)** - */ -export class ODCheckerCustomStructure_UrlString extends ODCheckerStringStructure { - /**The settings for this url */ - readonly urlSettings: ODCheckerCustomStructureOptions_UrlString - /**Is this url allowed to be empty? */ - readonly emptyAllowed: boolean - - constructor(id:ODValidId, emptyAllowed:boolean, urlSettings:ODCheckerCustomStructureOptions_UrlString, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string") return false - else if (emptyAllowed && value.length == 0){ - return true - }else if (!this.#urlIsValid(value)){ - checker.createMessage("opendiscord:url-invalid","error","This url is invalid!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.urlSettings.allowHttp != "undefined" && !this.urlSettings.allowHttp && !/^(https:\/\/)/.test(value)){ - checker.createMessage("opendiscord:url-invalid-http","error","This url can only use the https:// protocol!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (!/^(http(s)?:\/\/)/.test(value)){ - checker.createMessage("opendiscord:url-invalid-protocol","error","This url can only use the http:// & https:// protocols!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.urlSettings.allowedHostnames != "undefined" && !this.#urlHasValidHostname(value,this.urlSettings.allowedHostnames)){ - checker.createMessage("opendiscord:url-invalid-hostname","error","This url has a disallowed hostname!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.urlSettings.allowedExtensions != "undefined" && !this.#urlHasValidExtension(value,this.urlSettings.allowedExtensions)){ - checker.createMessage("opendiscord:url-invalid-extension","error",`This url has an invalid extension! Choose between: ${this.urlSettings.allowedExtensions.join(", ")}!"`,lt,null,[this.urlSettings.allowedExtensions.join(", ")],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.urlSettings.allowedPaths != "undefined" && !this.#urlHasValidPath(value,this.urlSettings.allowedPaths)){ - checker.createMessage("opendiscord:url-invalid-path","error","This url has an invalid path!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else if (typeof this.urlSettings.regex != "undefined" && !this.urlSettings.regex.test(value)){ - checker.createMessage("opendiscord:url-invalid","error","This url is invalid!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else return true - } - super(id,newOptions) - this.urlSettings = urlSettings - this.emptyAllowed = emptyAllowed - } - - /**Check for the hostname */ - #urlHasValidHostname(url:string,hostnames:(string|RegExp)[]): boolean { - try { - const hostname = new URL(url).hostname - return hostnames.some((rule) => { - if (typeof rule == "string"){ - return rule == hostname - }else{ - return rule.test(hostname) - } - }) - - }catch{ - return false - } - } - /**Check for the extension */ - #urlHasValidExtension(url:string,extensions:string[]): boolean { - try { - const path = new URL(url).pathname - return extensions.some((rule) => { - return path.endsWith(rule) - }) - }catch{ - return false - } - } - /**Check for the path */ - #urlHasValidPath(url:string,paths:(string|RegExp)[]): boolean { - try { - const path = new URL(url).pathname - return paths.some((rule) => { - if (typeof rule == "string"){ - return rule == path - }else{ - return rule.test(path) - } - }) - }catch{ - return false - } - } - /**Do general syntax check on url */ - #urlIsValid(url:string){ - try { - new URL(url) - return true - }catch{ - return false - } - } -} - -/**## ODCheckerCustomStructure_UniqueId `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for a unique id (per source & scope)** - */ -export class ODCheckerCustomStructure_UniqueId extends ODCheckerStringStructure { - /**The source of this unique id (generally the plugin name or `openticket`) */ - readonly source: string - /**The scope of this unique id (id needs to be unique in this scope) */ - readonly scope: string - - constructor(id:ODValidId, source:string, scope:string, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string") return false - const uniqueArray: string[] = (checker.storage.get(source,scope) === null) ? [] : checker.storage.get(source,scope) - if (uniqueArray.includes(value)){ - //unique id already exists => throw error - checker.createMessage("opendiscord:id-not-unique","error","This id isn't unique, use another id instead!",lt,null,[],this.id,(this.options.docs ?? null)) - return false - }else{ - //unique id doesn't exists => add to list - uniqueArray.push(value) - checker.storage.set(source,scope,uniqueArray) - return true - } - } - super(id,newOptions) - this.source = source - this.scope = scope - } -} - -/**## ODCheckerCustomStructure_UniqueIdArray `class` - * This is an Open Ticket custom checker structure. - * - * This class extends a primitive config checker & adds another layer of checking in the `custom` function. - * You can compare it to a blueprint for a specific checker. - * - * **This custom checker is made for a unique id array (per source & scope)** - */ -export class ODCheckerCustomStructure_UniqueIdArray extends ODCheckerArrayStructure { - /**The source to read unique ids (generally the plugin name or `openticket`) */ - readonly source: string - /**The scope to read unique ids (id needs to be unique in this scope) */ - readonly scope: string - /**The scope to push unique ids when used in this array! */ - readonly usedScope: string|null - - constructor(id:ODValidId, source:string, scope:string, usedScope?:string, options?:ODCheckerArrayStructureOptions, idOptions?:Omit){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.propertyChecker = new ODCheckerStringStructure("opendiscord:unique-id",{...(idOptions ?? {}),minLength:1,custom:(checker,value,locationTrace,locationId,locationDocs) => { - if (typeof value != "string") return false - const localLt = checker.locationTraceDeref(locationTrace) - localLt.pop() - - const uniqueArray: string[] = checker.storage.get(source,scope) ?? [] - if (uniqueArray.includes(value)){ - //exists - if (usedScope){ - const current: string[] = checker.storage.get(source,usedScope) ?? [] - current.push(value) - checker.storage.set(source,usedScope,current) - } - return true - }else{ - //doesn't exist - checker.createMessage("opendiscord:id-non-existent","error",`The id "${value}" doesn't exist!`,localLt,null,[`"${value}"`],locationId,locationDocs) - return false - } - }}) - super(id,newOptions) - this.source = source - this.scope = scope - this.usedScope = usedScope ?? null - } -} - -/*TEMPLATE!!!! -export interface ODCheckerTemplateStructureOptions extends ODCheckerStructureOptions { - -} -export class ODCheckerTemplateStructure extends ODCheckerStructure { - declare options: ODCheckerTemplateStructureOptions - - constructor(id:ODValidId, options:ODCheckerTemplateStructureOptions){ - super(id,options) - } - - check(checker:ODChecker, value:any, locationTrace:ODCheckerLocationTrace): boolean { - const lt = checker.locationTraceDeref(locationTrace) - - return super.check(checker,value,locationTrace) - } -} -*/ -/*CUSTOM TEMPLATE!!!! -export class ODCheckerCustomStructure_Template extends ODCheckerTemplateStructure { - idk: string - - constructor(id:ODValidId, idk:string, options?:ODCheckerStringStructureOptions){ - //add premade custom structure checker - const newOptions = options ?? {} - newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => { - const lt = checker.locationTraceDeref(locationTrace) - - //do custom check & push error message. Return true if correct - return boolean - } - super(id,newOptions) - this.idk = idk - } -} -*/ diff --git a/src/core/api/modules/client.ts b/src/core/api/modules/client.ts deleted file mode 100644 index 5638a99..0000000 --- a/src/core/api/modules/client.ts +++ /dev/null @@ -1,2247 +0,0 @@ -/////////////////////////////////////// -//DISCORD CLIENT MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODSystemError, ODValidId } from "./base" -import * as discord from "discord.js" -import {REST} from "@discordjs/rest" -import { ODConsoleWarningMessage, ODDebugger } from "./console" -import { ODMessageBuildResult, ODMessageBuildSentResult } from "./builder" -import { ODManualProgressBar } from "./progressbar" - -/**## ODClientIntents `type` - * A list of intents required when inviting the bot. - */ -export type ODClientIntents = ("Guilds"|"GuildMembers"|"GuildModeration"|"GuildEmojisAndStickers"|"GuildIntegrations"|"GuildWebhooks"|"GuildInvites"|"GuildVoiceStates"|"GuildPresences"|"GuildMessages"|"GuildMessageReactions"|"GuildMessageTyping"|"DirectMessages"|"DirectMessageReactions"|"DirectMessageTyping"|"MessageContent"|"GuildScheduledEvents"|"AutoModerationConfiguration"|"AutoModerationExecution") -/**## ODClientPriviligedIntents `type` - * A list of priviliged intents required to be enabled in the developer portal. - */ -export type ODClientPriviligedIntents = ("GuildMembers"|"MessageContent"|"Presence") -/**## ODClientPartials `type` - * A list of partials required for the bot to work. (`Message` & `Channel` are for receiving DM messages from uncached channels) - */ -export type ODClientPartials = ("User"|"Channel"|"GuildMember"|"Message"|"Reaction"|"GuildScheduledEvent"|"ThreadMember") -/**## ODClientPermissions `type` - * A list of permissions required in the server that the bot is active in. - */ -export type ODClientPermissions = ("CreateInstantInvite"|"KickMembers"|"BanMembers"|"Administrator"|"ManageChannels"|"ManageGuild"|"AddReactions"|"ViewAuditLog"|"PrioritySpeaker"|"Stream"|"ViewChannel"|"SendMessages"|"SendTTSMessages"|"ManageMessages"|"EmbedLinks"|"AttachFiles"|"ReadMessageHistory"|"MentionEveryone"|"UseExternalEmojis"|"ViewGuildInsights"|"Connect"|"Speak"|"MuteMembers"|"DeafenMembers"|"MoveMembers"|"UseVAD"|"ChangeNickname"|"ManageNicknames"|"ManageRoles"|"ManageWebhooks"|"ManageGuildExpressions"|"UseApplicationCommands"|"RequestToSpeak"|"ManageEvents"|"ManageThreads"|"CreatePublicThreads"|"CreatePrivateThreads"|"UseExternalStickers"|"SendMessagesInThreads"|"UseEmbeddedActivities"|"ModerateMembers"|"ViewCreatorMonetizationAnalytics"|"UseSoundboard"|"UseExternalSounds"|"SendVoiceMessages") - -/**## ODClientManager `class` - * This is an Open Ticket client manager. - * - * It is responsible for managing the discord.js client. Here, you can set the status, register slash commands and much more! - * - * If you want, you can also listen for custom events on the `ODClientManager.client` variable (`discord.Client`) - */ -export class ODClientManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - /**List of required bot intents. Add intents to this list using the `onClientLoad` event. */ - intents: ODClientIntents[] = [] - /**List of required bot privileged intents. Add intents to this list using the `onClientLoad` event. */ - privileges: ODClientPriviligedIntents[] = [] - /**List of required bot partials. Add intents to this list using the `onClientLoad` event. **โŒ Only use when neccessery!** */ - partials: ODClientPartials[] = [] - /**List of required bot permissions. Add permissions to this list using the `onClientLoad` event. */ - permissions: ODClientPermissions[] = [] - /**The discord bot token, empty by default. */ - set token(value:string){ - this.#token = value - this.rest.setToken(value) - } - get token(){ - return this.#token - } - /**The discord bot token. **DON'T USE THIS!!!** (use `ODClientManager.token` instead) */ - #token: string = "" - - /**The discord.js `discord.Client`. Only use it when initiated! */ - client: discord.Client = new discord.Client({intents:[]}) //temporary client - /**The discord.js REST client. Used for stuff that discord.js can't handle :) */ - rest: discord.REST = new REST({version:"10"}) - /**Is the bot initiated? */ - initiated: boolean = false - /**Is the bot logged in? */ - loggedIn: boolean = false - /**Is the bot ready? */ - ready: boolean = false - - /**The main server of the bot. Provided by serverId in the config */ - mainServer: discord.Guild|null = null - /**(โŒ DO NOT OVERWRITE โŒ) Internal Open Ticket function to continue the startup when the client is ready! */ - readyListener: (() => Promise)|null = null - /**The status manager is responsible for setting the bot status. */ - activity: ODClientActivityManager - /**The slash command manager is responsible for all slash commands & their events inside the bot. */ - slashCommands: ODSlashCommandManager - /**The text command manager is responsible for all text commands & their events inside the bot. */ - textCommands: ODTextCommandManager - /**The context menu manager is responsible for all context menus & their events inside the bot. */ - contextMenus: ODContextMenuManager - /**The autocomplete manager is responsible for all autocomplete events inside the bot. */ - autocompletes: ODAutocompleteManager - - constructor(debug:ODDebugger){ - this.#debug = debug - this.activity = new ODClientActivityManager(this.#debug,this) - this.slashCommands = new ODSlashCommandManager(this.#debug,this) - this.textCommands = new ODTextCommandManager(this.#debug,this) - this.contextMenus = new ODContextMenuManager(this.#debug,this) - this.autocompletes = new ODAutocompleteManager(this.#debug,this) - } - - /**Initiate the `client` variable & add the intents & partials to the bot. */ - initClient(){ - if (!this.intents.every((value) => typeof discord.GatewayIntentBits[value] != "undefined")) throw new ODSystemError("Client has non-existing intents!") - if (!this.privileges.every((value) => typeof {GuildMembers:true,MessageContent:true,Presence:true}[value] != "undefined")) throw new ODSystemError("Client has non-existing privileged intents!") - if (!this.partials.every((value) => typeof discord.Partials[value] != "undefined")) throw new ODSystemError("Client has non-existing partials!") - if (!this.permissions.every((value) => typeof discord.PermissionFlagsBits[value] != "undefined")) throw new ODSystemError("Client has non-existing partials!") - - const intents = this.intents.map((value) => discord.GatewayIntentBits[value]) - const partials = this.partials.map((value) => discord.Partials[value]) - - const oldClient = this.client - this.client = new discord.Client({intents,partials}) - - //@ts-ignore - oldClient.eventNames().forEach((event:keyof discord.ClientEvents) => { - //@ts-ignore - const callbacks = oldClient.rawListeners(event) - callbacks.forEach((cb:() => void) => { - this.client.on(event,cb) - }) - }) - - this.initiated = true - - this.#debug.debug("Created client with intents: "+this.intents.join(", ")) - this.#debug.debug("Created client with privileged intents: "+this.privileges.join(", ")) - this.#debug.debug("Created client with partials: "+this.partials.join(", ")) - this.#debug.debug("Created client with permissions: "+this.permissions.join(", ")) - } - /**Get all servers the bot is part of. */ - async getGuilds(): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - return this.client.guilds.cache.map((guild) => guild) - } - /**Check if the bot is in a specific guild */ - checkBotInGuild(guild:discord.Guild){ - return (guild.members.me) ? true : false - } - /**Check if a specific guild has all required permissions (or `Administrator`) */ - checkGuildPerms(guild:discord.Guild){ - if (!guild.members.me) throw new ODSystemError("Client isn't a member in this server!") - const perms = guild.members.me.permissions - if (perms.has("Administrator")) return true - else{ - return this.permissions.every((perm) => { - return perms.has(perm) - }) - } - } - /**Log-in with a discord auth token. Rejects returns `false` using 'softErrors' on failure. */ - login(softErrors?:boolean): Promise { - return new Promise(async (resolve,reject) => { - if (!this.initiated) reject("Client isn't initiated yet!") - if (!this.token) reject("Client doesn't have a token!") - - try { - this.client.once("clientReady",async () => { - this.ready = true - - //set slashCommandManager & contextMenuManager to client applicationCommandManager - if (!this.client.application) throw new ODSystemError("Couldn't get client application for slashCommand & contextMenu managers!") - this.slashCommands.commandManager = this.client.application.commands - this.contextMenus.commandManager = this.client.application.commands - this.autocompletes.commandManager = this.client.application.commands - - if (this.readyListener) await this.readyListener() - resolve(true) - }) - - this.#debug.debug("Actual discord.js client.login()") - await this.client.login(this.token) - this.#debug.debug("Finished discord.js client.login()") - this.loggedIn = true - }catch(err){ - if (softErrors) return resolve(false) - else if (err.message.toLowerCase().includes("used disallowed intents")){ - process.emit("uncaughtException",new ODSystemError("Used disallowed intents")) - }else if (err.message.toLowerCase().includes("tokeninvalid") || err.message.toLowerCase().includes("an invalid token was provided")){ - process.emit("uncaughtException",new ODSystemError("Invalid discord bot token provided")) - }else reject("OT Login Error: "+err) - } - }) - } - /**A simplified shortcut to get a `discord.User` :) */ - async fetchUser(id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - return await this.client.users.fetch(id) - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.Guild` :) */ - async fetchGuild(id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - return await this.client.guilds.fetch(id) - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.Channel` :) */ - async fetchChannel(id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - return await this.client.channels.fetch(id) - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.GuildBasedChannel` :) */ - async fetchGuildChannel(guildId:string|discord.Guild, id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId) - if (!guild) return null - const channel = await guild.channels.fetch(id) - return channel - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.TextChannel` :) */ - async fetchGuildTextChannel(guildId:string|discord.Guild, id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId) - if (!guild) return null - const channel = await guild.channels.fetch(id) - if (!channel || channel.type != discord.ChannelType.GuildText) return null - return channel - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.CategoryChannel` :) */ - async fetchGuildCategoryChannel(guildId:string|discord.Guild, id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId) - if (!guild) return null - const channel = await guild.channels.fetch(id) - if (!channel || channel.type != discord.ChannelType.GuildCategory) return null - return channel - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.GuildMember` :) */ - async fetchGuildMember(guildId:string|discord.Guild, id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - if (typeof id != "string") throw new ODSystemError("TEMP ERROR => ODClientManager.fetchGuildMember() => id param isn't string") - - try{ - const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId) - if (!guild) return null - return await guild.members.fetch(id) - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.Role` :) */ - async fetchGuildRole(guildId:string|discord.Guild, id:string): Promise { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - if (typeof id != "string") throw new ODSystemError("TEMP ERROR => ODClientManager.fetchGuildRole() => id param isn't string") - - try{ - const guild = (guildId instanceof discord.Guild) ? guildId : await this.fetchGuild(guildId) - if (!guild) return null - return await guild.roles.fetch(id) - }catch{ - return null - } - } - /**A simplified shortcut to get a `discord.Message` :) */ - async fetchGuildChannelMessage(guildId:string|discord.Guild, channelId:string|discord.TextChannel, id:string): Promise|null> - async fetchGuildChannelMessage(channelId:discord.TextChannel, id:string): Promise|null> - async fetchGuildChannelMessage(guildId:string|discord.Guild|discord.TextChannel, channelId:string|discord.TextChannel|string, id?:string): Promise|null> { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - if (guildId instanceof discord.TextChannel && typeof channelId == "string"){ - const channel = guildId - return await channel.messages.fetch(channelId) - }else if (!(guildId instanceof discord.TextChannel) && id){ - const channel = (channelId instanceof discord.TextChannel) ? channelId : await this.fetchGuildTextChannel(guildId,channelId) - if (!channel) return null - return await channel.messages.fetch(id) - }else return null - }catch{ - return null - } - } - /**A simplified shortcut to send a DM to a user :) */ - async sendUserDm(user:string|discord.User, message:ODMessageBuildResult): Promise> { - if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!") - if (!this.ready) throw new ODSystemError("Client isn't ready yet!") - - try{ - if (user instanceof discord.User){ - if (user.bot) return {success:false,message:null} - const channel = await user.createDM() - const msg = await channel.send(message.message) - return {success:true,message:msg} - }else{ - const newUser = await this.fetchUser(user) - if (!newUser) throw new Error() - if (newUser.bot) return {success:false,message:null} - const channel = await newUser.createDM() - const msg = await channel.send(message.message) - return {success:true,message:msg} - } - }catch{ - try{ - this.#debug.console.log("Failed to send DM to user! ","warning",[ - {key:"id",value:(user instanceof discord.User ? user.id : user)}, - {key:"message",value:message.id.value} - ]) - }catch{} - return {success:false,message:null} - } - } -} - -/**## ODClientActivityType `type` - * Possible activity types for the bot. - */ -export type ODClientActivityType = ("playing"|"listening"|"watching"|"custom"|false) -/**## ODClientActivityMode `type` - * Possible activity statuses for the bot. - */ -export type ODClientActivityMode = ("online"|"invisible"|"idle"|"dnd") - - -/**## ODClientActivityManager `class` - * This is an Open Ticket client activity manager. - * - * It's responsible for managing the client status. Here, you can set the activity & status of the bot. - * - * It also has a built-in refresh function, so the status will refresh every 10 minutes to keep it visible. - */ -export class ODClientActivityManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - /**Copy of discord.js client */ - manager: ODClientManager - /**The current status type */ - type: ODClientActivityType = false - /**The current status text */ - text: string = "" - /**The current status mode */ - mode: ODClientActivityMode = "online" - /**Additional state text */ - state: string = "" - - /**The timer responsible for refreshing the status. Stop it using `clearInterval(interval)` */ - interval?: NodeJS.Timeout - /**status refresh interval in seconds (5 minutes by default)*/ - refreshInterval: number = 600 - /**Is the status already initiated? */ - initiated: boolean = false - - constructor(debug:ODDebugger, manager:ODClientManager){ - this.#debug = debug - this.manager = manager - } - - /**Update the status. When already initiated, it can take up to 10min to see the updated status in discord. */ - setStatus(type:ODClientActivityType, text:string, mode:ODClientActivityMode, state:string, forceUpdate?:boolean){ - this.type = type - this.text = text - this.mode = mode - this.state = state - if (forceUpdate) this.#updateClientActivity(this.type,this.text) - } - - /**When initiating the status, the bot starts updating the status using `discord.js`. Returns `true` when successfull. */ - initStatus(): boolean { - if (this.initiated || !this.manager.ready) return false - this.#updateClientActivity(this.type,this.text) - this.interval = setInterval(() => { - this.#updateClientActivity(this.type,this.text) - this.#debug.debug("Client status update cycle") - },this.refreshInterval*1000) - this.initiated = true - this.#debug.debug("Client status initiated") - return true - } - - /**Update the client status */ - #updateClientActivity(type:ODClientActivityType,text:string){ - if (!this.manager.client.user) throw new ODSystemError("Couldn't set client status: client.user == undefined") - if (type == false){ - this.manager.client.user.setActivity() - return - } - this.manager.client.user.setPresence({ - activities:[{ - type:this.#getStatusTypeEnum(type), - state:this.state ? this.state : undefined, - name:text, - }], - status:this.mode - }) - } - /**Get the enum that links to the correct type */ - #getStatusTypeEnum(type:Exclude){ - if (type == "playing") return discord.ActivityType.Playing - else if (type == "listening") return discord.ActivityType.Listening - else if (type == "watching") return discord.ActivityType.Watching - else if (type == "custom") return discord.ActivityType.Custom - else return discord.ActivityType.Listening - } - /**Get the status type (for displaying the status) */ - getStatusType(): "listening "|"playing "|"watching "|"" { - if (this.type == "listening" || this.type == "playing" || this.type == "watching") return this.type+" " as "listening "|"playing "|"watching "|"" - else return "" - } -} - -/**## ODSlashCommandUniversalTranslation `interface` - * A universal template for a slash command translation. (used in names & descriptions) - * - * Why universal? Both **existing slash commands** & **unregistered templates** can be converted to this type. - */ -export interface ODSlashCommandUniversalTranslation { - /**The language code or locale of this language. */ - language:`${discord.Locale}`, - /**The translation of the name in this language. */ - value:string -} - -/**## ODSlashCommandUniversalOptionChoice `interface` - * A universal template for a slash command option choice. (used in `string` options) - * - * Why universal? Both **existing slash commands** & **unregistered templates** can be converted to this type. - */ -export interface ODSlashCommandUniversalOptionChoice { - /**The name of this choice. */ - name:string, - /**All localized names of this choice. */ - nameLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**The value of this choice. */ - value:string -} - -/**## ODSlashCommandUniversalOption `interface` - * A universal template for a slash command option. - * - * Why universal? Both **existing slash commands** & **unregistered templates** can be converted to this type. - */ -export interface ODSlashCommandUniversalOption { - /**The type of this option. */ - type:discord.ApplicationCommandOptionType, - /**The name of this option. */ - name:string, - /**All localized names of this option. */ - nameLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**The description of this option. */ - description:string, - /**All localized descriptions of this option. */ - descriptionLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**Is this option required? */ - required:boolean, - - /**Is autocomplete enabled in this option? */ - autocomplete:boolean|null, - /**Choices for this option (only when type is `string`) */ - choices:ODSlashCommandUniversalOptionChoice[], - /**A list of sub-options for this option (only when type is `subCommand` or `subCommandGroup`) */ - options:readonly ODSlashCommandUniversalOption[], - /**A list of allowed channel types for this option (only when type is `channel`) */ - channelTypes:readonly discord.ChannelType[], - /**The minimum amount required for this option (only when type is `number` or `integer`) */ - minValue:number|null, - /**The maximum amount required for this option (only when type is `number` or `integer`) */ - maxValue:number|null, - /**The minimum length required for this option (only when type is `string`) */ - minLength:number|null, - /**The maximum length required for this option (only when type is `string`) */ - maxLength:number|null -} - -/**## ODSlashCommandUniversalCommand `interface` - * A universal template for a slash command. - * - * Why universal? Both **existing slash commands** & **unregistered templates** can be converted to this type. - */ -export interface ODSlashCommandUniversalCommand { - /**The type of this command. (required => `ChatInput`) */ - type:discord.ApplicationCommandType.ChatInput, - /**The name of this command. */ - name:string, - /**All localized names of this command. */ - nameLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**The description of this command. */ - description:string, - /**All localized descriptions of this command. */ - descriptionLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**The id of the guild this command is registered in. */ - guildId:string|null, - /**Is this command for 18+ users only? */ - nsfw:boolean, - /**A list of options for this command. */ - options:readonly ODSlashCommandUniversalOption[], - /**A bitfield of the user permissions required to use this command. */ - defaultMemberPermissions:bigint, - /**Is this command available in DM? */ - dmPermission:boolean, - /**A list of contexts where you can install this command. */ - integrationTypes:readonly discord.ApplicationIntegrationType[], - /**A list of contexts where you can use this command. */ - contexts:readonly discord.InteractionContextType[] -} - -/**## ODSlashCommandBuilder `interface` - * The builder for slash commands. Here you can add options to the command. - */ -export interface ODSlashCommandBuilder extends discord.ChatInputApplicationCommandData { - /**This field is required in Open Ticket for future compatibility. */ - integrationTypes:discord.ApplicationIntegrationType[], - /**This field is required in Open Ticket for future compatibility. */ - contexts:discord.InteractionContextType[] -} - -/**## ODSlashCommandComparator `class` - * A utility class to compare existing slash commands with newly registered ones. - */ -export class ODSlashCommandComparator { - /**Convert a `discord.ApplicationCommandOptionChoiceData` to a universal Open Ticket slash command option choice object for comparison. */ - #convertOptionChoice(choice:discord.ApplicationCommandOptionChoiceData): ODSlashCommandUniversalOptionChoice { - const nameLoc = choice.nameLocalizations ?? {} - return { - name:choice.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - value:choice.value - } - } - /**Convert a `discord.ApplicationCommandOptionData` to a universal Open Ticket slash command option object for comparison. */ - #convertBuilderOption(option:discord.ApplicationCommandOptionData): ODSlashCommandUniversalOption { - const nameLoc = option.nameLocalizations ?? {} - const descLoc = option.descriptionLocalizations ?? {} - return { - type:option.type, - name:option.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - description:option.description, - descriptionLocalizations:Object.keys(descLoc).map((key) => {return {language:key as `${discord.Locale}`,value:descLoc[key]}}), - required:(option.type != discord.ApplicationCommandOptionType.SubcommandGroup && option.type != discord.ApplicationCommandOptionType.Subcommand && option.required) ? true : false, - - autocomplete:option.autocomplete ?? false, - choices:(option.type == discord.ApplicationCommandOptionType.String && !option.autocomplete && option.choices) ? option.choices.map((choice) => this.#convertOptionChoice(choice)) : [], - options:((option.type == discord.ApplicationCommandOptionType.SubcommandGroup || option.type == discord.ApplicationCommandOptionType.Subcommand) && option.options) ? option.options.map((opt) => this.#convertBuilderOption(opt)) : [], - channelTypes:(option.type == discord.ApplicationCommandOptionType.Channel && option.channelTypes) ? option.channelTypes : [], - minValue:(option.type == discord.ApplicationCommandOptionType.Number && option.minValue) ? option.minValue : null, - maxValue:(option.type == discord.ApplicationCommandOptionType.Number && option.maxValue) ? option.maxValue : null, - minLength:(option.type == discord.ApplicationCommandOptionType.String && option.minLength) ? option.minLength : null, - maxLength:(option.type == discord.ApplicationCommandOptionType.String && option.maxLength) ? option.maxLength : null - } - } - /**Convert a `discord.ApplicationCommandOption` to a universal Open Ticket slash command option object for comparison. */ - #convertCommandOption(option:discord.ApplicationCommandOption): ODSlashCommandUniversalOption { - const nameLoc = option.nameLocalizations ?? {} - const descLoc = option.descriptionLocalizations ?? {} - - return { - type:option.type, - name:option.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - description:option.description, - descriptionLocalizations:Object.keys(descLoc).map((key) => {return {language:key as `${discord.Locale}`,value:descLoc[key]}}), - required:(option.type != discord.ApplicationCommandOptionType.SubcommandGroup && option.type != discord.ApplicationCommandOptionType.Subcommand && option.required) ? true : false, - - autocomplete:option.autocomplete ?? false, - choices:(option.type == discord.ApplicationCommandOptionType.String && !option.autocomplete && option.choices) ? option.choices.map((choice) => this.#convertOptionChoice(choice)) : [], - options:((option.type == discord.ApplicationCommandOptionType.SubcommandGroup || option.type == discord.ApplicationCommandOptionType.Subcommand) && option.options) ? option.options.map((opt) => this.#convertBuilderOption(opt)) : [], - channelTypes:(option.type == discord.ApplicationCommandOptionType.Channel && option.channelTypes) ? option.channelTypes : [], - minValue:(option.type == discord.ApplicationCommandOptionType.Number && option.minValue) ? option.minValue : null, - maxValue:(option.type == discord.ApplicationCommandOptionType.Number && option.maxValue) ? option.maxValue : null, - minLength:(option.type == discord.ApplicationCommandOptionType.String && option.minLength) ? option.minLength : null, - maxLength:(option.type == discord.ApplicationCommandOptionType.String && option.maxLength) ? option.maxLength : null - } - } - /**Convert a `ODSlashCommandBuilder` to a universal Open Ticket slash command object for comparison. */ - convertBuilder(builder:ODSlashCommandBuilder,guildId:string|null): ODSlashCommandUniversalCommand|null { - if (builder.type != discord.ApplicationCommandType.ChatInput) return null //throw new ODSystemError("ODSlashCommandComparator:convertBuilder() is not supported for other types than 'ChatInput'!") - const nameLoc = builder.nameLocalizations ?? {} - const descLoc = builder.descriptionLocalizations ?? {} - return { - type:1, - name:builder.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - description:builder.description, - descriptionLocalizations:Object.keys(descLoc).map((key) => {return {language:key as `${discord.Locale}`,value:descLoc[key]}}), - guildId:guildId, - nsfw:builder.nsfw ?? false, - options:builder.options ? builder.options.map((opt) => this.#convertBuilderOption(opt)) : [], - defaultMemberPermissions:discord.PermissionsBitField.resolve(builder.defaultMemberPermissions ?? ["ViewChannel"]), - dmPermission:(builder.contexts && builder.contexts.includes(discord.InteractionContextType.BotDM)) ?? false, - integrationTypes:builder.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall], - contexts:builder.contexts ?? [] - } - } - /**Convert a `discord.ApplicationCommand` to a universal Open Ticket slash command object for comparison. */ - convertCommand(cmd:discord.ApplicationCommand): ODSlashCommandUniversalCommand|null { - if (cmd.type != discord.ApplicationCommandType.ChatInput) return null //throw new ODSystemError("ODSlashCommandComparator:convertCommand() is not supported for other types than 'ChatInput'!") - const nameLoc = cmd.nameLocalizations ?? {} - const descLoc = cmd.descriptionLocalizations ?? {} - return { - type:1, - name:cmd.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - description:cmd.description, - descriptionLocalizations:Object.keys(descLoc).map((key) => {return {language:key as `${discord.Locale}`,value:descLoc[key]}}), - guildId:cmd.guildId, - nsfw:cmd.nsfw, - options:cmd.options ? cmd.options.map((opt) => this.#convertCommandOption(opt)) : [], - defaultMemberPermissions:discord.PermissionsBitField.resolve(cmd.defaultMemberPermissions ?? ["ViewChannel"]), - dmPermission:(cmd.contexts && cmd.contexts.includes(discord.InteractionContextType.BotDM)) ? true : false, - integrationTypes:cmd.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall], - contexts:cmd.contexts ?? [] - } - } - /**Returns `true` when the 2 slash command options are the same. */ - compareOption(optA:ODSlashCommandUniversalOption,optB:ODSlashCommandUniversalOption): boolean { - if (optA.name != optB.name) return false - if (optA.description != optB.description) return false - if (optA.type != optB.type) return false - if (optA.required != optB.required) return false - if (optA.autocomplete != optB.autocomplete) return false - if (optA.minValue != optB.minValue) return false - if (optA.maxValue != optB.maxValue) return false - if (optA.minLength != optB.minLength) return false - if (optA.maxLength != optB.maxLength) return false - - //nameLocalizations - if (optA.nameLocalizations.length != optB.nameLocalizations.length) return false - if (!optA.nameLocalizations.every((nameA) => { - const nameB = optB.nameLocalizations.find((nameB) => nameB.language == nameA.language) - if (!nameB || nameA.value != nameB.value) return false - else return true - })) return false - - //descriptionLocalizations - if (optA.descriptionLocalizations.length != optB.descriptionLocalizations.length) return false - if (!optA.descriptionLocalizations.every((descA) => { - const descB = optB.descriptionLocalizations.find((descB) => descB.language == descA.language) - if (!descB || descA.value != descB.value) return false - else return true - })) return false - - //choices - if (optA.choices.length != optB.choices.length) return false - if (!optA.choices.every((choiceA,index) => { - const choiceB = optB.choices[index] - if (choiceA.name != choiceB.name) return false - if (choiceA.value != choiceB.value) return false - - //nameLocalizations - if (choiceA.nameLocalizations.length != choiceB.nameLocalizations.length) return false - if (!choiceA.nameLocalizations.every((nameA) => { - const nameB = choiceB.nameLocalizations.find((nameB) => nameB.language == nameA.language) - if (!nameB || nameA.value != nameB.value) return false - else return true - })) return false - - return true - })) return false - - //channelTypes - if (optA.channelTypes.length != optB.channelTypes.length) return false - if (!optA.channelTypes.every((typeA) => { - return optB.channelTypes.includes(typeA) - })) return false - - //options - if (optA.options.length != optB.options.length) return false - if (!optA.options.every((subOptA,index) => { - return this.compareOption(subOptA,optB.options[index]) - })) return false - - return true - } - /**Returns `true` when the 2 slash commands are the same. */ - compare(cmdA:ODSlashCommandUniversalCommand,cmdB:ODSlashCommandUniversalCommand): boolean { - if (cmdA.name != cmdB.name) return false - if (cmdA.description != cmdB.description) return false - if (cmdA.type != cmdB.type) return false - if (cmdA.nsfw != cmdB.nsfw) return false - if (cmdA.guildId != cmdB.guildId) return false - if (cmdA.dmPermission != cmdB.dmPermission) return false - if (cmdA.defaultMemberPermissions != cmdB.defaultMemberPermissions) return false - - //nameLocalizations - if (cmdA.nameLocalizations.length != cmdB.nameLocalizations.length) return false - if (!cmdA.nameLocalizations.every((nameA) => { - const nameB = cmdB.nameLocalizations.find((nameB) => nameB.language == nameA.language) - if (!nameB || nameA.value != nameB.value) return false - else return true - })) return false - - //descriptionLocalizations - if (cmdA.descriptionLocalizations.length != cmdB.descriptionLocalizations.length) return false - if (!cmdA.descriptionLocalizations.every((descA) => { - const descB = cmdB.descriptionLocalizations.find((descB) => descB.language == descA.language) - if (!descB || descA.value != descB.value) return false - else return true - })) return false - - //contexts - if (cmdA.contexts.length != cmdB.contexts.length) return false - if (!cmdA.contexts.every((contextA) => { - return cmdB.contexts.includes(contextA) - })) return false - - //integrationTypes - if (cmdA.integrationTypes.length != cmdB.integrationTypes.length) return false - if (!cmdA.integrationTypes.every((integrationA) => { - return cmdB.integrationTypes.includes(integrationA) - })) return false - - //options - if (cmdA.options.length != cmdB.options.length) return false - if (!cmdA.options.every((optA,index) => { - return this.compareOption(optA,cmdB.options[index]) - })) return false - - return true - } -} - -/**## ODSlashCommandInteractionCallback `type` - * Callback for the slash command interaction listener. - */ -export type ODSlashCommandInteractionCallback = (interaction:discord.ChatInputCommandInteraction,cmd:ODSlashCommand) => void - -/**## ODSlashCommandRegisteredResult `type` - * The result which will be returned when getting all (un)registered slash commands from the manager. - */ -export type ODSlashCommandRegisteredResult = { - /**A list of all registered commands. */ - registered:{ - /**The instance (`ODSlashCommand`) from this command. */ - instance:ODSlashCommand, - /**The (universal) slash command object/template of this command. */ - cmd:ODSlashCommandUniversalCommand, - /**Does this command require an update? */ - requiresUpdate:boolean - }[], - /**A list of all unregistered commands. */ - unregistered:{ - /**The instance (`ODSlashCommand`) from this command. */ - instance:ODSlashCommand, - /**The (universal) slash command object/template of this command. */ - cmd:null, - /**Does this command require an update? */ - requiresUpdate:true - }[], - /**A list of all unused commands (not found in `ODSlashCommandManager`). */ - unused:{ - /**The instance (`ODSlashCommand`) from this command. */ - instance:null, - /**The (universal) slash command object/template of this command. */ - cmd:ODSlashCommandUniversalCommand, - /**Does this command require an update? */ - requiresUpdate:false - }[] -} - -/**## ODSlashCommandManager `class` - * This is an Open Ticket client slash manager. - * - * It's responsible for managing all the slash commands from the client. - * - * Here, you can add & remove slash commands & the bot will do the (de)registering. - */ -export class ODSlashCommandManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - /**Refrerence to discord.js client. */ - manager: ODClientManager - /**Discord.js application commands manager. */ - commandManager: discord.ApplicationCommandManager|null - /**Collection of all interaction listeners. */ - #interactionListeners: {name:string|RegExp, callback:ODSlashCommandInteractionCallback}[] = [] - /**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */ - listenerLimit: number = 100 - /**A utility class used to compare 2 slash commands with each other. */ - comparator: ODSlashCommandComparator = new ODSlashCommandComparator() - - constructor(debug:ODDebugger, manager:ODClientManager){ - super(debug,"slash command") - this.#debug = debug - this.manager = manager - this.commandManager = (manager.client.application) ? manager.client.application.commands : null - } - - /**Get all registered & unregistered slash commands. */ - async getAllRegisteredCommands(guildId?:string): Promise { - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register slash commands!") - - const cmds = (await this.commandManager.fetch({guildId})).toJSON() - const registered: {instance:ODSlashCommand, cmd:ODSlashCommandUniversalCommand, requiresUpdate:boolean}[] = [] - const unregistered: {instance:ODSlashCommand, cmd:null, requiresUpdate:true}[] = [] - const unused: {instance:null, cmd:ODSlashCommandUniversalCommand, requiresUpdate:false}[] = [] - - await this.loopAll((instance) => { - if (guildId && instance.guildId != guildId) return - - const index = cmds.findIndex((cmd) => cmd.name == instance.name) - const cmd = cmds[index] - cmds.splice(index,1) - if (cmd){ - //command is registered (and may need to be updated) - const universalBuilder = this.comparator.convertBuilder(instance.builder,instance.guildId) - const universalCmd = this.comparator.convertCommand(cmd) - - //command is not of the type 'chatinput' - if (!universalBuilder || !universalCmd) return - - const didChange = !this.comparator.compare(universalBuilder,universalCmd) - const requiresUpdate = didChange || (instance.requiresUpdate ? instance.requiresUpdate(universalCmd) : false) - registered.push({instance,cmd:universalCmd,requiresUpdate}) - - //command is not registered - }else unregistered.push({instance,cmd:null,requiresUpdate:true}) - }) - - cmds.forEach((cmd) => { - //command does not exist in the manager (only append to unused when type == 'chatinput') - const universalCmd = this.comparator.convertCommand(cmd) - if (!universalCmd) return - unused.push({instance:null,cmd:universalCmd,requiresUpdate:false}) - }) - - return {registered,unregistered,unused} - } - /**Create all commands that are not registered yet.*/ - async createNewCommands(instances:ODSlashCommand[],progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register slash commands!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - for (const instance of instances){ - await this.createCmd(instance) - this.#debug.debug("Created new slash command",[ - {key:"id",value:instance.id.value}, - {key:"name",value:instance.name} - ]) - if (progress) progress.increase(1) - } - } - /**Update all commands that are already registered. */ - async updateExistingCommands(instances:ODSlashCommand[],progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register slash commands!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - for (const instance of instances){ - await this.createCmd(instance) - this.#debug.debug("Updated existing slash command",[{key:"id",value:instance.id.value},{key:"name",value:instance.name}]) - if (progress) progress.increase(1) - } - } - /**Remove all commands that are registered but unused by Open Ticket. */ - async removeUnusedCommands(instances:ODSlashCommandUniversalCommand[],guildId?:string,progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register slash commands!") - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register slash commands!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - const cmds = await this.commandManager.fetch({guildId}) - - for (const instance of instances){ - const cmd = cmds.find((cmd) => cmd.name == instance.name) - if (cmd){ - try { - await cmd.delete() - this.#debug.debug("Removed existing slash command",[{key:"name",value:cmd.name},{key:"guildId",value:guildId ?? "/"}]) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to delete slash command '/"+cmd.name+"'!") - } - } - if (progress) progress.increase(1) - } - } - /**Create a slash command. **(SYSTEM ONLY)** => Use `ODSlashCommandManager` for registering commands the default way! */ - async createCmd(cmd:ODSlashCommand){ - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register slash commands!") - try { - await this.commandManager.create(cmd.builder,(cmd.guildId ?? undefined)) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to register slash command '/"+cmd.name+"'!") - } - } - /**Start listening to the discord.js client `interactionCreate` event. */ - startListeningToInteractions(){ - this.manager.client.on("interactionCreate",(interaction) => { - //return when not in main server or DM - if (!this.manager.mainServer || (interaction.guild && interaction.guild.id != this.manager.mainServer.id)) return - - if (!interaction.isChatInputCommand()) return - const cmd = this.getFiltered((cmd) => cmd.name == interaction.commandName)[0] - if (!cmd) return - - this.#interactionListeners.forEach((listener) => { - if (typeof listener.name == "string" && (interaction.commandName != listener.name)) return - else if (listener.name instanceof RegExp && !listener.name.test(interaction.commandName)) return - - //this is a valid listener - listener.callback(interaction,cmd) - }) - }) - } - /**Callback on interaction from one or multiple slash commands. */ - onInteraction(commandName:string|RegExp, callback:ODSlashCommandInteractionCallback){ - this.#interactionListeners.push({ - name:commandName, - callback - }) - - if (this.#interactionListeners.length > this.listenerLimit){ - this.#debug.console.log(new ODConsoleWarningMessage("Possible slash command interaction memory leak detected!",[ - {key:"listeners",value:this.#interactionListeners.length.toString()} - ])) - } - } -} - -/**## ODSlashCommandUpdateFunction `type` - * The function responsible for updating slash commands when they already exist. - */ -export type ODSlashCommandUpdateFunction = (command:ODSlashCommandUniversalCommand) => boolean - -/**## ODSlashCommand `class` - * This is an Open Ticket slash command. - * - * When registered, you can listen for this command using the `ODCommandResponder`. The advantages of using this class for creating a slash command are: - * - automatic option parsing (even for channels, users, roles & mentions)! - * - automatic registration in discord.js - * - error reporting to the user when the bot fails to respond - * - plugins can extend this command - * - the bot won't re-register the command when it already exists (except when requested)! - * - * And more! - */ -export class ODSlashCommand extends ODManagerData { - /**The discord.js builder for this slash command. */ - builder: ODSlashCommandBuilder - /**The id of the guild this command is for. Null when not set. */ - guildId: string|null - /**Function to check if the slash command requires to be updated (when it already exists). */ - requiresUpdate: ODSlashCommandUpdateFunction|null = null - - constructor(id:ODValidId, builder:ODSlashCommandBuilder, requiresUpdate?:ODSlashCommandUpdateFunction, guildId?:string){ - super(id) - if (builder.type != discord.ApplicationCommandType.ChatInput) throw new ODSystemError("ApplicationCommandData is required to be the 'ChatInput' type!") - - this.builder = builder - this.guildId = guildId ?? null - this.requiresUpdate = requiresUpdate ?? null - } - - /**The name of this slash command. */ - get name(): string { - return this.builder.name - } - set name(name:string){ - this.builder.name = name - } -} - -/**## ODTextCommandBuilderBaseOptionType `type` - * The types available in the text command option builder. - */ -export type ODTextCommandBuilderBaseOptionType = "string"|"number"|"boolean"|"user"|"guildmember"|"role"|"mentionable"|"channel" - -/**## ODTextCommandBuilderBaseOption `interface` - * The default option builder for text commands. - */ -export interface ODTextCommandBuilderBaseOption { - /**The name of this option */ - name:string, - /**The type of this option */ - type:ODTextCommandBuilderBaseOptionType, - /**Is this option required? (optional options can only exist at the end of the command!) */ - required?:boolean -} - -/**## ODTextCommandBuilderStringOption `interface` - * The string option builder for text commands. - */ -export interface ODTextCommandBuilderStringOption extends ODTextCommandBuilderBaseOption { - type:"string", - /**Set the maximum length of this string */ - maxLength?:number, - /**Set the minimum length of this string */ - minLength?:number, - /**The string needs to match this regex or it will be invalid */ - regex?:RegExp, - /**The string needs to match one of these choices or it will be invalid */ - choices?:string[], - /**When this is the last option, allow this string to contain spaces */ - allowSpaces?:boolean -} - -/**## ODTextCommandBuilderNumberOption `interface` - * The number option builder for text commands. - */ -export interface ODTextCommandBuilderNumberOption extends ODTextCommandBuilderBaseOption { - type:"number", - /**The number can't be higher than this value */ - max?:number, - /**The number can't be lower than this value */ - min?:number, - /**Allow the number to be negative */ - allowNegative?:boolean, - /**Allow the number to be positive */ - allowPositive?:boolean, - /**Allow the number to be zero */ - allowZero?:boolean, - /**Allow a number with decimal */ - allowDecimal?:boolean -} - -/**## ODTextCommandBuilderBooleanOption `interface` - * The boolean option builder for text commands. - */ -export interface ODTextCommandBuilderBooleanOption extends ODTextCommandBuilderBaseOption { - type:"boolean", - /**The value when `true` */ - trueValue?:string, - /**The value when `false` */ - falseValue?:string -} - -/**## ODTextCommandBuilderChannelOption `interface` - * The channel option builder for text commands. - */ -export interface ODTextCommandBuilderChannelOption extends ODTextCommandBuilderBaseOption { - type:"channel", - /**When specified, only allow the following channel types */ - channelTypes?:discord.GuildChannelType[] -} - -/**## ODTextCommandBuilderRoleOption `interface` - * The role option builder for text commands. - */ -export interface ODTextCommandBuilderRoleOption extends ODTextCommandBuilderBaseOption { - type:"role" -} - -/**## ODTextCommandBuilderUserOption `interface` - * The user option builder for text commands. - */ -export interface ODTextCommandBuilderUserOption extends ODTextCommandBuilderBaseOption { - type:"user" -} - -/**## ODTextCommandBuilderGuildMemberOption `interface` - * The guild member option builder for text commands. - */ -export interface ODTextCommandBuilderGuildMemberOption extends ODTextCommandBuilderBaseOption { - type:"guildmember" -} - -/**## ODTextCommandBuilderMentionableOption `interface` - * The mentionable option builder for text commands. - */ -export interface ODTextCommandBuilderMentionableOption extends ODTextCommandBuilderBaseOption { - type:"mentionable" -} - -/**## ODTextCommandBuilderOption `type` - * The option builder for text commands. - */ -export type ODTextCommandBuilderOption = ( - ODTextCommandBuilderStringOption| - ODTextCommandBuilderBooleanOption| - ODTextCommandBuilderNumberOption| - ODTextCommandBuilderChannelOption| - ODTextCommandBuilderRoleOption| - ODTextCommandBuilderUserOption| - ODTextCommandBuilderGuildMemberOption| - ODTextCommandBuilderMentionableOption -) - -/**## ODTextCommandBuilder `interface` - * The builder for text commands. Here you can add options to the command. - */ -export interface ODTextCommandBuilder { - /**The prefix of this command */ - prefix:string, - /**The name of this command (can include spaces for subcommands) */ - name:string, - /**Is this command allowed in dm? */ - dmPermission?:boolean, - /**Is this command allowed in guilds? */ - guildPermission?:boolean, - /**When specified, only allow this command to be executed in the following guilds */ - allowedGuildIds?:string[], - /**Are bots allowed to execute this command? */ - allowBots?:boolean - /**The options for this text command (like slash commands) */ - options?:ODTextCommandBuilderOption[] -} - -/**## ODTextCommand `class` - * This is an Open Ticket text command. - * - * When registered, you can listen for this command using the `ODCommandResponder`. The advantages of using this class for creating a text command are: - * - automatic option parsing (even for channels, users, roles & mentions)! - * - automatic errors on invalid parameters - * - error reporting to the user when the bot fails to respond - * - plugins can extend this command - * - * And more! - */ -export class ODTextCommand extends ODManagerData { - /**The builder for this slash command. */ - builder: ODTextCommandBuilder - /**The name of this slash command. */ - name: string - - constructor(id:ODValidId, builder:ODTextCommandBuilder){ - super(id) - this.builder = builder - this.name = builder.name - } -} - -/**## ODTextCommandInteractionOptionBase `interface` - * The object returned for options from a text command interaction. - */ -export interface ODTextCommandInteractionOptionBase { - /**The name of this option */ - name:string, - /**The type of this option */ - type:Name, - /**The value of this option */ - value:Type -} - -/**## ODTextCommandInteractionOption `type` - * A list of types returned for options from a text command interaction. - */ -export type ODTextCommandInteractionOption = ( - ODTextCommandInteractionOptionBase<"string",string>| - ODTextCommandInteractionOptionBase<"number",number>| - ODTextCommandInteractionOptionBase<"boolean",boolean>| - ODTextCommandInteractionOptionBase<"channel",discord.GuildBasedChannel>| - ODTextCommandInteractionOptionBase<"role",discord.Role>| - ODTextCommandInteractionOptionBase<"user",discord.User>| - ODTextCommandInteractionOptionBase<"guildmember",discord.GuildMember>| - ODTextCommandInteractionOptionBase<"mentionable",discord.Role|discord.User> -) - -/**## ODTextCommandInteractionCallback `type` - * Callback for the text command interaction listener. - */ -export type ODTextCommandInteractionCallback = (msg:discord.Message, cmd:ODTextCommand, options:ODTextCommandInteractionOption[]) => void - -/**## ODTextCommandErrorBase `interface` - * The object returned from a text command error callback. - */ -export interface ODTextCommandErrorBase { - /**The type of text command error */ - type:"unknown_prefix"|"unknown_command"|"invalid_option"|"missing_option", - /**The message this error originates from */ - msg:discord.Message -} - -/**## ODTextCommandErrorUnknownPrefix `interface` - * The object returned from a text command unknown prefix error callback. - */ -export interface ODTextCommandErrorUnknownPrefix extends ODTextCommandErrorBase { - type:"unknown_prefix" -} - -/**## ODTextCommandErrorUnknownCommand `interface` - * The object returned from a text command unknown command error callback. - */ -export interface ODTextCommandErrorUnknownCommand extends ODTextCommandErrorBase { - type:"unknown_command" -} - -/**## ODTextCommandErrorInvalidOptionReason `type` - * A list of reasons for the invalid_option error to be thrown. - */ -export type ODTextCommandErrorInvalidOptionReason = ( - "boolean"| - "number_max"| - "number_min"| - "number_decimal"| - "number_negative"| - "number_positive"| - "number_zero"| - "number_invalid"| - "string_max_length"| - "string_min_length"| - "string_regex"| - "string_choice"| - "not_in_guild"| - "channel_not_found"| - "channel_type"| - "user_not_found"| - "member_not_found"| - "role_not_found"| - "mentionable_not_found" -) - -/**## ODTextCommandErrorInvalidOption `interface` - * The object returned from a text command invalid option error callback. - */ -export interface ODTextCommandErrorInvalidOption extends ODTextCommandErrorBase { - type:"invalid_option", - /**The command this error originates from */ - command:ODTextCommand, - /**The command prefix this error originates from */ - prefix:string, - /**The command name this error originates from (can include spaces for subcommands) */ - name:string, - /**The option that this error originates from */ - option:ODTextCommandBuilderOption - /**The location that this option was found */ - location:number, - /**The current value of this invalid option */ - value:string, - /**The reason for this invalid option */ - reason:ODTextCommandErrorInvalidOptionReason -} - -/**## ODTextCommandErrorMissingOption `interface` - * The object returned from a text command missing option error callback. - */ -export interface ODTextCommandErrorMissingOption extends ODTextCommandErrorBase { - type:"missing_option", - /**The command this error originates from */ - command:ODTextCommand, - /**The command prefix this error originates from */ - prefix:string, - /**The command name this error originates from (can include spaces for subcommands) */ - name:string, - /**The option that this error originates from */ - option:ODTextCommandBuilderOption - /**The location that this option was found */ - location:number -} - -/**## ODTextCommandError `type` - * A list of types returned for errors from a text command interaction. - */ -export type ODTextCommandError = ( - ODTextCommandErrorUnknownPrefix| - ODTextCommandErrorUnknownCommand| - ODTextCommandErrorInvalidOption| - ODTextCommandErrorMissingOption -) - -/**## ODTextCommandErrorCallback `type` - * Callback for the text command error listener. - */ -export type ODTextCommandErrorCallback = (error:ODTextCommandError) => void - -/**## ODTextCommandManager `class` - * This is an Open Ticket client text manager. - * - * It's responsible for managing all the text commands from the client. - * - * Here, you can add & remove text commands & the bot will do the (de)registering. - */ -export class ODTextCommandManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - /**Copy of discord.js client. */ - manager: ODClientManager - /**Collection of all interaction listeners. */ - #interactionListeners: {prefix:string, name:string|RegExp, callback:ODTextCommandInteractionCallback}[] = [] - /**Collection of all error listeners. */ - #errorListeners: ODTextCommandErrorCallback[] = [] - /**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */ - listenerLimit: number = 100 - - constructor(debug:ODDebugger, manager:ODClientManager){ - super(debug,"text command") - this.#debug = debug - this.manager = manager - } - - /*Check if a message is a registered command. */ - async #checkMessage(msg:discord.Message){ - if (this.manager.client.user && msg.author.id == this.manager.client.user.id) return false - - //filter commands for correct prefix - const validPrefixCommands: {cmd:ODTextCommand,newContent:string}[] = [] - await this.loopAll((cmd) => { - if (msg.content.startsWith(cmd.builder.prefix)) validPrefixCommands.push({ - cmd:cmd, - newContent:msg.content.substring(cmd.builder.prefix.length) - }) - }) - - //return when no command with prefix - if (validPrefixCommands.length == 0){ - this.#errorListeners.forEach((cb) => cb({ - type:"unknown_prefix", - msg:msg - })) - return false - } - - //filter commands for correct name - const validNameCommands: {cmd:ODTextCommand,newContent:string}[] = [] - validPrefixCommands.forEach((cmd) => { - if (cmd.newContent.startsWith(cmd.cmd.builder.name+" ") || cmd.newContent == cmd.cmd.builder.name) validNameCommands.push({ - cmd:cmd.cmd, - newContent:cmd.newContent.substring(cmd.cmd.builder.name.length+1) //+1 because of space after command name - }) - }) - - //return when no command with name - if (validNameCommands.length == 0){ - this.#errorListeners.forEach((cb) => cb({ - type:"unknown_command", - msg:msg - })) - return false - } - - //the final command - const command = validNameCommands[0] - const builder = command.cmd.builder - - //check additional options - if (typeof builder.allowBots != "undefined" && !builder.allowBots && msg.author.bot) return false - else if (typeof builder.dmPermission != "undefined" && !builder.dmPermission && msg.channel.type == discord.ChannelType.DM) return false - else if (typeof builder.guildPermission != "undefined" && !builder.guildPermission && msg.guild) return false - else if (typeof builder.allowedGuildIds != "undefined" && msg.guild && !builder.allowedGuildIds.includes(msg.guild.id)) return false - - //check all command options & return when incorrect - const options = await this.#checkOptions(command.cmd,command.newContent,msg) - if (!options.valid) return false - - //a command matched this message => emit event - this.#interactionListeners.forEach((listener) => { - if (typeof listener.prefix == "string" && (command.cmd.builder.prefix != listener.prefix)) return - if (typeof listener.name == "string" && (command.cmd.name.split(" ")[0] != listener.name)) return - else if (listener.name instanceof RegExp && !listener.name.test(command.cmd.name.split(" ")[0])) return - - //this is a valid listener - listener.callback(msg,command.cmd,options.data) - }) - return true - } - /**Check if all options of a command are correct. */ - async #checkOptions(cmd:ODTextCommand, newContent:string, msg:discord.Message){ - const options = cmd.builder.options - if (!options) return {valid:true,data:[]} - - let tempContent = newContent - let optionInvalid = false - const optionData: ODTextCommandInteractionOption[] = [] - - const optionError = (type:"invalid_option"|"missing_option", option:ODTextCommandBuilderOption, location:number, value?:string, reason?:ODTextCommandErrorInvalidOptionReason) => { - //ERROR INVALID - if (type == "invalid_option" && value && reason){ - this.#errorListeners.forEach((cb) => cb({ - type:"invalid_option", - msg:msg, - prefix:cmd.builder.prefix, - command:cmd, - name:cmd.builder.name, - option, - location, - value, - reason - })) - }else if (type == "missing_option"){ - this.#errorListeners.forEach((cb) => cb({ - type:"missing_option", - msg:msg, - prefix:cmd.builder.prefix, - command:cmd, - name:cmd.builder.name, - option, - location - })) - } - optionInvalid = true - } - - for (let location = 0;location < options.length;location++){ - const option = options[location] - if (optionInvalid) break - - //CHECK BOOLEAN - if (option.type == "boolean"){ - const falseValue = option.falseValue ?? "false" - const trueValue = option.trueValue ?? "true" - - if (tempContent.startsWith(falseValue+" ")){ - //FALSE VALUE - optionData.push({ - name:option.name, - type:"boolean", - value:false - }) - tempContent = tempContent.substring(falseValue.length+1) - - }else if (tempContent.startsWith(trueValue+" ")){ - //TRUE VALUE - optionData.push({ - name:option.name, - type:"boolean", - value:true - }) - tempContent = tempContent.substring(trueValue.length+1) - - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidregex = /^[^ ]+/ - const invalidRes = invalidregex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"boolean") - else optionError("missing_option",option,location) - } - - //CHECK NUMBER - }else if (option.type == "number"){ - const numRegex = /^[0-9\.\,]+/ - const res = numRegex.exec(tempContent) - if (res){ - const value = res[0].replace(/\,/g,".") - tempContent = tempContent.substring(value.length+1) - const numValue = Number(value) - - if (isNaN(numValue)){ - optionError("invalid_option",option,location,value,"number_invalid") - - }else if (typeof option.allowDecimal == "boolean" && !option.allowDecimal && (numValue % 1) !== 0){ - optionError("invalid_option",option,location,value,"number_decimal") - - }else if (typeof option.allowNegative == "boolean" && !option.allowNegative && numValue < 0){ - optionError("invalid_option",option,location,value,"number_negative") - - }else if (typeof option.allowPositive == "boolean" && !option.allowPositive && numValue > 0){ - optionError("invalid_option",option,location,value,"number_positive") - - }else if (typeof option.allowZero == "boolean" && !option.allowZero && numValue == 0){ - optionError("invalid_option",option,location,value,"number_zero") - - }else if (typeof option.max == "number" && numValue > option.max){ - optionError("invalid_option",option,location,value,"number_max") - - }else if (typeof option.min == "number" && numValue < option.min){ - optionError("invalid_option",option,location,value,"number_min") - - }else{ - //VALID NUMBER - optionData.push({ - name:option.name, - type:"number", - value:numValue - }) - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"number_invalid") - else optionError("missing_option",option,location) - } - //CHECK STRING - }else if (option.type == "string"){ - if (option.allowSpaces){ - //STRING WITH SPACES - const value = tempContent - tempContent = "" - - if (typeof option.minLength == "number" && value.length < option.minLength){ - optionError("invalid_option",option,location,value,"string_min_length") - - }else if (typeof option.maxLength == "number" && value.length > option.maxLength){ - optionError("invalid_option",option,location,value,"string_max_length") - - }else if (option.regex && !option.regex.test(value)){ - optionError("invalid_option",option,location,value,"string_regex") - - }else if (option.choices && !option.choices.includes(value)){ - optionError("invalid_option",option,location,value,"string_choice") - - }else if (option.required && value === ""){ - //REQUIRED => ERROR IF NOT EXISTING - optionError("missing_option",option,location) - - }else{ - //VALID STRING - optionData.push({ - name:option.name, - type:"string", - value - }) - } - }else{ - //STRING WITHOUT SPACES - const stringRegex = /^[^ ]+/ - const res = stringRegex.exec(tempContent) - if (res){ - const value = res[0] - tempContent = tempContent.substring(value.length+1) - - if (typeof option.minLength == "number" && value.length < option.minLength){ - optionError("invalid_option",option,location,value,"string_min_length") - - }else if (typeof option.maxLength == "number" && value.length > option.maxLength){ - optionError("invalid_option",option,location,value,"string_max_length") - - }else if (option.regex && !option.regex.test(value)){ - optionError("invalid_option",option,location,value,"string_regex") - - }else if (option.choices && !option.choices.includes(value)){ - optionError("invalid_option",option,location,value,"string_choice") - - }else{ - //VALID STRING - optionData.push({ - name:option.name, - type:"string", - value - }) - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - optionError("missing_option",option,location) - } - } - //CHECK CHANNEL - }else if (option.type == "channel"){ - const channelRegex = /^(?:<#)?([0-9]+)>?/ - const res = channelRegex.exec(tempContent) - if (res){ - const value = res[0] - tempContent = tempContent.substring(value.length+1) - const channelId = res[1] - - if (!msg.guild){ - optionError("invalid_option",option,location,value,"not_in_guild") - }else{ - try{ - const channel = await msg.guild.channels.fetch(channelId) - if (!channel){ - optionError("invalid_option",option,location,value,"channel_not_found") - - }else if (option.channelTypes && !option.channelTypes.includes(channel.type)){ - optionError("invalid_option",option,location,value,"channel_type") - - }else{ - //VALID CHANNEL - optionData.push({ - name:option.name, - type:"channel", - value:channel - }) - } - }catch{ - optionError("invalid_option",option,location,value,"channel_not_found") - } - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"channel_not_found") - else optionError("missing_option",option,location) - } - //CHECK ROLE - }else if (option.type == "role"){ - const roleRegex = /^(?:<@&)?([0-9]+)>?/ - const res = roleRegex.exec(tempContent) - if (res){ - const value = res[0] - tempContent = tempContent.substring(value.length+1) - const roleId = res[1] - - if (!msg.guild){ - optionError("invalid_option",option,location,value,"not_in_guild") - }else{ - try{ - const role = await msg.guild.roles.fetch(roleId) - if (!role){ - optionError("invalid_option",option,location,value,"role_not_found") - }else{ - //VALID ROLE - optionData.push({ - name:option.name, - type:"role", - value:role - }) - } - }catch{ - optionError("invalid_option",option,location,value,"role_not_found") - } - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"role_not_found") - else optionError("missing_option",option,location) - } - //CHECK GUILD MEMBER - }else if (option.type == "guildmember"){ - const memberRegex = /^(?:<@)?([0-9]+)>?/ - const res = memberRegex.exec(tempContent) - if (res){ - const value = res[0] - tempContent = tempContent.substring(value.length+1) - const memberId = res[1] - - if (!msg.guild){ - optionError("invalid_option",option,location,value,"not_in_guild") - }else{ - try{ - const member = await msg.guild.members.fetch(memberId) - if (!member){ - optionError("invalid_option",option,location,value,"member_not_found") - }else{ - //VALID GUILD MEMBER - optionData.push({ - name:option.name, - type:"guildmember", - value:member - }) - } - }catch{ - optionError("invalid_option",option,location,value,"member_not_found") - } - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"member_not_found") - else optionError("missing_option",option,location) - } - //CHECK USER - }else if (option.type == "user"){ - const userRegex = /^(?:<@)?([0-9]+)>?/ - const res = userRegex.exec(tempContent) - if (res){ - const value = res[0] - tempContent = tempContent.substring(value.length+1) - const userId = res[1] - - try{ - const user = await this.manager.client.users.fetch(userId) - if (!user){ - optionError("invalid_option",option,location,value,"user_not_found") - }else{ - //VALID USER - optionData.push({ - name:option.name, - type:"user", - value:user - }) - } - }catch{ - optionError("invalid_option",option,location,value,"user_not_found") - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"user_not_found") - else optionError("missing_option",option,location) - } - //CHECK MENTIONABLE - }else if (option.type == "mentionable"){ - const mentionableRegex = /^<(@&?)([0-9]+)>/ - const res = mentionableRegex.exec(tempContent) - if (res){ - const value = res[0] - const type = (res[1] == "@&") ? "role" : "user" - tempContent = tempContent.substring(value.length+1) - const mentionableId = res[2] - - if (!msg.guild){ - optionError("invalid_option",option,location,value,"not_in_guild") - }else if (type == "role"){ - try { - const role = await msg.guild.roles.fetch(mentionableId) - if (!role){ - optionError("invalid_option",option,location,value,"mentionable_not_found") - }else{ - //VALID ROLE - optionData.push({ - name:option.name, - type:"mentionable", - value:role - }) - } - }catch{ - optionError("invalid_option",option,location,value,"mentionable_not_found") - } - }else if (type == "user"){ - try{ - const user = await this.manager.client.users.fetch(mentionableId) - if (!user){ - optionError("invalid_option",option,location,value,"mentionable_not_found") - }else{ - //VALID USER - optionData.push({ - name:option.name, - type:"mentionable", - value:user - }) - } - }catch{ - optionError("invalid_option",option,location,value,"mentionable_not_found") - } - } - }else if (option.required){ - //REQUIRED => ERROR IF NOT EXISTING - const invalidRegex = /^[^ ]+/ - const invalidRes = invalidRegex.exec(tempContent) - if (invalidRes) optionError("invalid_option",option,location,invalidRes[0],"mentionable_not_found") - else optionError("missing_option",option,location) - } - } - } - return {valid:!optionInvalid,data:optionData} - } - /**Start listening to the discord.js client `messageCreate` event. */ - startListeningToInteractions(){ - this.manager.client.on("messageCreate",(msg) => { - //return when not in main server or DM - if (!this.manager.mainServer || (msg.guild && msg.guild.id != this.manager.mainServer.id)) return - this.#checkMessage(msg) - }) - } - /**Check if optional values are only present at the end of the command. */ - #checkBuilderOptions(builder:ODTextCommandBuilder): {valid:boolean,reason:"required_after_optional"|"allowspaces_not_last"|null} { - let optionalVisited = false - let valid = true - let reason: "required_after_optional"|"allowspaces_not_last"|null = null - if (!builder.options) return {valid:true,reason:null} - builder.options.forEach((opt,index,list) => { - if (!opt.required) optionalVisited = true - if (optionalVisited && opt.required){ - valid = false - reason = "required_after_optional" - } - - if (opt.type == "string" && opt.allowSpaces && ((index+1) != list.length)){ - valid = false - reason = "allowspaces_not_last" - } - }) - - return {valid,reason} - } - /**Callback on interaction from one of the registered text commands */ - onInteraction(commandPrefix:string,commandName:string|RegExp, callback:ODTextCommandInteractionCallback){ - this.#interactionListeners.push({ - prefix:commandPrefix, - name:commandName, - callback - }) - - if (this.#interactionListeners.length > this.listenerLimit){ - this.#debug.console.log(new ODConsoleWarningMessage("Possible text command interaction memory leak detected!",[ - {key:"listeners",value:this.#interactionListeners.length.toString()} - ])) - } - } - /**Callback on error from all the registered text commands */ - onError(callback:ODTextCommandErrorCallback){ - this.#errorListeners.push(callback) - } - - add(data:ODTextCommand, overwrite?:boolean): boolean { - const checkResult = this.#checkBuilderOptions(data.builder) - if (!checkResult.valid && checkResult.reason == "required_after_optional") throw new ODSystemError("Invalid text command '"+data.id.value+"' => optional options are only allowed at the end of a command!") - else if (!checkResult.valid && checkResult.reason == "allowspaces_not_last") throw new ODSystemError("Invalid text command '"+data.id.value+"' => string option with 'allowSpaces' is only allowed at the end of a command!") - else return super.add(data,overwrite) - } -} - -/**## ODContextMenuUniversalMenu `interface` - * A universal template for a context menu. - * - * Why universal? Both **existing context menus** & **unregistered templates** can be converted to this type. - */ -export interface ODContextMenuUniversalMenu { - /**The type of this context menu. (required => `Message`|`User`) */ - type:discord.ApplicationCommandType.Message|discord.ApplicationCommandType.User, - /**The name of this context menu. */ - name:string, - /**All localized names of this context menu. */ - nameLocalizations:readonly ODSlashCommandUniversalTranslation[], - /**The id of the guild this context menu is registered in. */ - guildId:string|null, - /**Is this context menu for 18+ users only? */ - nsfw:boolean, - /**A bitfield of the user permissions required to use this context menu. */ - defaultMemberPermissions:bigint, - /**Is this context menu available in DM? */ - dmPermission:boolean, - /**A list of contexts where you can install this context menu. */ - integrationTypes:readonly discord.ApplicationIntegrationType[], - /**A list of contexts where you can use this context menu. */ - contexts:readonly discord.InteractionContextType[] -} - -/**## ODContextMenuBuilderMessage `interface` - * The builder for message context menus. - */ -export interface ODContextMenuBuilderMessage extends discord.MessageApplicationCommandData { - /**This field is required in Open Ticket for future compatibility. */ - integrationTypes:discord.ApplicationIntegrationType[], - /**This field is required in Open Ticket for future compatibility. */ - contexts:discord.InteractionContextType[] -} - -/**## ODContextMenuBuilderUser `interface` - * The builder for user context menus. - */ -export interface ODContextMenuBuilderUser extends discord.UserApplicationCommandData { - /**This field is required in Open Ticket for future compatibility. */ - integrationTypes:discord.ApplicationIntegrationType[], - /**This field is required in Open Ticket for future compatibility. */ - contexts:discord.InteractionContextType[] -} - -/**## ODContextMenuBuilderUser `interface` - * The builder for context menus. - */ -export type ODContextMenuBuilder = (ODContextMenuBuilderMessage|ODContextMenuBuilderUser) - -/**## ODContextMenuComparator `class` - * A utility class to compare existing context menu's with newly registered ones. - */ -export class ODContextMenuComparator { - /**Convert a `ODContextMenuBuilder` to a universal Open Ticket context menu object for comparison. */ - convertBuilder(builder:ODContextMenuBuilder,guildId:string|null): ODContextMenuUniversalMenu|null { - if (builder.type != discord.ApplicationCommandType.Message && builder.type != discord.ApplicationCommandType.User) return null - const nameLoc = builder.nameLocalizations ?? {} - - return { - type:builder.type, - name:builder.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - guildId:guildId, - nsfw:builder.nsfw ?? false, - defaultMemberPermissions:discord.PermissionsBitField.resolve(builder.defaultMemberPermissions ?? ["ViewChannel"]), - dmPermission:(builder.contexts && builder.contexts.includes(discord.InteractionContextType.BotDM)) ?? false, - integrationTypes:builder.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall], - contexts:builder.contexts ?? [] - } - } - /**Convert a `discord.ApplicationCommand` to a universal Open Ticket context menu object for comparison. */ - convertMenu(cmd:discord.ApplicationCommand): ODContextMenuUniversalMenu|null { - if (cmd.type != discord.ApplicationCommandType.Message && cmd.type != discord.ApplicationCommandType.User) return null - const nameLoc = cmd.nameLocalizations ?? {} - - return { - type:cmd.type, - name:cmd.name, - nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}), - guildId:cmd.guildId, - nsfw:cmd.nsfw, - defaultMemberPermissions:discord.PermissionsBitField.resolve(cmd.defaultMemberPermissions ?? ["ViewChannel"]), - dmPermission:(cmd.contexts && cmd.contexts.includes(discord.InteractionContextType.BotDM)) ? true : false, - integrationTypes:cmd.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall], - contexts:cmd.contexts ?? [] - } - } - /**Returns `true` when the 2 context menus are the same. */ - compare(ctxA:ODContextMenuUniversalMenu,ctxB:ODContextMenuUniversalMenu): boolean { - if (ctxA.name != ctxB.name) return false - if (ctxA.type != ctxB.type) return false - if (ctxA.nsfw != ctxB.nsfw) return false - if (ctxA.guildId != ctxB.guildId) return false - if (ctxA.dmPermission != ctxB.dmPermission) return false - if (ctxA.defaultMemberPermissions != ctxB.defaultMemberPermissions) return false - - //nameLocalizations - if (ctxA.nameLocalizations.length != ctxB.nameLocalizations.length) return false - if (!ctxA.nameLocalizations.every((nameA) => { - const nameB = ctxB.nameLocalizations.find((nameB) => nameB.language == nameA.language) - if (!nameB || nameA.value != nameB.value) return false - else return true - })) return false - - //contexts - if (ctxA.contexts.length != ctxB.contexts.length) return false - if (!ctxA.contexts.every((contextA) => { - return ctxB.contexts.includes(contextA) - })) return false - - //integrationTypes - if (ctxA.integrationTypes.length != ctxB.integrationTypes.length) return false - if (!ctxA.integrationTypes.every((integrationA) => { - return ctxB.integrationTypes.includes(integrationA) - })) return false - - return true - } -} - -/**## ODContextMenuInteractionCallback `type` - * Callback for the context menu interaction listener. - */ -export type ODContextMenuInteractionCallback = (interaction:discord.ContextMenuCommandInteraction,cmd:ODContextMenu) => void - -/**## ODContextMenuRegisteredResult `type` - * The result which will be returned when getting all (un)registered user context menu's from the manager. - */ -export type ODContextMenuRegisteredResult = { - /**A list of all registered context menus. */ - registered:{ - /**The instance (`ODContextMenu`) from this context menu. */ - instance:ODContextMenu, - /**The universal object/template/builder of this context menu. */ - menu:ODContextMenuUniversalMenu, - /**Does this context menu require an update? */ - requiresUpdate:boolean - }[], - /**A list of all unregistered context menus. */ - unregistered:{ - /**The instance (`ODContextMenu`) from this context menu. */ - instance:ODContextMenu, - /**The universal object/template/builder of this context menu. */ - menu:null, - /**Does this context menu require an update? */ - requiresUpdate:true - }[], - /**A list of all unused context menus (not found in `ODContextMenuManager`). */ - unused:{ - /**The instance (`ODContextMenu`) from this context menu. */ - instance:null, - /**The universal object/template/builder of this context menu. */ - menu:ODContextMenuUniversalMenu, - /**Does this context menu require an update? */ - requiresUpdate:false - }[] -} - -/**## ODContextMenuManager `class` - * This is an Open Ticket client context menu manager. - * - * It's responsible for managing all the context interactions from the client. - * - * Here, you can add & remove context interactions & the bot will do the (de)registering. - */ -export class ODContextMenuManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - /**Refrerence to discord.js client. */ - manager: ODClientManager - /**Discord.js application commands manager. */ - commandManager: discord.ApplicationCommandManager|null - /**Collection of all interaction listeners. */ - #interactionListeners: {name:string|RegExp, callback:ODContextMenuInteractionCallback}[] = [] - /**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */ - listenerLimit: number = 100 - /**A utility class used to compare 2 context menus with each other. */ - comparator: ODContextMenuComparator = new ODContextMenuComparator() - - constructor(debug:ODDebugger, manager:ODClientManager){ - super(debug,"context menu") - this.#debug = debug - this.manager = manager - this.commandManager = (manager.client.application) ? manager.client.application.commands : null - } - - /**Get all registered & unregistered message context menu commands. */ - async getAllRegisteredMenus(guildId?:string): Promise { - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register message context menus!") - - const menus = (await this.commandManager.fetch({guildId})).toJSON() - const registered: {instance:ODContextMenu, menu:ODContextMenuUniversalMenu, requiresUpdate:boolean}[] = [] - const unregistered: {instance:ODContextMenu, menu:null, requiresUpdate:true}[] = [] - const unused: {instance:null, menu:ODContextMenuUniversalMenu, requiresUpdate:false}[] = [] - - await this.loopAll((instance) => { - if (guildId && instance.guildId != guildId) return - - const index = menus.findIndex((menu) => menu.name == instance.name) - const menu = menus[index] - menus.splice(index,1) - if (menu){ - //menu is registered (and may need to be updated) - const universalBuilder = this.comparator.convertBuilder(instance.builder,instance.guildId) - const universalMenu = this.comparator.convertMenu(menu) - - //menu is not of the type 'message'|'user' - if (!universalBuilder || !universalMenu) return - - const didChange = !this.comparator.compare(universalBuilder,universalMenu) - const requiresUpdate = didChange || (instance.requiresUpdate ? instance.requiresUpdate(universalMenu) : false) - registered.push({instance,menu:universalMenu,requiresUpdate}) - - //menu is not registered - }else unregistered.push({instance,menu:null,requiresUpdate:true}) - }) - - menus.forEach((menu) => { - //menu does not exist in the manager (only append to unused when type == 'message'|'user') - const universalCmd = this.comparator.convertMenu(menu) - if (!universalCmd) return - unused.push({instance:null,menu:universalCmd,requiresUpdate:false}) - }) - - return {registered,unregistered,unused} - } - /**Create all context menus that are not registered yet.*/ - async createNewMenus(instances:ODContextMenu[],progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register context menus!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - for (const instance of instances){ - await this.createMenu(instance) - this.#debug.debug("Created new context menu",[ - {key:"id",value:instance.id.value}, - {key:"name",value:instance.name}, - {key:"type",value:(instance.builder.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context"} - ]) - if (progress) progress.increase(1) - } - } - /**Update all context menus that are already registered. */ - async updateExistingMenus(instances:ODContextMenu[],progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register context menus!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - for (const instance of instances){ - await this.createMenu(instance) - this.#debug.debug("Updated existing context menu",[ - {key:"id",value:instance.id.value}, - {key:"name",value:instance.name}, - {key:"type",value:(instance.builder.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context"} - ]) - if (progress) progress.increase(1) - } - } - /**Remove all context menus that are registered but unused by Open Ticket. */ - async removeUnusedMenus(instances:ODContextMenuUniversalMenu[],guildId?:string,progress?:ODManualProgressBar){ - if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register context menus!") - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register context menus!") - if (instances.length > 0 && progress){ - progress.max = instances.length - progress.start() - } - - const menus = await this.commandManager.fetch({guildId}) - - for (const instance of instances){ - const menu = menus.find((menu) => menu.name == instance.name) - if (menu){ - try { - await menu.delete() - this.#debug.debug("Removed existing context menu",[ - {key:"name",value:menu.name}, - {key:"guildId",value:guildId ?? "/"}, - {key:"type",value:(instance.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context"} - ]) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to delete context menu '"+menu.name+"'!") - } - } - if (progress) progress.increase(1) - } - } - /**Create a context menu. **(SYSTEM ONLY)** => Use `ODContextMenuManager` for registering context menu's the default way! */ - async createMenu(menu:ODContextMenu){ - if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register context menu's!") - try { - await this.commandManager.create(menu.builder,(menu.guildId ?? undefined)) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Failed to register context menu '"+menu.name+"'!") - } - } - /**Start listening to the discord.js client `interactionCreate` event. */ - startListeningToInteractions(){ - this.manager.client.on("interactionCreate",(interaction) => { - //return when not in main server or DM - if (!this.manager.mainServer || (interaction.guild && interaction.guild.id != this.manager.mainServer.id)) return - - if (!interaction.isContextMenuCommand()) return - const menu = this.getFiltered((menu) => menu.name == interaction.commandName)[0] - if (!menu) return - - this.#interactionListeners.forEach((listener) => { - if (typeof listener.name == "string" && (interaction.commandName != listener.name)) return - else if (listener.name instanceof RegExp && !listener.name.test(interaction.commandName)) return - - //this is a valid listener - listener.callback(interaction,menu) - }) - }) - } - /**Callback on interaction from one or multiple context menu's. */ - onInteraction(menuName:string|RegExp, callback:ODContextMenuInteractionCallback){ - this.#interactionListeners.push({ - name:menuName, - callback - }) - - if (this.#interactionListeners.length > this.listenerLimit){ - this.#debug.console.log("Possible context menu interaction memory leak detected!","warning",[ - {key:"listeners",value:this.#interactionListeners.length.toString()} - ]) - } - } -} - -/**## ODContextMenuUpdateFunction `type` - * The function responsible for updating context menu's when they already exist. - */ -export type ODContextMenuUpdateFunction = (menu:ODContextMenuUniversalMenu) => boolean - -/**## ODContextMenu `class` - * This is an Open Ticket context menu. - * - * When registered, you can listen for this context menu using the `ODContextResponder`. The advantages of using this class for creating a context menu are: - * - automatic registration in discord.js - * - error reporting to the user when the bot fails to respond - * - plugins can extend this context menu - * - the bot won't re-register the context menu when it already exists (except when requested)! - * - * And more! - */ -export class ODContextMenu extends ODManagerData { - /**The discord.js builder for this context menu. */ - builder: ODContextMenuBuilder - /**The id of the guild this context menu is for. `null` when not set. */ - guildId: string|null - /**Function to check if the context menu requires to be updated (when it already exists). */ - requiresUpdate: ODContextMenuUpdateFunction|null = null - - constructor(id:ODValidId, builder:ODContextMenuBuilder, requiresUpdate?:ODContextMenuUpdateFunction, guildId?:string){ - super(id) - if (builder.type != discord.ApplicationCommandType.Message && builder.type != discord.ApplicationCommandType.User) throw new ODSystemError("ApplicationCommandData is required to be the 'Message'|'User' type!") - - this.builder = builder - this.guildId = guildId ?? null - this.requiresUpdate = requiresUpdate ?? null - } - - /**The name of this context menu. */ - get name(): string { - return this.builder.name - } - set name(name:string){ - this.builder.name = name - } -} - -/**## ODAutocompleteInteractionCallback `type` - * Callback for the autocomplete interaction listener. - */ -export type ODAutocompleteInteractionCallback = (interaction:discord.AutocompleteInteraction) => void - -/**## ODAutocompleteManager `class` - * This is an Open Ticket client autocomplete interaction manager. - * - * It's responsible for managing all the autocomplete interactions from the client. - */ -export class ODAutocompleteManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - /**Refrerence to discord.js client. */ - manager: ODClientManager - /**Discord.js application commands manager. */ - commandManager: discord.ApplicationCommandManager|null - /**Collection of all interaction listeners. */ - #interactionListeners: {cmdName:string|RegExp, optName:string|RegExp, callback:ODAutocompleteInteractionCallback}[] = [] - /**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */ - listenerLimit: number = 100 - - constructor(debug:ODDebugger, manager:ODClientManager){ - this.#debug = debug - this.manager = manager - this.commandManager = (manager.client.application) ? manager.client.application.commands : null - } - - /**Start listening to the discord.js client `interactionCreate` event. */ - startListeningToInteractions(){ - this.manager.client.on("interactionCreate",(interaction) => { - //return when not in main server or DM - if (!this.manager.mainServer || (interaction.guild && interaction.guild.id != this.manager.mainServer.id)) return - - if (!interaction.isAutocomplete()) return - this.#interactionListeners.forEach((listener) => { - - if (typeof listener.cmdName == "string" && (interaction.commandName != listener.cmdName)) return - else if (listener.cmdName instanceof RegExp && !listener.cmdName.test(interaction.commandName)) return - if (typeof listener.optName == "string" && (interaction.options.getFocused(true).name != listener.optName)) return - else if (listener.optName instanceof RegExp && !listener.optName.test(interaction.options.getFocused(true).name)) return - - //this is a valid listener - listener.callback(interaction) - }) - }) - } - /**Callback on interaction from one or multiple autocompletes. */ - onInteraction(cmdName:string|RegExp,optName:string|RegExp,callback:ODAutocompleteInteractionCallback){ - this.#interactionListeners.push({ - cmdName,optName,callback - }) - - if (this.#interactionListeners.length > this.listenerLimit){ - this.#debug.console.log("Possible autocomplete interaction memory leak detected!","warning",[ - {key:"listeners",value:this.#interactionListeners.length.toString()} - ]) - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/code.ts b/src/core/api/modules/code.ts deleted file mode 100644 index a655b46..0000000 --- a/src/core/api/modules/code.ts +++ /dev/null @@ -1,58 +0,0 @@ -/////////////////////////////////////// -//CODE MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODDebugger } from "./console" - - -/**## ODCode `class` - * This is an Open Ticket code runner. - * - * Using this, you're able to execute a function just before the startup screen. (90% of the code is already loaded) - * You can also specify a priority to change the execution order. - * In Open Ticket, this is used for the following processes: - * - Autoclose/delete - * - Database syncronisation (with tickets, stats & used options) - * - Panel auto-update - * - Database Garbage Collection (removing tickets that don't exist anymore) - * - And more! - */ -export class ODCode extends ODManagerData { - /**The priority of this code */ - priority: number - /**The main function of this code */ - func: () => void|Promise - - constructor(id:ODValidId, priority:number, func:() => void|Promise){ - super(id) - this.priority = priority - this.func = func - } -} - -/**## ODCodeManager `class` - * This is an Open Ticket code manager. - * - * It manages & executes `ODCode`'s in the correct order. - * - * Use this to register a function/code which executes just before the startup screen. (90% is already loaded) - */ -export class ODCodeManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"code") - } - - /**Execute all `ODCode` functions in order of their priority (high to low). */ - async execute(){ - const derefArray = [...this.getAll()] - const workers = derefArray.sort((a,b) => b.priority-a.priority) - - for (const worker of workers){ - try { - await worker.func() - }catch(err){ - process.emit("uncaughtException",err) - } - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/config.ts b/src/core/api/modules/config.ts deleted file mode 100644 index c59b11a..0000000 --- a/src/core/api/modules/config.ts +++ /dev/null @@ -1,159 +0,0 @@ -/////////////////////////////////////// -//CONFIG MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODPromiseVoid, ODSystemError, ODValidId } from "./base" -import nodepath from "path" -import { ODDebugger } from "./console" -import fs from "fs" -import * as fjs from "formatted-json-stringify" - -/**## ODConfigManager `class` - * This is an Open Ticket config manager. - * - * It manages all config files in the bot and allows plugins to access config files from Open Ticket & other plugins! - * - * You can use this class to get/change/add a config file (`ODConfig`) in your plugin! - */ -export class ODConfigManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - - constructor(debug:ODDebugger){ - super(debug,"config") - this.#debug = debug - } - add(data:ODConfig|ODConfig[],overwrite?:boolean): boolean { - if (Array.isArray(data)) data.forEach((d) => d.useDebug(this.#debug)) - else data.useDebug(this.#debug) - return super.add(data,overwrite) - } - /**Init all config files. */ - async init(){ - for (const config of this.getAll()){ - try{ - await config.init() - }catch(err){ - process.emit("uncaughtException",new ODSystemError(err)) - } - } - } -} - -/**## ODConfig `class` - * This is an Open Ticket config helper. - * This class doesn't do anything at all, it just gives a template & basic methods for a config. Use `ODJsonConfig` instead! - * - * You can use this class if you want to create your own config implementation (e.g. `yml`, `xml`,...)! - */ -export class ODConfig extends ODManagerData { - /**The name of the file with extension. */ - file: string = "" - /**The path to the file relative to the main directory. */ - path: string = "" - /**An object/array of the entire config file! Variables inside it can be edited while the bot is running! */ - data: any - /**Is this config already initiated? */ - initiated: boolean = false - /**An array of listeners to run when the config gets reloaded. These are not executed on the initial loading. */ - protected reloadListeners: Function[] = [] - /**Alias to Open Ticket debugger. */ - protected debug: ODDebugger|null = null - - constructor(id:ODValidId, data:any){ - super(id) - this.data = data - } - - /**Use the Open Ticket debugger for logs. */ - useDebug(debug:ODDebugger|null){ - this.debug = debug - } - /**Init the config. */ - init(): ODPromiseVoid { - this.initiated = true - if (this.debug) this.debug.debug("Initiated config '"+this.file+"' in ODConfigManager.",[{key:"id",value:this.id.value}]) - //please implement this feature in your own config extension & extend this function. - } - /**Reload the config. Be aware that this doesn't update the config data everywhere in the bot! */ - reload(): ODPromiseVoid { - if (this.debug) this.debug.debug("Reloaded config '"+this.file+"' in ODConfigManager.",[{key:"id",value:this.id.value}]) - //please implement this feature in your own config extension & extend this function. - } - /**Save the edited config to the filesystem. This is used by the Interactive Setup CLI. It's not recommended to use this while the bot is running. */ - save(): ODPromiseVoid { - if (this.debug) this.debug.debug("Saved config '"+this.file+"' in ODConfigManager.",[{key:"id",value:this.id.value}]) - //please implement this feature in your own config extension & extend this function. - } - /**Listen for a reload of this JSON file! */ - onReload(cb:Function){ - this.reloadListeners.push(cb) - } - /**Remove all reload listeners. Not recommended! */ - removeAllReloadListeners(){ - this.reloadListeners = [] - } -} - -/**## ODJsonConfig `class` - * This is an Open Ticket JSON config. - * You can use this class to get & edit variables from the config files or to create your own JSON config! - * @example - * //create a config from: ./config/test.json with the id "some-config" - * const config = new api.ODJsonConfig("some-config","test.json") - * - * //create a config with custom dir: ./plugins/testplugin/test.json - * const config = new api.ODJsonConfig("plugin-config","test.json","./plugins/testplugin/") - */ -export class ODJsonConfig extends ODConfig { - formatter: fjs.custom.BaseFormatter - - constructor(id:ODValidId, file:string, customPath?:string, formatter?:fjs.custom.BaseFormatter){ - super(id,{}) - this.file = (file.endsWith(".json")) ? file : file+".json" - this.path = customPath ? nodepath.join("./",customPath,this.file) : nodepath.join("./config/",this.file) - this.formatter = formatter ?? new fjs.DefaultFormatter(null,true," ") - } - - /**Init the config. */ - init(): ODPromiseVoid { - if (!fs.existsSync(this.path)) throw new ODSystemError("Unable to parse config \""+nodepath.join("./",this.path)+"\", the file doesn't exist!") - try{ - this.data = JSON.parse(fs.readFileSync(this.path).toString()) - super.init() - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Unable to parse config \""+nodepath.join("./",this.path)+"\"!") - } - } - /**Reload the config. Be aware that this doesn't update the config data everywhere in the bot! */ - reload(){ - if (!this.initiated) throw new ODSystemError("Unable to reload config \""+nodepath.join("./",this.path)+"\", the file hasn't been initiated yet!") - if (!fs.existsSync(this.path)) throw new ODSystemError("Unable to reload config \""+nodepath.join("./",this.path)+"\", the file doesn't exist!") - try{ - this.data = JSON.parse(fs.readFileSync(this.path).toString()) - super.reload() - this.reloadListeners.forEach((cb) => { - try{ - cb() - }catch(err){ - process.emit("uncaughtException",err) - } - }) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Unable to reload config \""+nodepath.join("./",this.path)+"\"!") - } - } - /**Save the edited config to the filesystem. This is used by the Interactive Setup CLI. It's not recommended to use this while the bot is running. */ - save(): ODPromiseVoid { - if (!this.initiated) throw new ODSystemError("Unable to save config \""+nodepath.join("./",this.path)+"\", the file hasn't been initiated yet!") - try{ - const contents = this.formatter.stringify(this.data) - fs.writeFileSync(this.path,contents) - super.save() - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Unable to save config \""+nodepath.join("./",this.path)+"\"!") - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/console.ts b/src/core/api/modules/console.ts deleted file mode 100644 index f34399c..0000000 --- a/src/core/api/modules/console.ts +++ /dev/null @@ -1,665 +0,0 @@ -/////////////////////////////////////// -//CONSOLE MODULE -/////////////////////////////////////// -import { ODHTTPGetRequest, ODVersion, ODSystemError, ODPluginError, ODManager, ODManagerData, ODValidId } from "./base" -import { ODMain } from "../main" -import nodepath from "path" -import fs from "fs" -import ansis from "ansis" - -/**## ODValidConsoleColor `type` - * This is a collection of all the supported console colors within Open Ticket. - */ -export type ODValidConsoleColor = "white"|"red"|"yellow"|"green"|"blue"|"gray"|"cyan"|"magenta" - -/**## ODConsoleMessageParam `type` - * This interface contains all data required for a console log parameter within Open Ticket. - */ -export interface ODConsoleMessageParam { - /**The key of this parameter. */ - key:string, - /**The value of this parameter. */ - value:string, - /**When enabled, this parameter will only be shown in the debug file. */ - hidden?:boolean -} - -/**## ODConsoleMessage `class` - * This is an Open Ticket console message. - * - * It is used to create beautiful & styled logs in the console with a prefix, message & parameters. - * It also has full color support using `ansis` and parameters are parsed for you! - */ -export class ODConsoleMessage { - /**The main message sent in the console */ - message: string - /**An array of all the parameters in this message */ - params: ODConsoleMessageParam[] - /**The prefix of this message (!uppercase recommended!) */ - prefix: string - /**The color of the prefix of this message */ - color: ODValidConsoleColor - - constructor(message:string, prefix:string, color:ODValidConsoleColor, params?:ODConsoleMessageParam[]){ - this.message = message - this.params = params ? params : [] - this.prefix = prefix - - if (["white","red","yellow","green","blue","gray","cyan","magenta"].includes(color)){ - this.color = color - }else{ - this.color = "white" - } - } - /**Render this message to the console using `console.log`! Returns `false` when something went wrong. */ - render(){ - try { - const prefixcolor = ansis[this.color] - - const paramsstring = " "+this.createParamsString("gray") - const message = prefixcolor("["+this.prefix+"] ")+this.message - - console.log(message+paramsstring) - return true - }catch{ - return false - } - } - /**Create a more-detailed, non-colored version of this message to store it in the `otdebug.txt` file! */ - toDebugString(){ - const pstrings: string[] = [] - this.params.forEach((p) => { - pstrings.push(p.key+": "+p.value) - }) - const pstring = (pstrings.length > 0) ? " ("+pstrings.join(", ")+")" : "" - const date = new Date() - const dstring = `${date.getDate()}/${date.getMonth()+1}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}` - return `[${dstring} ${this.prefix}] ${this.message}${pstring}` - } - /**Render the parameters of this message in a specific color. */ - createParamsString(color:ODValidConsoleColor){ - let validcolor: ODValidConsoleColor = "white" - if (["white","red","yellow","green","blue","gray","cyan","magenta"].includes(color)){ - validcolor = color - } - - const pstrings: string[] = [] - this.params.forEach((p) => { - if (!p.hidden) pstrings.push(p.key+": "+p.value) - }) - - return (pstrings.length > 0) ? ansis[validcolor](" ("+pstrings.join(", ")+")") : "" - } - /**Set the message */ - setMessage(message:string){ - this.message = message - return this - } - /**Set the params */ - setParams(params:ODConsoleMessageParam[]){ - this.params = params - return this - } - /**Set the prefix */ - setPrefix(prefix:string){ - this.prefix = prefix - return this - } - /**Set the prefix color */ - setColor(color:ODValidConsoleColor){ - if (["white","red","yellow","green","blue","gray","cyan","magenta"].includes(color)){ - this.color = color - }else{ - this.color = "white" - } - return this - } -} - -/**## ODConsoleInfoMessage `class` - * This is an Open Ticket console info message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "INFO" messages! - */ -export class ODConsoleInfoMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"INFO","blue",params) - } -} - -/**## ODConsoleSystemMessage `class` - * This is an Open Ticket console system message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "SYSTEM" messages! - */ -export class ODConsoleSystemMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"SYSTEM","green",params) - } -} - -/**## ODConsolePluginMessage `class` - * This is an Open Ticket console plugin message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "PLUGIN" messages! - */ -export class ODConsolePluginMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"PLUGIN","magenta",params) - } -} - -/**## ODConsoleDebugMessage `class` - * This is an Open Ticket console debug message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "DEBUG" messages! - */ -export class ODConsoleDebugMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"DEBUG","cyan",params) - } -} - -/**## ODConsoleWarningMessage `class` - * This is an Open Ticket console warning message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "WARNING" messages! - */ -export class ODConsoleWarningMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"WARNING","yellow",params) - } -} - -/**## ODConsoleErrorMessage `class` - * This is an Open Ticket console error message. - * - * It is the same as a normal `ODConsoleMessage`, but it has a predefined prefix & color scheme for the "ERROR" messages! - */ -export class ODConsoleErrorMessage extends ODConsoleMessage { - constructor(message:string,params?:ODConsoleMessageParam[]){ - super(message,"ERROR","red",params) - } -} - -/**## ODError `class` - * This is an Open Ticket error. - * - * It is used to render and log Node.js errors & crashes in a styled way to the console & `otdebug.txt` file! - */ -export class ODError { - /**The original error that this class wraps around */ - error: Error|ODSystemError|ODPluginError - /**The origin of the original error */ - origin: NodeJS.UncaughtExceptionOrigin - - constructor(error:Error|ODSystemError|ODPluginError, origin:NodeJS.UncaughtExceptionOrigin){ - this.error = error - this.origin = origin - } - - /**Render this error to the console using `console.log`! Returns `false` when something went wrong. */ - render(){ - try { - let prefix = (this.error["_ODErrorType"] == "plugin") ? "PLUGIN ERROR" : ((this.error["_ODErrorType"] == "system") ? "OPENTICKET ERROR" : "UNKNOWN ERROR") - //title - console.log(ansis.red("["+prefix+"]: ")+this.error.message+" | origin: "+this.origin) - //stack trace - if (this.error.stack) console.log(ansis.gray(this.error.stack)) - //additional message - if (this.error["_ODErrorType"] == "plugin") console.log(ansis.red.bold("\nPlease report this error to the plugin developer and help us create a more stable plugin!")) - else console.log(ansis.red.bold("\nPlease report this error to our discord server and help us create a more stable ticket bot!")) - console.log(ansis.red("Also send the "+ansis.cyan.bold("otdebug.txt")+" file! It would help a lot!\n")) - return true - }catch{ - return false - } - } - /**Create a more-detailed, non-colored version of this error to store it in the `otdebug.txt` file! */ - toDebugString(){ - return "[UNKNOWN OD ERROR]: "+this.error.message+" | origin: "+this.origin+"\n"+this.error.stack - } -} - -/**## ODConsoleMessageTypes `type` - * This is a collection of all the default console message types within Open Ticket. - */ -export type ODConsoleMessageTypes = "info"|"system"|"plugin"|"debug"|"warning"|"error" - -/**## ODConsoleManager `class` - * This is the Open Ticket console manager. - * - * It handles the entire console system of Open Ticket. It's also the place where you need to log `ODConsoleMessage`'s. - * This manager keeps a short history of messages sent to the console which is configurable by plugins. - * - * The debug file (`otdebug.txt`) is handled in a sub-manager! - */ -export class ODConsoleManager { - /**The history of `ODConsoleMessage`'s and `ODError`'s since startup */ - history: (ODConsoleMessage|ODError)[] = [] - /**The max length of the history. The oldest messages will be removed when over the limit */ - historylength = 100 - /**An alias to the debugfile manager. (`otdebug.txt`) */ - debugfile: ODDebugFileManager - /**Is silent mode enabled? */ - silent: boolean = false - - constructor(historylength:number, debugfile:ODDebugFileManager){ - this.historylength = historylength - this.debugfile = debugfile - } - - /**Log a message to the console ... But in the Open Ticket way :) */ - log(message:ODConsoleMessage): void - log(message:ODError): void - log(message:string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]): void - log(message:ODConsoleMessage|ODError|string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]){ - if (message instanceof ODConsoleMessage){ - if (!this.silent) message.render() - if (this.debugfile) this.debugfile.writeConsoleMessage(message) - this.history.push(message) - - }else if (message instanceof ODError){ - if (!this.silent) message.render() - if (this.debugfile) this.debugfile.writeErrorMessage(message) - this.history.push(message) - - }else if (["string","number","boolean","object"].includes(typeof message)){ - let newMessage: ODConsoleMessage - if (type == "info") newMessage = new ODConsoleInfoMessage(message,params) - else if (type == "system") newMessage = new ODConsoleSystemMessage(message,params) - else if (type == "plugin") newMessage = new ODConsolePluginMessage(message,params) - else if (type == "debug") newMessage = new ODConsoleDebugMessage(message,params) - else if (type == "warning") newMessage = new ODConsoleWarningMessage(message,params) - else if (type == "error") newMessage = new ODConsoleErrorMessage(message,params) - else newMessage = new ODConsoleSystemMessage(message,params) - - if (!this.silent) newMessage.render() - if (this.debugfile) this.debugfile.writeConsoleMessage(newMessage) - this.history.push(newMessage) - } - this.#purgeHistory() - } - /**Shorten the history when it exceeds the max history length! */ - #purgeHistory(){ - if (this.history.length > this.historylength) this.history.shift() - } -} - -/**## ODDebugFileManager `class` - * This is the Open Ticket debug file manager. - * - * It manages the Open Ticket debug file (`otdebug.txt`) which keeps a history of all system logs. - * There are even internal logs that aren't logged to the console which are available in this file! - * - * Using this class, you can change the max length of this file and some other cool things! - */ -export class ODDebugFileManager { - /**The path to the debugfile (`./otdebug.txt` by default) */ - path: string - /**The filename of the debugfile (`otdebug.txt` by default) */ - filename: string - /**The current version of the bot used in the debug file. */ - version: ODVersion - /**The max length of the debug file. */ - maxlines: number - - constructor(path:string, filename:string, maxlines:number, version:ODVersion){ - this.path = nodepath.join(path,filename) - this.filename = filename - this.version = version - this.maxlines = maxlines - - this.#writeStartupStats() - } - - /**Check if the debug file exists */ - #existsDebugFile(){ - return fs.existsSync(this.path) - } - /**Read from the debug file */ - #readDebugFile(){ - if (this.#existsDebugFile()){ - try { - return fs.readFileSync(this.path).toString() - }catch{ - return false - } - }else{ - return false - } - } - /**Write to the debug file and shorten it when needed. */ - #writeDebugFile(text:string){ - const currenttext = this.#readDebugFile() - if (currenttext){ - const splitted = currenttext.split("\n") - - if (splitted.length+text.split("\n").length > this.maxlines){ - splitted.splice(7,(text.split("\n").length)) - } - - splitted.push(text) - fs.writeFileSync(this.path,splitted.join("\n")) - }else{ - //write new file: - const newtext = this.#createStatsText()+text - fs.writeFileSync(this.path,newtext) - } - } - /**Generate the stats/header of the debug file (containing the version) */ - #createStatsText(){ - const date = new Date() - const dstring = `${date.getDate()}/${date.getMonth()+1}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}` - return [ - "=========================", - "OPEN TICKET DEBUG FILE:", - "version: "+this.version.toString(), - "last startup: "+dstring, - "=========================\n\n" - ].join("\n") - } - /**Write the stats/header to the debug file on startup */ - #writeStartupStats(){ - const currenttext = this.#readDebugFile() - if (currenttext){ - //edit previous file: - const splitted = currenttext.split("\n") - splitted.splice(0,7) - - if (splitted.length+11 > this.maxlines){ - splitted.splice(0,((splitted.length+11) - this.maxlines)) - } - - splitted.unshift(this.#createStatsText()) - splitted.push("\n---------------------------------------------------------------------\n---------------------------------------------------------------------\n") - - fs.writeFileSync(this.path,splitted.join("\n")) - }else{ - //write new file: - const newtext = this.#createStatsText() - fs.writeFileSync(this.path,newtext) - } - } - /**Write an `ODConsoleMessage` to the debug file */ - writeConsoleMessage(message:ODConsoleMessage){ - this.#writeDebugFile(message.toDebugString()) - } - /**Write an `ODError` to the debug file */ - writeErrorMessage(error:ODError){ - this.#writeDebugFile(error.toDebugString()) - } - /**Write custom text to the debug file */ - writeText(text:string){ - this.#writeDebugFile(text) - } - /**Write a custom note to the debug file (starting with `[NOTE]:`) */ - writeNote(text:string){ - this.#writeDebugFile("[NOTE]: "+text) - } -} - -/**## ODDebugger `class` - * This is the Open Ticket debugger. - * - * It is a simple wrapper around the `ODConsoleManager` to handle debugging (primarily for `ODManagers`). - * Messages created using this debugger are only logged to the debug file unless specified otherwise. - * - * You will probably notice this class being used in the `ODManager` constructor. - * - * Using this system, all additions & removals inside a manager are logged to the debug file. This makes searching for errors a lot easier! - */ -export class ODDebugger { - /**An alias to the Open Ticket console manager. */ - console: ODConsoleManager - /**When enabled, debug logs are also shown in the console. */ - visible: boolean = false - - constructor(console:ODConsoleManager){ - this.console = console - } - - /**Create a debug message. This will always be logged to `otdebug.txt` & sometimes to the console (when enabled). Returns `true` when visible */ - debug(message:string, params?:{key:string,value:string}[]): boolean { - if (this.visible){ - this.console.log(new ODConsoleDebugMessage(message,params)) - return true - }else{ - this.console.debugfile.writeConsoleMessage(new ODConsoleDebugMessage(message,params)) - return false - } - } -} - -/**## ODLivestatusColor `type` - * This is a collection of all the colors available within the LiveStatus system. - */ -export type ODLiveStatusColor = "normal"|"red"|"green"|"blue"|"yellow"|"white"|"gray"|"magenta"|"cyan" - -/**## ODLiveStatusSourceData `interface` - * This is an interface containing all raw data received from the LiveStatus system. - */ -export interface ODLiveStatusSourceData { - /**The message to display */ - message:{ - /**The title of the message to display */ - title:string, - /**The title color of the message to display */ - titleColor:ODLiveStatusColor, - /**The description of the message to display */ - description:string, - /**The description color of the message to display */ - descriptionColor:ODLiveStatusColor - }, - /**The message will only be shown when the bot matches all statements */ - active:{ - /**A list of versions to match */ - versions:string[], - /**A list of languages to match */ - languages:string[], - /**All languages should match */ - allLanguages:boolean, - /**Match when the bot is using plugins */ - usingPlugins:boolean, - /**Match when the bot is not using plugins */ - notUsingPlugins:boolean, - /**Match when the bot is using slash commands */ - usingSlashCommands:boolean, - /**Match when the bot is not using slash commands */ - notUsingSlashCommands:boolean, - /**Match when the bot is not using transcripts */ - notUsingTranscripts:boolean, - /**Match when the bot is using text transcripts */ - usingTextTranscripts:boolean, - /**Match when the bot is using html transcripts */ - usingHtmlTranscripts:boolean - } -} - -/**## ODLiveStatusSource `class` - * This is the Open Ticket livestatus source. - * - * It is an empty template for a livestatus source. - * By default, you should use `ODLiveStatusUrlSource` or `ODLiveStatusFileSource`, - * unless you want to create one on your own! - * - * This class doesn't do anything on it's own! It's just a template! - */ -export class ODLiveStatusSource extends ODManagerData { - /**The raw data of this source */ - data: ODLiveStatusSourceData[] - - constructor(id:ODValidId, data:ODLiveStatusSourceData[]){ - super(id) - this.data = data - } - - /**Change the current data using this method! */ - setData(data:ODLiveStatusSourceData[]){ - this.data = data - } - /**Get all messages relevant to the bot based on some parameters. */ - async getMessages(main:ODMain): Promise { - const validMessages: ODLiveStatusSourceData[] = [] - - //parse data from ODMain - const currentVersion: string = main.versions.get("opendiscord:version").toString(true) - const usingSlashCommands: boolean = main.configs.get("opendiscord:general").data.slashCommands - const usingTranscripts: false|"text"|"html" = false as false|"text"|"html" //TODO - const currentLanguage: string = main.languages.getCurrentLanguageId() - const usingPlugins: boolean = (main.plugins.getLength() > 0) - - //check data for each message - this.data.forEach((msg) => { - const {active} = msg - - const correctVersion = active.versions.includes(currentVersion) - const correctSlashMode = (usingSlashCommands && active.usingSlashCommands) || (!usingSlashCommands && active.notUsingSlashCommands) - const correctTranscriptMode = (usingTranscripts == "text" && active.usingTextTranscripts) || (usingTranscripts == "html" && active.usingHtmlTranscripts) || (!usingTranscripts && active.notUsingTranscripts) - const correctLanguage = active.languages.includes(currentLanguage) || active.allLanguages - const correctPlugins = (usingPlugins && active.usingPlugins) || (!usingPlugins && active.notUsingPlugins) - - if (correctVersion && correctLanguage && correctPlugins && correctSlashMode && correctTranscriptMode) validMessages.push(msg) - }) - - //return the valid messages - return validMessages - } -} - -/**## ODLiveStatusFileSource `class` - * This is the Open Ticket livestatus file source. - * - * It is a LiveStatus source that will read the data from a local file. - * - * This can be used for testing/extending the LiveStatus system! - */ -export class ODLiveStatusFileSource extends ODLiveStatusSource { - /**The path to the source file */ - path: string - - constructor(id:ODValidId, path:string){ - if (fs.existsSync(path)){ - super(id,JSON.parse(fs.readFileSync(path).toString())) - }else throw new ODSystemError("LiveStatus source file doesn't exist!") - this.path = path - } -} - -/**## ODLiveStatusUrlSource `class` - * This is the Open Ticket livestatus url source. - * - * It is a LiveStatus source that will read the data from a http URL (json file). - * - * This is the default way of receiving LiveStatus messages! - */ -export class ODLiveStatusUrlSource extends ODLiveStatusSource { - /**The url used in the request */ - url: string - /**The `ODHTTPGetRequest` helper to fetch the url! */ - request: ODHTTPGetRequest - - constructor(id:ODValidId, url:string){ - super(id,[]) - this.url = url - this.request = new ODHTTPGetRequest(url,false) - } - async getMessages(main:ODMain): Promise { - //additional setup - this.request.url = this.url - const rawRes = await this.request.run() - if (rawRes.status != 200) throw new ODSystemError("ODLiveStatusUrlSource => Request Failed!") - try{ - this.setData(JSON.parse(rawRes.body)) - }catch{ - throw new ODSystemError("ODLiveStatusUrlSource => Request Failed!") - } - - //default - return super.getMessages(main) - } -} - -/**## ODLiveStatusManager `class` - * This is the Open Ticket livestatus manager. - * - * It manages all LiveStatus sources and has the renderer for all LiveStatus messages. - * - * You can use this to customise or add stuff to the LiveStatus system. - * Access it in the global `opendiscord.startscreen.livestatus` variable! - */ -export class ODLiveStatusManager extends ODManager { - /**The class responsible for rendering the livestatus messages. */ - renderer: ODLiveStatusRenderer - /**A reference to the ODMain or "openticket" global variable */ - #main: ODMain - - constructor(debug:ODDebugger, main:ODMain){ - super(debug,"livestatus source") - this.renderer = new ODLiveStatusRenderer(main.console) - this.#main = main - } - - /**Get the messages from all sources combined! */ - async getAllMessages(): Promise { - const messages: ODLiveStatusSourceData[] = [] - for (const source of this.getAll()){ - try { - messages.push(...(await source.getMessages(this.#main))) - }catch{} - } - return messages - } -} - -/**## ODLiveStatusRenderer `class` - * This is the Open Ticket livestatus renderer. - * - * It's responsible for rendering all LiveStatus messages to the console. - */ -export class ODLiveStatusRenderer { - /**A reference to the ODConsoleManager or "opendiscord.console" global variable */ - #console: ODConsoleManager - - constructor(console:ODConsoleManager){ - this.#console = console - } - - /**Render all messages */ - render(messages:ODLiveStatusSourceData[]): string { - try { - //process data - const final: string[] = [] - messages.forEach((msg) => { - const titleColor = msg.message.titleColor - const title = "["+msg.message.title+"] " - - const descriptionColor = msg.message.descriptionColor - const description = msg.message.description.split("\n").map((text,row) => { - //first row row doesn't need prefix - if (row < 1) return text - //other rows do need a prefix - let text2 = text - for (const i of title){ - text2 = " "+text2 - } - return text2 - }).join("\n") - - - if (!["red","yellow","green","blue","gray","magenta","cyan"].includes(titleColor)) var finalTitle = ansis.white(title) - else var finalTitle = ansis[titleColor](title) - if (!["red","yellow","green","blue","gray","magenta","cyan"].includes(descriptionColor)) var finalDescription = ansis.white(description) - else var finalDescription = ansis[descriptionColor](description) - - final.push(finalTitle+finalDescription) - }) - - //return all messages - return final.join("\n") - }catch{ - this.#console.log("Failed to render LiveStatus messages!","error") - return "" - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/cooldown.ts b/src/core/api/modules/cooldown.ts deleted file mode 100644 index ad5abfe..0000000 --- a/src/core/api/modules/cooldown.ts +++ /dev/null @@ -1,348 +0,0 @@ -/////////////////////////////////////// -//COOLDOWN MODULE -/////////////////////////////////////// -import { ODId, ODValidId, ODManager, ODSystemError, ODManagerData } from "./base" -import { ODDebugger } from "./console" - -/**## ODCooldownManager `class` - * This is an Open Ticket cooldown manager. - * - * It is responsible for managing all cooldowns in Open Ticket. An example of this is the ticket creation cooldown. - * - * There are many types of cooldowns available, but you can also create your own! - */ -export class ODCooldownManager extends ODManager> { - constructor(debug:ODDebugger){ - super(debug,"cooldown") - } - /**Initiate all cooldowns in this manager. */ - async init(){ - for (const cooldown of this.getAll()){ - await cooldown.init() - } - } -} - -/**## ODCooldownData `class` - * This is Open Ticket cooldown data. - * - * It contains the instance of an active cooldown (e.g. for a user). It is handled by the cooldown itself. - */ -export class ODCooldownData extends ODManagerData { - /**Is this cooldown active? */ - active: boolean - /**Additional data of this cooldown instance. (different for each cooldown type) */ - data: Data - - constructor(id:ODValidId,active:boolean,data:Data){ - super(id) - this.active = active - this.data = data - } -} - -/**## ODCooldown `class` - * This is an Open Ticket cooldown. - * - * It doesn't do anything on it's own, but it provides the methods that are used to interact with a cooldown. - * This class can be extended from to create a working cooldown. - * - * There are also premade cooldowns available in the bot! - */ -export class ODCooldown extends ODManagerData { - data: ODManager> = new ODManager() - /**Is this cooldown already initialized? */ - ready: boolean = false - - constructor(id:ODValidId){ - super(id) - } - - /**Check this id and start cooldown when it exeeds the limit! Returns `true` when on cooldown! */ - use(id:string): boolean { - throw new ODSystemError("Tried to use an unimplemented ODCooldown!") - } - /**Check this id without starting or updating the cooldown. Returns `true` when on cooldown! */ - check(id:string): boolean { - throw new ODSystemError("Tried to use an unimplemented ODCooldown!") - } - /**Remove the cooldown for an id when available.*/ - delete(id:string){ - throw new ODSystemError("Tried to use an unimplemented ODCooldown!") - } - /**Initialize the internal systems of this cooldown. */ - async init(){ - throw new ODSystemError("Tried to use an unimplemented ODCooldown!") - } -} - -/**## ODCounterCooldown `class` - * This is an Open Ticket counter cooldown. - * - * It is is a cooldown based on a counter. When the number exceeds the limit, the cooldown is activated. - * The number will automatically be decreased with a set amount & interval. - */ -export class ODCounterCooldown extends ODCooldown<{value:number}> { - /**The cooldown will activate when exceeding this limit. */ - activeLimit: number - /**The cooldown will deactivate when below this limit. */ - cancelLimit: number - /**The amount to increase the counter with everytime the cooldown is triggered/updated. */ - increment: number - /**The amount to decrease the counter over time. */ - decrement: number - /**The interval between decrements in milliseconds. */ - invervalMs: number - - constructor(id:ODValidId, activeLimit:number, cancelLimit:number, increment:number, decrement:number, intervalMs:number){ - super(id) - this.activeLimit = activeLimit - this.cancelLimit = cancelLimit - this.increment = increment - this.decrement = decrement - this.invervalMs = intervalMs - } - - use(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - if (cooldown.active){ - return true - - }else if (cooldown.data.value < this.activeLimit){ - cooldown.data.value = cooldown.data.value + this.increment - return false - - }else{ - cooldown.active = true - return false - } - }else{ - //cooldown for this id doesn't exist - this.data.add(new ODCooldownData(id,(this.increment >= this.activeLimit),{ - value:this.increment - })) - return false - } - } - check(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - return cooldown.active - }else return false - } - delete(id:string): void { - this.data.remove(id) - } - async init(){ - if (this.ready) return - setInterval(async () => { - await this.data.loopAll((cooldown) => { - cooldown.data.value = cooldown.data.value - this.decrement - if (cooldown.data.value <= this.cancelLimit){ - cooldown.active = false - } - if (cooldown.data.value <= 0){ - this.data.remove(cooldown.id) - } - }) - },this.invervalMs) - this.ready = true - } -} - -/**## ODIncrementalCounterCooldown `class` - * This is an Open Ticket incremental counter cooldown. - * - * It is is a cooldown based on an incremental counter. It is exactly the same as the normal counter, - * with the only difference being that it still increments when the limit is already exeeded. - */ -export class ODIncrementalCounterCooldown extends ODCooldown<{value:number}> { - /**The cooldown will activate when exceeding this limit. */ - activeLimit: number - /**The cooldown will deactivate when below this limit. */ - cancelLimit: number - /**The amount to increase the counter with everytime the cooldown is triggered/updated. */ - increment: number - /**The amount to decrease the counter over time. */ - decrement: number - /**The interval between decrements in milliseconds. */ - invervalMs: number - - constructor(id:ODValidId, activeLimit:number, cancelLimit:number, increment:number, decrement:number, intervalMs:number){ - super(id) - this.activeLimit = activeLimit - this.cancelLimit = cancelLimit - this.increment = increment - this.decrement = decrement - this.invervalMs = intervalMs - } - - use(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - if (cooldown.active){ - cooldown.data.value = cooldown.data.value + this.increment - return true - - }else if (cooldown.data.value < this.activeLimit){ - cooldown.data.value = cooldown.data.value + this.increment - return false - - }else{ - cooldown.active = true - return false - } - }else{ - //cooldown for this id doesn't exist - this.data.add(new ODCooldownData(id,(this.increment >= this.activeLimit),{ - value:this.increment - })) - return false - } - } - check(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - return cooldown.active - }else return false - } - delete(id:string): void { - this.data.remove(id) - } - async init(){ - if (this.ready) return - setInterval(async () => { - await this.data.loopAll((cooldown) => { - cooldown.data.value = cooldown.data.value - this.decrement - if (cooldown.data.value <= this.cancelLimit){ - cooldown.active = false - } - if (cooldown.data.value <= 0){ - this.data.remove(cooldown.id) - } - }) - },this.invervalMs) - this.ready = true - } -} - -/**## ODTimeoutCooldown `class` - * This is an Open Ticket timeout cooldown. - * - * It is a cooldown based on a timer. When triggered/updated, the cooldown is activated for the set amount of time. - * After the timer has timed out, the cooldown will be deleted. - */ -export class ODTimeoutCooldown extends ODCooldown<{date:number}> { - /**The amount of milliseconds before the cooldown times-out */ - timeoutMs: number - - constructor(id:ODValidId, timeoutMs:number){ - super(id) - this.timeoutMs = timeoutMs - } - - use(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - if ((new Date().getTime() - cooldown.data.date) > this.timeoutMs){ - this.data.remove(id) - return false - }else{ - return true - } - }else{ - //cooldown for this id doesn't exist - this.data.add(new ODCooldownData(id,true,{ - date:new Date().getTime() - })) - return false - } - } - check(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - return true - }else return false - } - delete(id:string): void { - this.data.remove(id) - } - /**Get the remaining amount of milliseconds before the timeout stops. */ - remaining(id:string): number|null { - const cooldown = this.data.get(id) - if (!cooldown) return null - const rawResult = this.timeoutMs - (new Date().getTime() - cooldown.data.date) - return (rawResult > 0) ? rawResult : 0 - } - async init(){ - if (this.ready) return - this.ready = true - } -} - -/**## ODIncrementalTimeoutCooldown `class` - * This is an Open Ticket incremental timeout cooldown. - * - * It is is a cooldown based on an incremental timer. It is exactly the same as the normal timer, - * with the only difference being that it adds additional time when triggered/updated while the cooldown is already active. - */ -export class ODIncrementalTimeoutCooldown extends ODCooldown<{date:number}> { - /**The amount of milliseconds before the cooldown times-out */ - timeoutMs: number - /**The amount of milliseconds to add when triggered/updated while the cooldown is already active. */ - incrementMs: number - - constructor(id:ODValidId, timeoutMs:number, incrementMs:number){ - super(id) - this.timeoutMs = timeoutMs - this.incrementMs = incrementMs - } - - use(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - if ((new Date().getTime() - cooldown.data.date) > this.timeoutMs){ - this.data.remove(id) - return false - }else{ - cooldown.data.date = cooldown.data.date + this.incrementMs - return true - } - }else{ - //cooldown for this id doesn't exist - this.data.add(new ODCooldownData(id,true,{ - date:new Date().getTime() - })) - return false - } - } - check(id:string): boolean { - const cooldown = this.data.get(id) - if (cooldown){ - //cooldown for this id already exists - return true - }else return false - } - delete(id:string): void { - this.data.remove(id) - } - /**Get the remaining amount of milliseconds before the timeout stops. */ - remaining(id:string): number|null { - const cooldown = this.data.get(id) - if (!cooldown) return null - const rawResult = this.timeoutMs - (new Date().getTime() - cooldown.data.date) - return (rawResult > 0) ? rawResult : 0 - } - async init(){ - if (this.ready) return - this.ready = true - } -} \ No newline at end of file diff --git a/src/core/api/modules/database.ts b/src/core/api/modules/database.ts deleted file mode 100644 index ab6d601..0000000 --- a/src/core/api/modules/database.ts +++ /dev/null @@ -1,278 +0,0 @@ -/////////////////////////////////////// -//DATABASE MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODOptionalPromise, ODPromiseVoid, ODSystemError, ODValidId, ODValidJsonType } from "./base" -import fs from "fs" -import nodepath from "path" -import { ODDebugger } from "./console" -import * as fjs from "formatted-json-stringify" - -/**## ODDatabaseManager `class` - * This is an Open Ticket database manager. - * - * It manages all databases in the bot and allows to permanently store data from the bot! - * - * You can use this class to get/add a database (`ODDatabase`) in your plugin! - */ -export class ODDatabaseManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"database") - } - - /**Init all database files. */ - async init(){ - for (const database of this.getAll()){ - try{ - await database.init() - }catch(err){ - process.emit("uncaughtException",new ODSystemError(err)) - } - } - } -} - -/**## ODDatabase `class` - * This is an Open Ticket database template. - * This class doesn't do anything at all, it just gives a template & basic methods for a database. Use `ODJsonDatabase` instead! - * - * You can use this class if you want to create your own database implementation (e.g. `mongodb`, `mysql`,...)! - */ -export class ODDatabase extends ODManagerData { - /**The name of the file with extension. */ - file: string = "" - /**The path to the file relative to the main directory. */ - path: string = "" - - /**Init the database. */ - init(): ODPromiseVoid { - //nothing - } - /**Add/Overwrite a specific category & key in the database. Returns `true` when overwritten. */ - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - return false - } - /**Get a specific category & key in the database */ - get(category:string, key:string): ODOptionalPromise { - return undefined - } - /**Delete a specific category & key in the database */ - delete(category:string, key:string): ODOptionalPromise { - return false - } - /**Check if a specific category & key exists in the database */ - exists(category:string, key:string): ODOptionalPromise { - return false - } - /**Get a specific category in the database */ - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - return undefined - } - /**Get all values in the database */ - getAll(): ODOptionalPromise { - return [] - } -} - -/**## ODJsonDatabaseStructure `type` - * This is the structure of how a JSON database file! - */ -export type ODJsonDatabaseStructure = {category:string, key:string, value:ODValidJsonType}[] - -/**## ODJsonDatabase `class` - * This is an Open Ticket JSON database. - * It stores data in a `json` file as a large `Array` using the `category`, `key`, `value` strategy. - * You can store the following types: `string`, `number`, `boolean`, `array`, `object` & `null`! - * - * You can use this class if you want to add your own database or to use an existing one! - */ -export class ODJsonDatabase extends ODDatabase { - constructor(id:ODValidId, file:string, customPath?:string){ - super(id) - this.file = (file.endsWith(".json")) ? file : file+".json" - this.path = customPath ? nodepath.join("./",customPath,this.file) : nodepath.join("./database/",this.file) - } - - /**Init the database. */ - init(): ODPromiseVoid { - this.#system.getData() - } - /**Set/overwrite the value of `category` & `key`. Returns `true` when overwritten! - * @example - * const didOverwrite = database.setData("category","key","value") //value can be any of the valid types - * //You need an ODJsonDatabase class named "database" for this example to work! - */ - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - const currentList = this.#system.getData() - const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) - - //overwrite when already present - if (currentData){ - currentList[currentList.indexOf(currentData)].value = value - }else{ - currentList.push({category,key,value}) - } - - this.#system.setData(currentList) - return currentData ? true : false - } - /**Get the value of `category` & `key`. Returns `undefined` when non-existent! - * @example - * const data = database.getData("category","key") //data will be the value - * //You need an ODJsonDatabase class named "database" for this example to work! - */ - get(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) - return tempresult ? tempresult.value : undefined - } - /**Remove the value of `category` & `key`. Returns `undefined` when non-existent! - * @example - * const didExist = database.deleteData("category","key") //delete this value - * //You need an ODJsonDatabase class named "database" for this example to work! - */ - delete(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) - if (currentData) currentList.splice(currentList.indexOf(currentData),1) - - this.#system.setData(currentList) - return currentData ? true : false - } - /**Check if a value of `category` & `key` exists. Returns `false` when non-existent! */ - exists(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) - return tempresult ? true : false - } - /**Get all values in `category`. Returns `undefined` when non-existent! */ - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - const currentList = this.#system.getData() - const tempresult = currentList.filter((d) => (d.category === category)) - return tempresult ? tempresult.map((data) => {return {key:data.key,value:data.value}}) : undefined - } - /**Get all values in `category`. */ - getAll(): ODOptionalPromise { - return this.#system.getData() - } - - #system = { - /**Read parsed data from the json file */ - getData: (): ODJsonDatabaseStructure => { - if (fs.existsSync(this.path)){ - try{ - return JSON.parse(fs.readFileSync(this.path).toString()) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Unable to read database "+this.path+"! getData() read error. (see error above)") - } - }else{ - fs.writeFileSync(this.path,"[]") - return [] - } - }, - /**Write parsed data to the json file */ - setData: (data:ODJsonDatabaseStructure) => { - fs.writeFileSync(this.path,JSON.stringify(data,null,"\t")) - } - } -} - - -/**## ODFormattedJsonDatabase `class` - * This is an Open Ticket Formatted JSON database. - * It stores data in a `json` file as a large `Array` using the `category`, `key`, `value` strategy. - * You can store the following types: `string`, `number`, `boolean`, `array`, `object` & `null`! - * - * This one is exactly the same as `ODJsonDatabase`, but it has a formatter from the `formatted-json-stringify` package. - * This can help you organise it a little bit better! - */ -export class ODFormattedJsonDatabase extends ODDatabase { - /**The formatter to use on the database array */ - formatter: fjs.ArrayFormatter - - constructor(id:ODValidId, file:string, formatter:fjs.ArrayFormatter, customPath?:string){ - super(id) - this.file = (file.endsWith(".json")) ? file : file+".json" - this.path = customPath ? nodepath.join("./",customPath,this.file) : nodepath.join("./database/",this.file) - this.formatter = formatter - } - - /**Init the database. */ - init(): ODPromiseVoid { - this.#system.getData() - } - /**Set/overwrite the value of `category` & `key`. Returns `true` when overwritten! - * @example - * const didOverwrite = database.setData("category","key","value") //value can be any of the valid types - * //You need an ODFormattedJsonDatabase class named "database" for this example to work! - */ - set(category:string, key:string, value:ODValidJsonType): ODOptionalPromise { - const currentList = this.#system.getData() - const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) - - //overwrite when already present - if (currentData){ - currentList[currentList.indexOf(currentData)].value = value - }else{ - currentList.push({category,key,value}) - } - - this.#system.setData(currentList) - return currentData ? true : false - } - /**Get the value of `category` & `key`. Returns `undefined` when non-existent! - * @example - * const data = database.getData("category","key") //data will be the value - * //You need an ODFormattedJsonDatabase class named "database" for this example to work! - */ - get(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) - return tempresult ? tempresult.value : undefined - } - /**Remove the value of `category` & `key`. Returns `undefined` when non-existent! - * @example - * const didExist = database.deleteData("category","key") //delete this value - * //You need an ODFormattedJsonDatabase class named "database" for this example to work! - */ - delete(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const currentData = currentList.find((d) => (d.category === category) && (d.key === key)) - if (currentData) currentList.splice(currentList.indexOf(currentData),1) - - this.#system.setData(currentList) - return currentData ? true : false - } - /**Check if a value of `category` & `key` exists. Returns `false` when non-existent! */ - exists(category:string, key:string): ODOptionalPromise { - const currentList = this.#system.getData() - const tempresult = currentList.find((d) => (d.category === category) && (d.key === key)) - return tempresult ? true : false - } - /**Get all values in `category`. Returns `undefined` when non-existent! */ - getCategory(category:string): ODOptionalPromise<{key:string, value:ODValidJsonType}[]|undefined> { - const currentList = this.#system.getData() - const tempresult = currentList.filter((d) => (d.category === category)) - return tempresult ? tempresult.map((data) => {return {key:data.key,value:data.value}}) : undefined - } - /**Get all values in `category`. */ - getAll(): ODOptionalPromise { - return this.#system.getData() - } - - #system = { - /**Read parsed data from the json file */ - getData: (): ODJsonDatabaseStructure => { - if (fs.existsSync(this.path)){ - return JSON.parse(fs.readFileSync(this.path).toString()) - }else{ - fs.writeFileSync(this.path,"[]") - return [] - } - }, - /**Write parsed data to the json file */ - setData: (data:ODJsonDatabaseStructure) => { - fs.writeFileSync(this.path,this.formatter.stringify(data)) - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/defaults.ts b/src/core/api/modules/defaults.ts deleted file mode 100644 index c0b4491..0000000 --- a/src/core/api/modules/defaults.ts +++ /dev/null @@ -1,366 +0,0 @@ -/////////////////////////////////////// -//DEFAULTS MODULE -/////////////////////////////////////// - -/**## ODDefaults `interface` - * This type is a list of all defaults available in the `ODDefaultsManager` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODDefaults { - /**Enable the default error handling system. */ - errorHandling:boolean, - /**Crash when there is an unknown bot error. */ - crashOnError:boolean, - /**Enable the system responsible for the `--debug` flag. */ - debugLoading:boolean, - /**Enable the system responsible for the `--silent` flag. */ - silentLoading:boolean, - /**When enabled, you're able to use the "!OPENTICKET:dump" command to send the OT debug file. This is only possible when you're the owner of the bot. */ - allowDumpCommand:boolean, - /**Enable loading all Open Ticket plugins, sadly enough is only useful for the system :) */ - pluginLoading:boolean, - /**Don't crash the bot when a plugin crashes! */ - softPluginLoading:boolean, - - /**Load the default Open Ticket plugin classes. */ - pluginClassLoading:boolean, - - /**Load the default Open Ticket flags. */ - flagLoading:boolean, - /**Enable the default initializer for Open Ticket flags. */ - flagInitiating:boolean, - /**Load the default Open Ticket progress bar renderers. */ - progressBarRendererLoading:boolean, - /**Load the default Open Ticket progress bars. */ - progressBarLoading:boolean, - /**Load the default Open Ticket configs. */ - configLoading:boolean, - /**Enable the default initializer for Open Ticket config. */ - configInitiating:boolean, - /**Load the default Open Ticket databases. */ - databaseLoading:boolean, - /**Enable the default initializer for Open Ticket database. */ - databaseInitiating:boolean, - /**Load the default Open Ticket sessions. */ - sessionLoading:boolean, - - /**Load the default Open Ticket languages. */ - languageLoading:boolean, - /**Enable the default initializer for Open Ticket languages. */ - languageInitiating:boolean, - /**Enable selecting the current language from `config/general.json`. */ - languageSelection:boolean, - /**Set the backup language when the primary language is missing a property. */ - backupLanguage:string, - /****[NOT FOR PLUGIN TRANSLATIONS]** The full list of available languages (used in the default config checker). */ - languageList:string[], - - /**Load the default Open Ticket config checker. */ - checkerLoading:boolean, - /**Load the default Open Ticket config checker functions. */ - checkerFunctionLoading:boolean, - /**Enable the default execution of the config checkers. */ - checkerExecution:boolean, - /**Load the default Open Ticket config checker translations. */ - checkerTranslationLoading:boolean, - /**Enable the default rendering of the config checkers. */ - checkerRendering:boolean, - /**Enable the default quit action when there is an error in the config checker. */ - checkerQuit:boolean, - /**Render the checker even when there are no errors & warnings. */ - checkerRenderEmpty:boolean, - - /**Load the default Open Ticket client configuration. */ - clientLoading:boolean, - /**Load the default Open Ticket client initialization. */ - clientInitiating:boolean, - /**Load the default Open Ticket client ready actions (status, commands, permissions, ...). */ - clientReady:boolean, - /**Create a warning when the bot is present in multiple guilds. */ - clientMultiGuildWarning:boolean, - /**Load the default Open Ticket client activity (from `config/general.json`). */ - clientActivityLoading:boolean, - /**Load the default Open Ticket client activity initialization (& status refresh). */ - clientActivityInitiating:boolean, - - /**Load the default Open Ticket priority levels. */ - priorityLoading:boolean, - - /**Load the default Open Ticket slash commands. */ - slashCommandLoading:boolean, - /**Load the default Open Ticket slash command registerer (register slash cmds in discord). */ - slashCommandRegistering:boolean, - /**When enabled, the bot is forced to re-register all slash commands in the server. This can be used in case of a auto-update malfunction. */ - forceSlashCommandRegistration:boolean, - /**When enabled, the bot is allowed to unregister all slash commands which aren't used in Open Ticket. Disable this if you don't want to use the Open Ticket `ODSlashCommand` classes. */ - allowSlashCommandRemoval:boolean, - /**Load the default Open Ticket context menus. */ - contextMenuLoading:boolean, - /**Load the default Open Ticket context menu registerer (register menus in discord). */ - contextMenuRegistering:boolean, - /**When enabled, the bot is forced to re-register all context menus in the server. This can be used in case of a auto-update malfunction. */ - forceContextMenuRegistration:boolean, - /**When enabled, the bot is allowed to unregister all context menus which aren't used in Open Ticket. Disable this if you don't want to use the Open Ticket `ODContextMenu` classes. */ - allowContextMenuRemoval:boolean, - /**Load the default Open Ticket text commands. */ - textCommandLoading:boolean, - - /**Load the default Open Ticket questions (from `config/questions.json`) */ - questionLoading:boolean, - /**Load the default Open Ticket options (from `config/options.json`) */ - optionLoading:boolean, - /**Load the default Open Ticket panels (from `config/panels.json`) */ - panelLoading:boolean, - /**Load the default Open Ticket tickets (from `database/tickets.json`) */ - ticketLoading:boolean, - /**Load the default Open Ticket reaction roles (from `config/options.json`) */ - roleLoading:boolean, - /**Load the default Open Ticket blacklist (from `database/users.json`) */ - blacklistLoading:boolean, - /**Load the default Open Ticket transcript compilers. */ - transcriptCompilerLoading:boolean, - /**Load the default Open Ticket transcript history (from `database/transcripts.json`) */ - transcriptHistoryLoading:boolean, - - /**Load the default Open Ticket button builders. */ - buttonBuildersLoading:boolean, - /**Load the default Open Ticket dropdown builders. */ - dropdownBuildersLoading:boolean, - /**Load the default Open Ticket file builders. */ - fileBuildersLoading:boolean, - /**Load the default Open Ticket embed builders. */ - embedBuildersLoading:boolean, - /**Load the default Open Ticket message builders. */ - messageBuildersLoading:boolean, - /**Load the default Open Ticket modal builders. */ - modalBuildersLoading:boolean, - - /**Load the default Open Ticket command responders. */ - commandRespondersLoading:boolean, - /**Load the default Open Ticket button responders. */ - buttonRespondersLoading:boolean, - /**Load the default Open Ticket dropdown responders. */ - dropdownRespondersLoading:boolean, - /**Load the default Open Ticket modal responders. */ - modalRespondersLoading:boolean, - /**Load the default Open Ticket context menu responders. */ - contextMenuRespondersLoading:boolean, - /**Load the default Open Ticket autocomplete responders. */ - autocompleteRespondersLoading:boolean, - /**Set the time (in ms) before Open Ticket sends an error message when no reply is sent in a responder. */ - responderTimeoutMs:number, - - /**Load the default Open Ticket actions. */ - actionsLoading:boolean, - - /**Load the default Open Ticket verify bars. */ - verifyBarsLoading:boolean, - /**Load the default Open Ticket permissions. */ - permissionsLoading:boolean, - /**Load the default Open Ticket posts. */ - postsLoading:boolean, - /**Initiate the default Open Ticket posts. */ - postsInitiating:boolean, - /**Load the default Open Ticket cooldowns. */ - cooldownsLoading:boolean, - /**Initiate the default Open Ticket cooldowns. */ - cooldownsInitiating:boolean, - /**Load the default Open Ticket help menu categories. */ - helpMenuCategoryLoading:boolean, - /**Load the default Open Ticket help menu components. */ - helpMenuComponentLoading:boolean, - - /**Load the default Open Ticket stat scopes. */ - statScopesLoading:boolean, - /**Load the default Open Ticket stats. */ - statLoading:boolean, - /**Initiate the default Open Ticket stats. */ - statInitiating:boolean, - - /**Load the default Open Ticket code/functions. */ - codeLoading:boolean, - /**Execute the default Open Ticket code/functions. */ - codeExecution:boolean, - - /**Load the default Open Ticket livestatus. */ - liveStatusLoading:boolean, - /**Load the default Open Ticket startscreen. */ - startScreenLoading:boolean, - /**Render the default Open Ticket startscreen. */ - startScreenRendering:boolean, - - /**Load the emoji style from the Open Ticket general config. */ - emojiTitleStyleLoading:boolean, - /**The emoji style to use in embed & message titles using `utilities.emoijTitle()` */ - emojiTitleStyle:"disabled"|"before"|"after"|"double", - /**The emoji divider to use in embed & message titles using `utilities.emoijTitle()` */ - emojiTitleDivider:string - /**The interval in milliseconds that are between autoclose timeout checkers. */ - autocloseCheckInterval:number - /**The interval in milliseconds that are between autodelete timeout checkers. */ - autodeleteCheckInterval:number -} - -/**## ODDefaultsBooleans `type` - * This type is a list of boolean defaults available in the `ODDefaultsManager` class. - * It's used to generate typescript declarations for this class. - */ -export type ODDefaultsBooleans = { - [Key in keyof ODDefaults]: ODDefaults[Key] extends boolean ? Key : never -}[keyof ODDefaults] - -/**## ODDefaultsStrings `type` - * This type is a list of string defaults available in the `ODDefaultsManager` class. - * It's used to generate typescript declarations for this class. - */ -export type ODDefaultsStrings = { - [Key in keyof ODDefaults]: ODDefaults[Key] extends string ? Key : never -}[keyof ODDefaults] - -/**## ODDefaultsNumbers `type` - * This type is a list of number defaults available in the `ODDefaultsManager` class. - * It's used to generate typescript declarations for this class. - */ -export type ODDefaultsNumbers = { - [Key in keyof ODDefaults]: ODDefaults[Key] extends number ? Key : never -}[keyof ODDefaults] - -/**## ODDefaultsStringArray `type` - * This type is a list of string[] defaults available in the `ODDefaultsManager` class. - * It's used to generate typescript declarations for this class. - */ -export type ODDefaultsStringArray = { - [Key in keyof ODDefaults]: ODDefaults[Key] extends string[] ? Key : never -}[keyof ODDefaults] - -/**## ODDefaultsManager `class` - * This is an Open Ticket defaults manager. - * - * It manages all settings in Open Ticket that are not meant to be in the config. - * Here you can disable certain default features to replace them or to specifically enable them! - * - * You are unable to add your own defaults, you can only edit Open Ticket defaults! - */ -export class ODDefaultsManager { - /**A list of all the defaults */ - #defaults: ODDefaults - - constructor(){ - this.#defaults = { - errorHandling:true, - crashOnError:false, - debugLoading:true, - silentLoading:true, - allowDumpCommand:true, - pluginLoading:true, - softPluginLoading:false, - - pluginClassLoading:true, - - flagLoading:true, - flagInitiating:true, - progressBarRendererLoading:true, - progressBarLoading:true, - configLoading:true, - configInitiating:true, - databaseLoading:true, - databaseInitiating:true, - sessionLoading:true, - - languageLoading:true, - languageInitiating:true, - languageSelection:true, - backupLanguage:"opendiscord:english", - languageList:[], - - checkerLoading:true, - checkerFunctionLoading:true, - checkerExecution:true, - checkerTranslationLoading:true, - checkerRendering:true, - checkerQuit:true, - checkerRenderEmpty:false, - - clientLoading:true, - clientInitiating:true, - clientReady:true, - clientMultiGuildWarning:true, - clientActivityLoading:true, - clientActivityInitiating:true, - - priorityLoading:true, - - slashCommandLoading:true, - slashCommandRegistering:true, - forceSlashCommandRegistration:false, - allowSlashCommandRemoval:true, - contextMenuLoading:true, - contextMenuRegistering:true, - forceContextMenuRegistration:false, - allowContextMenuRemoval:true, - textCommandLoading:true, - - questionLoading:true, - optionLoading:true, - panelLoading:true, - ticketLoading:true, - roleLoading:true, - blacklistLoading:true, - transcriptCompilerLoading:true, - transcriptHistoryLoading:true, - - buttonBuildersLoading:true, - dropdownBuildersLoading:true, - fileBuildersLoading:true, - embedBuildersLoading:true, - messageBuildersLoading:true, - modalBuildersLoading:true, - - commandRespondersLoading:true, - buttonRespondersLoading:true, - dropdownRespondersLoading:true, - modalRespondersLoading:true, - contextMenuRespondersLoading:true, - autocompleteRespondersLoading:true, - responderTimeoutMs:2500, - - actionsLoading:true, - - verifyBarsLoading:true, - permissionsLoading:true, - postsLoading:true, - postsInitiating:true, - cooldownsLoading:true, - cooldownsInitiating:true, - helpMenuCategoryLoading:true, - helpMenuComponentLoading:true, - - statScopesLoading:true, - statLoading:true, - statInitiating:true, - - codeLoading:true, - codeExecution:true, - - liveStatusLoading:true, - startScreenLoading:true, - startScreenRendering:true, - - emojiTitleStyleLoading:true, - emojiTitleStyle:"before", - emojiTitleDivider:" ", - autocloseCheckInterval:300000, //5 minutes - autodeleteCheckInterval:300000 //5 minutes - } - } - - /**Set a default to a specific value. Remember! All plugins can edit these values, so your value could be overwritten! */ - setDefault(key:DefaultName, value:ODDefaults[DefaultName]): void { - this.#defaults[key] = value - } - - /**Get a default. Remember! All plugins can edit these values, so this value could be overwritten! */ - getDefault(key:DefaultName): ODDefaults[DefaultName] { - return this.#defaults[key] - } -} \ No newline at end of file diff --git a/src/core/api/modules/event.ts b/src/core/api/modules/event.ts deleted file mode 100644 index c7a20a2..0000000 --- a/src/core/api/modules/event.ts +++ /dev/null @@ -1,99 +0,0 @@ -/////////////////////////////////////// -//EVENT MODULE -/////////////////////////////////////// -import { ODManagerData, ODManager, ODValidId } from "./base" -import { ODConsoleWarningMessage, ODDebugger } from "./console" - -/**## ODEvent `class` - * This is an Open Ticket event. - * - * This class is made to work with the `ODEventManager` to handle events. - * The function of this specific class is to manage all listeners for a specifc event! - */ -export class ODEvent extends ODManagerData { - /**Alias to Open Ticket debugger. */ - #debug?: ODDebugger - /**The list of permanent listeners. */ - listeners: Function[] = [] - /**The list of one-time listeners. List is cleared every time the event is emitted. */ - oncelisteners: Function[] = [] - /**The max listener limit before a possible memory leak will be announced */ - listenerLimit: number = 25 - - /**Use the Open Ticket debugger in this manager for logs*/ - useDebug(debug:ODDebugger|null){ - this.#debug = debug ?? undefined - } - /**Get a collection of listeners combined from both types. Also clears the one-time listeners array! */ - #getCurrentListeners(){ - const final: Function[] = [] - this.oncelisteners.forEach((l) => final.push(l)) - this.listeners.forEach((l) => final.push(l)) - - this.oncelisteners = [] - return final - } - /**Edit the listener limit */ - setListenerLimit(limit:number){ - this.listenerLimit = limit - } - /**Add a permanent callback to this event. This will stay as long as the bot is running! */ - listen(callback:Function){ - this.listeners.push(callback) - - if (this.listeners.length > this.listenerLimit){ - if (this.#debug) this.#debug.console.log(new ODConsoleWarningMessage("Possible event memory leak detected!",[ - {key:"event",value:this.id.value}, - {key:"listeners",value:this.listeners.length.toString()} - ])) - } - } - /**Add a one-time-only callback to this event. This will only trigger the callback once! */ - listenOnce(callback:Function){ - this.oncelisteners.push(callback) - } - /**Wait until this event is fired! Be carefull with it, because it could block the entire bot when wrongly used! */ - async wait(): Promise { - return new Promise((resolve,reject) => { - this.oncelisteners.push((...args:any) => {resolve(args)}) - }) - } - /**Emit this event to all listeners. You are required to provide all parameters of the event! */ - async emit(params:any[]): Promise { - for (const listener of this.#getCurrentListeners()){ - try{ - await listener(...params) - }catch(err){ - process.emit("uncaughtException",err) - } - } - } -} - -/**## ODEventManager `class` - * This is an Open Ticket event manager. - * - * This class is made to manage all events in the bot. You can compare it with the built-in node.js `EventEmitter` - * - * It's not recommended to create this class yourself. Plugin events should be registered in their `plugin.json` file instead. - * All events are available in the `opendiscord.events` global! - */ -export class ODEventManager extends ODManager { - /**Reference to the Open Ticket debugger */ - #debug: ODDebugger - - constructor(debug:ODDebugger){ - super(debug,"event") - this.#debug = debug - } - - add(data:ODEvent, overwrite?:boolean): boolean { - data.useDebug(this.#debug) - return super.add(data,overwrite) - } - remove(id:ODValidId): ODEvent|null { - const data = super.remove(id) - if (data) data.useDebug(null) - return data - } -} \ No newline at end of file diff --git a/src/core/api/modules/flag.ts b/src/core/api/modules/flag.ts deleted file mode 100644 index 3794f3a..0000000 --- a/src/core/api/modules/flag.ts +++ /dev/null @@ -1,73 +0,0 @@ -/////////////////////////////////////// -//FLAG MODULE -/////////////////////////////////////// -import { ODId, ODValidId, ODManager, ODManagerData } from "./base" -import { ODDebugger } from "./console" - -/**## ODFlag `class` - * This is an Open Ticket flag. - * - * A flag is a boolean that can be specified by a parameter in the console. - * It's useful for small settings that are only required once in a while. - * - * Flags can also be enabled manually by plugins! - */ -export class ODFlag extends ODManagerData { - /**The method that has been used to set the value of this flag. (`null` when not set) */ - method: "param"|"manual"|null = null - /**The name of this flag. Visible to the user. */ - name: string - /**The description of this flag. Visible to the user. */ - description: string - /**The name of the parameter in the console. (e.g. `--test`) */ - param: string - /**A list of aliases for the parameter in the console. */ - aliases: string[] - /**The value of this flag. */ - value: boolean = false - - constructor(id:ODValidId, name:string, description:string, param:string, aliases?:string[], initialValue?:boolean){ - super(id) - this.name = name - this.description = description - this.param = param - this.aliases = aliases ?? [] - this.value = initialValue ?? false - } - - /**Set the value of this flag. */ - setValue(value:boolean,method?:"param"|"manual"){ - this.value = value - this.method = method ?? "manual" - } - /**Detect if the process contains the param or aliases & set the value. Use `force` to overwrite a manually set value. */ - detectProcessParams(force?:boolean){ - if (force){ - const params = [this.param,...this.aliases] - this.setValue(params.some((p) => process.argv.includes(p)),"param") - - }else if (this.method != "manual"){ - const params = [this.param,...this.aliases] - this.setValue(params.some((p) => process.argv.includes(p)),"param") - } - } -} - -/**## ODFlagManager `class` - * This is an Open Ticket flag manager. - * - * This class is responsible for managing & initiating all flags of the bot. - * It also contains a shortcut for initiating all flags. - */ -export class ODFlagManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"flag") - } - - /**Set all flags to their `process.argv` value. */ - async init(){ - await this.loopAll((flag) => { - flag.detectProcessParams(false) - }) - } -} \ No newline at end of file diff --git a/src/core/api/modules/helpmenu.ts b/src/core/api/modules/helpmenu.ts deleted file mode 100644 index 44deec2..0000000 --- a/src/core/api/modules/helpmenu.ts +++ /dev/null @@ -1,216 +0,0 @@ -/////////////////////////////////////// -//HELP MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODDebugger } from "./console" - -/**## ODHelpMenuComponentRenderer `type` - * This is the callback of the help menu component renderer. It also contains information about how & where it is rendered. - */ -export type ODHelpMenuComponentRenderer = (page:number, category:number, location:number, mode:"slash"|"text") => string|Promise - -/**## ODHelpMenuComponent `class` - * This is an Open Ticket help menu component. - * - * It can render something on the Open Ticket help menu. - */ -export class ODHelpMenuComponent extends ODManagerData { - /**The priority of this component. The higher, the earlier it will appear in the help menu. */ - priority: number - /**The render function for this component. */ - render: ODHelpMenuComponentRenderer - - constructor(id:ODValidId, priority:number, render:ODHelpMenuComponentRenderer){ - super(id) - this.priority = priority - this.render = render - } -} - -/**## ODHelpMenuTextComponent `class` - * This is an Open Ticket help menu text component. - * - * It can render a static piece of text on the Open Ticket help menu. - */ -export class ODHelpMenuTextComponent extends ODHelpMenuComponent { - constructor(id:ODValidId, priority:number, text:string){ - super(id,priority,() => { - return text - }) - } -} - -/**## ODHelpMenuCommandComponentOption `interface` - * This interface contains a command option for the `ODHelpMenuCommandComponent`. - */ -export interface ODHelpMenuCommandComponentOption { - /**The name of this option. */ - name:string, - /**Is this option optional? */ - optional:boolean -} - -/**## ODHelpMenuCommandComponentSettings `interface` - * This interface contains the settings for the `ODHelpMenuCommandComponent`. - */ -export interface ODHelpMenuCommandComponentSettings { - /**The name of this text command. */ - textName?:string, - /**The name of this slash command. */ - slashName?:string, - /**Options available in the text command. */ - textOptions?:ODHelpMenuCommandComponentOption[], - /**Options available in the slash command. */ - slashOptions?:ODHelpMenuCommandComponentOption[], - /**The description for the text command. */ - textDescription?:string, - /**The description for the slash command. */ - slashDescription?:string -} - -/**## ODHelpMenuCommandComponent `class` - * This is an Open Ticket help menu command component. - * - * It contains a useful helper to render a command in the Open Ticket help menu. - */ -export class ODHelpMenuCommandComponent extends ODHelpMenuComponent { - constructor(id:ODValidId, priority:number, settings:ODHelpMenuCommandComponentSettings){ - super(id,priority,(page,category,location,mode) => { - if (mode == "slash" && settings.slashName){ - return `\`${settings.slashName}${(settings.slashOptions) ? this.#renderOptions(settings.slashOptions) : ""}\` โžœ ${settings.slashDescription ?? ""}` - - }else if (mode == "text" && settings.textName){ - return `\`${settings.textName}${(settings.textOptions) ? this.#renderOptions(settings.textOptions) : ""}\` โžœ ${settings.textDescription ?? ""}` - - }else return "" - }) - } - - /**Utility function to render all command options. */ - #renderOptions(options:ODHelpMenuCommandComponentOption[]){ - return " "+options.map((opt) => (opt.optional) ? `[${opt.name}]` : `<${opt.name}>`).join(" ") - } -} - -/**## ODHelpMenuCategory `class` - * This is an Open Ticket help menu category. - * - * Every category in the help menu is an embed field by default. - * Try to limit the amount of components per category. - */ -export class ODHelpMenuCategory extends ODManager { - /**The id of this category. */ - id: ODId - /**The priority of this category. The higher, the earlier it will appear in the menu. */ - priority: number - /**The name of this category. (can include emoji's) */ - name: string - /**When enabled, it automatically starts this category on a new page. */ - newPage: boolean - - constructor(id:ODValidId, priority:number, name:string, newPage?:boolean){ - super() - this.id = new ODId(id) - this.priority = priority - this.name = name - this.newPage = newPage ?? false - } - - /**Render this category and it's components. */ - async render(page:number, category:number, mode:"slash"|"text"){ - //sort from high priority to low - const derefArray = [...this.getAll()] - derefArray.sort((a,b) => { - return b.priority-a.priority - }) - const result: string[] = [] - - let i = 0 - for (const component of derefArray){ - try { - result.push(await component.render(page,category,i,mode)) - }catch(err){ - process.emit("uncaughtException",err) - } - i++ - } - - //only return the non-empty components - return result.filter((component) => component !== "").join("\n\n") - } -} - -/**## ODHelpMenuRenderResult `type` - * This is the array returned when the help menu has been rendered successfully. - * - * It contains a list of pages, which contain categories by name & value (content). - */ -export type ODHelpMenuRenderResult = {name:string, value:string}[][] - -/**## ODHelpMenuManager `class` - * This is an Open Ticket help menu manager. - * - * It is responsible for rendering the entire help menu content. - * You are also able to configure the amount of categories per page here. - * - * Fewer Categories == More Clean Menu - */ -export class ODHelpMenuManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - /**The amount of categories per-page. */ - categoriesPerPage: number = 3 - - constructor(debug:ODDebugger){ - super(debug,"help menu category") - this.#debug = debug - } - - add(data:ODHelpMenuCategory, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"help menu component") - return super.add(data,overwrite) - } - - /**Render this entire help menu & return a `ODHelpMenuRenderResult`. */ - async render(mode:"slash"|"text"): Promise { - //sort from high priority to low - const derefArray = [...this.getAll()] - derefArray.sort((a,b) => { - return b.priority-a.priority - }) - const result: {name:string, value:string}[][] = [] - let currentPage: {name:string, value:string}[] = [] - - for (const category of derefArray){ - try { - const renderedCategory = await category.render(result.length,currentPage.length,mode) - - if (renderedCategory !== ""){ - //create new page when category wants to - if (currentPage.length > 0 && category.newPage){ - result.push(currentPage) - currentPage = [] - } - - currentPage.push({ - name:category.name, - value:renderedCategory - }) - - //create new page when page is full - if (currentPage.length >= this.categoriesPerPage){ - result.push(currentPage) - currentPage = [] - } - } - }catch(err){ - process.emit("uncaughtException",err) - } - } - - //push current page when not-empty - if (currentPage.length > 0) result.push(currentPage) - - return result - } -} \ No newline at end of file diff --git a/src/core/api/modules/language.ts b/src/core/api/modules/language.ts deleted file mode 100644 index 5333945..0000000 --- a/src/core/api/modules/language.ts +++ /dev/null @@ -1,201 +0,0 @@ -/////////////////////////////////////// -//LANGUAGE MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODPromiseVoid, ODSystemError, ODValidId } from "./base" -import nodepath from "path" -import { ODDebugger } from "./console" -import fs from "fs" - -/**## ODLanguageMetadata `interface` - * This interface contains all metadata available in the language files. - */ -export interface ODLanguageMetadata { - /**The version of Open Ticket this translation is made for. */ - otversion:string, - /**The name of the language in english (with capital letter). */ - language:string, - /**A list of translators (discord/github username) who've contributed to this language. */ - translators:string[], - /**The last date that this translation has been modified (format: DD/MM/YYYY) */ - lastedited:string, - /**When `true`, the translator made use of some sort of automation while creating the translation. (e.g. ChatGPT, Google Translate, DeepL, ...) */ - automated:boolean -} - -/**## ODLanguageManager `class` - * This is an Open Ticket language manager. - * - * It manages all languages in the bot and manages translation for you! - * Get a translation via the `getTranslation()` or `getTranslationWithParams()` methods. - * - * Add new languages using the `ODlanguage` class in your plugin! - */ -export class ODLanguageManager extends ODManager { - /**The currently selected language. */ - current: ODLanguage|null = null - /**The currently selected backup language. (used when translation missing in current language) */ - backup: ODLanguage|null = null - /**An alias to Open Ticket debugger. */ - #debug: ODDebugger - - constructor(debug:ODDebugger, presets:boolean){ - super(debug,"language") - if (presets) this.add(new ODLanguage("english","english.json")) - this.current = presets ? new ODLanguage("english","english.json") : null - this.backup = presets ? new ODLanguage("english","english.json") : null - this.#debug = debug - } - - /**Set the current language by providing the ID of a language which is registered in this manager. */ - setCurrentLanguage(id:ODValidId){ - this.current = this.get(id) - const languageId = this.current?.id.value ?? "" - const languageAutomated = this.current?.metadata?.automated.toString() ?? "" - this.#debug.debug("Selected current language",[ - {key:"id",value:languageId}, - {key:"automated",value:languageAutomated}, - ]) - } - /**Get the current language (same as `this.current`) */ - getCurrentLanguage(){ - return (this.current) ? this.current : null - } - /**Set the backup language by providing the ID of a language which is registered in this manager. */ - setBackupLanguage(id:ODValidId){ - this.backup = this.get(id) - const languageId = this.backup?.id.value ?? "" - const languageAutomated = this.backup?.metadata?.automated.toString() ?? "" - this.#debug.debug("Selected backup language",[ - {key:"id",value:languageId}, - {key:"automated",value:languageAutomated}, - ]) - } - /**Get the backup language (same as `this.backup`) */ - getBackupLanguage(){ - return (this.backup) ? this.backup : null - } - /**Get the metadata of the current/backup language. */ - getLanguageMetadata(frombackup?:boolean): ODLanguageMetadata|null { - if (frombackup) return (this.backup) ? this.backup.metadata : null - return (this.current) ? this.current.metadata : null - } - /**Get the ID (string) of the current language. (Not backup language) */ - getCurrentLanguageId(){ - return (this.current) ? this.current.id.value : "" - } - /**Get a translation string by JSON location. (e.g. `"checker.system.typeError"`) */ - getTranslation(id:string): string|null { - if (!this.current) return this.#getBackupTranslation(id) - - const splitted = id.split(".") - let currentObject = this.current.data - let result: string|false = false - splitted.forEach((id) => { - if (typeof currentObject[id] == "object"){ - currentObject = currentObject[id] - }else if (typeof currentObject[id] == "string"){ - result = currentObject[id] - } - }) - - if (typeof result == "string") return result - else return this.#getBackupTranslation(id) - } - /**Get a backup translation string by JSON location. (system only) */ - #getBackupTranslation(id:string): string|null { - if (!this.backup) return null - - const splitted = id.split(".") - let currentObject = this.backup.data - let result: string|false = false - splitted.forEach((id) => { - if (typeof currentObject[id] == "object"){ - currentObject = currentObject[id] - }else if (typeof currentObject[id] == "string"){ - result = currentObject[id] - } - }) - - if (typeof result == "string") return result - else return null - } - /**Get a backup translation string by JSON location and replace `{0}`,`{1}`,`{2}`,... with the provided parameters. */ - getTranslationWithParams(id:string, params:string[]): string|null { - let translation = this.getTranslation(id) - if (!translation) return translation - - params.forEach((value,index) => { - if (!translation) return - translation = translation.replace(`{${index}}`,value) - }) - return translation - } - - /**Init all language files. */ - async init(){ - for (const language of this.getAll()){ - try{ - await language.init() - }catch(err){ - process.emit("uncaughtException",new ODSystemError(err)) - } - } - } -} - -/**## ODLanguage `class` - * This is an Open Ticket language file. - * - * It contains metadata and all translation strings available in this language. - * Register this class to an `ODLanguageManager` to use it! - * - * JSON languages should be created using the `ODJsonLanguage` class instead! - */ -export class ODLanguage extends ODManagerData { - /**The name of the file with extension. */ - file: string = "" - /**The path to the file relative to the main directory. */ - path: string = "" - /**The raw object data of the translation. */ - data: any - /**The metadata of the language if available. */ - metadata: ODLanguageMetadata|null = null - - constructor(id:ODValidId, data:any){ - super(id) - this.data = data - } - - /**Init the language. */ - init(): ODPromiseVoid { - //nothing - } -} - -/**## ODJsonLanguage `class` - * This is an Open Ticket JSON language file. - * - * It contains metadata and all translation strings from a certain JSON file (in `./languages/`). - * Register this class to an `ODLanguageManager` to use it! - * - * Use the `ODLanguage` class to use translations from non-JSON files! - */ -export class ODJsonLanguage extends ODLanguage { - constructor(id:ODValidId, file:string, customPath?:string){ - super(id,{}) - this.file = (file.endsWith(".json")) ? file : file+".json" - this.path = customPath ? nodepath.join("./",customPath,this.file) : nodepath.join("./languages/",this.file) - } - - /**Init the langauge. */ - init(): ODPromiseVoid { - if (!fs.existsSync(this.path)) throw new ODSystemError("Unable to parse language \""+nodepath.join("./",this.path)+"\", the file doesn't exist!") - try{ - this.data = JSON.parse(fs.readFileSync(this.path).toString()) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("Unable to parse language \""+nodepath.join("./",this.path)+"\"!") - } - if (this.data["_TRANSLATION"]) this.metadata = this.data["_TRANSLATION"] - } -} \ No newline at end of file diff --git a/src/core/api/modules/permission.ts b/src/core/api/modules/permission.ts deleted file mode 100644 index 4b4f883..0000000 --- a/src/core/api/modules/permission.ts +++ /dev/null @@ -1,340 +0,0 @@ -/////////////////////////////////////// -//PERMISSION MODULE -/////////////////////////////////////// -import { ODId, ODValidId, ODManager, ODSystemError, ODManagerData } from "./base" -import * as discord from "discord.js" -import { ODDebugger } from "./console" -import { ODClientManager } from "./client" - -/**## ODPermissionType `type` - * All available permission types/levels. Can be used in the `ODPermission` class. - */ -export type ODPermissionType = "member"|"support"|"moderator"|"admin"|"owner"|"developer" - -/**## ODPermissionScope `type` - * The scope in which a certain permission is active. - */ -export type ODPermissionScope = "global-user"|"channel-user"|"global-role"|"channel-role" - -/**## ODPermissionResult `interface` - * The result returned by `ODPermissionManager.getPermissions()`. - */ -export interface ODPermissionResult { - /**The permission type. */ - type:ODPermissionType - /**The permission scope. */ - scope:ODPermissionScope|"default" - /**The highest level available for this scope. */ - level:ODPermissionLevel, - /**The permission which returned this level. */ - source:ODPermission|null -} - -/**## ODPermissionLevel `enum` - * All available permission types/levels. But as `enum` instead of `type`. Used to calculate the level. - */ -export enum ODPermissionLevel { - /**A normal member. (Default for everyone) */ - member, - /**Support team. Higher than a normal member. (Used for ticket-admins) */ - support, - /**Moderator. Higher than the support team. (Unused) */ - moderator, - /**Admin. Higher than a moderator. (Used for global-admins) */ - admin, - /**Server owner. (Able to use all commands including `/stats reset`) */ - owner, - /**Bot owner or all users from dev team. (Able to use all commands including `/stats reset`) */ - developer -} - -/**## ODPermission `class` - * This is an Open Ticket permission. - * - * It defines a single permission level for a specific scope (global/channel & user/role) - * These permissions only apply to commands & interactions. - * They are not related to channel permissions in the ticket system. - * - * Register this class to an `ODPermissionManager` to use it! - */ -export class ODPermission extends ODManagerData { - /**The scope of this permission. */ - readonly scope: ODPermissionScope - /**The type/level of this permission. */ - readonly permission: ODPermissionType - /**The user/role of this permission. */ - readonly value: discord.Role|discord.User - /**The channel that this permission applies to. (`null` when global) */ - readonly channel: discord.Channel|null - - constructor(id:ODValidId, scope:"global-user", permission:ODPermissionType, value:discord.User) - constructor(id:ODValidId, scope:"global-role", permission:ODPermissionType, value:discord.Role) - constructor(id:ODValidId, scope:"channel-user", permission:ODPermissionType, value:discord.User, channel:discord.Channel) - constructor(id:ODValidId, scope:"channel-role", permission:ODPermissionType, value:discord.Role, channel:discord.Channel) - constructor(id:ODValidId, scope:ODPermissionScope, permission:ODPermissionType, value:discord.Role|discord.User, channel?:discord.Channel){ - super(id) - this.scope = scope - this.permission = permission - this.value = value - this.channel = channel ?? null - } -} - -/**## ODPermissionSettings `interface` - * Optional settings for the `getPermissions()` method in the `ODPermissionManager`. - */ -export interface ODPermissionSettings { - /**Include permissions from the global user scope. */ - allowGlobalUserScope?:boolean, - /**Include permissions from the global role scope. */ - allowGlobalRoleScope?:boolean, - /**Include permissions from the channel user scope. */ - allowChannelUserScope?:boolean, - /**Include permissions from the channel role scope. */ - allowChannelRoleScope?:boolean, - /**Only include permissions of which the id matches this regex. */ - idRegex?:RegExp -} - -/**## ODPermissionCalculationCallback `type` - * The callback of the permission calculation. (Used in `ODPermissionManager`) - */ -export type ODPermissionCalculationCallback = (user:discord.User, channel?:discord.Channel|null, guild?:discord.Guild|null, settings?:ODPermissionSettings|null) => Promise - -/**## ODPermissionCommandResult `type` - * The result of calculating permissions for a command. - */ -export type ODPermissionCommandResult = { - /**Returns `true` when the user has valid permissions. */ - hasPerms:false, - reason:"no-perms"|"disabled"|"not-in-server" -}|{ - /**Returns `true` when the user has valid permissions. */ - hasPerms:true, - /**Is the user a server admin or a normal member? This does not decide if the user has permissions or not. */ - isAdmin:boolean -} - -/**## ODPermissionManager `class` - * This is an Open Ticket permission manager. - * - * It manages all permissions in the bot! - * Use the `getPermissions()` and `hasPermissions()` methods to get user perms. - * - * Add new permissions using the `ODPermission` class in your plugin! - */ -export class ODPermissionManager extends ODManager { - /**Alias for Open Ticket debugger. */ - #debug: ODDebugger - /**The function for calculating permissions in this manager. */ - #calculation: ODPermissionCalculationCallback|null - /**An alias to the Open Discord client manager. */ - #client: ODClientManager - /**The result which is returned when no other permissions match. (`member` by default) */ - defaultResult: ODPermissionResult = { - level:ODPermissionLevel["member"], - scope:"default", - type:"member", - source:null - } - - constructor(debug:ODDebugger, client:ODClientManager, useDefaultCalculation?:boolean){ - super(debug,"permission") - this.#debug = debug - this.#calculation = useDefaultCalculation ? this.#defaultCalculation : null - this.#client = client - } - - /**Edit the permission calculation function in this manager. */ - setCalculation(calculation:ODPermissionCalculationCallback){ - this.#calculation = calculation - } - /**Edit the result which is returned when no other permissions match. (`member` by default) */ - setDefaultResult(result:ODPermissionResult){ - this.defaultResult = result - } - /**Get an `ODPermissionResult` based on a few context factors. Use `hasPermissions()` to simplify the result. */ - getPermissions(user:discord.User, channel?:discord.Channel|null, guild?:discord.Guild|null, settings?:ODPermissionSettings|null): Promise { - try{ - if (!this.#calculation) throw new ODSystemError("ODPermissionManager:getPermissions() => missing perms calculation") - return this.#calculation(user,channel,guild,settings) - }catch(err){ - process.emit("uncaughtException",err) - throw new ODSystemError("ODPermissionManager:getPermissions() => failed perms calculation") - } - } - /**Simplifies the `ODPermissionResult` returned from `getPermissions()` and returns a boolean to check if the user matches the required permissions. */ - hasPermissions(minimum:ODPermissionType, data:ODPermissionResult){ - if (minimum == "member") return true - else if (minimum == "support") return (data.level >= ODPermissionLevel["support"]) - else if (minimum == "moderator") return (data.level >= ODPermissionLevel["moderator"]) - else if (minimum == "admin") return (data.level >= ODPermissionLevel["admin"]) - else if (minimum == "owner") return (data.level >= ODPermissionLevel["owner"]) - else if (minimum == "developer") return (data.level >= ODPermissionLevel["developer"]) - else throw new ODSystemError("Invalid minimum permission type at ODPermissionManager.hasPermissions()") - } - /**Check for permissions. (default calculation) */ - async #defaultCalculation(user:discord.User,channel?:discord.Channel|null,guild?:discord.Guild|null, settings?:ODPermissionSettings|null): Promise { - const globalCalc = await this.#defaultGlobalCalculation(user,channel,guild,settings) - const channelCalc = await this.#defaultChannelCalculation(user,channel,guild,settings) - - if (globalCalc.level > channelCalc.level) return globalCalc - else return channelCalc - } - /**Check for global permissions. Result will be compared with the channel perms in `#defaultCalculation()`. */ - async #defaultGlobalCalculation(user:discord.User,channel?:discord.Channel|null,guild?:discord.Guild|null, settings?:ODPermissionSettings|null): Promise { - const idRegex = (settings && typeof settings.idRegex != "undefined") ? settings.idRegex : null - const allowGlobalUserScope = (settings && typeof settings.allowGlobalUserScope != "undefined") ? settings.allowGlobalUserScope : true - const allowGlobalRoleScope = (settings && typeof settings.allowGlobalRoleScope != "undefined") ? settings.allowGlobalRoleScope : true - - //check for global user permissions - if (allowGlobalUserScope){ - const users = this.getFiltered((permission) => (!idRegex || (idRegex && idRegex.test(permission.id.value))) && permission.scope == "global-user" && (permission.value instanceof discord.User) && permission.value.id == user.id) - - if (users.length > 0){ - //sort all permisions from highest to lowest - users.sort((a,b) => { - const levelA = ODPermissionLevel[a.permission] - const levelB = ODPermissionLevel[b.permission] - - if (levelB > levelA) return 1 - else if (levelA > levelB) return -1 - else return 0 - }) - - return { - type:users[0].permission, - scope:"global-user", - level:ODPermissionLevel[users[0].permission], - source:users[0] ?? null - } - } - } - - //check for global role permissions - if (allowGlobalRoleScope){ - if (guild){ - const member = await this.#client.fetchGuildMember(guild,user.id) - if (member){ - const memberRoles = member.roles.cache.map((role) => role.id) - const roles = this.getFiltered((permission) => (!idRegex || (idRegex && idRegex.test(permission.id.value))) && permission.scope == "global-role" && (permission.value instanceof discord.Role) && memberRoles.includes(permission.value.id) && permission.value.guild.id == guild.id) - - if (roles.length > 0){ - //sort all permisions from highest to lowest - roles.sort((a,b) => { - const levelA = ODPermissionLevel[a.permission] - const levelB = ODPermissionLevel[b.permission] - - if (levelB > levelA) return 1 - else if (levelA > levelB) return -1 - else return 0 - }) - - return { - type:roles[0].permission, - scope:"global-role", - level:ODPermissionLevel[roles[0].permission], - source:roles[0] ?? null - } - } - } - } - } - - //spread result to prevent accidental referencing - return {...this.defaultResult} - } - /**Check for channel permissions. Result will be compared with the global perms in `#defaultCalculation()`. */ - async #defaultChannelCalculation(user:discord.User,channel?:discord.Channel|null,guild?:discord.Guild|null, settings?:ODPermissionSettings|null): Promise { - const idRegex = (settings && typeof settings.idRegex != "undefined") ? settings.idRegex : null - const allowChannelUserScope = (settings && typeof settings.allowChannelUserScope != "undefined") ? settings.allowChannelUserScope : true - const allowChannelRoleScope = (settings && typeof settings.allowChannelRoleScope != "undefined") ? settings.allowChannelRoleScope : true - - if (guild && channel && !channel.isDMBased()){ - //check for channel user permissions - if (allowChannelUserScope){ - const users = this.getFiltered((permission) => (!idRegex || (idRegex && idRegex.test(permission.id.value))) && permission.scope == "channel-user" && permission.channel && (permission.channel.id == channel.id) && (permission.value instanceof discord.User) && permission.value.id == user.id) - - if (users.length > 0){ - //sort all permisions from highest to lowest - users.sort((a,b) => { - const levelA = ODPermissionLevel[a.permission] - const levelB = ODPermissionLevel[b.permission] - - if (levelB > levelA) return 1 - else if (levelA > levelB) return -1 - else return 0 - }) - - return { - type:users[0].permission, - scope:"channel-user", - level:ODPermissionLevel[users[0].permission], - source:users[0] ?? null - } - } - } - - //check for channel role permissions - if (allowChannelRoleScope){ - const member = await this.#client.fetchGuildMember(guild,user.id) - if (member){ - const memberRoles = member.roles.cache.map((role) => role.id) - const roles = this.getFiltered((permission) => (!idRegex || (idRegex && idRegex.test(permission.id.value))) && permission.scope == "channel-role" && permission.channel && (permission.channel.id == channel.id) && (permission.value instanceof discord.Role) && memberRoles.includes(permission.value.id) && permission.value.guild.id == guild.id) - - if (roles.length > 0){ - //sort all permisions from highest to lowest - roles.sort((a,b) => { - const levelA = ODPermissionLevel[a.permission] - const levelB = ODPermissionLevel[b.permission] - - if (levelB > levelA) return 1 - else if (levelA > levelB) return -1 - else return 0 - }) - - return { - type:roles[0].permission, - scope:"channel-role", - level:ODPermissionLevel[roles[0].permission], - source:roles[0] ?? null - } - } - } - } - } - - //spread result to prevent accidental modification because of referencing - return {...this.defaultResult} - } - - /**Check the permissions for a certain command of the bot. */ - async checkCommandPerms(permissionMode:string,requiredLevel:ODPermissionType,user:discord.User,member?:discord.GuildMember|null,channel?:discord.Channel|null,guild?:discord.Guild|null,settings?:ODPermissionSettings): Promise { - if (permissionMode === "none"){ - return {hasPerms:false,reason:"disabled"} - - }else if (permissionMode === "everyone"){ - const isAdmin = this.hasPermissions(requiredLevel,await this.getPermissions(user,channel,guild,settings)) - return {hasPerms:true,isAdmin} - - }else if (permissionMode === "admin"){ - const isAdmin = this.hasPermissions(requiredLevel,await this.getPermissions(user,channel,guild,settings)) - if (!isAdmin) return {hasPerms:false,reason:"no-perms"} - else return {hasPerms:true,isAdmin} - }else{ - if (!guild || !member){ - this.#debug.debug("ODPermissionManager.checkCommandPerms(): Permission Error, Not in server! (#1)") - return {hasPerms:false,reason:"not-in-server"} - } - const role = await this.#client.fetchGuildRole(guild,permissionMode) - if (!role){ - this.#debug.debug("ODPermissionManager.checkCommandPerms(): Permission Error, Not in server! (#2)") - return {hasPerms:false,reason:"not-in-server"} - } - if (!role.members.has(member.id)) return {hasPerms:false,reason:"no-perms"} - - const isAdmin = this.hasPermissions(requiredLevel,await this.getPermissions(user,channel,guild,settings)) - return {hasPerms:true,isAdmin} - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/plugin.ts b/src/core/api/modules/plugin.ts deleted file mode 100644 index 81ae500..0000000 --- a/src/core/api/modules/plugin.ts +++ /dev/null @@ -1,242 +0,0 @@ -/////////////////////////////////////// -//PLUGIN MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODSystemError, ODValidId, ODVersion } from "./base" -import nodepath from "path" -import { ODConsolePluginMessage, ODConsoleWarningMessage, ODDebugger } from "./console" - -/**## ODUnknownCrashedPlugin `interface` - * Basic details for a plugin that crashed while loading the `plugin.json` file. - */ -export interface ODUnknownCrashedPlugin { - /**The name of the plugin. (path when plugin crashed before `name` was loaded) */ - name:string, - /**The description of the plugin. (when found before crashing) */ - description:string -} - -/**## ODPluginManager `class` - * This is an Open Ticket plugin manager. - * - * It manages all active plugins in the bot! - * It also contains all "plugin classes" which are managers registered by plugins. - * These are accessible via the `opendiscord.plugins.classes` global. - * - * Use `isPluginLoaded()` to check if a plugin has been loaded. - */ -export class ODPluginManager extends ODManager { - /**A manager for all custom managers registered by plugins. */ - classes: ODPluginClassManager - /**A list of basic details from all plugins that crashed while loading the `plugin.json` file. */ - unknownCrashedPlugins: ODUnknownCrashedPlugin[] = [] - - constructor(debug:ODDebugger){ - super(debug,"plugin") - this.classes = new ODPluginClassManager(debug) - } - - /**Check if a plugin has been loaded successfully and is available for usage.*/ - isPluginLoaded(id:ODValidId): boolean { - const newId = new ODId(id) - const plugin = this.get(newId) - return (plugin !== null && plugin.executed) - } -} - -/**## ODPluginData `interface` - * Parsed data from the `plugin.json` file in a plugin. - */ -export interface ODPluginData { - /**The name of this plugin (shown on startup) */ - name:string, - /**The id of this plugin. (Must be identical to directory name) */ - id:string, - /**The version of this plugin. */ - version:string, - /**The location of the start file of the plugin relative to the rootDir of the plugin */ - startFile:string, - /**A list of compatible versions. (e.g. `["OTv4.0.x", "OMv1.x.x"]`) (optional, will be required in future version) - * - `OT` --> Open Ticket support - * - `OM` --> Open Moderation support - */ - supportedVersions?:string[], - - /**Is this plugin enabled? */ - enabled:boolean, - /**The priority of this plugin. Higher priority will load before lower priority. */ - priority:number, - /**A list of events to register to the `opendiscord.events` global before loading any plugins. This way, plugins with a higher priority are able to use events from this plugin as well! */ - events:string[] - - /**Npm dependencies which are required for this plugin to work. */ - npmDependencies:string[], - /**Plugins which are required for this plugin to work. */ - requiredPlugins:string[], - /**Plugins which are incompatible with this plugin. */ - incompatiblePlugins:string[], - - /**Additional details about this plugin. */ - details:ODPluginDetails -} - -/**## ODPluginDetails `interface` - * Additional details in the `plugin.json` file from a plugin. - */ -export interface ODPluginDetails { - /**The main author of the plugin. Additional contributors can be specified in `contributors`. */ - author:string, - /**A list of plugin contributors. (optional, will be required in future version) */ - contributors?:string[], - /**A short description of this plugin. */ - shortDescription:string, - /**A large description of this plugin. */ - longDescription:string, - /**A URL to a cover image of this plugin. (currently unused) */ - imageUrl:string, - /**A URL to the website/project page of this plugin. (currently unused) */ - projectUrl:string, - /**A list of tags/categories that this plugin affects. */ - tags:string[] -} - -/**## ODPlugin `class` - * This is an Open Ticket plugin. - * - * It represents a single plugin in the `./plugins/` directory. - * All plugins are accessible via the `opendiscord.plugins` global. - * - * Don't re-execute plugins which are already enabled! It might break the bot or plugin. - */ -export class ODPlugin extends ODManagerData { - /**The name of the directory of this plugin. (same as id) */ - dir: string - /**All plugin data found in the `plugin.json` file. */ - data: ODPluginData - /**The name of this plugin. */ - name: string - /**The priority of this plugin. */ - priority: number - /**The version of this plugin. */ - version: ODVersion - /**The additional details of this plugin. */ - details: ODPluginDetails - - /**Is this plugin enabled? */ - enabled: boolean - /**Did this plugin execute successfully?. */ - executed: boolean - /**Did this plugin crash? (A reason is available in the `crashReason`) */ - crashed: boolean - /**The reason which caused this plugin to crash. */ - crashReason: null|"incompatible.plugin"|"missing.plugin"|"missing.dependency"|"incompatible.version"|"executed" = null - - constructor(dir:string, jsondata:ODPluginData){ - super(jsondata.id) - this.dir = dir - this.data = jsondata - this.name = jsondata.name - this.priority = jsondata.priority - this.version = ODVersion.fromString("plugin",jsondata.version) - this.details = jsondata.details - - this.enabled = jsondata.enabled - this.executed = false - this.crashed = false - } - - /**Get the startfile location relative to the `./plugins/` directory. (`./dist/plugins/`) when compiled) */ - getStartFile(){ - const newFile = this.data.startFile.replace(/\.ts$/,".js") - return nodepath.join(this.dir,newFile) - } - /**Execute this plugin. Returns `false` on crash. */ - async execute(debug:ODDebugger,force?:boolean): Promise { - if ((this.enabled && !this.crashed) || force){ - try{ - //import relative plugin directory path (works on windows & unix based systems) - const pluginPath = nodepath.join("../../../../plugins/",this.getStartFile()).replaceAll("\\","/") - await import(pluginPath) - debug.console.log("Plugin \""+this.id.value+"\" loaded successfully!","plugin") - this.executed = true - return true - }catch(error){ - this.crashed = true - this.crashReason = "executed" - - debug.console.log(error.message+", canceling plugin execution...","plugin",[ - {key:"path",value:"./plugins/"+this.dir} - ]) - debug.console.log("You can see more about this error in the ./otdebug.txt file!","info") - debug.console.debugfile.writeText(error.stack) - - return false - } - }else return true - } - - /**Check if a npm dependency exists. */ - #checkDependency(id:string){ - try{ - require.resolve(id) - return true - }catch{ - return false - } - } - - /**Get a list of all missing npm dependencies that are required for this plugin. */ - dependenciesInstalled(){ - const missing: string[] = [] - this.data.npmDependencies.forEach((d) => { - if (!this.#checkDependency(d)){ - missing.push(d) - } - }) - - return missing - } - /**Get a list of all missing plugins that are required for this plugin. */ - pluginsInstalled(manager:ODPluginManager){ - const missing: string[] = [] - this.data.requiredPlugins.forEach((p) => { - const plugin = manager.get(p) - if (!plugin || !plugin.enabled){ - missing.push(p) - } - }) - - return missing - } - /**Get a list of all enabled incompatible plugins that interfere with this plugin. */ - pluginsIncompatible(manager:ODPluginManager){ - const incompatible: string[] = [] - this.data.incompatiblePlugins.forEach((p) => { - const plugin = manager.get(p) - if (plugin && plugin.enabled){ - incompatible.push(p) - } - }) - - return incompatible - } - /**Get a list of all authors & contributors of this plugin. */ - getAuthors(): string[] { - return [this.details.author,...(this.details.contributors ?? [])] - } -} - -/**## ODPluginClassManager `class` - * This is an Open Ticket plugin class manager. - * - * It manages all managers registered by plugins! - * Plugins are able to register their own managers, handlers, functions, classes, ... here. - * By doing this, other plugins are also able to make use of it. - * This can be useful for plugins that want to extend other plugins. - * - * Use `isPluginLoaded()` to check if a plugin has been loaded before trying to access the manager. - */ -export class ODPluginClassManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"plugin class") - } -} \ No newline at end of file diff --git a/src/core/api/modules/post.ts b/src/core/api/modules/post.ts deleted file mode 100644 index ef6dfca..0000000 --- a/src/core/api/modules/post.ts +++ /dev/null @@ -1,90 +0,0 @@ -/////////////////////////////////////// -//POST MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODMessageBuildResult, ODMessageBuildSentResult } from "./builder" -import { ODDebugger } from "./console" -import * as discord from "discord.js" - -/**## ODPostManager `class` - * This is an Open Ticket post manager. - * - * It manages `ODPosts`'s for you. - * - * You can use this to get the logs channel of the bot (or some other static channel/category). - */ -export class ODPostManager extends ODManager> { - /**A reference to the main server of the bot */ - #guild: discord.Guild|null = null - - constructor(debug:ODDebugger){ - super(debug,"post") - } - - add(data:ODPost, overwrite?:boolean): boolean { - if (this.#guild) data.useGuild(this.#guild) - return super.add(data,overwrite) - } - /**Initialize the post manager & all posts. */ - async init(guild:discord.Guild){ - this.#guild = guild - for (const post of this.getAll()){ - post.useGuild(guild) - await post.init() - } - } -} - -/**## ODPost `class` - * This is an Open Ticket post class. - * - * A post is just a shortcut to a static discord channel or category. - * This can be used to get a specific channel over and over again! - * - * This class also contains utilities for sending messages via the Open Ticket builders. - */ -export class ODPost extends ODManagerData { - /**A reference to the main server of the bot */ - #guild: discord.Guild|null = null - /**Is this post already initialized? */ - ready: boolean = false - /**The discord.js channel */ - channel: ChannelType|null = null - /**The discord channel id */ - channelId: string - - constructor(id:ODValidId, channelId:string){ - super(id) - this.channelId = channelId - } - - /**Use a specific guild in this class for fetching the channel*/ - useGuild(guild:discord.Guild|null){ - this.#guild = guild - } - /**Change the channel id to another channel! */ - setChannelId(id:string){ - this.channelId = id - } - /**Initialize the discord.js channel of this post. */ - async init(){ - if (this.ready) return - if (!this.#guild) return this.channel = null - try{ - this.channel = await this.#guild.channels.fetch(this.channelId) as ChannelType - }catch{ - this.channel = null - } - this.ready = true - } - /**Send a message to this channel using the Open Ticket builder system */ - async send(msg:ODMessageBuildResult): Promise> { - if (!this.channel || !this.channel.isTextBased()) return {success:false,message:null} - try{ - const sent = await this.channel.send(msg.message) - return {success:true,message:sent} - }catch{ - return {success:false,message:null} - } - } -} \ No newline at end of file diff --git a/src/core/api/modules/progressbar.ts b/src/core/api/modules/progressbar.ts deleted file mode 100644 index b9b8289..0000000 --- a/src/core/api/modules/progressbar.ts +++ /dev/null @@ -1,232 +0,0 @@ -/////////////////////////////////////// -//PROGRESS BAR MODULE -/////////////////////////////////////// -import { ODSystemError, ODManager, ODManagerData, ODValidId } from "./base" -import { ODDebugger } from "./console" -import readline from "readline" - -/**## ODProgressBarRendererManager `class` - * This is an Open Ticket progress bar renderer manager. - * - * It is responsible for managing all console progress bar renderers in Open Ticket. - * - * A renderer is a function which will try to visualize the progress bar in the console. - */ -export class ODProgressBarRendererManager extends ODManager> { - constructor(debug:ODDebugger){ - super(debug,"progress bar renderer") - } -} - -/**## ODProgressBarManager `class` - * This is an Open Ticket progress bar manager. - * - * It is responsible for managing all console progress bars in Open Ticket. An example of this is the slash command registration progress bar. - * - * There are many types of progress bars available, but you can also create your own! - */ -export class ODProgressBarManager extends ODManager { - renderers: ODProgressBarRendererManager - - constructor(debug:ODDebugger){ - super(debug,"progress bar") - this.renderers = new ODProgressBarRendererManager(debug) - } -} - -/**## ODProgressBarRenderFunc `type` - * This is the render function for an Open Ticket console progress bar. - */ -export type ODProgressBarRenderFunc = (settings:Settings,min:number,max:number,value:number,prefix:string|null,suffix:string|null) => string - -/**## ODProgressBarRenderer `class` - * This is an Open Ticket console progress bar renderer. - * - * It is used to render a progress bar in the console of the bot. - * - * There are already a lot of default options available if you just want an easy progress bar! - */ -export class ODProgressBarRenderer extends ODManagerData { - settings: Settings - #render: ODProgressBarRenderFunc - - constructor(id:ODValidId,render:ODProgressBarRenderFunc,settings:Settings){ - super(id) - this.#render = render - this.settings = settings - } - - /**Render a progress bar using this renderer. */ - render(min:number,max:number,value:number,prefix:string|null,suffix:string|null){ - try { - return this.#render(this.settings,min,max,value,prefix,suffix) - }catch(err){ - process.emit("uncaughtException",err) - return "" - } - } - - withAdditionalSettings(settings:Partial): ODProgressBarRenderer { - const newSettings: Settings = {...this.settings} - for (const key of Object.keys(settings)){ - if (typeof settings[key] != "undefined") newSettings[key] = settings[key] - } - return new ODProgressBarRenderer(this.id,this.#render,newSettings) - } -} - -/**## ODProgressBar `class` - * This is an Open Ticket console progress bar. - * - * It is used to create a simple or advanced progress bar in the console of the bot. - * These progress bars are not visible in the `otdebug.txt` file and should only be used as extra visuals. - * - * Use other classes as existing templates or create your own progress bar from scratch using this class. - */ -export class ODProgressBar extends ODManagerData { - /**The renderer of this progress bar. */ - renderer: ODProgressBarRenderer<{}> - /**Is this progress bar currently active? */ - #active: boolean = false - /**A list of listeners when the progress bar stops. */ - #stopListeners: Function[] = [] - /**The current value of the progress bar. */ - protected value: number - /**The minimum value of the progress bar. */ - min: number - /**The maximum value of the progress bar. */ - max: number - /**The initial value of the progress bar. */ - initialValue: number - /**The prefix displayed in the progress bar. */ - prefix:string|null - /**The prefix displayed in the progress bar. */ - suffix:string|null - - /**Enable automatic stopping when reaching `min` or `max`. */ - autoStop: null|"min"|"max" - - constructor(id:ODValidId,renderer:ODProgressBarRenderer<{}>,min:number,max:number,value:number,autoStop:null|"min"|"max",prefix:string|null,suffix:string|null){ - super(id) - this.renderer = renderer - this.min = min - this.max = max - this.initialValue = this.#parseValue(value) - this.value = this.#parseValue(value) - this.autoStop = autoStop - this.prefix = prefix - this.suffix = suffix - } - /**Parse a value in such a way that it doesn't go below/above the min/max limits. */ - #parseValue(value:number){ - if (value > this.max) return this.max - else if (value < this.min) return this.min - else return value - } - /**Render progress bar to the console. */ - #renderStdout(){ - if (!this.#active) return - readline.clearLine(process.stdout,0) - readline.cursorTo(process.stdout,0) - process.stdout.write(this.renderer.render(this.min,this.max,this.value,this.prefix,this.suffix)) - } - /**Start showing this progress bar in the console. */ - start(): boolean { - if (this.#active) return false - this.value = this.#parseValue(this.initialValue) - this.#active = true - this.#renderStdout() - return true - } - /**Update this progress bar while active. (will automatically update the progress bar in the console) */ - protected update(value:number,stop?:boolean): boolean { - if (!this.#active) return false - this.value = this.#parseValue(value) - this.#renderStdout() - if (stop || (this.autoStop == "max" && this.value == this.max) || (this.autoStop == "min" && this.value == this.min)){ - process.stdout.write("\n") - this.#active = false - this.#stopListeners.forEach((cb) => cb()) - this.#stopListeners = [] - } - return true - } - /**Wait for the progress bar to finish. */ - finished(): Promise { - return new Promise((resolve) => { - this.#stopListeners.push(resolve) - }) - } -} - -/**## ODTimedProgressBar `class` - * This is an Open Ticket timed console progress bar. - * - * It is used to create a simple timed progress bar in the console. - * You can set a fixed duration (milliseconds) in the constructor. - */ -export class ODTimedProgressBar extends ODProgressBar { - /**The time in milliseconds. */ - time: number - /**The mode of the timer. */ - mode: "increasing"|"decreasing" - - constructor(id:ODValidId,renderer:ODProgressBarRenderer<{}>,time:number,mode:"increasing"|"decreasing",prefix:string|null,suffix:string|null){ - super(id,renderer,0,time,0,(mode == "increasing") ? "max" : "min",prefix,suffix) - this.time = time - this.mode = mode - } - - /**The timer which is used. */ - async #timer(ms:number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve() - },ms) - }) - } - /**Run the timed progress bar. */ - async #execute(){ - let i = 0 - const fragment = this.time/100 - while (i < 100){ - await this.#timer(fragment) - i++ - super.update((this.mode == "increasing") ? (i*fragment) : this.time-(i*fragment)) - } - } - start(){ - const res = super.start() - if (!res) return false - this.#execute() - return true - } -} - -/**## ODManualProgressBar `class` - * This is an Open Ticket manual console progress bar. - * - * It is used to create a simple manual progress bar in the console. - * You can update the progress manually using `update()`. - */ -export class ODManualProgressBar extends ODProgressBar { - constructor(id:ODValidId,renderer:ODProgressBarRenderer<{}>,amount:number,autoStop:null|"min"|"max",prefix:string|null,suffix:string|null){ - super(id,renderer,0,amount,0,autoStop,prefix,suffix) - } - /**Set the value of the progress bar. */ - set(value:number,stop?:boolean){ - super.update(value,stop) - } - /**Get the current value of the progress bar. */ - get(){ - return this.value - } - /**Increase the value of the progress bar. */ - increase(amount:number,stop?:boolean){ - super.update(this.value+amount,stop) - } - /**Decrease the value of the progress bar. */ - decrease(amount:number,stop?:boolean){ - super.update(this.value-amount,stop) - } -} \ No newline at end of file diff --git a/src/core/api/modules/responder.ts b/src/core/api/modules/responder.ts deleted file mode 100644 index 9f1da86..0000000 --- a/src/core/api/modules/responder.ts +++ /dev/null @@ -1,1418 +0,0 @@ -/////////////////////////////////////// -//RESPONDER MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODValidId, ODSystemError, ODManagerData } from "./base" -import * as discord from "discord.js" -import { ODWorkerManager, ODWorkerCallback, ODWorker } from "./worker" -import { ODDebugger } from "./console" -import { ODClientManager, ODContextMenu, ODSlashCommand, ODTextCommand, ODTextCommandInteractionOption } from "./client" -import { ODDropdownData, ODMessageBuildResult, ODMessageBuildSentResult, ODModalBuildResult } from "./builder" - -/**## ODResponderImplementation `class` - * This is an Open Ticket responder implementation. - * - * It is a basic implementation of the `ODWorkerManager` used by all `ODResponder` classes. - * - * This class can't be used stand-alone & needs to be extended from! - */ -export class ODResponderImplementation extends ODManagerData { - /**The manager that has all workers of this implementation */ - workers: ODWorkerManager - /**The `commandName` or `customId` needs to match this string or regex for this responder to be executed. */ - match: string|RegExp - - constructor(id:ODValidId, match:string|RegExp, callback?:ODWorkerCallback, priority?:number, callbackId?:ODValidId){ - super(id) - this.match = match - this.workers = new ODWorkerManager("descending") - if (callback) this.workers.add(new ODWorker(callbackId ? callbackId : id,priority ?? 0,callback)) - } - /**Execute all workers & return the result. */ - async respond(instance:Instance, source:Source, params:Params): Promise { - throw new ODSystemError("Tried to build an unimplemented ODResponderImplementation") - } -} - -/**## ODResponderTimeoutErrorCallback `type` - * This is the callback for the responder timeout function. It will be executed when something went wrong or the action takes too much time. - */ -export type ODResponderTimeoutErrorCallback = (instance:Instance, source:Source) => void|Promise - -/**## ODResponderManager `class` - * This is an Open Ticket responder manager. - * - * It contains all Open Ticket responders. Responders can respond to an interaction, button, dropdown, modal or command. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - write code once => reply to both slash & text commands at the same time! - * - know where the request came from & parse options/subcommands & without errors! - * - And so much more! - */ -export class ODResponderManager { - /**A manager for all (text & slash) command responders. */ - commands: ODCommandResponderManager - /**A manager for all button responders. */ - buttons: ODButtonResponderManager - /**A manager for all dropdown/select menu responders. */ - dropdowns: ODDropdownResponderManager - /**A manager for all modal responders. */ - modals: ODModalResponderManager - /**A manager for all context menu responders. */ - contextMenus: ODContextMenuResponderManager - /**A manager for all autocomplete responders. */ - autocomplete: ODAutocompleteResponderManager - - constructor(debug:ODDebugger, client:ODClientManager){ - this.commands = new ODCommandResponderManager(debug,"command responder",client) - this.buttons = new ODButtonResponderManager(debug,"button responder",client) - this.dropdowns = new ODDropdownResponderManager(debug,"dropdown responder",client) - this.modals = new ODModalResponderManager(debug,"modal responder",client) - this.contextMenus = new ODContextMenuResponderManager(debug,"context menu responder",client) - this.autocomplete = new ODAutocompleteResponderManager(debug,"autocomplete responder",client) - } -} - -/**## ODCommandResponderManager `class` - * This is an Open Ticket command responder manager. - * - * It contains all Open Ticket command responders. These can respond to text & slash commands. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - write code once => reply to both slash & text commands at the same time! - * - know where the request came from & parse options/subcommands & without errors! - * - And so much more! - */ -export class ODCommandResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODCommandResponder<"slash"|"text",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - //add the callback to the slash command manager - this.#client.slashCommands.onInteraction(data.match,(interaction,cmd) => { - const newData = this.get(data.id) - if (!newData) return - newData.respond(new ODCommandResponderInstance(interaction,cmd,this.#timeoutErrorCallback,this.#timeoutMs),"slash",{}) - }) - - //add the callback to the text command manager - this.#client.textCommands.onInteraction(data.prefix,data.match,(interaction,cmd,options) => { - const newData = this.get(data.id) - if (!newData) return - newData.respond(new ODCommandResponderInstance(interaction,cmd,this.#timeoutErrorCallback,this.#timeoutMs,options),"text",{}) - }) - - return res - } -} - -/**## ODCommandResponderInstanceOptions `class` - * This is an Open Ticket command responder instance options manager. - * - * This class will manage all options & subcommands from slash & text commands. - */ -export class ODCommandResponderInstanceOptions { - /**The interaction to get data from. */ - #interaction: discord.ChatInputCommandInteraction|discord.Message - /**The command which is related to the interaction. */ - #cmd:ODSlashCommand|ODTextCommand - /**A list of options which have been parsed by the text command parser. */ - #options: ODTextCommandInteractionOption[] - - constructor(interaction:discord.ChatInputCommandInteraction|discord.Message, cmd:ODSlashCommand|ODTextCommand, options?:ODTextCommandInteractionOption[]){ - this.#interaction = interaction - this.#cmd = cmd - this.#options = options ?? [] - } - - /**Get a string option. */ - getString(name:string,required:true): string - getString(name:string,required:false): string|null - getString(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getString(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getString() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "string" && opt.name == name) - if (opt && typeof opt.value == "string") return opt.value - else return null - - }else return null - } - /**Get a boolean option. */ - getBoolean(name:string,required:true): boolean - getBoolean(name:string,required:false): boolean|null - getBoolean(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getBoolean(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getBoolean() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "boolean" && opt.name == name) - if (opt && typeof opt.value == "boolean") return opt.value - else return null - - }else return null - } - /**Get a number option. */ - getNumber(name:string,required:true): number - getNumber(name:string,required:false): number|null - getNumber(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getNumber(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getNumber() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "number" && opt.name == name) - if (opt && typeof opt.value == "number") return opt.value - else return null - - }else return null - } - /**Get a channel option. */ - getChannel(name:string,required:true): discord.TextChannel|discord.VoiceChannel|discord.StageChannel|discord.NewsChannel|discord.MediaChannel|discord.ForumChannel|discord.CategoryChannel - getChannel(name:string,required:false): discord.TextChannel|discord.VoiceChannel|discord.StageChannel|discord.NewsChannel|discord.MediaChannel|discord.ForumChannel|discord.CategoryChannel|null - getChannel(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getChannel(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getChannel() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "channel" && opt.name == name) - if (opt && (opt.value instanceof discord.TextChannel || opt.value instanceof discord.VoiceChannel || opt.value instanceof discord.StageChannel || opt.value instanceof discord.NewsChannel || opt.value instanceof discord.MediaChannel || opt.value instanceof discord.ForumChannel || opt.value instanceof discord.CategoryChannel)) return opt.value - else return null - - }else return null - } - /**Get a role option. */ - getRole(name:string,required:true): discord.Role - getRole(name:string,required:false): discord.Role|null - getRole(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getRole(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getRole() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "role" && opt.name == name) - if (opt && opt.value instanceof discord.Role) return opt.value - else return null - - }else return null - } - /**Get a user option. */ - getUser(name:string,required:true): discord.User - getUser(name:string,required:false): discord.User|null - getUser(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getUser(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getUser() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "user" && opt.name == name) - if (opt && opt.value instanceof discord.User) return opt.value - else return null - - }else return null - } - /**Get a guild member option. */ - getGuildMember(name:string,required:true): discord.GuildMember - getGuildMember(name:string,required:false): discord.GuildMember|null - getGuildMember(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - const member = this.#interaction.options.getMember(name) - if (!member && required) throw new ODSystemError("ODCommandResponderInstanceOptions:getGuildMember() slash command option not found!") - return member - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getGuildMember() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "guildmember" && opt.name == name) - if (opt && opt.value instanceof discord.GuildMember) return opt.value - else return null - - }else return null - } - /**Get a mentionable option. */ - getMentionable(name:string,required:true): discord.User|discord.GuildMember|discord.Role - getMentionable(name:string,required:false): discord.User|discord.GuildMember|discord.Role|null - getMentionable(name:string,required:boolean){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getMentionable(name,required) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getGuildMember() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message){ - const opt = this.#options.find((opt) => opt.type == "mentionable" && opt.name == name) - if (opt && (opt.value instanceof discord.User || opt.value instanceof discord.GuildMember || opt.value instanceof discord.Role)) return opt.value - else return null - - }else return null - } - /**Get a subgroup. */ - getSubGroup(): string|null - getSubGroup(){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getSubcommandGroup(true) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getSubGroup() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message && this.#cmd instanceof ODTextCommand){ - //0: name, 1:sub/group, 2:sub - const splittedName: string[] = this.#cmd.builder.name.split(" ") - return splittedName[1] ?? null - - }else return null - } - /**Get a subcommand. */ - getSubCommand(): string|null - getSubCommand(){ - if (this.#interaction instanceof discord.ChatInputCommandInteraction){ - try { - return this.#interaction.options.getSubcommand(true) - }catch{ - throw new ODSystemError("ODCommandResponderInstanceOptions:getSubCommand() slash command option not found!") - } - - }else if (this.#interaction instanceof discord.Message && this.#cmd instanceof ODTextCommand){ - //0: name, 1:sub/group, 2:sub - const splittedName: string[] = this.#cmd.builder.name.split(" ") - - //return the second subcommand when there is a subgroup - if (splittedName.length > 2){ - return splittedName[2] ?? null - }else return splittedName[1] ?? null - - }else return null - } -} - - -/**## ODCommandResponderInstance `class` - * This is an Open Ticket command responder instance. - * - * An instance is an active slash interaction or used text command. You can reply to the command using `reply()` for both slash & text commands. - */ -export class ODCommandResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.ChatInputCommandInteraction|discord.Message - /**The command wich is the source of this instance. */ - cmd:ODSlashCommand|ODTextCommand - /**The type/source of instance. (from text or slash command) */ - type: "message"|"interaction" - /**Did a worker already reply to this instance/interaction? */ - didReply: boolean = false - /**The manager for all options of this command. */ - options: ODCommandResponderInstanceOptions - /**The user who triggered this command. */ - user: discord.User - /**The guild member who triggered this command. */ - member: discord.GuildMember|null - /**The guild where this command was triggered. */ - guild: discord.Guild|null - /**The channel where this command was triggered. */ - channel: discord.TextBasedChannel - - constructor(interaction:discord.ChatInputCommandInteraction|discord.Message, cmd:ODSlashCommand|ODTextCommand, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null, options?:ODTextCommandInteractionOption[]){ - if (!interaction.channel) throw new ODSystemError("ODCommandResponderInstance: Unable to find interaction channel!") - this.interaction = interaction - this.cmd = cmd - this.type = (interaction instanceof discord.Message) ? "message" : "interaction" - this.options = new ODCommandResponderInstanceOptions(interaction,cmd,options) - this.user = (interaction instanceof discord.Message) ? interaction.author : interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - - - setTimeout(async () => { - if (!this.didReply){ - try { - if (!errorCallback){ - this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ - content:":x: **Something went wrong while replying to this command!**" - }}) - }else{ - await errorCallback(this,(this.type == "interaction") ? "slash" : "text") - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - },timeoutMs ?? 2500) - } - - /**Reply to this command. */ - async reply(msg:ODMessageBuildResult): Promise> { - try { - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.type == "interaction" && this.interaction instanceof discord.ChatInputCommandInteraction){ - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:sent} - }else{ - const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }else if (this.type == "message" && this.interaction instanceof discord.Message && this.interaction.channel.type != discord.ChannelType.GroupDM){ - const sent = await this.interaction.channel.send(msg.message) - this.didReply = true - return {success:true,message:sent} - }else return {success:false,message:null} - }catch{ - return {success:false,message:null} - } - } - /**Defer this command. */ - async defer(ephemeral:boolean){ - if (this.type != "interaction" || !(this.interaction instanceof discord.ChatInputCommandInteraction)) return false - if (this.interaction.deferred || this.interaction.replied) return false - const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : [] - await this.interaction.deferReply({flags:msgFlags}) - this.didReply = true - return true - } - /**Show a modal as reply to this command. */ - async modal(modal:ODModalBuildResult){ - if (this.type != "interaction" || !(this.interaction instanceof discord.ChatInputCommandInteraction)) return false - if (this.interaction.deferred || this.interaction.replied) return false - await this.interaction.showModal(modal.modal) - this.didReply = true - return true - } -} - -/**## ODCommandResponder `class` - * This is an Open Ticket command responder. - * - * This class manages all workers which are executed when the related command is triggered. - */ -export class ODCommandResponder extends ODResponderImplementation { - /**The prefix of the text command needs to match this */ - prefix: string - - constructor(id:ODValidId, prefix:string, match:string|RegExp, callback?:ODWorkerCallback, priority?:number, callbackId?:ODValidId){ - super(id,match,callback,priority,callbackId) - this.prefix = prefix - } - - /**Respond to this command */ - async respond(instance:ODCommandResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} - -/**## ODButtonResponderManager `class` - * This is an Open Ticket button responder manager. - * - * It contains all Open Ticket button responders. These can respond to button interactions. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - know where the request came from! - * - And so much more! - */ -export class ODButtonResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - /**A list of listeners which will listen to the raw interactionCreate event from discord.js */ - #listeners: ((interaction:discord.ButtonInteraction) => void)[] = [] - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - - this.#client.client.on("interactionCreate",(interaction) => { - if (!interaction.isButton()) return - this.#listeners.forEach((cb) => cb(interaction)) - }) - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODButtonResponder<"button",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - this.#listeners.push((interaction) => { - const newData = this.get(data.id) - if (!newData) return - if ((typeof newData.match == "string") ? interaction.customId == newData.match : newData.match.test(interaction.customId)) newData.respond(new ODButtonResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"button",{}) - }) - - return res - } -} - -/**## ODButtonResponderInstance `class` - * This is an Open Ticket button responder instance. - * - * An instance is an active button interaction. You can reply to the button using `reply()`. - */ -export class ODButtonResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.ButtonInteraction - /**Did a worker already reply to this instance/interaction? */ - didReply: boolean = false - /**The user who triggered this button. */ - user: discord.User - /**The guild member who triggered this button. */ - member: discord.GuildMember|null - /**The guild where this button was triggered. */ - guild: discord.Guild|null - /**The channel where this button was triggered. */ - channel: discord.TextBasedChannel - /**The message this button originates from. */ - message: discord.Message - - constructor(interaction:discord.ButtonInteraction, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null){ - if (!interaction.channel) throw new ODSystemError("ODButtonResponderInstance: Unable to find interaction channel!") - this.interaction = interaction - this.user = interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - this.message = interaction.message - - setTimeout(async () => { - if (!this.didReply){ - try { - if (!errorCallback){ - this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ - content:":x: **Something went wrong while replying to this button!**" - }}) - }else{ - await errorCallback(this,"button") - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - },timeoutMs ?? 2500) - } - - /**Reply to this button. */ - async reply(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:sent} - }else{ - const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Update the message of this button. */ - async update(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - }else{ - const sent = await this.interaction.update(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Defer this button. */ - async defer(type:"reply"|"update", ephemeral:boolean){ - if (this.interaction.deferred || this.interaction.replied) return false - if (type == "reply"){ - const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : [] - await this.interaction.deferReply({flags:msgFlags}) - }else{ - await this.interaction.deferUpdate() - } - this.didReply = true - return true - } - /**Show a modal as reply to this button. */ - async modal(modal:ODModalBuildResult){ - if (this.interaction.deferred || this.interaction.replied) return false - await this.interaction.showModal(modal.modal) - this.didReply = true - return true - } - - /**Get a component from the original message of this button. */ - getMessageComponent(type:"button",id:string|RegExp): discord.ButtonComponent|null - getMessageComponent(type:"string-dropdown",id:string|RegExp): discord.StringSelectMenuComponent|null - getMessageComponent(type:"user-dropdown",id:string|RegExp): discord.UserSelectMenuComponent|null - getMessageComponent(type:"channel-dropdown",id:string|RegExp): discord.ChannelSelectMenuComponent|null - getMessageComponent(type:"role-dropdown",id:string|RegExp): discord.RoleSelectMenuComponent|null - getMessageComponent(type:"mentionable-dropdown",id:string|RegExp): discord.MentionableSelectMenuComponent|null - - getMessageComponent(type:"button"|"string-dropdown"|"user-dropdown"|"channel-dropdown"|"role-dropdown"|"mentionable-dropdown", id:string|RegExp): discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null { - let result: discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null = null - this.message.components.forEach((row) => { - if (row.type != discord.ComponentType.ActionRow) return - row.components.forEach((component) => { - if (type == "button" && component.type == discord.ComponentType.Button && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "string-dropdown" && component.type == discord.ComponentType.StringSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "user-dropdown" && component.type == discord.ComponentType.UserSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "channel-dropdown" && component.type == discord.ComponentType.ChannelSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "role-dropdown" && component.type == discord.ComponentType.RoleSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "mentionable-dropdown" && component.type == discord.ComponentType.MentionableSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - }) - }) - - return result - } - - /**Get the first embed of the original message if it exists. */ - getMessageEmbed(): discord.Embed|null { - return this.message.embeds[0] ?? null - } -} - -/**## ODButtonResponder `class` - * This is an Open Ticket button responder. - * - * This class manages all workers which are executed when the related button is triggered. - */ -export class ODButtonResponder extends ODResponderImplementation { - /**Respond to this button */ - async respond(instance:ODButtonResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} - -/**## ODDropdownResponderManager `class` - * This is an Open Ticket dropdown responder manager. - * - * It contains all Open Ticket dropdown responders. These can respond to dropdown interactions. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - know where the request came from! - * - And so much more! - */ -export class ODDropdownResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - /**A list of listeners which will listen to the raw interactionCreate event from discord.js */ - #listeners: ((interaction:discord.AnySelectMenuInteraction) => void)[] = [] - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - - this.#client.client.on("interactionCreate",(interaction) => { - if (!interaction.isAnySelectMenu()) return - this.#listeners.forEach((cb) => cb(interaction)) - }) - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODDropdownResponder<"dropdown",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - this.#listeners.push((interaction) => { - const newData = this.get(data.id) - if (!newData) return - if ((typeof newData.match == "string") ? interaction.customId == newData.match : newData.match.test(interaction.customId)) newData.respond(new ODDropdownResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"dropdown",{}) - }) - - return res - } -} - -/**## ODDropdownResponderInstanceValues `class` - * This is an Open Ticket dropdown responder instance values manager. - * - * This class will manage all values from the dropdowns & select menus. - */ -export class ODDropdownResponderInstanceValues { - /**The interaction to get data from. */ - #interaction: discord.AnySelectMenuInteraction - /**The type of this dropdown. */ - #type: ODDropdownData["type"] - - constructor(interaction:discord.AnySelectMenuInteraction, type:ODDropdownData["type"]){ - this.#interaction = interaction - this.#type = type - - if (interaction.isChannelSelectMenu()){ - interaction.values - } - } - - /**Get the selected values. */ - getStringValues(): string[] { - try { - return this.#interaction.values - }catch{ - throw new ODSystemError("ODDropdownResponderInstanceValues:getStringValues() invalid values!") - } - } - /**Get the selected roles. */ - async getRoleValues(): Promise { - if (this.#type != "role") throw new ODSystemError("ODDropdownResponderInstanceValues:getRoleValues() dropdown type isn't role!") - try { - const result: discord.Role[] = [] - for (const id of this.#interaction.values){ - if (!this.#interaction.guild) break - const role = await this.#interaction.guild.roles.fetch(id) - if (role) result.push(role) - } - return result - }catch{ - throw new ODSystemError("ODDropdownResponderInstanceValues:getRoleValues() invalid values!") - } - } - /**Get the selected users. */ - async getUserValues(): Promise { - if (this.#type != "role") throw new ODSystemError("ODDropdownResponderInstanceValues:getUserValues() dropdown type isn't user!") - try { - const result: discord.User[] = [] - for (const id of this.#interaction.values){ - const user = await this.#interaction.client.users.fetch(id) - if (user) result.push(user) - } - return result - }catch{ - throw new ODSystemError("ODDropdownResponderInstanceValues:getUserValues() invalid values!") - } - } - /**Get the selected channels. */ - async getChannelValues(): Promise { - if (this.#type != "role") throw new ODSystemError("ODDropdownResponderInstanceValues:getChannelValues() dropdown type isn't channel!") - try { - const result: discord.GuildBasedChannel[] = [] - for (const id of this.#interaction.values){ - if (!this.#interaction.guild) break - const guild = await this.#interaction.guild.channels.fetch(id) - if (guild) result.push(guild) - } - return result - }catch{ - throw new ODSystemError("ODDropdownResponderInstanceValues:getChannelValues() invalid values!") - } - } -} - -/**## ODDropdownResponderInstance `class` - * This is an Open Ticket dropdown responder instance. - * - * An instance is an active dropdown interaction. You can reply to the dropdown using `reply()`. - */ -export class ODDropdownResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.AnySelectMenuInteraction - /**Did a worker already reply to this instance/interaction? */ - didReply: boolean = false - /**The dropdown type. */ - type: ODDropdownData["type"] - /**The manager for all values of this dropdown. */ - values: ODDropdownResponderInstanceValues - /**The user who triggered this dropdown. */ - user: discord.User - /**The guild member who triggered this dropdown. */ - member: discord.GuildMember|null - /**The guild where this dropdown was triggered. */ - guild: discord.Guild|null - /**The channel where this dropdown was triggered. */ - channel: discord.TextBasedChannel - /**The message this dropdown originates from. */ - message: discord.Message - - constructor(interaction:discord.AnySelectMenuInteraction, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null){ - if (!interaction.channel) throw new ODSystemError("ODDropdownResponderInstance: Unable to find interaction channel!") - this.interaction = interaction - if (interaction.isStringSelectMenu()){ - this.type = "string" - }else if (interaction.isRoleSelectMenu()){ - this.type = "role" - }else if (interaction.isUserSelectMenu()){ - this.type = "user" - }else if (interaction.isChannelSelectMenu()){ - this.type = "channel" - }else if (interaction.isMentionableSelectMenu()){ - this.type = "mentionable" - }else throw new ODSystemError("ODDropdownResponderInstance: invalid dropdown type!") - - this.values = new ODDropdownResponderInstanceValues(interaction,this.type) - this.user = interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - this.message = interaction.message - - setTimeout(async () => { - if (!this.didReply){ - try { - if (!errorCallback){ - this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ - content:":x: **Something went wrong while replying to this dropdown!**" - }}) - }else{ - await errorCallback(this,"dropdown") - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - },timeoutMs ?? 2500) - } - - /**Reply to this dropdown. */ - async reply(msg:ODMessageBuildResult): Promise> { - try { - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:sent} - }else{ - const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Update the message of this dropdown. */ - async update(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - }else{ - const sent = await this.interaction.update(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Defer this dropdown. */ - async defer(type:"reply"|"update", ephemeral:boolean){ - if (this.interaction.deferred || this.interaction.replied) return false - if (type == "reply"){ - const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : [] - await this.interaction.deferReply({flags:msgFlags}) - }else{ - await this.interaction.deferUpdate() - } - this.didReply = true - return true - } - /**Show a modal as reply to this dropdown. */ - async modal(modal:ODModalBuildResult){ - if (this.interaction.deferred || this.interaction.replied) return false - await this.interaction.showModal(modal.modal) - this.didReply = true - return true - } - - /**Get a component from the original message of this dropdown. */ - getMessageComponent(type:"button",id:string|RegExp): discord.ButtonComponent|null - getMessageComponent(type:"string-dropdown",id:string|RegExp): discord.StringSelectMenuComponent|null - getMessageComponent(type:"user-dropdown",id:string|RegExp): discord.UserSelectMenuComponent|null - getMessageComponent(type:"channel-dropdown",id:string|RegExp): discord.ChannelSelectMenuComponent|null - getMessageComponent(type:"role-dropdown",id:string|RegExp): discord.RoleSelectMenuComponent|null - getMessageComponent(type:"mentionable-dropdown",id:string|RegExp): discord.MentionableSelectMenuComponent|null - - getMessageComponent(type:"button"|"string-dropdown"|"user-dropdown"|"channel-dropdown"|"role-dropdown"|"mentionable-dropdown", id:string|RegExp): discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null { - let result: discord.ButtonComponent|discord.StringSelectMenuComponent|discord.RoleSelectMenuComponent|discord.ChannelSelectMenuComponent|discord.MentionableSelectMenuComponent|discord.UserSelectMenuComponent|null = null - this.message.components.forEach((row) => { - if (row.type != discord.ComponentType.ActionRow) return - row.components.forEach((component) => { - if (type == "button" && component.type == discord.ComponentType.Button && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "string-dropdown" && component.type == discord.ComponentType.StringSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "user-dropdown" && component.type == discord.ComponentType.UserSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "channel-dropdown" && component.type == discord.ComponentType.ChannelSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "role-dropdown" && component.type == discord.ComponentType.RoleSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - else if (type == "mentionable-dropdown" && component.type == discord.ComponentType.MentionableSelect && component.customId && ((typeof id == "string") ? component.customId == id : id.test(component.customId))) result = component - }) - }) - - return result - } - - /**Get the first embed of the original message if it exists. */ - getMessageEmbed(): discord.Embed|null { - return this.message.embeds[0] ?? null - } -} - -/**## ODDropdownResponder `class` - * This is an Open Ticket dropdown responder. - * - * This class manages all workers which are executed when the related dropdown is triggered. - */ -export class ODDropdownResponder extends ODResponderImplementation { - /**Respond to this dropdown */ - async respond(instance:ODDropdownResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} - -/**## ODModalResponderManager `class` - * This is an Open Ticket modal responder manager. - * - * It contains all Open Ticket modal responders. These can respond to modal interactions. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - know where the request came from! - * - And so much more! - */ -export class ODModalResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - /**A list of listeners which will listen to the raw interactionCreate event from discord.js */ - #listeners: ((interaction:discord.ModalSubmitInteraction) => void)[] = [] - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - - this.#client.client.on("interactionCreate",(interaction) => { - if (!interaction.isModalSubmit()) return - this.#listeners.forEach((cb) => cb(interaction)) - }) - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODModalResponder<"modal",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - this.#listeners.push((interaction) => { - const newData = this.get(data.id) - if (!newData) return - if ((typeof newData.match == "string") ? interaction.customId == newData.match : newData.match.test(interaction.customId)) newData.respond(new ODModalResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"modal",{}) - }) - - return res - } -} - -/**## ODModalResponderInstanceValues `class` - * This is an Open Ticket modal responder instance values manager. - * - * This class will manage all fields from the modals. - */ -export class ODModalResponderInstanceValues { - /**The interaction to get data from. */ - #interaction: discord.ModalSubmitInteraction - - constructor(interaction:discord.ModalSubmitInteraction){ - this.#interaction = interaction - } - - /**Get the value of a text field. */ - getTextField(name:string,required:true): string - getTextField(name:string,required:false): string|null - getTextField(name:string,required:boolean){ - try { - const data = this.#interaction.fields.getField(name,discord.ComponentType.TextInput) - if (!data && required) throw new ODSystemError("ODModalResponderInstanceValues:getTextField() field not found!") - return (data) ? data.value : null - }catch{ - throw new ODSystemError("ODModalResponderInstanceValues:getTextField() field not found!") - } - } -} - -/**## ODModalResponderInstance `class` - * This is an Open Ticket modal responder instance. - * - * An instance is an active modal interaction. You can reply to the modal using `reply()`. - */ -export class ODModalResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.ModalSubmitInteraction - /**Did a worker already reply to this instance/interaction? */ - didReply: boolean = false - /**The manager for all fields of this modal. */ - values: ODModalResponderInstanceValues - /**The user who triggered this modal. */ - user: discord.User - /**The guild member who triggered this modal. */ - member: discord.GuildMember|null - /**The guild where this modal was triggered. */ - guild: discord.Guild|null - /**The channel where this modal was triggered. */ - channel: discord.TextBasedChannel|null - - constructor(interaction:discord.ModalSubmitInteraction, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null){ - this.interaction = interaction - this.values = new ODModalResponderInstanceValues(interaction) - this.user = interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - - setTimeout(async () => { - if (!this.didReply){ - try { - if (!errorCallback){ - this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ - content:":x: **Something went wrong while replying to this modal!**" - }}) - }else{ - await errorCallback(this,"modal") - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - },timeoutMs ?? 2500) - } - - /**Reply to this modal. */ - async reply(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - const sent = await this.interaction.followUp(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:sent} - }catch{ - return {success:false,message:null} - } - } - /**Update the message of this modal. */ - async update(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - }else{ - const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Defer this modal. */ - async defer(type:"reply"|"update", ephemeral:boolean){ - if (this.interaction.deferred || this.interaction.replied) return false - if (type == "reply"){ - const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : [] - await this.interaction.deferReply({flags:msgFlags}) - }else{ - await this.interaction.deferUpdate() - } - this.didReply = true - return true - } -} - -/**## ODModalResponder `class` - * This is an Open Ticket modal responder. - * - * This class manages all workers which are executed when the related modal is triggered. - */ -export class ODModalResponder extends ODResponderImplementation { - /**Respond to this modal */ - async respond(instance:ODModalResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} - -/**## ODContextMenuResponderManager `class` - * This is an Open Ticket context menu responder manager. - * - * It contains all Open Ticket context menu responders. These can respond to user/message context menu interactions. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - know where the request came from! - * - And so much more! - */ -export class ODContextMenuResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODContextMenuResponder<"context-menu",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - this.#client.contextMenus.onInteraction(data.match,(interaction,cmd) => { - const newData = this.get(data.id) - if (!newData) return - newData.respond(new ODContextMenuResponderInstance(interaction,cmd,this.#timeoutErrorCallback,this.#timeoutMs),"context-menu",{}) - }) - - return res - } -} - -/**## ODContextMenuResponderInstance `class` - * This is an Open Ticket context menu responder instance. - * - * An instance is an active context menu interaction. You can reply to the context menu using `reply()`. - */ -export class ODContextMenuResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.ContextMenuCommandInteraction - /**Did a worker already reply to this instance/interaction? */ - didReply: boolean = false - /**The context menu wich is the source of this instance. */ - menu:ODContextMenu - /**The user who triggered this context menu. */ - user: discord.User - /**The guild member who triggered this context menu. */ - member: discord.GuildMember|null - /**The guild where this context menu was triggered. */ - guild: discord.Guild|null - /**The channel where this context menu was triggered. */ - channel: discord.TextBasedChannel - /**The target of this context menu (user or message). */ - target: discord.Message|discord.User - - constructor(interaction:discord.ContextMenuCommandInteraction, menu:ODContextMenu, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null){ - if (!interaction.channel) throw new ODSystemError("ODContextMenuResponderInstance: Unable to find interaction channel!") - this.interaction = interaction - this.menu = menu - this.user = interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - if (interaction.isMessageContextMenuCommand()) this.target = interaction.targetMessage - else if (interaction.isUserContextMenuCommand()) this.target = interaction.targetUser - else throw new ODSystemError("ODContextMenuResponderInstance: Invalid context menu type. Should be of the type User/Message!") - - setTimeout(async () => { - if (!this.didReply){ - try { - if (!errorCallback){ - this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ - content:":x: **Something went wrong while replying to this context menu!**" - }}) - }else{ - await errorCallback(this,"context-menu") - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - },timeoutMs ?? 2500) - } - - /**Reply to this context menu. */ - async reply(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:sent} - }else{ - const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - } - }catch{ - return {success:false,message:null} - } - } - /**Update the message of this context menu. */ - async update(msg:ODMessageBuildResult): Promise> { - try{ - const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : [] - if (this.interaction.replied || this.interaction.deferred){ - const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) - this.didReply = true - return {success:true,message:await sent.fetch()} - }else throw new ODSystemError("Unable to update context menu interaction!") - }catch{ - return {success:false,message:null} - } - } - /**Defer this context menu. */ - async defer(type:"reply", ephemeral:boolean){ - if (this.interaction.deferred || this.interaction.replied) return false - if (type == "reply"){ - const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : [] - await this.interaction.deferReply({flags:msgFlags}) - } - this.didReply = true - return true - } - /**Show a modal as reply to this context menu. */ - async modal(modal:ODModalBuildResult){ - if (this.interaction.deferred || this.interaction.replied) return false - await this.interaction.showModal(modal.modal) - this.didReply = true - return true - } -} - -/**## ODContextMenuResponder `class` - * This is an Open Ticket context menu responder. - * - * This class manages all workers which are executed when the related context menu is triggered. - */ -export class ODContextMenuResponder extends ODResponderImplementation { - /**Respond to this button */ - async respond(instance:ODContextMenuResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} - -/**## ODAutocompleteResponderManager `class` - * This is an Open Ticket autocomplete responder manager. - * - * It contains all Open Ticket autocomplete responders. These can respond to autocomplete interactions. - * - * Using the Open Ticket responder system has a few advantages compared to vanilla discord.js: - * - plugins can extend/edit replies - * - automatically reply on error - * - independent workers (with priority) - * - fail-safe design using try-catch - * - know where the request came from! - * - And so much more! - */ -export class ODAutocompleteResponderManager extends ODManager> { - /**An alias to the Open Ticket client manager. */ - #client: ODClientManager - /**The callback executed when the default workers take too much time to reply. */ - #timeoutErrorCallback: ODResponderTimeoutErrorCallback|null = null - /**The amount of milliseconds before the timeout error callback is executed. */ - #timeoutMs: number|null = null - - constructor(debug:ODDebugger, debugname:string, client:ODClientManager){ - super(debug,debugname) - this.#client = client - } - - /**Set the message to send when the response times out! */ - setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback|null, ms:number|null){ - this.#timeoutErrorCallback = callback - this.#timeoutMs = ms - } - - add(data:ODAutocompleteResponder<"autocomplete",any>, overwrite?:boolean){ - const res = super.add(data,overwrite) - - this.#client.autocompletes.onInteraction(data.cmdMatch,data.match,(interaction) => { - const newData = this.get(data.id) - if (!newData) return - newData.respond(new ODAutocompleteResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"autocomplete",{}) - }) - - return res - } -} - -/**## ODAutocompleteResponderInstance `class` - * This is an Open Ticket autocomplete responder instance. - * - * An instance is an active autocomplete interaction. You can reply to the autocomplete using `reply()`. - */ -export class ODAutocompleteResponderInstance { - /**The interaction which is the source of this instance. */ - interaction: discord.AutocompleteInteraction - /**Did a worker already respond to this instance/interaction? */ - didRespond: boolean = false - /**The user who triggered this autocomplete. */ - user: discord.User - /**The guild member who triggered this autocomplete. */ - member: discord.GuildMember|null - /**The guild where this autocomplete was triggered. */ - guild: discord.Guild|null - /**The channel where this autocomplete was triggered. */ - channel: discord.TextBasedChannel - /**The target slash command option of this autocomplete. */ - target: discord.AutocompleteFocusedOption - - constructor(interaction:discord.AutocompleteInteraction, errorCallback:ODResponderTimeoutErrorCallback|null, timeoutMs:number|null){ - if (!interaction.channel) throw new ODSystemError("ODAutocompleteResponderInstance: Unable to find interaction channel!") - this.interaction = interaction - this.user = interaction.user - this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null - this.guild = interaction.guild - this.channel = interaction.channel - this.target = interaction.options.getFocused(true) - - setTimeout(async () => { - if (!this.didRespond){ - process.emit("uncaughtException",new ODSystemError("Autocomplete responder instance failed to respond widthin 2.5sec!")) - } - },timeoutMs ?? 2500) - } - - /**Reply to this autocomplete. */ - async autocomplete(choices:(string|discord.ApplicationCommandOptionChoiceData)[]): Promise<{success:boolean}> { - const newChoices: (discord.ApplicationCommandOptionChoiceData)[] = choices.map((raw) => { - if (typeof raw == "string") return {name:raw,value:raw} - else return raw - }) - - try{ - if (this.interaction.responded){ - return {success:false} - }else{ - await this.interaction.respond(newChoices) - this.didRespond = true - return {success:true} - } - }catch(err){ - process.emit("uncaughtException",err) - return {success:false} - } - } - /**Reply to this autocomplete, but filter choices based on the input of the user. */ - async filteredAutocomplete(choices:(string|discord.ApplicationCommandOptionChoiceData)[]): Promise<{success:boolean}> { - const newChoices: (discord.ApplicationCommandOptionChoiceData)[] = choices.map((raw) => { - if (typeof raw == "string") return {name:raw,value:raw} - else return raw - }) - - const filteredChoices = newChoices.filter((choice) => choice.name.startsWith(this.target.value) || choice.value.toString().startsWith(this.target.value)).slice(0,25) - return await this.autocomplete(filteredChoices) - } -} - -/**## ODAutocompleteResponder `class` - * This is an Open Ticket autocomplete responder. - * - * This class manages all workers which are executed when the related autocomplete is triggered. - */ -export class ODAutocompleteResponder extends ODResponderImplementation { - /**The slash command of the autocomplete should match the following regex. */ - cmdMatch: string|RegExp - - constructor(id:ODValidId,cmdMatch:string|RegExp,match:string|RegExp,callback?:ODWorkerCallback,priority?:number,callbackId?:ODValidId){ - super(id,match,callback,priority,callbackId) - this.cmdMatch = cmdMatch - } - - /**Respond to this autocomplete interaction. */ - async respond(instance:ODAutocompleteResponderInstance, source:Source, params:Params){ - //wait for workers to finish - await this.workers.executeWorkers(instance,source,params) - } -} \ No newline at end of file diff --git a/src/core/api/modules/session.ts b/src/core/api/modules/session.ts deleted file mode 100644 index 5be116b..0000000 --- a/src/core/api/modules/session.ts +++ /dev/null @@ -1,155 +0,0 @@ -/////////////////////////////////////// -//SESSION MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODDebugger } from "./console" -import * as crypto from "crypto" - -/**## ODSessionManager `class` - * This is an Open Ticket session manager. - * - * It contains all sessions in Open Ticket. Sessions are a sort of temporary storage which will be cleared when the bot stops. - * Data in sessions have a randomly generated key which will always be unique. - * - * Visit the `ODSession` class for more info - */ -export class ODSessionManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"session") - } -} - -/**## ODSessionInstance `interface` - * This interface represents a single session instance. It contains an id, data & some dates. - */ -export interface ODSessionInstance { - /**The id of this session instance. */ - id:string, - /**The creation date of this session instance. */ - creation:number, - /**The custom amount of minutes before this session expires. */ - timeout:number|null, - /**This is the data from this session instance */ - data:any -} - -/**## ODSessionTimeoutCallback `type` - * This is the callback used for session timeout listeners. - */ -export type ODSessionTimeoutCallback = (id:string, timeout:"default"|"custom", data:any, creation:Date) => void - -/**## ODSession `class` - * This is an Open Ticket session. - * - * It can be used to create 100% unique id's for usage in the bot. An id can also store additional data which isn't saved to the filesystem. - * You can almost compare it to the PHP session system. - */ -export class ODSession extends ODManagerData { - /**The history of previously generated instance ids. Used to reduce the risk of generating the same id twice. */ - #idHistory: string[] = [] - /**The max length of the instance id history. */ - #maxIdHistoryLength: number = 500 - /**An array of all the currently active session instances. */ - sessions: ODSessionInstance[] = [] - /**The default amount of minutes before a session automatically stops. */ - timeoutMinutes: number = 30 - /**The id of the auto-timeout session checker interval */ - #intervalId: NodeJS.Timeout - /**Listeners for when a session times-out. */ - #timeoutListeners: ODSessionTimeoutCallback[] = [] - - constructor(id:ODValidId, intervalSeconds?:number){ - super(id) - - //create the auto-timeout session checker - this.#intervalId = setInterval(() => { - const deletableSessions: {instance:ODSessionInstance,reason:"default"|"custom"}[] = [] - - //collect all deletable sessions - this.sessions.forEach((session) => { - if (session.timeout && (new Date().getTime() - session.creation) > session.timeout*60000){ - //stop session => custom timeout - deletableSessions.push({instance:session,reason:"custom"}) - }else if (!session.timeout && (new Date().getTime() - session.creation) > this.timeoutMinutes*60000){ - //stop session => default timeout - deletableSessions.push({instance:session,reason:"default"}) - } - }) - - //permanently delete sessions - deletableSessions.forEach((session) => { - const index = this.sessions.findIndex((s) => s.id === session.instance.id) - this.sessions.splice(index,1) - - //emit timeout listeners - this.#timeoutListeners.forEach((cb) => cb(session.instance.id,session.reason,session.instance.data,new Date(session.instance.creation))) - }) - - },((intervalSeconds) ? (intervalSeconds * 1000) : 60000)) - } - - /**Create a unique hex id of 8 characters and add it to the instance id history */ - #createUniqueId(): string { - const hex = crypto.randomBytes(4).toString("hex") - if (this.#idHistory.includes(hex)){ - return this.#createUniqueId() - }else{ - this.#idHistory.push(hex) - if (this.#idHistory.length > this.#maxIdHistoryLength) this.#idHistory.shift() - return hex - } - } - /**Stop the global interval that automatically deletes timed-out sessions. (This action can't be reverted!) */ - stopAutoTimeout(){ - clearInterval(this.#intervalId) - } - - /**Start a session instance with data. Returns the unique id required to access the session. */ - start(data?:any): string { - const id = this.#createUniqueId() - this.sessions.push({ - id,data, - creation:new Date().getTime(), - timeout:null - }) - return id - } - /**Get the data of a session instance. Returns `null` when not found. */ - data(id:string): any|null { - const session = this.sessions.find((session) => session.id === id) - if (!session) return null - return session.data - } - /**Stop & delete a session instance. Returns `true` when sucessful. */ - stop(id:string): boolean { - const index = this.sessions.findIndex((session) => session.id === id) - if (index < 0) return false - this.sessions.splice(index,1) - return true - } - /**Update the data of a session instance. Returns `true` when sucessful. */ - update(id:string, data:any): boolean { - const session = this.sessions.find((session) => session.id === id) - if (!session) return false - session.data = data - return true - } - /**Change the global or session timeout minutes. Returns `true` when sucessful. */ - setTimeout(min:number, id?:string): boolean { - if (!id){ - //change global timeout minutes - this.timeoutMinutes = min - return true - }else{ - //change session instance timeout minutes - const session = this.sessions.find((session) => session.id === id) - if (!session) return false - session.timeout = min - return true - } - } - /**Listen for a session timeout (default or custom) */ - onTimeout(callback:ODSessionTimeoutCallback){ - this.#timeoutListeners.push(callback) - } -} \ No newline at end of file diff --git a/src/core/api/modules/startscreen.ts b/src/core/api/modules/startscreen.ts deleted file mode 100644 index 9aab04b..0000000 --- a/src/core/api/modules/startscreen.ts +++ /dev/null @@ -1,320 +0,0 @@ -/////////////////////////////////////// -//STARTSCREEN MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODDebugger, ODError, ODLiveStatusManager } from "./console" -import { ODFlag } from "./flag" -import { ODPlugin, ODUnknownCrashedPlugin } from "./plugin" -import ansis from "ansis" - -/**## ODStartScreenComponentRenderCallback `type` - * This is the render function of a startscreen component. It also sends the location of where the component is rendered. - */ -export type ODStartScreenComponentRenderCallback = (location:number) => string|Promise - -/**## ODStartScreenManager `class` - * This is an Open Ticket startscreen manager. - * - * This class is responsible for managing & rendering the startscreen of the bot. - * The startscreen is the part you see when the bot has started up successfully. (e.g. the Open Ticket logo, logs, livestatus, flags, ...) - */ -export class ODStartScreenManager extends ODManager { - /**Alias to the Open Ticket debugger. */ - #debug: ODDebugger - /**Alias to the livestatus manager. */ - livestatus: ODLiveStatusManager - - constructor(debug:ODDebugger,livestatus:ODLiveStatusManager){ - super(debug,"startscreen component") - this.#debug = debug - this.livestatus = livestatus - } - - /**Get all components in sorted order. */ - getSortedComponents(priority:"ascending"|"descending"){ - return this.getAll().sort((a,b) => { - if (priority == "ascending") return a.priority-b.priority - else return b.priority-a.priority - }) - } - /**Render all startscreen components in priority order. */ - async renderAllComponents(){ - const components = this.getSortedComponents("descending") - - let location = 0 - for (const component of components){ - try { - const renderedText = await component.renderAll(location) - console.log(renderedText) - this.#debug.console.debugfile.writeText("[STARTSCREEN] Component: \""+component.id+"\"\n"+ansis.strip(renderedText)) - }catch(e){ - this.#debug.console.log("Unable to render \""+component.id+"\" startscreen component!","error") - this.#debug.console.debugfile.writeErrorMessage(new ODError(e,"uncaughtException")) - } - location++ - } - } -} - -/**## ODStartScreenComponent `class` - * This is an Open Ticket startscreen component. - * - * This component can be rendered to the start screen of the bot. - * An optional priority can be specified to choose the location of the component. - * - * It's recommended to use pre-built components except if you really need a custom one. - */ -export class ODStartScreenComponent extends ODManagerData { - /**The priority of this component. */ - priority: number - /**An optional render function which will be inserted before the default renderer. */ - renderBefore: ODStartScreenComponentRenderCallback|null = null - /**The render function which will render the contents of this component. */ - render: ODStartScreenComponentRenderCallback - /**An optional render function which will be inserted behind the default renderer. */ - renderAfter: ODStartScreenComponentRenderCallback|null = null - - constructor(id:ODValidId, priority:number, render:ODStartScreenComponentRenderCallback){ - super(id) - this.priority = priority - this.render = render - } - - /**Render this component and combine it with the `renderBefore` & `renderAfter` contents. */ - async renderAll(location:number){ - const textBefore = (this.renderBefore) ? await this.renderBefore(location) : "" - const text = await this.render(location) - const textAfter = (this.renderAfter) ? await this.renderAfter(location) : "" - return (textBefore ? textBefore+"\n" : "")+text+(textAfter ? "\n"+textAfter : "") - } -} - -/**## ODStartScreenProperty `type` - * This interface contains properties used in a few default templates of the startscreen component. - */ -export interface ODStartScreenProperty { - /**The key or name of this property. */ - key:string, - /**The value or contents of this property. */ - value:string -} - -/**## ODStartScreenLogoComponent `class` - * This is an Open Ticket startscreen logo component. - * - * This component will render an ASCII art logo (from an array) to the startscreen. Every property in the array is another row. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenLogoComponent extends ODStartScreenComponent { - /**The ASCII logo contents. */ - logo: string[] - /**When enabled, the component will add a new line above the logo. */ - topPadding: boolean - /**When enabled, the component will add a new line below the logo. */ - bottomPadding: boolean - /**The color of the logo in hex format. */ - logoHexColor: string - - constructor(id:ODValidId, priority:number, logo:string[], topPadding?:boolean, bottomPadding?:boolean, logoHexColor?:string){ - super(id,priority,() => { - const renderedTop = (this.topPadding ? "\n" : "") - const renderedLogo = this.logo.join("\n") - const renderedBottom = (this.bottomPadding ? "\n" : "") - return ansis.hex(this.logoHexColor)(renderedTop+renderedLogo+renderedBottom) - }) - this.logo = logo - this.topPadding = topPadding ?? false - this.bottomPadding = bottomPadding ?? false - this.logoHexColor = logoHexColor ?? "#f8ba00" - } -} - -/**## ODStartScreenHeaderAlignmentSettings `type` - * This interface contains all settings used in the startscreen header component. - */ -export interface ODStartScreenHeaderAlignmentSettings { - /**The alignment settings for this header. */ - align:"center"|"left"|"right", - /**The width or component to use when calculating center & right alignment. */ - width:number|ODStartScreenComponent -} - -/**## ODStartScreenHeaderComponent `class` - * This is an Open Ticket startscreen header component. - * - * This component will render a header to the startscreen. Properties can be aligned left, right or centered. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenHeaderComponent extends ODStartScreenComponent { - /**All properties of this header component. */ - properties: ODStartScreenProperty[] - /**The spacer used between properties. */ - spacer: string - /**The alignment settings of this header component. */ - align: ODStartScreenHeaderAlignmentSettings|null - - constructor(id:ODValidId, priority:number, properties:ODStartScreenProperty[], spacer?:string, align?:ODStartScreenHeaderAlignmentSettings){ - super(id,priority,async () => { - const renderedProperties = ansis.bold(this.properties.map((prop) => prop.key+": "+prop.value).join(this.spacer)) - if (!this.align || this.align.align == "left"){ - return renderedProperties - }else if (this.align.align == "right"){ - const width = (typeof this.align.width == "number") ? this.align.width : ( - ansis.strip(await this.align.width.renderAll(0)).split("\n").map((row) => row.length).reduce((prev,curr) => { - if (prev < curr) return curr - else return prev - },0) - ) - const offset = width - ansis.strip(renderedProperties).length - if (offset < 0) return renderedProperties - else{ - return (" ".repeat(offset) + renderedProperties) - } - }else if (this.align.align == "center"){ - const width = (typeof this.align.width == "number") ? this.align.width : ( - ansis.strip(await this.align.width.renderAll(0)).split("\n").map((row) => row.length).reduce((prev,curr) => { - if (prev < curr) return curr - else return prev - }) - ) - const offset = Math.round((width - ansis.strip(renderedProperties).length)/2) - if (offset < 0) return renderedProperties - else{ - return (" ".repeat(offset) + renderedProperties) - } - } - return renderedProperties - }) - this.properties = properties - this.spacer = spacer ?? " - " - this.align = align ?? null - } -} - -/**## ODStartScreenCategoryComponent `class` - * This is an Open Ticket startscreen category component. - * - * This component will render a category to the startscreen. This will only render the category name. You'll need to provide your own renderer for the contents. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenCategoryComponent extends ODStartScreenComponent { - /**The name of this category. */ - name: string - /**When enabled, this category will still be rendered when the contents are empty. (enabled by default) */ - renderIfEmpty: boolean - - constructor(id:ODValidId, priority:number, name:string, render:ODStartScreenComponentRenderCallback, renderIfEmpty?:boolean){ - super(id,priority,async (location) => { - const contents = await render(location) - if (contents != "" || this.renderIfEmpty){ - return ansis.bold.underline("\n"+name.toUpperCase()+(contents != "" ? ":\n" : ":")) + contents - }else return "" - }) - this.name = name - this.renderIfEmpty = renderIfEmpty ?? true - } -} - -/**## ODStartScreenPropertiesCategoryComponent `class` - * This is an Open Ticket startscreen properties category component. - * - * This component will render a properties category to the startscreen. This will list the properties in the category. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenPropertiesCategoryComponent extends ODStartScreenCategoryComponent { - /**The properties of this category component. */ - properties: ODStartScreenProperty[] - /**The hex color for the key/name of all the properties. */ - propertyHexColor: string - - constructor(id:ODValidId, priority:number, name:string, properties:ODStartScreenProperty[], propertyHexColor?:string, renderIfEmpty?:boolean){ - super(id,priority,name,() => { - return this.properties.map((prop) => ansis.hex(this.propertyHexColor)(prop.key+": ")+prop.value).join("\n") - },renderIfEmpty) - - this.properties = properties - this.propertyHexColor = propertyHexColor ?? "#f8ba00" - } -} - -/**## ODStartScreenFlagsCategoryComponent `class` - * This is an Open Ticket startscreen flags category component. - * - * This component will render a flags category to the startscreen. This will list the enabled flags in the category. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenFlagsCategoryComponent extends ODStartScreenCategoryComponent { - /**A list of all flags to render. */ - flags: ODFlag[] - - constructor(id:ODValidId, priority:number, flags:ODFlag[]){ - super(id,priority,"flags",() => { - return this.flags.filter((flag) => (flag.value == true)).map((flag) => ansis.blue("["+flag.name+"] "+flag.description)).join("\n") - },false) - this.flags = flags - } -} - -/**## ODStartScreenPluginsCategoryComponent `class` - * This is an Open Ticket startscreen plugins category component. - * - * This component will render a plugins category to the startscreen. This will list the enabled, disabled & crashed plugins in the category. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenPluginsCategoryComponent extends ODStartScreenCategoryComponent { - /**A list of all plugins to render. */ - plugins: ODPlugin[] - /**A list of all crashed plugins to render. */ - unknownCrashedPlugins: ODUnknownCrashedPlugin[] - - constructor(id:ODValidId, priority:number, plugins:ODPlugin[], unknownCrashedPlugins:ODUnknownCrashedPlugin[]){ - super(id,priority,"plugins",() => { - const disabledPlugins = this.plugins.filter((plugin) => !plugin.enabled) - - const renderedActivePlugins = this.plugins.filter((plugin) => plugin.enabled && plugin.executed).sort((a,b) => b.priority-a.priority).map((plugin) => ansis.green("โœ… ["+plugin.name+"] "+plugin.details.shortDescription)) - const renderedCrashedPlugins = this.plugins.filter((plugin) => plugin.enabled && plugin.crashed).sort((a,b) => b.priority-a.priority).map((plugin) => ansis.red("โŒ ["+plugin.name+"] "+plugin.details.shortDescription)) - const renderedDisabledPlugins = (disabledPlugins.length > 4) ? [ansis.gray("๐Ÿ’ค (+"+disabledPlugins.length+" disabled plugins)")] : disabledPlugins.sort((a,b) => b.priority-a.priority).map((plugin) => ansis.gray("๐Ÿ’ค ["+plugin.name+"] "+plugin.details.shortDescription)) - const renderedUnknownPlugins = unknownCrashedPlugins.map((plugin) => ansis.red("โŒ ["+plugin.name+"] "+plugin.description)) - - return [ - ...renderedActivePlugins, - ...renderedDisabledPlugins, - ...renderedCrashedPlugins, - ...renderedUnknownPlugins - ].join("\n") - },false) - this.plugins = plugins - this.unknownCrashedPlugins = unknownCrashedPlugins - } -} - -/**## ODStartScreenLiveStatusCategoryComponent `class` - * This is an Open Ticket startscreen livestatus category component. - * - * This component will render a livestatus category to the startscreen. This will list the livestatus messages in the category. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenLiveStatusCategoryComponent extends ODStartScreenCategoryComponent { - /**A reference to the Open Ticket livestatus manager. */ - livestatus: ODLiveStatusManager - - constructor(id:ODValidId, priority:number, livestatus:ODLiveStatusManager){ - super(id,priority,"livestatus",async () => { - const messages = await this.livestatus.getAllMessages() - return this.livestatus.renderer.render(messages) - },false) - this.livestatus = livestatus - } -} - -/**## ODStartScreenLogsCategoryComponent `class` - * This is an Open Ticket startscreen logs category component. - * - * This component will render a logs category to the startscreen. This will only render the logs category name. - * An optional priority can be specified to choose the location of the component. - */ -export class ODStartScreenLogCategoryComponent extends ODStartScreenCategoryComponent { - constructor(id:ODValidId, priority:number){ - super(id,priority,"logs",() => "",true) - } -} \ No newline at end of file diff --git a/src/core/api/modules/stat.ts b/src/core/api/modules/stat.ts deleted file mode 100644 index dccc1e6..0000000 --- a/src/core/api/modules/stat.ts +++ /dev/null @@ -1,313 +0,0 @@ -/////////////////////////////////////// -//STAT MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODSystemError, ODValidId } from "./base" -import { ODDebugger } from "./console" -import { ODDatabase, ODJsonDatabaseStructure } from "./database" -import * as discord from "discord.js" - -/**## ODValidStatValue `type` - * These are the only allowed types for a stat value to improve compatibility with different database systems. - */ -export type ODValidStatValue = string|number|boolean - -/**## ODStatsManagerInitCallback `type` - * This callback can be used to execute something when the stats have been initiated. - * - * By default this is used to clear stats from users that left the server or tickets which don't exist anymore. - */ -export type ODStatsManagerInitCallback = (database:ODJsonDatabaseStructure, deletables:ODJsonDatabaseStructure) => void|Promise - -/**## ODStatScopeSetMode `type` - * This type contains all valid methods for changing the value of a stat. - */ -export type ODStatScopeSetMode = "set"|"increase"|"decrease" - -/**## ODStatsManager `class` - * This is an Open Ticket stats manager. - * - * This class is responsible for managing all stats of the bot. - * Stats are categorized in "scopes" which can be accessed in this manager. - * - * Stats can be accessed in the individual scopes. - */ -export class ODStatsManager extends ODManager { - /**Alias to Open Ticket debugger. */ - #debug: ODDebugger - /**Alias to Open Ticket stats database. */ - database: ODDatabase|null = null - /**All the listeners for the init event. */ - #initListeners: ODStatsManagerInitCallback[] = [] - - constructor(debug:ODDebugger){ - super(debug,"stat scope") - this.#debug = debug - } - - /**Select the database to use to read/write all stats from/to. */ - useDatabase(database:ODDatabase){ - this.database = database - } - add(data:ODStatScope, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"stat") - if (this.database) data.useDatabase(this.database) - return super.add(data,overwrite) - } - /**Init all stats and run `onInit()` listeners. */ - async init(){ - if (!this.database) throw new ODSystemError("Unable to initialize stats scopes due to missing database!") - - //get all valid categories - const validCategories: string[] = [] - for (const scope of this.getAll()){ - validCategories.push(...scope.init()) - } - - //filter out the deletable stats - const deletableStats: ODJsonDatabaseStructure = [] - const data = await this.database.getAll() - data.forEach((data) => { - if (!validCategories.includes(data.category)) deletableStats.push(data) - }) - - //do additional deletion - for (const cb of this.#initListeners){ - await cb(data,deletableStats) - } - - //delete all deletable stats - for (const data of deletableStats){ - if (!this.database) return - await this.database.delete(data.category,data.key) - } - } - /**Reset all stats. (clears the entire database) */ - async reset(){ - if (!this.database) return - const data = await this.database.getAll() - for (const d of data){ - if (!this.database) return - await this.database.delete(d.category,d.key) - } - } - /**Run a function when the stats are initialized. This can be used to clear stats from users that left the server or tickets which don't exist anymore. */ - onInit(callback:ODStatsManagerInitCallback){ - this.#initListeners.push(callback) - } -} - -/**## ODStatScope `class` - * This is an Open Ticket stat scope. - * - * A scope can contain multiple stats. Every scope is seperated from other scopes. - * Here, you can read & write the values of all stats. - * - * The built-in Open Ticket scopes are: `global`, `user`, `ticket` - */ -export class ODStatScope extends ODManager { - /**The id of this statistics scope. */ - id: ODId - /**Is this stat scope already initialized? */ - ready: boolean = false - /**Alias to Open Ticket stats database. */ - database: ODDatabase|null = null - /**The name of this scope (used in embed title) */ - name:string - - constructor(id:ODValidId, name:string){ - super() - this.id = new ODId(id) - this.name = name - } - - /**Select the database to use to read/write all stats from/to. (Automatically assigned when used in `ODStatsManager`) */ - useDatabase(database:ODDatabase){ - this.database = database - } - /**Get the value of a statistic. The `scopeId` is the unique id of the user, channel, role, etc that the stats are related to. */ - async getStat(id:ODValidId, scopeId:string): Promise { - if (!this.database) return null - const newId = new ODId(id) - const data = await this.database.get(this.id.value+"_"+newId.value,scopeId) - - if (typeof data == "undefined"){ - //set stats to default value & return - return this.resetStat(id,scopeId) - }else if (typeof data == "string" || typeof data == "boolean" || typeof data == "number"){ - //return value received from database - return data - } - //return null on error - return null - } - /**Get the value of a statistic for all `scopeId`'s. The `scopeId` is the unique id of the user, channel, role, etc that the stats are related to. */ - async getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - if (!this.database) return [] - const newId = new ODId(id) - const data = await this.database.getCategory(this.id.value+"_"+newId.value) ?? [] - const output: {id:string,value:ODValidStatValue}[] = [] - - for (const stat of data){ - if (typeof stat.value == "string" || typeof stat.value == "boolean" || typeof stat.value == "number"){ - //return value received from database - output.push({id:stat.key,value:stat.value}) - } - } - - //return null on error - return output - } - /**Set, increase or decrease the value of a statistic. The `scopeId` is the unique id of the user, channel, role, etc that the stats are related to. */ - async setStat(id:ODValidId, scopeId:string, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - if (!this.database) return false - const stat = this.get(id) - if (!stat) return false - if (mode == "set" || typeof value != "number"){ - await this.database.set(this.id.value+"_"+stat.id.value,scopeId,value) - }else if (mode == "increase"){ - const currentValue = await this.getStat(id,scopeId) - if (typeof currentValue != "number") await this.database.set(this.id.value+"_"+stat.id.value,scopeId,0+value) - else await this.database.set(this.id.value+"_"+stat.id.value,scopeId,currentValue+value) - }else if (mode == "decrease"){ - const currentValue = await this.getStat(id,scopeId) - if (typeof currentValue != "number") await this.database.set(this.id.value+"_"+stat.id.value,scopeId,0-value) - else await this.database.set(this.id.value+"_"+stat.id.value,scopeId,currentValue-value) - } - return true - } - /**Reset the value of a statistic to the initial value. The `scopeId` is the unique id of the user, channel, role, etc that the stats are related to. */ - async resetStat(id:ODValidId, scopeId:string): Promise { - if (!this.database) return null - const stat = this.get(id) - if (!stat) return null - if (stat.value != null) await this.database.set(this.id.value+"_"+stat.id.value,scopeId,stat.value) - return stat.value - } - /**Initialize this stat scope & return a list of all statistic ids in the following format: `_` */ - init(): string[] { - //get all valid stats categories - this.ready = true - return this.getAll().map((stat) => this.id.value+"_"+stat.id.value) - } - /**Render all stats in this scope for usage in a discord message/embed. */ - async render(scopeId:string, guild:discord.Guild, channel:discord.TextBasedChannel, user:discord.User): Promise { - //sort from high priority to low - const derefArray = [...this.getAll()] - derefArray.sort((a,b) => { - return b.priority-a.priority - }) - const result: string[] = [] - - for (const stat of derefArray){ - try { - if (stat instanceof ODDynamicStat){ - //dynamic render (without value) - result.push(await stat.render("",scopeId,guild,channel,user)) - }else{ - //normal render (with value) - const value = await this.getStat(stat.id,scopeId) - if (value != null) result.push(await stat.render(value,scopeId,guild,channel,user)) - } - - }catch(err){ - process.emit("uncaughtException",err) - } - } - - return result.filter((stat) => stat !== "").join("\n") - } -} - -/**## ODStatGlobalScope `class` - * This is an Open Ticket stat global scope. - * - * A scope can contain multiple stats. Every scope is seperated from other scopes. - * Here, you can read & write the values of all stats. - * - * This scope is made specifically for the global stats of Open Ticket. - */ -export class ODStatGlobalScope extends ODStatScope { - getStat(id:ODValidId): Promise { - return super.getStat(id,"GLOBAL") - } - getAllStats(id:ODValidId): Promise<{id:string,value:ODValidStatValue}[]> { - return super.getAllStats(id) - } - setStat(id:ODValidId, value:ODValidStatValue, mode:ODStatScopeSetMode): Promise { - return super.setStat(id,"GLOBAL",value,mode) - } - resetStat(id:ODValidId): Promise { - return super.resetStat(id,"GLOBAL") - } - render(scopeId:"GLOBAL", guild:discord.Guild, channel:discord.TextBasedChannel, user: discord.User): Promise { - return super.render("GLOBAL",guild,channel,user) - } -} - -/**## ODStatRenderer `type` - * This callback will render a single statistic for a discord embed/message. - */ -export type ODStatRenderer = (value:ODValidStatValue, scopeId:string, guild:discord.Guild, channel:discord.TextBasedChannel, user:discord.User) => string|Promise - -/**## ODStat `class` - * This is an Open Ticket statistic. - * - * This single statistic doesn't do anything except defining the rules of this statistic. - * Use it in a stats scope to register a new statistic. A statistic can also include a priority to choose the render priority. - * - * It's recommended to use the `ODBasicStat` & `ODDynamicStat` classes instead of this one! - */ -export class ODStat extends ODManagerData { - /**The priority of this statistic. */ - priority: number - /**The render function of this statistic. */ - render: ODStatRenderer - /**The value of this statistic. */ - value: ODValidStatValue|null - - constructor(id:ODValidId, priority:number, render:ODStatRenderer, value?:ODValidStatValue){ - super(id) - this.priority = priority - this.render = render - this.value = value ?? null - } -} - -/**## ODBasicStat `class` - * This is an Open Ticket basic statistic. - * - * This single statistic will store a number, boolean or string in the database. - * Use it to create a simple statistic for any stats scope. - */ -export class ODBasicStat extends ODStat { - /**The name of this stat. Rendered in discord embeds/messages. */ - name: string - - constructor(id:ODValidId, priority:number, name:string, value:ODValidStatValue){ - super(id,priority,(value) => { - return ""+name+": `"+value.toString()+"`" - },value) - this.name = name - } -} - -/**## ODDynamicStatRenderer `type` - * This callback will render a single dynamic statistic for a discord embed/message. - */ -export type ODDynamicStatRenderer = (scopeId:string, guild:discord.Guild, channel:discord.TextBasedChannel, user:discord.User) => string|Promise - -/**## ODDynamicStat `class` - * This is an Open Ticket dynamic statistic. - * - * A dynamic statistic does not store anything in the database! Instead, it will execute a function to return a custom result. - * This can be used to show statistics which are not stored in the database. - * - * This is used in Open Ticket for the live ticket status, participants & system status. - */ -export class ODDynamicStat extends ODStat { - constructor(id:ODValidId, priority:number, render:ODDynamicStatRenderer){ - super(id,priority,(value,scopeId,guild,channel,user) => { - return render(scopeId,guild,channel,user) - }) - } -} \ No newline at end of file diff --git a/src/core/api/modules/verifybar.ts b/src/core/api/modules/verifybar.ts deleted file mode 100644 index 2b74277..0000000 --- a/src/core/api/modules/verifybar.ts +++ /dev/null @@ -1,61 +0,0 @@ -/////////////////////////////////////// -//VERIFYBAR MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" -import { ODMessage } from "./builder" -import { ODDebugger } from "./console" -import { ODButtonResponderInstance } from "./responder" -import * as discord from "discord.js" -import { ODWorkerManager } from "./worker" - -/**## ODVerifyBar `class` - * This is an Open Ticket verifybar. - * - * It is contains 2 sets of workers and a lot of utilities for the (โœ… โŒ) verifybars in the bot. - * - * It doesn't contain the code which activates or spawns the verifybars! - */ -export class ODVerifyBar extends ODManagerData { - /**All workers that will run when the verifybar is accepted. */ - success: ODWorkerManager|null}> - /**All workers that will run when the verifybar is stopped. */ - failure: ODWorkerManager|null}> - /**The message that will be built wen activating this verifybar. */ - message: ODMessage<"verifybar",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:ODVerifyBar,originalMessage:discord.Message}> - /**When disabled, it will skip the verifybar and instantly fire the `success` workers. */ - enabled: boolean - - constructor(id:ODValidId, message:ODMessage<"verifybar",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalMessage:discord.Message}>, enabled?:boolean){ - super(id) - this.success = new ODWorkerManager("descending") - this.failure = new ODWorkerManager("descending") - this.message = message - this.enabled = enabled ?? true - } - - /**Build the message and reply to a button with this verifybar. */ - async activate(responder:ODButtonResponderInstance){ - if (this.enabled){ - //show verifybar - const {guild,channel,user,message} = responder - await responder.update(await this.message.build("verifybar",{guild,channel,user,verifybar:this,originalMessage:message})) - }else{ - //instant success - if (this.success) await this.success.executeWorkers(responder,"verifybar",{data:null,verifybarMessage:null}) - } - } -} - -/**## ODVerifyBarManager `class` - * This is an Open Ticket verifybar manager. - * - * It contains all (โœ… โŒ) verifybars in the bot. - * The `ODVerifyBar` classes contain `ODWorkerManager`'s that will be fired when the continue/stop buttons are pressed. - * - * It doesn't contain the code which activates the verifybars! This should be implemented by your own. - */ -export class ODVerifyBarManager extends ODManager { - constructor(debug:ODDebugger){ - super(debug,"verifybar") - } -} \ No newline at end of file diff --git a/src/core/api/modules/worker.ts b/src/core/api/modules/worker.ts deleted file mode 100644 index 566603d..0000000 --- a/src/core/api/modules/worker.ts +++ /dev/null @@ -1,93 +0,0 @@ -/////////////////////////////////////// -//WORKER MODULE -/////////////////////////////////////// -import { ODId, ODManager, ODManagerData, ODValidId } from "./base" - -/**## ODWorkerCallback `type` - * This is the callback used in `ODWorker`! - */ -export type ODWorkerCallback = (instance:Instance, params:Params, source:Source, cancel:() => void) => void|Promise - -/**## ODWorker `class` - * This is an Open Ticket worker. - * - * You can compare it with a normal javascript callback, but slightly more advanced! - * - * - It has an `id` for identification of the function - * - A `priority` to know when to execute this callback (related to others) - * - It knows who called this callback (`source`) - * - And much more! - */ -export class ODWorker extends ODManagerData { - /**The priority of this worker */ - priority: number - /**The main callback of this worker */ - callback: ODWorkerCallback - - constructor(id:ODValidId, priority:number, callback:ODWorkerCallback){ - super(id) - this.priority = priority - this.callback = callback - } -} - -/**## ODWorker `class` - * This is an Open Ticket worker manager. - * - * It manages & executes `ODWorker`'s in the correct order. - * - * You can register a custom worker in this class to create a message or button. - */ -export class ODWorkerManager extends ODManager> { - /**The order of execution for workers inside this manager. */ - #priorityOrder: "ascending"|"descending" - /**The backup worker will be executed when one of the workers fails or cancels execution. */ - backupWorker: ODWorker<{reason:"error"|"cancel"},Source,Params>|null = null - - constructor(priorityOrder:"ascending"|"descending"){ - super() - this.#priorityOrder = priorityOrder - } - - /**Get all workers in sorted order. */ - getSortedWorkers(priority:"ascending"|"descending"){ - const derefArray = [...this.getAll()] - - return derefArray.sort((a,b) => { - if (priority == "ascending") return a.priority-b.priority - else return b.priority-a.priority - }) - } - /**Execute all workers on an instance using the given source & parameters. */ - async executeWorkers(instance:Instance, source:Source, params:Params){ - const derefParams = {...params} - const workers = this.getSortedWorkers(this.#priorityOrder) - let didCancel = false - let didCrash = false - - for (const worker of workers){ - if (didCancel) break - try { - await worker.callback(instance,derefParams,source,() => { - didCancel = true - }) - }catch(err){ - process.emit("uncaughtException",err) - didCrash = true - } - } - if (didCancel && this.backupWorker){ - try{ - await this.backupWorker.callback({reason:"cancel"},derefParams,source,() => {}) - }catch(err){ - process.emit("uncaughtException",err) - } - }else if (didCrash && this.backupWorker){ - try{ - await this.backupWorker.callback({reason:"error"},derefParams,source,() => {}) - }catch(err){ - process.emit("uncaughtException",err) - } - } - } -} \ No newline at end of file diff --git a/src/core/api/openticket/option.ts b/src/core/api/option.ts similarity index 73% rename from src/core/api/openticket/option.ts rename to src/core/api/option.ts index 69d2be9..b5034a6 100644 --- a/src/core/api/openticket/option.ts +++ b/src/core/api/option.ts @@ -1,13 +1,100 @@ /////////////////////////////////////// //OPENTICKET OPTION MODULE /////////////////////////////////////// -import { ODDatabase } from "../modules/database" -import { ODJsonConfig_DefaultOptionEmbedSettingsType, ODJsonConfig_DefaultOptionPingSettingsType } from "../defaults/config" -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODValidButtonColor, ODManagerData, ODSystemError } from "../modules/base" -import { ODDebugger } from "../modules/console" +import * as api from "@open-discord-bots/framework/api" import * as discord from "discord.js" import * as crypto from "crypto" -import { ODRoleUpdateMode } from "./role" +import { ODOptionsJsonConfig_TicketOptionEmbedSettings, ODOptionsJsonConfig_TicketOptionPingSettings } from "../mappings/config.js" +import { ODRoleUpdateMode } from "./role.js" + +/**## ODOptionIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODOption` class. + */ +export type ODOptionIdConstraint = Record> + +/**## ODTicketOptionIdMappings `interface` + * A list of all available IDs in the default `ODTicketOption` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTicketOptionIdMappings extends ODOptionIdConstraint { + "opendiscord:name":ODOptionData, + "opendiscord:description":ODOptionData, + + "opendiscord:button-emoji":ODOptionData, + "opendiscord:button-label":ODOptionData, + "opendiscord:button-color":ODOptionData, + + "opendiscord:admins":ODOptionData, + "opendiscord:admins-readonly":ODOptionData, + "opendiscord:allow-blacklisted-users":ODOptionData, + "opendiscord:questions":ODOptionData, + + "opendiscord:channel-prefix":ODOptionData, + "opendiscord:channel-suffix":ODOptionData<"user-name"|"user-nickname"|"user-id"|"random-number"|"random-hex"|"counter-dynamic"|"counter-fixed">, + "opendiscord:channel-category":ODOptionData, + "opendiscord:channel-topic":ODOptionData, + + "opendiscord:dm-message-enabled":ODOptionData, + "opendiscord:dm-message-text":ODOptionData, + "opendiscord:dm-message-embed":ODOptionData, + + "opendiscord:ticket-message-enabled":ODOptionData, + "opendiscord:ticket-message-text":ODOptionData, + "opendiscord:ticket-message-embed":ODOptionData, + "opendiscord:ticket-message-ping":ODOptionData, + + "opendiscord:autoclose-enable-hours":ODOptionData, + "opendiscord:autoclose-enable-leave":ODOptionData, + "opendiscord:autoclose-disable-claim":ODOptionData, + "opendiscord:autoclose-hours":ODOptionData, + + "opendiscord:autodelete-enable-days":ODOptionData, + "opendiscord:autodelete-enable-leave":ODOptionData, + "opendiscord:autodelete-disable-claim":ODOptionData, + "opendiscord:autodelete-days":ODOptionData, + + "opendiscord:cooldown-enabled":ODOptionData, + "opendiscord:cooldown-minutes":ODOptionData, + + "opendiscord:limits-enabled":ODOptionData, + "opendiscord:limits-maximum-global":ODOptionData, + "opendiscord:limits-maximum-user":ODOptionData + + "opendiscord:slowmode-enabled":ODOptionData, + "opendiscord:slowmode-seconds":ODOptionData, +} + +/**## ODWebsiteOptionIdMappings `interface` + * A list of all available IDs in the default `ODWebsiteOption` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODWebsiteOptionIdMappings extends ODOptionIdConstraint { + "opendiscord:name":ODOptionData, + "opendiscord:description":ODOptionData, + + "opendiscord:button-emoji":ODOptionData, + "opendiscord:button-label":ODOptionData, + + "opendiscord:url":ODOptionData, +} + +/**## ODRoleOptionIdMappings `interface` + * A list of all available IDs in the default `ODRoleOption` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODRoleOptionIdMappings extends ODOptionIdConstraint { + "opendiscord:name":ODOptionData, + "opendiscord:description":ODOptionData, + + "opendiscord:button-emoji":ODOptionData, + "opendiscord:button-label":ODOptionData, + "opendiscord:button-color":ODOptionData, + + "opendiscord:roles":ODOptionData, + "opendiscord:mode":ODOptionData, + "opendiscord:remove-roles-on-add":ODOptionData, + "opendiscord:add-on-join":ODOptionData +} /**## ODOptionManager `class` * This is an Open Ticket option manager. @@ -16,20 +103,17 @@ import { ODRoleUpdateMode } from "./role" * * All option types including: tickets, websites & reaction roles are stored here. */ -export class ODOptionManager extends ODManager { - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger +export class ODOptionManager extends api.ODManager { /**The option suffix manager used to generate channel suffixes for ticket names. */ suffix: ODOptionSuffixManager - constructor(debug:ODDebugger){ + constructor(debug:api.ODDebugger){ super(debug,"option") - this.#debug = debug this.suffix = new ODOptionSuffixManager(debug) } add(data:ODOption, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"option data") + data.useDebug(this.debug,"option data") return super.add(data,overwrite) } } @@ -41,7 +125,7 @@ export interface ODOptionDataJson { /**The id of this property. */ id:string, /**The value of this property. */ - value:ODValidJsonType + value:api.ODValidJsonType } /**## ODOptionDataJson `interface` @@ -65,15 +149,15 @@ export interface ODOptionJson { * * It's recommended to use `ODTicketOption`, `ODWebsiteOption` or `ODRoleOption` instead! */ -export class ODOption extends ODManager> { +export class ODOption extends api.ODManager> { /**The id of this option. (from the config) */ - id:ODId + id:api.ODId /**The type of this option. (e.g. `opendiscord:ticket`, `opendiscord:website`, `opendiscord:role`) */ type: string - constructor(id:ODValidId, type:string, data:ODOptionData[]){ + constructor(id:api.ODValidId, type:string, data:ODOptionData[]){ super() - this.id = new ODId(id) + this.id = new api.ODId(id) this.type = type data.forEach((data) => { this.add(data) @@ -81,7 +165,7 @@ export class ODOption extends ODManager> { } /**Convert this option to a JSON object for storing this option in the database. */ - toJson(version:ODVersion): ODOptionJson { + toJson(version:api.ODVersion): ODOptionJson { const data = this.getAll().map((data) => { return { id:data.id.toString(), @@ -110,22 +194,22 @@ export class ODOption extends ODManager> { * * When this property is edited, the database will be updated automatically. */ -export class ODOptionData extends ODManagerData { +export class ODOptionData extends api.ODManagerData { /**The value of this property. */ - #value: DataType + private rawValue: DataType - constructor(id:ODValidId, value:DataType){ + constructor(id:api.ODValidId, value:DataType){ super(id) - this.#value = value + this.rawValue = value } /**The value of this property. */ set value(value:DataType){ - this.#value = value + this.rawValue = value this._change() } get value(): DataType { - return this.#value + return this.rawValue } /**Refresh the database. Is only required to be used when updating `ODOptionData` with an object/array as value. */ refreshDatabase(){ @@ -133,61 +217,6 @@ export class ODOptionData extends ODManagerDat } } -/**## ODTicketOptionIds `type` - * This interface is a list of ids available in the `ODTicketOption` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODTicketOptionIds { - "opendiscord:name":ODOptionData, - "opendiscord:description":ODOptionData, - - "opendiscord:button-emoji":ODOptionData, - "opendiscord:button-label":ODOptionData, - "opendiscord:button-color":ODOptionData, - - "opendiscord:admins":ODOptionData, - "opendiscord:admins-readonly":ODOptionData, - "opendiscord:allow-blacklisted-users":ODOptionData, - "opendiscord:questions":ODOptionData, - - "opendiscord:channel-prefix":ODOptionData, - "opendiscord:channel-suffix":ODOptionData<"user-name"|"user-nickname"|"user-id"|"random-number"|"random-hex"|"counter-dynamic"|"counter-fixed">, - "opendiscord:channel-category":ODOptionData, - "opendiscord:channel-category-closed":ODOptionData, - "opendiscord:channel-category-backup":ODOptionData, - "opendiscord:channel-categories-claimed":ODOptionData<{user:string,category:string}[]>, - "opendiscord:channel-topic":ODOptionData, - - "opendiscord:dm-message-enabled":ODOptionData, - "opendiscord:dm-message-text":ODOptionData, - "opendiscord:dm-message-embed":ODOptionData, - - "opendiscord:ticket-message-enabled":ODOptionData, - "opendiscord:ticket-message-text":ODOptionData, - "opendiscord:ticket-message-embed":ODOptionData, - "opendiscord:ticket-message-ping":ODOptionData, - - "opendiscord:autoclose-enable-hours":ODOptionData, - "opendiscord:autoclose-enable-leave":ODOptionData, - "opendiscord:autoclose-disable-claim":ODOptionData, - "opendiscord:autoclose-hours":ODOptionData, - - "opendiscord:autodelete-enable-days":ODOptionData, - "opendiscord:autodelete-enable-leave":ODOptionData, - "opendiscord:autodelete-disable-claim":ODOptionData, - "opendiscord:autodelete-days":ODOptionData, - - "opendiscord:cooldown-enabled":ODOptionData, - "opendiscord:cooldown-minutes":ODOptionData, - - "opendiscord:limits-enabled":ODOptionData, - "opendiscord:limits-maximum-global":ODOptionData, - "opendiscord:limits-maximum-user":ODOptionData - - "opendiscord:slowmode-enabled":ODOptionData, - "opendiscord:slowmode-seconds":ODOptionData, -} - /**## ODTicketOption `class` * This is an Open Ticket ticket option. * @@ -198,28 +227,28 @@ export interface ODTicketOptionIds { export class ODTicketOption extends ODOption { type: "opendiscord:ticket" = "opendiscord:ticket" - constructor(id:ODValidId, data:ODOptionData[]){ + constructor(id:api.ODValidId, data:ODOptionData[]){ super(id,"opendiscord:ticket",data) } - get(id:OptionId): ODTicketOptionIds[OptionId] - get(id:ODValidId): ODOptionData|null + get>(id:OptionId): ODTicketOptionIdMappings[OptionId] + get(id:api.ODValidId): ODOptionData|null - get(id:ODValidId): ODOptionData|null { + get(id:api.ODValidId): ODOptionData|null { return super.get(id) } - remove(id:OptionId): ODTicketOptionIds[OptionId] - remove(id:ODValidId): ODOptionData|null + remove>(id:OptionId): ODTicketOptionIdMappings[OptionId] + remove(id:api.ODValidId): ODOptionData|null - remove(id:ODValidId): ODOptionData|null { + remove(id:api.ODValidId): ODOptionData|null { return super.remove(id) } - exists(id:keyof ODTicketOptionIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } @@ -228,20 +257,6 @@ export class ODTicketOption extends ODOption { } } -/**## ODWebsiteOptionIds `type` - * This interface is a list of ids available in the `ODWebsiteOption` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODWebsiteOptionIds { - "opendiscord:name":ODOptionData, - "opendiscord:description":ODOptionData, - - "opendiscord:button-emoji":ODOptionData, - "opendiscord:button-label":ODOptionData, - - "opendiscord:url":ODOptionData, -} - /**## ODWebsiteOption `class` * This is an Open Ticket website option. * @@ -252,28 +267,28 @@ export interface ODWebsiteOptionIds { export class ODWebsiteOption extends ODOption { type: "opendiscord:website" = "opendiscord:website" - constructor(id:ODValidId, data:ODOptionData[]){ + constructor(id:api.ODValidId, data:ODOptionData[]){ super(id,"opendiscord:website",data) } - get(id:OptionId): ODWebsiteOptionIds[OptionId] - get(id:ODValidId): ODOptionData|null + get>(id:OptionId): ODWebsiteOptionIdMappings[OptionId] + get(id:api.ODValidId): ODOptionData|null - get(id:ODValidId): ODOptionData|null { + get(id:api.ODValidId): ODOptionData|null { return super.get(id) } - remove(id:OptionId): ODWebsiteOptionIds[OptionId] - remove(id:ODValidId): ODOptionData|null + remove>(id:OptionId): ODWebsiteOptionIdMappings[OptionId] + remove(id:api.ODValidId): ODOptionData|null - remove(id:ODValidId): ODOptionData|null { + remove(id:api.ODValidId): ODOptionData|null { return super.remove(id) } - exists(id:keyof ODWebsiteOptionIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } @@ -282,24 +297,6 @@ export class ODWebsiteOption extends ODOption { } } -/**## ODRoleOptionIds `type` - * This interface is a list of ids available in the `ODRoleOption` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODRoleOptionIds { - "opendiscord:name":ODOptionData, - "opendiscord:description":ODOptionData, - - "opendiscord:button-emoji":ODOptionData, - "opendiscord:button-label":ODOptionData, - "opendiscord:button-color":ODOptionData, - - "opendiscord:roles":ODOptionData, - "opendiscord:mode":ODOptionData, - "opendiscord:remove-roles-on-add":ODOptionData, - "opendiscord:add-on-join":ODOptionData -} - /**## ODRoleOption `class` * This is an Open Ticket role option. * @@ -310,28 +307,28 @@ export interface ODRoleOptionIds { export class ODRoleOption extends ODOption { type: "opendiscord:role" = "opendiscord:role" - constructor(id:ODValidId, data:ODOptionData[]){ + constructor(id:api.ODValidId, data:ODOptionData[]){ super(id,"opendiscord:role",data) } - get(id:OptionId): ODRoleOptionIds[OptionId] - get(id:ODValidId): ODOptionData|null + get>(id:OptionId): ODRoleOptionIdMappings[OptionId] + get(id:api.ODValidId): ODOptionData|null - get(id:ODValidId): ODOptionData|null { + get(id:api.ODValidId): ODOptionData|null { return super.get(id) } - remove(id:OptionId): ODRoleOptionIds[OptionId] - remove(id:ODValidId): ODOptionData|null + remove>(id:OptionId): ODRoleOptionIdMappings[OptionId] + remove(id:api.ODValidId): ODOptionData|null - remove(id:ODValidId): ODOptionData|null { + remove(id:api.ODValidId): ODOptionData|null { return super.remove(id) } - exists(id:keyof ODRoleOptionIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } @@ -347,8 +344,8 @@ export class ODRoleOption extends ODOption { * * All ticket options should have a corresponding option suffix class. */ -export class ODOptionSuffixManager extends ODManager { - constructor(debug:ODDebugger){ +export class ODOptionSuffixManager extends api.ODManager { + constructor(debug:api.ODDebugger){ super(debug,"ticket suffix") } @@ -357,7 +354,7 @@ export class ODOptionSuffixManager extends ODManager { const suffix = this.getAll().find((suffix) => suffix.option.id.value == option.id.value) if (!suffix) return null try{ - const member = await this.#getMember(guild,user) + const member = await this.getMember(guild,user) if (!member) return null return await suffix.getSuffix(member) }catch(err){ @@ -365,7 +362,7 @@ export class ODOptionSuffixManager extends ODManager { return null } } - async #getMember(guild:discord.Guild,user:discord.User){ + private async getMember(guild:discord.Guild,user:discord.User){ try{ return await guild.members.fetch(user.id) }catch{ @@ -381,19 +378,17 @@ export class ODOptionSuffixManager extends ODManager { * * Use `getSuffix()` to get the new suffix! */ -export class ODOptionSuffix extends ODManagerData { +export abstract class ODOptionSuffix extends api.ODManagerData { /**The option of this suffix. */ option: ODTicketOption - constructor(id:ODValidId, option:ODTicketOption){ + constructor(id:api.ODValidId, option:ODTicketOption){ super(id) this.option = option } /**Get the suffix for a new ticket. */ - async getSuffix(member:discord.GuildMember): Promise { - throw new ODSystemError("Tried to use an unimplemented ODOptionSuffix!") - } + abstract getSuffix(member:discord.GuildMember): Promise } /**## ODOptionUserNameSuffix `class` @@ -444,16 +439,16 @@ export class ODOptionUserIdSuffix extends ODOptionSuffix { */ export class ODOptionCounterDynamicSuffix extends ODOptionSuffix { /**The database where the value of this counter is stored. */ - database: ODDatabase + database: api.ODDatabase - constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ + constructor(id:api.ODValidId, option:ODTicketOption, database:api.ODDatabase){ super(id,option) this.database = database - this.#init() + this.init() } /**Initialize the database for this suffix. */ - async #init(){ + private async init(){ if (!await this.database.exists("opendiscord:option-suffix-counter",this.option.id.value)) await this.database.set("opendiscord:option-suffix-counter",this.option.id.value,0) } async getSuffix(member:discord.GuildMember): Promise { @@ -474,16 +469,16 @@ export class ODOptionCounterDynamicSuffix extends ODOptionSuffix { */ export class ODOptionCounterFixedSuffix extends ODOptionSuffix { /**The database where the value of this counter is stored. */ - database: ODDatabase + database: api.ODDatabase - constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ + constructor(id:api.ODValidId, option:ODTicketOption, database:api.ODDatabase){ super(id,option) this.database = database - this.#init() + this.init() } /**Initialize the database for this suffix. */ - async #init(){ + private async init(){ if (!await this.database.exists("opendiscord:option-suffix-counter",this.option.id.value)) await this.database.set("opendiscord:option-suffix-counter",this.option.id.value,0) } async getSuffix(member:discord.GuildMember): Promise { @@ -508,33 +503,33 @@ export class ODOptionCounterFixedSuffix extends ODOptionSuffix { */ export class ODOptionRandomNumberSuffix extends ODOptionSuffix { /**The database where previous random numbers are stored. */ - database: ODDatabase + database: api.ODDatabase - constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ + constructor(id:api.ODValidId, option:ODTicketOption, database:api.ODDatabase){ super(id,option) this.database = database - this.#init() + this.init() } /**Initialize the database for this suffix. */ - async #init(){ + private async init(){ if (!await this.database.exists("opendiscord:option-suffix-history",this.option.id.value)) await this.database.set("opendiscord:option-suffix-history",this.option.id.value,[]) } /**Get a unique number for this suffix. */ - #generateUniqueValue(history:string[]): string { + protected generateUniqueValue(history:string[]): string { const rawNumber = Math.round(Math.random()*1000).toString() let number = rawNumber if (rawNumber.length == 1) number = "000"+rawNumber else if (rawNumber.length == 2) number = "00"+rawNumber else if (rawNumber.length == 3) number = "0"+rawNumber - if (history.includes(number)) return this.#generateUniqueValue(history) + if (history.includes(number)) return this.generateUniqueValue(history) else return number } async getSuffix(member:discord.GuildMember): Promise { const rawCurrentValues = await this.database.get("opendiscord:option-suffix-history",this.option.id.value) const currentValues = ((Array.isArray(rawCurrentValues)) ? rawCurrentValues : []) as string[] - const newValue = this.#generateUniqueValue(currentValues) + const newValue = this.generateUniqueValue(currentValues) currentValues.push(newValue) if (currentValues.length > 50) currentValues.shift() await this.database.set("opendiscord:option-suffix-history",this.option.id.value,currentValues) @@ -551,28 +546,28 @@ export class ODOptionRandomNumberSuffix extends ODOptionSuffix { */ export class ODOptionRandomHexSuffix extends ODOptionSuffix { /**The database where previous random hexes are stored. */ - database: ODDatabase + database: api.ODDatabase - constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ + constructor(id:api.ODValidId, option:ODTicketOption, database:api.ODDatabase){ super(id,option) this.database = database - this.#init() + this.init() } /**Initialize the database for this suffix. */ - async #init(){ + private async init(){ if (!await this.database.exists("opendiscord:option-suffix-history",this.option.id.value)) await this.database.set("opendiscord:option-suffix-history",this.option.id.value,[]) } /**Get a unique hex-string for this suffix. */ - #generateUniqueValue(history:string[]): string { + protected generateUniqueValue(history:string[]): string { const hex = crypto.randomBytes(2).toString("hex") - if (history.includes(hex)) return this.#generateUniqueValue(history) + if (history.includes(hex)) return this.generateUniqueValue(history) else return hex } async getSuffix(member:discord.GuildMember): Promise { const rawCurrentValues = await this.database.get("opendiscord:option-suffix-history",this.option.id.value) const currentValues = ((Array.isArray(rawCurrentValues)) ? rawCurrentValues : []) as string[] - const newValue = this.#generateUniqueValue(currentValues) + const newValue = this.generateUniqueValue(currentValues) currentValues.push(newValue) if (currentValues.length > 50) currentValues.shift() await this.database.set("opendiscord:option-suffix-history",this.option.id.value,currentValues) diff --git a/src/core/api/openticket/panel.ts b/src/core/api/panel.ts similarity index 65% rename from src/core/api/openticket/panel.ts rename to src/core/api/panel.ts index 633a0a8..8f5886b 100644 --- a/src/core/api/openticket/panel.ts +++ b/src/core/api/panel.ts @@ -1,9 +1,38 @@ /////////////////////////////////////// //OPENTICKET PANEL MODULE /////////////////////////////////////// -import { ODJsonConfig_DefaultPanelEmbedSettingsType } from "../defaults/config" -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODValidButtonColor, ODManagerData } from "../modules/base" -import { ODDebugger } from "../modules/console" +import * as api from "@open-discord-bots/framework/api" +import { ODPanelsJsonConfig_PanelEmbedSettings } from "../mappings/config.js" + + +/**## ODPanelIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODPanel` class. + */ +export type ODPanelIdConstraint = Record> + +/**## ODPanelIdMappings `interface` + * A list of all available IDs in the default `ODPanel` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODPanelIdMappings extends ODPanelIdConstraint { + "opendiscord:name":ODPanelData, + "opendiscord:options":ODPanelData, + "opendiscord:dropdown":ODPanelData, + + "opendiscord:text":ODPanelData, + "opendiscord:embed":ODPanelData, + + "opendiscord:dropdown-placeholder":ODPanelData, + + "opendiscord:enable-max-tickets-warning-text":ODPanelData, + "opendiscord:enable-max-tickets-warning-embed":ODPanelData, + + "opendiscord:describe-options-layout":ODPanelData<"simple"|"normal"|"detailed">, + "opendiscord:describe-options-custom-title":ODPanelData, + "opendiscord:describe-options-in-text":ODPanelData, + "opendiscord:describe-options-in-embed-fields":ODPanelData, + "opendiscord:describe-options-in-embed-description":ODPanelData +} /**## ODPanelManager `class` * This is an Open Ticket panel manager. @@ -12,17 +41,13 @@ import { ODDebugger } from "../modules/console" * * Panels are not stored in the database and will be parsed from the config every startup. */ -export class ODPanelManager extends ODManager { - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger - - constructor(debug:ODDebugger){ +export class ODPanelManager extends api.ODManager { + constructor(debug:api.ODDebugger){ super(debug,"option") - this.#debug = debug } add(data:ODPanel, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"option data") + data.useDebug(this.debug,"option data") return super.add(data,overwrite) } } @@ -34,7 +59,7 @@ export interface ODPanelDataJson { /**The id of this property. */ id:string, /**The value of this property. */ - value:ODValidJsonType + value:api.ODValidJsonType } /**## ODPanelDataJson `interface` @@ -49,49 +74,25 @@ export interface ODPanelJson { data:ODPanelDataJson[] } -/**## ODPanelIds `type` - * This interface is a list of ids available in the `ODPanel` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODPanelIds { - "opendiscord:name":ODPanelData, - "opendiscord:options":ODPanelData, - "opendiscord:dropdown":ODPanelData, - - "opendiscord:text":ODPanelData, - "opendiscord:embed":ODPanelData, - - "opendiscord:dropdown-placeholder":ODPanelData, - - "opendiscord:enable-max-tickets-warning-text":ODPanelData, - "opendiscord:enable-max-tickets-warning-embed":ODPanelData, - - "opendiscord:describe-options-layout":ODPanelData<"simple"|"normal"|"detailed">, - "opendiscord:describe-options-custom-title":ODPanelData, - "opendiscord:describe-options-in-text":ODPanelData, - "opendiscord:describe-options-in-embed-fields":ODPanelData, - "opendiscord:describe-options-in-embed-description":ODPanelData -} - /**## ODPanel `class` * This is an Open Ticket panel. * * This class contains all data related to this panel (parsed from the config). */ -export class ODPanel extends ODManager> { +export class ODPanel extends api.ODManager> { /**The id of this panel. (from the config) */ - id:ODId + id:api.ODId - constructor(id:ODValidId, data:ODPanelData[]){ + constructor(id:api.ODValidId, data:ODPanelData[]){ super() - this.id = new ODId(id) + this.id = new api.ODId(id) data.forEach((data) => { this.add(data) }) } /**Convert this panel to a JSON object for storing this panel in the database. */ - toJson(version:ODVersion): ODPanelJson { + toJson(version:api.ODVersion): ODPanelJson { const data = this.getAll().map((data) => { return { id:data.id.toString(), @@ -111,24 +112,24 @@ export class ODPanel extends ODManager> { return new ODPanel(json.id,json.data.map((data) => new ODPanelData(data.id,data.value))) } - get(id:PanelId): ODPanelIds[PanelId] - get(id:ODValidId): ODPanelData|null + get>(id:PanelId): ODPanelIdMappings[PanelId] + get(id:api.ODValidId): ODPanelData|null - get(id:ODValidId): ODPanelData|null { + get(id:api.ODValidId): ODPanelData|null { return super.get(id) } - remove(id:PanelId): ODPanelIds[PanelId] - remove(id:ODValidId): ODPanelData|null + remove>(id:PanelId): ODPanelIdMappings[PanelId] + remove(id:api.ODValidId): ODPanelData|null - remove(id:ODValidId): ODPanelData|null { + remove(id:api.ODValidId): ODPanelData|null { return super.remove(id) } - exists(id:keyof ODPanelIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } } @@ -140,22 +141,22 @@ export class ODPanel extends ODManager> { * * When this property is edited, the database will be updated automatically. */ -export class ODPanelData extends ODManagerData { +export class ODPanelData extends api.ODManagerData { /**The value of this property. */ - #value: DataType + private rawValue: DataType - constructor(id:ODValidId, value:DataType){ + constructor(id:api.ODValidId, value:DataType){ super(id) - this.#value = value + this.rawValue = value } /**The value of this property. */ set value(value:DataType){ - this.#value = value + this.rawValue = value this._change() } get value(): DataType { - return this.#value + return this.rawValue } /**Refresh the database. Is only required to be used when updating `ODPanelData` with an object/array as value. */ refreshDatabase(){ diff --git a/src/core/api/openticket/priority.ts b/src/core/api/priority.ts similarity index 62% rename from src/core/api/openticket/priority.ts rename to src/core/api/priority.ts index 90ea38a..0ad414b 100644 --- a/src/core/api/openticket/priority.ts +++ b/src/core/api/priority.ts @@ -1,9 +1,26 @@ /////////////////////////////////////// //OPENTICKET PRIORITY MODULE /////////////////////////////////////// -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODManagerData } from "../modules/base" -import { ODDebugger } from "../modules/console" -import * as discord from "discord.js" +import * as api from "@open-discord-bots/framework/api" + +/**## ODPriorityManagerIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODPriorityManager` class. + */ +export type ODPriorityManagerIdConstraint = Record + +/**## ODPriorityManagerIdMappings `interface` + * A list of all available IDs in the default `ODPriorityManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODPriorityManagerIdMappings extends ODPriorityManagerIdConstraint { + "opendiscord:urgent":ODPriorityLevel, + "opendiscord:very-high":ODPriorityLevel, + "opendiscord:high":ODPriorityLevel, + "opendiscord:normal":ODPriorityLevel, + "opendiscord:low":ODPriorityLevel, + "opendiscord:very-low":ODPriorityLevel, + "opendiscord:none":ODPriorityLevel, +} /**## ODPriorityManager `class` * This is an Open Ticket priority manager. @@ -12,13 +29,9 @@ import * as discord from "discord.js" * * Priorities levels can be changed/updated/translated by plugins to allow for more customisability. */ -export class ODPriorityManager extends ODManager { - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger - - constructor(debug:ODDebugger){ +export class ODPriorityManager extends api.ODManager { + constructor(debug:api.ODDebugger){ super(debug,"priority") - this.#debug = debug } /**Get an `ODPriorityLevel` from the priority level value. Returns a dummy value when the level doesn't exist. */ @@ -29,51 +42,34 @@ export class ODPriorityManager extends ODManager { listAvailableLevels(){ return this.getAll().map((lvl) => lvl.priority) } -} -/**## ODPriorityManagerIds `type` - * This interface is a list of ids available in the `ODPriorityManager` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODPriorityManagerIds { - "opendiscord:urgent":ODPriorityLevel, - "opendiscord:very-high":ODPriorityLevel, - "opendiscord:high":ODPriorityLevel, - "opendiscord:normal":ODPriorityLevel, - "opendiscord:low":ODPriorityLevel, - "opendiscord:very-low":ODPriorityLevel, - "opendiscord:none":ODPriorityLevel, -} - -/**## ODPriorityManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODPriorityManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.priorities`! - */ -export class ODPriorityManager_Default extends ODPriorityManager { - get(id:PriorityId): ODPriorityManagerIds[PriorityId] - get(id:ODValidId): ODPriorityLevel|null + get>(id:PriorityId): IdList[PriorityId] + get(id:api.ODValidId): ODPriorityLevel|null - get(id:ODValidId): ODPriorityLevel|null { + get(id:api.ODValidId): ODPriorityLevel|null { return super.get(id) } - remove(id:PriorityId): ODPriorityManagerIds[PriorityId] - remove(id:ODValidId): ODPriorityLevel|null + remove>(id:PriorityId): IdList[PriorityId] + remove(id:api.ODValidId): ODPriorityLevel|null - remove(id:ODValidId): ODPriorityLevel|null { + remove(id:api.ODValidId): ODPriorityLevel|null { return super.remove(id) } - exists(id:keyof ODPriorityManagerIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } } +/**## ODMappedPriorityManager `class + * A special class with types for the Open Ticket `ODPriorityManager` class. + */ +export class ODMappedPriorityManager extends ODPriorityManager {} + /**## ODPriorityLevel `class` * This is an Open Ticket priority level. * @@ -83,7 +79,7 @@ export class ODPriorityManager_Default extends ODPriorityManager { * * #### ๐Ÿšจ Negative priorities are treated as `disabled/no-priority`! */ -export class ODPriorityLevel extends ODManagerData { +export class ODPriorityLevel extends api.ODManagerData { /**The priority level itself. A negative number (e.g. `-1`) is treated as `disabled/no-priority`. */ priority:number /**The raw name of the level (used in text/slash command inputs). */ @@ -95,7 +91,7 @@ export class ODPriorityLevel extends ODManagerData { /**The emoji added to the channel name when the level is applied to a ticket. */ channelEmoji:string|null - constructor(id:ODValidId,priority:number,rawName:string,displayName:string,displayEmoji:string|null,channelEmoji:string|null){ + constructor(id:api.ODValidId,priority:number,rawName:string,displayName:string,displayEmoji:string|null,channelEmoji:string|null){ super(id) this.priority = priority this.rawName = rawName diff --git a/src/core/api/openticket/question.ts b/src/core/api/question.ts similarity index 63% rename from src/core/api/openticket/question.ts rename to src/core/api/question.ts index 5f19a4b..bef825f 100644 --- a/src/core/api/openticket/question.ts +++ b/src/core/api/question.ts @@ -1,8 +1,40 @@ /////////////////////////////////////// //OPENTICKET OPTION MODULE /////////////////////////////////////// -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODManagerData } from "../modules/base" -import { ODDebugger } from "../modules/console" +import * as api from "@open-discord-bots/framework/api" + +/**## ODQuestionIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODQuestion` class. + */ +export type ODQuestionIdConstraint = Record> + +/**## ODShortQuestionIdMappings `interface` + * A list of all available IDs in the default `ODShortQuestion` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODShortQuestionIdMappings extends ODQuestionIdConstraint { + "opendiscord:name":ODQuestionData, + "opendiscord:required":ODQuestionData, + "opendiscord:placeholder":ODQuestionData, + + "opendiscord:length-enabled":ODQuestionData, + "opendiscord:length-min":ODQuestionData, + "opendiscord:length-max":ODQuestionData +} + +/**## ODParagraphQuestionIdMappings `interface` + * A list of all available IDs in the default `ODParagraphQuestion` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODParagraphQuestionIdMappings extends ODQuestionIdConstraint { + "opendiscord:name":ODQuestionData, + "opendiscord:required":ODQuestionData, + "opendiscord:placeholder":ODQuestionData, + + "opendiscord:length-enabled":ODQuestionData, + "opendiscord:length-min":ODQuestionData, + "opendiscord:length-max":ODQuestionData +} /**## ODQuestionManager `class` * This is an Open Ticket question manager. @@ -11,17 +43,13 @@ import { ODDebugger } from "../modules/console" * * Questions are not stored in the database and will be parsed from the config every startup. */ -export class ODQuestionManager extends ODManager { - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger - - constructor(debug:ODDebugger){ +export class ODQuestionManager extends api.ODManager { + constructor(debug:api.ODDebugger){ super(debug,"question") - this.#debug = debug } add(data:ODQuestion, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"question data") + data.useDebug(this.debug,"question data") return super.add(data,overwrite) } } @@ -33,7 +61,7 @@ export interface ODQuestionDataJson { /**The id of this property. */ id:string, /**The value of this property. */ - value:ODValidJsonType + value:api.ODValidJsonType } /**## ODQuestionDataJson `interface` @@ -57,15 +85,15 @@ export interface ODQuestionJson { * * Use `ODShortQuestion` or `ODParagraphQuestion` instead! */ -export class ODQuestion extends ODManager> { +export class ODQuestion extends api.ODManager> { /**The id of this question. (from the config) */ - id:ODId + id:api.ODId /**The type of this question (e.g. `opendiscord:short` or `opendiscord:paragraph`) */ type: string - constructor(id:ODValidId, type:string, data:ODQuestionData[]){ + constructor(id:api.ODValidId, type:string, data:ODQuestionData[]){ super() - this.id = new ODId(id) + this.id = new api.ODId(id) this.type = type data.forEach((data) => { this.add(data) @@ -73,7 +101,7 @@ export class ODQuestion extends ODManager> { } /**Convert this question to a JSON object for storing this question in the database. */ - toJson(version:ODVersion): ODQuestionJson { + toJson(version:api.ODVersion): ODQuestionJson { const data = this.getAll().map((data) => { return { id:data.id.toString(), @@ -102,22 +130,22 @@ export class ODQuestion extends ODManager> { * * When this property is edited, the database will be updated automatically. */ -export class ODQuestionData extends ODManagerData { +export class ODQuestionData extends api.ODManagerData { /**The value of this property. */ - #value: DataType + private rawValue: DataType - constructor(id:ODValidId, value:DataType){ + constructor(id:api.ODValidId, value:DataType){ super(id) - this.#value = value + this.rawValue = value } /**The value of this property. */ set value(value:DataType){ - this.#value = value + this.rawValue = value this._change() } get value(): DataType { - return this.#value + return this.rawValue } /**Refresh the database. Is only required to be used when updating `ODQuestionData` with an object/array as value. */ refreshDatabase(){ @@ -125,19 +153,6 @@ export class ODQuestionData extends ODManagerD } } -/**## ODShortQuestionIds `type` - * This interface is a list of ids available in the `ODShortQuestion` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODShortQuestionIds { - "opendiscord:name":ODQuestionData, - "opendiscord:required":ODQuestionData, - "opendiscord:placeholder":ODQuestionData, - - "opendiscord:length-enabled":ODQuestionData, - "opendiscord:length-min":ODQuestionData, - "opendiscord:length-max":ODQuestionData -} /**## ODShortQuestion `class` * This is an Open Ticket short question. @@ -149,28 +164,28 @@ export interface ODShortQuestionIds { export class ODShortQuestion extends ODQuestion { type: "opendiscord:short" = "opendiscord:short" - constructor(id:ODValidId, data:ODQuestionData[]){ + constructor(id:api.ODValidId, data:ODQuestionData[]){ super(id,"opendiscord:short",data) } - get(id:QuestionId): ODShortQuestionIds[QuestionId] - get(id:ODValidId): ODQuestionData|null + get>(id:QuestionId): ODShortQuestionIdMappings[QuestionId] + get(id:api.ODValidId): ODQuestionData|null - get(id:ODValidId): ODQuestionData|null { + get(id:api.ODValidId): ODQuestionData|null { return super.get(id) } - remove(id:QuestionId): ODShortQuestionIds[QuestionId] - remove(id:ODValidId): ODQuestionData|null + remove>(id:QuestionId): ODShortQuestionIdMappings[QuestionId] + remove(id:api.ODValidId): ODQuestionData|null - remove(id:ODValidId): ODQuestionData|null { + remove(id:api.ODValidId): ODQuestionData|null { return super.remove(id) } - exists(id:keyof ODShortQuestionIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } @@ -179,20 +194,6 @@ export class ODShortQuestion extends ODQuestion { } } -/**## ODParagraphQuestionIds `type` - * This interface is a list of ids available in the `ODParagraphQuestion` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODParagraphQuestionIds { - "opendiscord:name":ODQuestionData, - "opendiscord:required":ODQuestionData, - "opendiscord:placeholder":ODQuestionData, - - "opendiscord:length-enabled":ODQuestionData, - "opendiscord:length-min":ODQuestionData, - "opendiscord:length-max":ODQuestionData -} - /**## ODParagraphQuestion `class` * This is an Open Ticket paragraph question. * @@ -203,28 +204,28 @@ export interface ODParagraphQuestionIds { export class ODParagraphQuestion extends ODQuestion { type: "opendiscord:paragraph" = "opendiscord:paragraph" - constructor(id:ODValidId, data:ODQuestionData[]){ + constructor(id:api.ODValidId, data:ODQuestionData[]){ super(id,"opendiscord:paragraph",data) } - get(id:QuestionId): ODParagraphQuestionIds[QuestionId] - get(id:ODValidId): ODQuestionData|null + get>(id:QuestionId): ODParagraphQuestionIdMappings[QuestionId] + get(id:api.ODValidId): ODQuestionData|null - get(id:ODValidId): ODQuestionData|null { + get(id:api.ODValidId): ODQuestionData|null { return super.get(id) } - remove(id:QuestionId): ODParagraphQuestionIds[QuestionId] - remove(id:ODValidId): ODQuestionData|null + remove>(id:QuestionId): ODParagraphQuestionIdMappings[QuestionId] + remove(id:api.ODValidId): ODQuestionData|null - remove(id:ODValidId): ODQuestionData|null { + remove(id:api.ODValidId): ODQuestionData|null { return super.remove(id) } - exists(id:keyof ODParagraphQuestionIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } diff --git a/src/core/api/openticket/role.ts b/src/core/api/role.ts similarity index 67% rename from src/core/api/openticket/role.ts rename to src/core/api/role.ts index b61c462..c496ea1 100644 --- a/src/core/api/openticket/role.ts +++ b/src/core/api/role.ts @@ -1,10 +1,25 @@ /////////////////////////////////////// //OPENTICKET ROLE MODULE /////////////////////////////////////// -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODManagerData } from "../modules/base" -import { ODDebugger } from "../modules/console" +import * as api from "@open-discord-bots/framework/api" import * as discord from "discord.js" +/**## ODRoleIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODRole` class. + */ +export type ODRoleIdConstraint = Record> + +/**## ODRoleIdMappings `interface` + * A list of all available IDs in the default `ODRole` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODRoleIdMappings extends ODRoleIdConstraint { + "opendiscord:roles":ODRoleData, + "opendiscord:mode":ODRoleData, + "opendiscord:remove-roles-on-add":ODRoleData, + "opendiscord:add-on-join":ODRoleData +} + /**## ODRoleManager `class` * This is an Open Ticket role manager. * @@ -12,17 +27,13 @@ import * as discord from "discord.js" * * Roles are not stored in the database and will be parsed from the config every startup. */ -export class ODRoleManager extends ODManager { - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger - - constructor(debug:ODDebugger){ +export class ODRoleManager extends api.ODManager { + constructor(debug:api.ODDebugger){ super(debug,"role") - this.#debug = debug } add(data:ODRole, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"role data") + data.useDebug(this.debug,"role data") return super.add(data,overwrite) } } @@ -34,7 +45,7 @@ export interface ODRoleDataJson { /**The id of this property. */ id:string, /**The value of this property. */ - value:ODValidJsonType + value:api.ODValidJsonType } /**## ODRoleJson `interface` @@ -49,17 +60,6 @@ export interface ODRoleJson { data:ODRoleDataJson[] } -/**## ODRoleIds `type` - * This interface is a list of ids available in the `ODRole` class. - * It's used to generate typescript declarations for this class. - */ -export interface ODRoleIds { - "opendiscord:roles":ODRoleData, - "opendiscord:mode":ODRoleData, - "opendiscord:remove-roles-on-add":ODRoleData, - "opendiscord:add-on-join":ODRoleData -} - /**## ODRole `class` * This is an Open Ticket role. * @@ -67,20 +67,20 @@ export interface ODRoleIds { * * These properties will be used to handle reaction role options. */ -export class ODRole extends ODManager> { +export class ODRole extends api.ODManager> { /**The id of this role. (from the config) */ - id:ODId + id:api.ODId - constructor(id:ODValidId, data:ODRoleData[]){ + constructor(id:api.ODValidId, data:ODRoleData[]){ super() - this.id = new ODId(id) + this.id = new api.ODId(id) data.forEach((data) => { this.add(data) }) } /**Convert this role to a JSON object for storing this role in the database. */ - toJson(version:ODVersion): ODRoleJson { + toJson(version:api.ODVersion): ODRoleJson { const data = this.getAll().map((data) => { return { id:data.id.toString(), @@ -100,24 +100,24 @@ export class ODRole extends ODManager> { return new ODRole(json.id,json.data.map((data) => new ODRoleData(data.id,data.value))) } - get(id:OptionId): ODRoleIds[OptionId] - get(id:ODValidId): ODRoleData|null + get>(id:OptionId): ODRoleIdMappings[OptionId] + get(id:api.ODValidId): ODRoleData|null - get(id:ODValidId): ODRoleData|null { + get(id:api.ODValidId): ODRoleData|null { return super.get(id) } - remove(id:OptionId): ODRoleIds[OptionId] - remove(id:ODValidId): ODRoleData|null + remove>(id:OptionId): ODRoleIdMappings[OptionId] + remove(id:api.ODValidId): ODRoleData|null - remove(id:ODValidId): ODRoleData|null { + remove(id:api.ODValidId): ODRoleData|null { return super.remove(id) } - exists(id:keyof ODRoleIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } } @@ -129,22 +129,22 @@ export class ODRole extends ODManager> { * * When this property is edited, the database will be updated automatically. */ -export class ODRoleData extends ODManagerData { +export class ODRoleData extends api.ODManagerData { /**The value of this property. */ - #value: DataType + private rawValue: DataType - constructor(id:ODValidId, value:DataType){ + constructor(id:api.ODValidId, value:DataType){ super(id) - this.#value = value + this.rawValue = value } /**The value of this property. */ set value(value:DataType){ - this.#value = value + this.rawValue = value this._change() } get value(): DataType { - return this.#value + return this.rawValue } /**Refresh the database. Is only required to be used when updating `ODRoleData` with an object/array as value. */ refreshDatabase(){ diff --git a/src/core/api/openticket/ticket.ts b/src/core/api/ticket.ts similarity index 73% rename from src/core/api/openticket/ticket.ts rename to src/core/api/ticket.ts index 2d8203a..be33d65 100644 --- a/src/core/api/openticket/ticket.ts +++ b/src/core/api/ticket.ts @@ -1,163 +1,25 @@ /////////////////////////////////////// //OPENTICKET TICKET MODULE /////////////////////////////////////// -import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODManagerData } from "../modules/base" -import { ODDebugger } from "../modules/console" -import { ODClientManager_Default } from "../defaults/client" -import { ODTicketOption } from "./option" +import * as api from "@open-discord-bots/framework/api" +import { ODTicketOption } from "./option.js" import * as discord from "discord.js" -/**## ODTicketManager `class` - * This is an Open Ticket ticket manager. - * - * This class manages all currently created tickets in the bot. - * - * All tickets which are added, removed or modified in this manager will be updated automatically in the database. +/**## ODTicketIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODTicket` class. */ -export class ODTicketManager extends ODManager { - /**A reference to the main server of the bot */ - #guild: discord.Guild|null = null - /**A reference to the Open Ticket client manager. */ - #client: ODClientManager_Default - /**A reference to the Open Ticket debugger. */ - #debug: ODDebugger +export type ODTicketIdConstraint = Record> - constructor(debug:ODDebugger, client:ODClientManager_Default){ - super(debug,"ticket") - this.#debug = debug - this.#client = client - } - - add(data:ODTicket, overwrite?:boolean): boolean { - data.useDebug(this.#debug,"ticket data") - return super.add(data,overwrite) - } - /**Use a specific guild in this class for fetching the channel*/ - useGuild(guild:discord.Guild|null){ - this.#guild = guild - } - /**Get the discord channel for a specific ticket. */ - async getTicketChannel(ticket:ODTicket): Promise { - if (!this.#guild) return null - try { - const channel = await this.#guild.channels.fetch(ticket.id.value) - if (!channel || !channel.isTextBased()) return null - return channel - }catch{ - return null - } - } - /**Get the main ticket message of a ticket channel when found. */ - async getTicketMessage(ticket:ODTicket): Promise|null> { - const msgId = ticket.get("opendiscord:ticket-message").value - if (!this.#guild || !msgId) return null - try { - const channel = await this.getTicketChannel(ticket) - if (!channel) return null - return await channel.messages.fetch(msgId) - }catch{ - return null - } - } - /**Shortcut for getting a discord.js user within a ticket. */ - async getTicketUser(ticket:ODTicket, user:"creator"|"closer"|"claimer"|"pinner"): Promise { - if (!this.#guild) return null - try { - if (user == "creator"){ - const creatorId = ticket.get("opendiscord:opened-by").value - if (!creatorId) return null - else return (await this.#guild.client.users.fetch(creatorId)) - - }else if (user == "closer"){ - const closerId = ticket.get("opendiscord:closed-by").value - if (!closerId) return null - else return (await this.#guild.client.users.fetch(closerId)) - - }else if (user == "claimer"){ - const claimerId = ticket.get("opendiscord:claimed-by").value - if (!claimerId) return null - else return (await this.#guild.client.users.fetch(claimerId)) - - }else if (user == "pinner"){ - const pinnerId = ticket.get("opendiscord:pinned-by").value - if (!pinnerId) return null - else return (await this.#guild.client.users.fetch(pinnerId)) - - }else return null - }catch {return null} - } - /**Shortcut for getting all users that are able to view a ticket. */ - async getAllTicketParticipants(ticket:ODTicket): Promise<{user:discord.User,role:"creator"|"participant"|"admin"}[]|null> { - if (!this.#guild) return null - const final: {user:discord.User,role:"creator"|"participant"|"admin"}[] = [] - const channel = await this.getTicketChannel(ticket) - if (!channel) return null - - //add creator - const creatorId = ticket.get("opendiscord:opened-by").value - if (creatorId){ - const creator = await this.#client.fetchUser(creatorId) - if (creator) final.push({user:creator,role:"creator"}) - } - - //add participants - const participants = ticket.get("opendiscord:participants").value.filter((p) => p.type == "user") - for (const p of participants){ - if (!final.find((u) => u.user.id == p.id)){ - const participant = await this.#client.fetchUser(p.id) - if (participant) final.push({user:participant,role:"participant"}) - } - } - - //add admin roles - const roles = ticket.get("opendiscord:participants").value.filter((p) => p.type == "role") - for (const r of roles){ - const role = await this.#client.fetchGuildRole(channel.guild,r.id) - if (role){ - role.members.forEach((member) => { - if (final.find((u) => u.user.id == member.id)) return - final.push({user:member.user,role:"admin"}) - }) - } - } - - return final - } -} - -/**## ODTicketDataJson `interface` - * The JSON representatation from a single ticket property. - */ -export interface ODTicketDataJson { - /**The id of this property. */ - id:string, - /**The value of this property. */ - value:ODValidJsonType -} - -/**## ODTicketDataJson `interface` - * The JSON representatation from a single ticket. - */ -export interface ODTicketJson { - /**The id of this ticket. */ - id:string, - /**The option id related to this ticket. */ - option:string, - /**The version of Open Ticket used to create this ticket. */ - version:string, - /**The full list of properties/variables related to this ticket. */ - data:ODTicketDataJson[] -} - -/**## ODTicketIds `type` - * This interface is a list of ids available in the `ODTicket` class. +/**## ODTicketIdMappings `interface` + * A list of all available IDs in the default `ODTicket` class in `opendiscord`. * It's used to generate typescript declarations for this class. */ -export interface ODTicketIds { +export interface ODTicketIdMappings extends ODTicketIdConstraint { "opendiscord:busy":ODTicketData, "opendiscord:ticket-message":ODTicketData, "opendiscord:participants":ODTicketData<{type:"role"|"user",id:string}[]>, "opendiscord:channel-suffix":ODTicketData, + "opendiscord:channel-renamed":ODTicketData, "opendiscord:previous-creators":ODTicketData, "opendiscord:open":ODTicketData, @@ -178,7 +40,7 @@ export interface ODTicketIds { "opendiscord:for-deletion":ODTicketData, "opendiscord:category":ODTicketData, - "opendiscord:category-mode":ODTicketData, + "opendiscord:category-mode":ODTicketData, "opendiscord:autoclose-enabled":ODTicketData, "opendiscord:autoclose-hours":ODTicketData, @@ -193,6 +55,145 @@ export interface ODTicketIds { "opendiscord:admin-message-sent":ODTicketData, } +/**## ODTicketManager `class` + * This is an Open Ticket ticket manager. + * + * This class manages all currently created tickets in the bot. + * + * All tickets which are added, removed or modified in this manager will be updated automatically in the database. + */ +export class ODTicketManager extends api.ODManager { + /**A reference to the main server of the bot */ + private guild: discord.Guild|null = null + /**A reference to the Open Ticket client manager. */ + private client: api.ODClientManager + + constructor(debug:api.ODDebugger, client:api.ODClientManager){ + super(debug,"ticket") + this.client = client + } + + add(data:ODTicket, overwrite?:boolean): boolean { + data.useDebug(this.debug,"ticket data") + return super.add(data,overwrite) + } + /**Use a specific guild in this class for fetching the channel*/ + useGuild(guild:discord.Guild|null){ + this.guild = guild + } + /**Get the discord channel for a specific ticket. */ + async getTicketChannel(ticket:ODTicket): Promise { + if (!this.guild) return null + try { + const channel = await this.guild.channels.fetch(ticket.id.value) + if (!channel || !channel.isTextBased()) return null + return channel + }catch{ + return null + } + } + /**Get the main ticket message of a ticket channel when found. */ + async getTicketMessage(ticket:ODTicket): Promise|null> { + const msgId = ticket.get("opendiscord:ticket-message").value + if (!this.guild || !msgId) return null + try { + const channel = await this.getTicketChannel(ticket) + if (!channel) return null + return await channel.messages.fetch(msgId) + }catch{ + return null + } + } + /**Shortcut for getting a discord.js user within a ticket. */ + async getTicketUser(ticket:ODTicket, user:"creator"|"closer"|"claimer"|"pinner"): Promise { + if (!this.guild) return null + try { + if (user == "creator"){ + const creatorId = ticket.get("opendiscord:opened-by").value + if (!creatorId) return null + else return (await this.guild.client.users.fetch(creatorId)) + + }else if (user == "closer"){ + const closerId = ticket.get("opendiscord:closed-by").value + if (!closerId) return null + else return (await this.guild.client.users.fetch(closerId)) + + }else if (user == "claimer"){ + const claimerId = ticket.get("opendiscord:claimed-by").value + if (!claimerId) return null + else return (await this.guild.client.users.fetch(claimerId)) + + }else if (user == "pinner"){ + const pinnerId = ticket.get("opendiscord:pinned-by").value + if (!pinnerId) return null + else return (await this.guild.client.users.fetch(pinnerId)) + + }else return null + }catch {return null} + } + /**Shortcut for getting all users that are able to view a ticket. */ + async getAllTicketParticipants(ticket:ODTicket): Promise<{user:discord.User,role:"creator"|"participant"|"admin"}[]|null> { + if (!this.guild) return null + const final: {user:discord.User,role:"creator"|"participant"|"admin"}[] = [] + const channel = await this.getTicketChannel(ticket) + if (!channel) return null + + //add creator + const creatorId = ticket.get("opendiscord:opened-by").value + if (creatorId){ + const creator = await this.client.fetchUser(creatorId) + if (creator) final.push({user:creator,role:"creator"}) + } + + //add participants + const participants = ticket.get("opendiscord:participants").value.filter((p) => p.type == "user") + for (const p of participants){ + if (!final.find((u) => u.user.id == p.id)){ + const participant = await this.client.fetchUser(p.id) + if (participant) final.push({user:participant,role:"participant"}) + } + } + + //add admin roles + const roles = ticket.get("opendiscord:participants").value.filter((p) => p.type == "role") + for (const r of roles){ + const role = await this.client.fetchGuildRole(channel.guild,r.id) + if (role){ + role.members.forEach((member) => { + if (final.find((u) => u.user.id == member.id)) return + final.push({user:member.user,role:"admin"}) + }) + } + } + + return final + } +} + +/**## ODTicketDataJson `interface` + * The JSON representatation from a single ticket property. + */ +export interface ODTicketDataJson { + /**The id of this property. */ + id:string, + /**The value of this property. */ + value:api.ODValidJsonType +} + +/**## ODTicketDataJson `interface` + * The JSON representatation from a single ticket. + */ +export interface ODTicketJson { + /**The id of this ticket. */ + id:string, + /**The option id related to this ticket. */ + option:string, + /**The version of Open Ticket used to create this ticket. */ + version:string, + /**The full list of properties/variables related to this ticket. */ + data:ODTicketDataJson[] +} + /**## ODTicket `class` * This is an Open Ticket ticket. * @@ -200,32 +201,32 @@ export interface ODTicketIds { * * These properties contain the current state of the ticket & are used by actions like claiming, pinning, closing, ... */ -export class ODTicket extends ODManager> { +export class ODTicket extends api.ODManager> { /**The id of this ticket. (discord channel id) */ - id:ODId - /**The option related to this ticket. */ - #option: ODTicketOption + id:api.ODId + /**The option this ticket is made of. */ + private rawOption: ODTicketOption - constructor(id:ODValidId, option:ODTicketOption, data:ODTicketData[]){ + constructor(id:api.ODValidId, option:ODTicketOption, data:ODTicketData[]){ super() - this.id = new ODId(id) - this.#option = option + this.id = new api.ODId(id) + this.rawOption = option data.forEach((data) => { this.add(data) }) } - /**The option related to this ticket. */ + /**The option this ticket is made of. */ set option(option:ODTicketOption){ - this.#option = option + this.rawOption = option this._change() } get option(){ - return this.#option + return this.rawOption } /**Convert this ticket to a JSON object for storing this ticket in the database. */ - toJson(version:ODVersion): ODTicketJson { + toJson(version:api.ODVersion): ODTicketJson { const data = this.getAll().map((data) => { return { id:data.id.toString(), @@ -246,24 +247,24 @@ export class ODTicket extends ODManager> { return new ODTicket(json.id,option,json.data.map((data) => new ODTicketData(data.id,data.value))) } - get(id:OptionId): ODTicketIds[OptionId] - get(id:ODValidId): ODTicketData|null + get>(id:OptionId): ODTicketIdMappings[OptionId] + get(id:api.ODValidId): ODTicketData|null - get(id:ODValidId): ODTicketData|null { + get(id:api.ODValidId): ODTicketData|null { return super.get(id) } - remove(id:OptionId): ODTicketIds[OptionId] - remove(id:ODValidId): ODTicketData|null + remove>(id:OptionId): ODTicketIdMappings[OptionId] + remove(id:api.ODValidId): ODTicketData|null - remove(id:ODValidId): ODTicketData|null { + remove(id:api.ODValidId): ODTicketData|null { return super.remove(id) } - exists(id:keyof ODTicketIds): boolean - exists(id:ODValidId): boolean + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean - exists(id:ODValidId): boolean { + exists(id:api.ODValidId): boolean { return super.exists(id) } } @@ -275,22 +276,22 @@ export class ODTicket extends ODManager> { * * When this property is edited, the database will be updated automatically. */ -export class ODTicketData extends ODManagerData { +export class ODTicketData extends api.ODManagerData { /**The value of this property. */ - #value: DataType + private rawValue: DataType - constructor(id:ODValidId, value:DataType){ + constructor(id:api.ODValidId, value:DataType){ super(id) - this.#value = value + this.rawValue = value } /**The value of this property. */ set value(value:DataType){ - this.#value = value + this.rawValue = value this._change() } get value(): DataType { - return this.#value + return this.rawValue } /**Refresh the database. Is only required to be used when updating `ODTicketData` with an object/array as value. */ refreshDatabase(){ diff --git a/src/core/api/openticket/transcript.ts b/src/core/api/transcript.ts similarity index 88% rename from src/core/api/openticket/transcript.ts rename to src/core/api/transcript.ts index 9234ec9..9e4615b 100644 --- a/src/core/api/openticket/transcript.ts +++ b/src/core/api/transcript.ts @@ -1,13 +1,23 @@ /////////////////////////////////////// //OPENTICKET TRANSCRIPT MODULE /////////////////////////////////////// -import { ODId, ODManager, ODValidJsonType, ODValidId, ODManagerData, ODValidButtonColor } from "../modules/base" -import { ODDebugger } from "../modules/console" -import { ODTicket, ODTicketManager } from "./ticket" -import { ODMessageBuildResult } from "../modules/builder" -import { ODClientManager } from "../modules/client" +import * as api from "@open-discord-bots/framework/api" +import { ODTicket, ODTicketManager } from "./ticket.js" import * as discord from "discord.js" -import { ODPermissionManager_Default } from "#opendiscord-types" + +/**## ODTranscriptManagerIdConstraint `type` + * The constraint/layout for id mappings/interfaces of the `ODTranscriptManager` class. + */ +export type ODTranscriptManagerIdConstraint = Record> + +/**## ODTranscriptManagerIdMappings `interface` + * A list of all available IDs in the default `ODTranscriptManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTranscriptManagerIdMappings extends ODTranscriptManagerIdConstraint { + "opendiscord:html-compiler":ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>, + "opendiscord:text-compiler":ODTranscriptCompiler<{contents:string},null>, +} /**## ODTranscriptManager `class` * This is an Open Ticket transcript manager. @@ -16,19 +26,45 @@ import { ODPermissionManager_Default } from "#opendiscord-types" * * The 2 default built-in transcript generators are: `opendiscord:html-compiler` & `opendiscord:text-compiler`. */ -export class ODTranscriptManager extends ODManager> { +export class ODTranscriptManager extends api.ODManager> { /**The manager responsible for collecting all messages in a channel. */ collector: ODTranscriptCollector /**Alias for the client manager. */ - #client: ODClientManager + private client: api.ODClientManager - constructor(debug:ODDebugger, tickets:ODTicketManager, client:ODClientManager, permissions:ODPermissionManager_Default){ + constructor(debug:api.ODDebugger, tickets:ODTicketManager, client:api.ODClientManager, permissions:api.ODPermissionManager){ super(debug,"transcript compiler") - this.#client = client + this.client = client this.collector = new ODTranscriptCollector(tickets,client,permissions) } + + get>(id:CompilerId): IdList[CompilerId] + get(id:api.ODValidId): ODTranscriptCompiler|null + + get(id:api.ODValidId): ODTranscriptCompiler|null { + return super.get(id) + } + + remove>(id:CompilerId): IdList[CompilerId] + remove(id:api.ODValidId): ODTranscriptCompiler|null + + remove(id:api.ODValidId): ODTranscriptCompiler|null { + return super.remove(id) + } + + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean + + exists(id:api.ODValidId): boolean { + return super.exists(id) + } } +/**## ODMappedTranscriptManager `class + * A special class with types for the Open Ticket `ODTranscriptManager` class. + */ +export class ODMappedTranscriptManager extends ODTranscriptManager {} + /**## ODTranscriptCompilerInitFunction `type` * This function will initiate/prepare the transcript system for an incoming transcript. */ @@ -53,7 +89,7 @@ export interface ODTranscriptCompilerInitResult { /**When not successfull, what was the reason? This will also be shown to the user. */ errorReason:string|null, /**An optional message which will be sent while the transcript is being generated. */ - pendingMessage:ODMessageBuildResult|null, + pendingMessage:api.ODMessageBuildResult|api.ODMessageComponentBuildResult|null, /**An optional object containing data from the init() function which can be used in the compiler. */ initData:InitData, } @@ -83,15 +119,15 @@ export interface ODTranscriptCompilerCompileResult { */ export interface ODTranscriptCompilerReadyResult { /**The message to be sent in the specified channel in the server. */ - channelMessage?:ODMessageBuildResult, + channelMessage?:api.ODMessageBuildResult|api.ODMessageComponentBuildResult, /**The message to be sent to the DM of the ticket creator. */ - creatorDmMessage?:ODMessageBuildResult, + creatorDmMessage?:api.ODMessageBuildResult|api.ODMessageComponentBuildResult, /**The message to be sent to the DM of all participants. */ - participantDmMessage?:ODMessageBuildResult, + participantDmMessage?:api.ODMessageBuildResult|api.ODMessageComponentBuildResult, /**The message to be sent to the DM of all admins who actively participated in the ticket. */ - activeAdminDmMessage?:ODMessageBuildResult, + activeAdminDmMessage?:api.ODMessageBuildResult|api.ODMessageComponentBuildResult, /**The message to be sent to the DM of all admins who were assigned to this ticket. */ - everyAdminDmMessage?:ODMessageBuildResult + everyAdminDmMessage?:api.ODMessageBuildResult|api.ODMessageComponentBuildResult } /**## ODTranscriptCompiler `class` @@ -101,7 +137,7 @@ export interface ODTranscriptCompilerReadyResult { * * These functions should be defined when creating this compiler. Existing compilers already exist for html & text transcripts. */ -export class ODTranscriptCompiler extends ODManagerData { +export class ODTranscriptCompiler extends api.ODManagerData { /*Initialise the system every time a transcript is created. Returns optional "pending" message to display while the transcript is being compiled. */ init: ODTranscriptCompilerInitFunction|null /*Compile or create the transcript. Returns data to give to the ready() function for message creation. */ @@ -109,7 +145,7 @@ export class ODTranscriptCompiler|null - constructor(id:ODValidId, init?:ODTranscriptCompilerInitFunction, compile?:ODTranscriptCompilerCompileFunction, ready?:ODTranscriptCompilerReadyFunction|null){ + constructor(id:api.ODValidId, init?:ODTranscriptCompilerInitFunction, compile?:ODTranscriptCompilerCompileFunction, ready?:ODTranscriptCompilerReadyFunction|null){ super(id) this.init = init ?? null this.compile = compile ?? null @@ -117,44 +153,6 @@ export class ODTranscriptCompiler, - "opendiscord:text-compiler":ODTranscriptCompiler<{contents:string},null>, -} - -/**## ODTranscriptManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODTranscriptManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.transcripts`! - */ -export class ODTranscriptManager_Default extends ODTranscriptManager { - get(id:CompilerId): ODTranscriptCompilerIds[CompilerId] - get(id:ODValidId): ODTranscriptCompiler|null - - get(id:ODValidId): ODTranscriptCompiler|null { - return super.get(id) - } - - remove(id:CompilerId): ODTranscriptCompilerIds[CompilerId] - remove(id:ODValidId): ODTranscriptCompiler|null - - remove(id:ODValidId): ODTranscriptCompiler|null { - return super.remove(id) - } - - exists(id:keyof ODTranscriptCompilerIds): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - /**## ODTranscriptCollector `class` * This is an Open Ticket transcript collector. * @@ -164,22 +162,22 @@ export class ODTranscriptManager_Default extends ODTranscriptManager { */ export class ODTranscriptCollector { /**Alias for the ticket manager. */ - #tickets: ODTicketManager + private tickets: ODTicketManager /**Alias for the client manager. */ - #client: ODClientManager + private client: api.ODClientManager /**Alias for the permissions manager. */ - #permissions: ODPermissionManager_Default + private permissions: api.ODPermissionManager - constructor(tickets:ODTicketManager,client:ODClientManager,permissions:ODPermissionManager_Default){ - this.#tickets = tickets - this.#client = client - this.#permissions = permissions + constructor(tickets:ODTicketManager,client:api.ODClientManager,permissions:api.ODPermissionManager){ + this.tickets = tickets + this.client = client + this.permissions = permissions } /**Collect all messages from a given ticket channel. It may not include all messages depending on the ratelimit. */ async collectAllMessages(ticket:ODTicket, include?:ODTranscriptCollectorIncludeSettings): Promise[]|null> { const newInclude: ODTranscriptCollectorIncludeSettings = include ?? {users:true,bots:true,client:true} - const channel = await this.#tickets.getTicketChannel(ticket) + const channel = await this.tickets.getTicketChannel(ticket) if (!channel) return null const final: discord.Message[] = [] @@ -215,7 +213,7 @@ export class ODTranscriptCollector { const {guild,channel,id,createdTimestamp} = msg //create message author - const author = this.#handleUserData(msg.author,msg.member) + const author = this.handleUserData(msg.author,msg.member) //create message type let type: ODTranscriptMessageType = "default" @@ -271,8 +269,8 @@ export class ODTranscriptCollector { disabled:component.disabled, type:"button", label:component.label, - emoji:this.#handleComponentEmoji(msg,component.emoji), - color:this.#handleButtonComponentStyle(component.style), + emoji:this.handleComponentEmoji(msg,component.emoji), + color:this.handleButtonComponentStyle(component.style), mode:(component.style == discord.ButtonStyle.Link) ? "url" : "button", url:component.url }) @@ -287,7 +285,7 @@ export class ODTranscriptCollector { id:option.value, label:option.label, description:option.description ?? null, - emoji:this.#handleComponentEmoji(msg,option.emoji ?? null) + emoji:this.handleComponentEmoji(msg,option.emoji ?? null) } }) }) @@ -305,7 +303,7 @@ export class ODTranscriptCollector { if (replyChannel && !replyChannel.isDMBased() && replyChannel.isTextBased()){ const replyMessage = await replyChannel.messages.fetch(msg.reference.messageId) if (replyMessage){ - const replyUser = this.#handleUserData(replyMessage.author,replyMessage.member) + const replyUser = this.handleUserData(replyMessage.author,replyMessage.member) reply = { type:"message", @@ -322,7 +320,7 @@ export class ODTranscriptCollector { }else if (msg.interactionMetadata){ try{ //get slash command name from undocumented property in discord REST API - const restMsg = await this.#client.rest.get(discord.Routes.channelMessage(msg.channelId,msg.id)) as discord.APIMessage & {interaction_metadata:{name:string}} + const restMsg = await this.client.rest.get(discord.Routes.channelMessage(msg.channelId,msg.id)) as discord.APIMessage & {interaction_metadata:{name:string}} const commandName = restMsg.interaction_metadata.name ?? "unknown-command" //slash command reply let member: discord.GuildMember|null = null @@ -332,7 +330,7 @@ export class ODTranscriptCollector { reply = { type:"interaction", name:commandName, - user:this.#handleUserData(msg.interactionMetadata.user,member) + user:this.handleUserData(msg.interactionMetadata.user,member) } }catch(err){ process.emit("uncaughtException",err) @@ -397,7 +395,7 @@ export class ODTranscriptCollector { else return {size:Math.round(bytes/(1024*1024*1024*1024)),unit:"TB"} } /**Get the `ODTranscriptEmojiData` from a discord.js component emoji. */ - #handleComponentEmoji(message:discord.Message, rawEmoji:discord.APIMessageComponentEmoji|null): ODTranscriptEmojiData|null { + private handleComponentEmoji(message:discord.Message, rawEmoji:discord.APIMessageComponentEmoji|null): ODTranscriptEmojiData|null { if (!rawEmoji) return null //return built-in emoji if (rawEmoji.name) return { @@ -420,14 +418,14 @@ export class ODTranscriptCollector { } } /**Create the `ODValidButtonColor` from the discord.js button style. */ - #handleButtonComponentStyle(style:discord.ButtonStyle): ODValidButtonColor { + private handleButtonComponentStyle(style:discord.ButtonStyle): api.ODValidButtonColor { if (style == discord.ButtonStyle.Danger) return "red" else if (style == discord.ButtonStyle.Success) return "green" else if (style == discord.ButtonStyle.Primary) return "blue" else return "gray" } /**Create the `ODTranscriptUserData` from a discord.js user. */ - #handleUserData(user:discord.User, member?:discord.GuildMember|null): ODTranscriptUserData { + private handleUserData(user:discord.User, member?:discord.GuildMember|null): ODTranscriptUserData { const userData: ODTranscriptUserData = { id:user.id, username:user.username, @@ -452,10 +450,10 @@ export class ODTranscriptCollector { let adminMessages = 0 for (const msg of parsedMessages){ - if (msg.author.tag || msg.author.id == this.#client.client.user.id) continue - const user = await this.#client.fetchUser(msg.author.id) + if (msg.author.tag || msg.author.id == this.client.client.user.id) continue + const user = await this.client.fetchUser(msg.author.id) if (!user) continue - const isAdmin = this.#permissions.hasPermissions("support",await this.#permissions.getPermissions(user,channel,guild)) + const isAdmin = this.permissions.hasPermissions("support",await this.permissions.getPermissions(user,channel,guild)) if (isAdmin) adminMessages++ else userMessages++ } @@ -622,7 +620,7 @@ export interface ODTranscriptButtonComponentData extends ODTranscriptComponentDa /**The emoji of this button. */ emoji: ODTranscriptEmojiData|null, /**The color of this button. */ - color: ODValidButtonColor, + color: api.ODValidButtonColor, /**Is this button a url or button? */ mode: "url"|"button", /**The url of this button. */ diff --git a/src/core/cli/cli.ts b/src/core/cli/cli.ts index 6fcb2ce..e94853c 100644 --- a/src/core/cli/cli.ts +++ b/src/core/cli/cli.ts @@ -1,8 +1,7 @@ -import {opendiscord, api, utilities} from "../../index" -import {Terminal, terminal} from "terminal-kit" -import ansis from "ansis" +import {opendiscord, api, utilities} from "../../index.js" +import * as cli from "@open-discord-bots/framework/cli" -const logo = [ +export const logo = [ " โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— ", " โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ•šโ•โ•โ–ˆโ–ˆโ•”โ•โ•โ• ", " โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ ", @@ -10,66 +9,16 @@ const logo = [ " โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•‘ ", " โ•šโ•โ•โ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ•โ•โ• โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ• " ] - -/**A utility function to center text to a certain width. */ -export function centerText(text:string,width:number){ - if (width < text.length) return text - let newWidth = width-ansis.strip(text).length+1 - let final = " ".repeat(newWidth/2)+text - return final -} - -/**A utility function to terminate the interactive CLI. */ -export async function terminate(){ - terminal.grabInput(false) - terminal.clear() - terminal.green("๐Ÿ‘‹ Exited the Open Ticket Interactive Setup CLI.\n") - process.exit(0) -} -terminal.on("key",(name,matches,data) => { - if (name == "CTRL_C") terminate() -}) - -/**Render the header of the interactive CLI. */ -export function renderHeader(path:(string|number)[]|string){ - terminal.grabInput(true) - terminal.clear().moveTo(1,1) - terminal(ansis.hex("#f8ba00")(logo.join("\n")+"\n")) - terminal.bold(centerText("Interactive Setup CLI - Version: "+opendiscord.versions.get("opendiscord:version").toString()+" - Support: https://discord.dj-dj.be\n",88)) - if (typeof path == "string") terminal.cyan(centerText(path+"\n\n",88)) - else if (path.length < 1) terminal.cyan(centerText("๐Ÿ‘‹ Hi! Welcome to the Open Ticket Interactive Setup CLI! ๐Ÿ‘‹\n\n",88)) - else terminal.cyan(centerText("๐ŸŒ Current Location: "+path.map((v,i) => { - if (i == 0) return v.toString() - else if (typeof v == "string") return ".\""+v+"\"" - else if (typeof v == "number") return "."+v - }).join("")+"\n\n",88)) -} - -async function renderCliModeSelector(backFn:(() => api.ODPromiseVoid)){ - renderHeader([]) - terminal(ansis.bold.green("Please select what CLI module you want to use.\n")+ansis.italic.gray("(use arrow keys to navigate, exit using escape)\n")) - - const answer = await terminal.singleColumnMenu([ - "โœ๏ธ Edit Config "+ansis.gray("=> Edit the current config, add/remove new tickets/questions/panels & more!"), - "โฑ๏ธ Quick Setup "+ansis.gray("=> A quick and easy way of setting up the bot in your Discord server."), - ],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - else if (answer.selectedIndex == 0) await (await import("./editConfig.js")).renderEditConfig(async () => {await renderCliModeSelector(backFn)}) - else if (answer.selectedIndex == 1) await (await import("./quickSetup.js")).renderQuickSetup(async () => {await renderCliModeSelector(backFn)}) +export const headerOpts: cli.ODCliHeaderOpts = { + logo, + projectColor:"#f8ba00", + projectName:"Open Ticket", + projectVersion:opendiscord.versions.get("opendiscord:version") } export async function execute(){ - if (terminal.width < 100 || terminal.height < 35){ - terminal(ansis.red.bold("\n\nMake sure your console or cmd window has a "+ansis.cyan("minimum width & height")+" of "+ansis.cyan("100x35")+" characters.")) - terminal(ansis.red.bold("\nOtherwise the Open Ticket Interactive Setup CLI will be rendered incorrectly.")) - terminal(ansis.red.bold("\nThe current terminal dimensions are: "+ansis.cyan(terminal.width+"x"+terminal.height)+".")) - }else await renderCliModeSelector(terminate) + const editConfig = new cli.ODCliEditConfigInstance(headerOpts,opendiscord) + const renderQuickSetup = (await import("./quickSetup.js")).renderQuickSetup + + await cli.execute(headerOpts,async (backFn) => {return editConfig.renderEditConfig(backFn)},renderQuickSetup) } \ No newline at end of file diff --git a/src/core/cli/editConfig.ts b/src/core/cli/editConfig.ts deleted file mode 100644 index 9eebf29..0000000 --- a/src/core/cli/editConfig.ts +++ /dev/null @@ -1,933 +0,0 @@ -import {opendiscord, api, utilities} from "../../index" -import {Terminal, terminal} from "terminal-kit" -import ansis from "ansis" -import {renderHeader} from "./cli" - -export async function renderEditConfig(backFn:(() => api.ODPromiseVoid)){ - renderHeader([]) - terminal(ansis.bold.green("Please select which config you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const checkerList = opendiscord.checkers.getAll() - const checkerNameList = checkerList.map((checker) => (checker.options.cliDisplayName ? checker.options.cliDisplayName+" ("+checker.config.file+")" : checker.config.file)) - const checkerNameLength = utilities.getLongestLength(checkerNameList) - const finalCheckerNameList = checkerNameList.map((name,index) => name.padEnd(checkerNameLength+5," ")+ansis.gray(checkerList[index].options.cliDisplayDescription ? "=> "+checkerList[index].options.cliDisplayDescription : "")) - - const answer = await terminal.singleColumnMenu(finalCheckerNameList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - const checker = checkerList[answer.selectedIndex] - const configData = checker.config.data as api.ODValidJsonType - await chooseConfigStructure(checker,async () => {await renderEditConfig(backFn)},checker.structure,configData,{},NaN,["("+checker.config.path+")"]) -} - -async function chooseConfigStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object|any[],parentIndex:string|number,path:(string|number)[]){ - if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigEnabledObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigObjectSwitchStructureSelector(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) await renderConfigArrayStructureSelector(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") await renderConfigBooleanStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") await renderConfigNumberStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") await renderConfigStringStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerNullStructure && data === null) await renderConfigNullStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - else if (structure instanceof api.ODCheckerTypeSwitchStructure) await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - else terminal.red.bold("โŒ Unable to detect type of variable! Please try to edit this property in the JSON file itself!") -} - -async function renderConfigObjectStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerObjectStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){ - if (typeof data != "object" || Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("Please select which variable you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - if (!structure.options.children) return await backFn() - - if (structure.options.cliDisplayName){ - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName)+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - } - - const list = structure.options.children.filter((child) => !child.cliHideInEditMode) - const nameList = list.map((child) => (child.checker.options.cliDisplayName ? child.checker.options.cliDisplayName : child.key)) - const nameLength = utilities.getLongestLength(nameList) - const finalnameList = nameList.map((name,index) => name.padEnd(nameLength+5," ")+ansis.gray((!list[index].checker.options.cliHideDescriptionInParent && list[index].checker.options.cliDisplayDescription) ? "=> "+list[index].checker.options.cliDisplayDescription : "")) - - const answer = await terminal.singleColumnMenu(finalnameList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold.defaultColor, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - const subStructure = list[answer.selectedIndex] - const subData = data[subStructure.key] - await chooseConfigStructure(checker,async () => {await renderConfigObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)},subStructure.checker,subData,data,subStructure.key,[...path,subStructure.key]) -} - -async function renderConfigEnabledObjectStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerEnabledObjectStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){ - if (typeof data != "object" || Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - const enabledProperty = structure.options.property - const subStructure = structure.options.checker - if (!enabledProperty || !subStructure || !subStructure.options.children) return await backFn() - - if (!subStructure.options.children.find((child) => child.key === structure.options.property)){ - if (typeof structure.options.enabledValue == "string") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerStringStructure("opendiscord:CLI-checker-enabled-object-structure",{})}) - else if (typeof structure.options.enabledValue == "number") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerNumberStructure("opendiscord:CLI-checker-enabled-object-structure",{})}) - else if (typeof structure.options.enabledValue == "boolean") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerBooleanStructure("opendiscord:CLI-checker-enabled-object-structure",{})}) - } - - await chooseConfigStructure(checker,backFn,subStructure,data,parent,parentIndex,path) -} - -async function renderConfigObjectSwitchStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerObjectSwitchStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){ - if (typeof data != "object" || Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - if (!structure.options.objects) return await backFn() - - let didMatch: boolean = false - for (const objectTemplate of structure.options.objects){ - if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){ - //object template matches data - const subStructure = objectTemplate.checker - didMatch = true - await chooseConfigStructure(checker,backFn,subStructure,data,parent,parentIndex,path) - } - } - if (!didMatch) throw new api.ODSystemError("OT CLI => Unable to detect type of object in the object switch. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") -} - -async function renderConfigArrayStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerArrayStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){ - if (!Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'array'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("Please select what you would like to do.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - if (!structure.options.propertyChecker) return await backFn() - - if (structure.options.cliDisplayName || typeof parentIndex == "string" || !isNaN(parentIndex)){ - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? parentIndex.toString())+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - } - - const propertyName = structure.options.cliDisplayPropertyName ?? "index" - const answer = await terminal.singleColumnMenu(data.length < 1 ? ["Add "+propertyName] : [ - "Add "+propertyName, - "Edit "+propertyName, - "Move "+propertyName, - "Remove "+propertyName, - "Duplicate "+propertyName - ],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - const backFnFunc = async () => {await renderConfigArrayStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)} - - if (answer.canceled) return await backFn() - if (answer.selectedIndex == 0) await chooseAdditionConfigStructure(checker,backFnFunc,async (newData) => { - data[data.length] = newData - await checker.config.save() - await backFnFunc() - },structure.options.propertyChecker,data,data.length,path,[]) - else if (answer.selectedIndex == 1) await renderConfigArrayStructureEditSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path) - else if (answer.selectedIndex == 2) await renderconfigArrayStructureMoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path) - else if (answer.selectedIndex == 3) await renderconfigArrayStructureRemoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path) - else if (answer.selectedIndex == 4) await renderConfigArrayStructureDuplicateSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path) -} - -async function renderConfigArrayStructureEditSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){ - const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index" - renderHeader(path) - terminal(ansis.bold.green("Please select the "+propertyName+" you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i))) - const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName)) - const dataAnswer = await terminal.singleColumnMenu(dataList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (dataAnswer.canceled) return await backFn() - const subData = data[dataAnswer.selectedIndex] - await chooseConfigStructure(checker,async () => {await renderConfigArrayStructureEditSelector(checker,backFn,arrayStructure,structure,data,parent,parentIndex,path)},structure,subData,data,dataAnswer.selectedIndex,[...path,dataAnswer.selectedIndex]) -} - -async function renderconfigArrayStructureMoveSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){ - const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index" - renderHeader(path) - terminal(ansis.bold.green("Please select the "+propertyName+" you would like to move.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i))) - const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName)) - const dataAnswer = await terminal.singleColumnMenu(dataList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (dataAnswer.canceled) return await backFn() - - renderHeader([...path,dataAnswer.selectedIndex]) - terminal(ansis.bold.green("Please select the position you would like to move to.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const moveAnswer = await terminal.singleColumnMenu(data.map((d,i) => "Position "+(i+1)),{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (moveAnswer.canceled) return await renderconfigArrayStructureMoveSelector(checker,backFn,arrayStructure,structure,data,parent,parentIndex,path) - - const subData = data[dataAnswer.selectedIndex] - const slicedData = [...data.slice(0,dataAnswer.selectedIndex),...data.slice(dataAnswer.selectedIndex+1)] - const insertedData = [...slicedData.slice(0,moveAnswer.selectedIndex),subData,...slicedData.slice(moveAnswer.selectedIndex)] - insertedData.forEach((d,i) => data[i] = d) - - await checker.config.save() - terminal.bold.blue("\n\nโœ… Property moved succesfully!") - await utilities.timer(400) - await backFn() -} - -async function renderconfigArrayStructureRemoveSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){ - const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index" - renderHeader(path) - terminal(ansis.bold.green("Please select the "+propertyName+" you would like to delete.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i))) - const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName)) - const dataAnswer = await terminal.singleColumnMenu(dataList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (dataAnswer.canceled) return await backFn() - data.splice(dataAnswer.selectedIndex,1) - - await checker.config.save() - terminal.bold.blue("\n\nโœ… Property deleted succesfully!") - await utilities.timer(400) - await backFn() -} - -async function renderConfigArrayStructureDuplicateSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){ - const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index" - renderHeader(path) - terminal(ansis.bold.green("Please select the "+propertyName+" you would like to duplicate.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i))) - const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName)) - const dataAnswer = await terminal.singleColumnMenu(dataList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (dataAnswer.canceled) return await backFn() - data.push(JSON.parse(JSON.stringify(data[dataAnswer.selectedIndex]))) - - await checker.config.save() - terminal.bold.blue("\n\nโœ… Property duplicated succesfully!") - await utilities.timer(400) - await backFn() -} - -async function renderConfigBooleanStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerBooleanStructure,data:boolean,parent:object,parentIndex:string|number,path:(string|number)[]){ - if (typeof data != "boolean") throw new api.ODSystemError("OT CLI => Property is not of the type 'boolean'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the boolean property "+ansis.blue("\""+parentIndex+"\"") : "boolean property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nCurrent value: "+ansis.bold[data ? "green" : "red"](data.toString())+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.singleColumnMenu(["false (Disabled)","true (Enabled)"],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run config checker - const newValue = (answer.selectedIndex == 0) ? false : true - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - parent[parentIndex] = newValue - - await checker.config.save() - terminal.bold.blue("\n\nโœ… Variable saved succesfully!") - await utilities.timer(400) - await backFn() - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\nโŒ Variable is invalid! Please try again!") - terminal.gray("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderConfigBooleanStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - } -} - -async function renderConfigNumberStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerNumberStructure,data:number,parent:object,parentIndex:string|number,path:(string|number)[],prefillValue?:string){ - if (typeof data != "number") throw new api.ODSystemError("OT CLI => Property is not of the type 'number'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the number property "+ansis.blue("\""+parentIndex+"\"") : "number property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n")) - - terminal.gray("\nCurrent value: "+ansis.bold.blue(data.toString())+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.inputField({ - default:prefillValue, - style:terminal.cyan, - cancelable:true - }).promise - - if (typeof answer != "string") return await backFn() - - //run config checker - const newValue = Number(answer.replaceAll(",",".")) - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - parent[parentIndex] = newValue - - await checker.config.save() - terminal.bold.blue("\n\nโœ… Variable saved succesfully!") - await utilities.timer(400) - await backFn() - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\nโŒ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderConfigNumberStructureEditor(checker,backFn,structure,data,parent,parentIndex,path,answer) - } -} - -async function renderConfigStringStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerStringStructure,data:string,parent:object,parentIndex:string|number,path:(string|number)[],prefillValue?:string){ - if (typeof data != "string") throw new api.ODSystemError("OT CLI => Property is not of the type 'string'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the string property "+ansis.blue("\""+parentIndex+"\"") : "string property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n")) - - terminal.gray("\nCurrent value:"+(data.includes("\n") ? "\n" : " \"")+ansis.bold.blue(data)+ansis.gray(!data.includes("\n") ? "\"\n" : "\n")) - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const customExtraOptions = (structure instanceof api.ODCheckerCustomStructure_DiscordId) ? structure.extraOptions : undefined - const customAutocompleteFunc = structure.options.cliAutocompleteFunc ? await structure.options.cliAutocompleteFunc() : null - const autocompleteList = ((customAutocompleteFunc ?? structure.options.cliAutocompleteList) ?? customExtraOptions) ?? structure.options.choices - const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = { - style:terminal.white, - selectedStyle:terminal.bgBlue.white - } - - const input = terminal.inputField({ - default:prefillValue, - style:terminal.cyan, - hintStyle:terminal.gray, - cancelable:false, - autoComplete:autocompleteList, - autoCompleteHint:(!!autocompleteList), - autoCompleteMenu:(autocompleteList) ? autoCompleteMenuOpts as Terminal.Autocompletion : false - }) - - terminal.on("key",async (name:string,matches:string[],data:object) => { - if (name == "ESCAPE"){ - terminal.removeListener("key","cli-render-string-structure-edit") - input.abort() - await backFn() - } - },({id:"cli-render-string-structure-edit"} as any)) - - const answer = await input.promise - terminal.removeListener("key","cli-render-string-structure-edit") - if (typeof answer != "string") return - - //run config checker - const newValue = answer.replaceAll("\\n","\n") - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - parent[parentIndex] = newValue - - await checker.config.save() - terminal.bold.blue("\n\nโœ… Variable saved succesfully!") - await utilities.timer(400) - await backFn() - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\nโŒ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderConfigStringStructureEditor(checker,backFn,structure,data,parent,parentIndex,path,answer) - } -} - -async function renderConfigNullStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerNullStructure,data:null,parent:object,parentIndex:string|number,path:(string|number)[]){ - if (data !== null) throw new api.ODSystemError("OT CLI => Property is not of the type 'null'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")") - renderHeader(path) - terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the null property "+ansis.blue("\""+parentIndex+"\"") : "null property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nCurrent value: "+ansis.bold.blue("null")+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.singleColumnMenu(["null"],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run config checker - const newValue = null - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - parent[parentIndex] = newValue - - await checker.config.save() - terminal.bold.blue("\n\nโœ… Variable saved succesfully!") - await utilities.timer(400) - await backFn() - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\nโŒ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderConfigNullStructureEditor(checker,backFn,structure,data,parent,parentIndex,path) - } -} - -async function renderConfigTypeSwitchStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerTypeSwitchStructure,data:any,parent:object,parentIndex:string|number,path:(string|number)[]){ - renderHeader(path) - terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the property "+ansis.blue("\""+parentIndex+"\"") : "property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nCurrent value: "+ansis.bold.blue(data.toString())+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const actionsList: string[] = [] - if (structure.options.boolean) actionsList.push("Edit as boolean") - if (structure.options.string) actionsList.push("Edit as string") - if (structure.options.number) actionsList.push("Edit as number") - if (structure.options.object) actionsList.push("Edit as object") - if (structure.options.array) actionsList.push("Edit as array/list") - if (structure.options.null) actionsList.push("Edit as null") - - const answer = await terminal.singleColumnMenu(actionsList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run selected structure editor (untested) - if (answer.selectedText.startsWith("Edit as boolean") && structure.options.boolean) await renderConfigBooleanStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.boolean,false,parent,parentIndex,path) - else if (answer.selectedText.startsWith("Edit as string") && structure.options.string) await renderConfigStringStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.string,data.toString(),parent,parentIndex,path) - else if (answer.selectedText.startsWith("Edit as number") && structure.options.number) await renderConfigNumberStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.number,0,parent,parentIndex,path) - else if (answer.selectedText.startsWith("Edit as object") && structure.options.object) await renderConfigObjectStructureSelector(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.object,data,parent,parentIndex,path) - else if (answer.selectedText.startsWith("Edit as array/list") && structure.options.array) await renderConfigArrayStructureSelector(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.array,data,parent,parentIndex,path) - else if (answer.selectedText.startsWith("Edit as null") && structure.options.null) await renderConfigNullStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.null,null,parent,parentIndex,path) -} - -function getArrayPreviewStructureNameLength(structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object,parentIndex:string|number): number { - if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") return data.toString().length - else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") return data.toString().length - else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") return data.length - else if (structure instanceof api.ODCheckerNullStructure && data === null) return "Null".length - else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) return "Array".length - else if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data){ - if (!structure.options.cliDisplayKeyInParentArray) return "Object".length - else return data[structure.options.cliDisplayKeyInParentArray].toString().length - - }else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data){ - const subStructure = structure.options.checker - if (!subStructure) return "".length - return getArrayPreviewStructureNameLength(subStructure,data,parent,parentIndex) - - }else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data){ - for (const objectTemplate of (structure.options.objects ?? [])){ - if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){ - //object template matches data - const subStructure = objectTemplate.checker - return getArrayPreviewStructureNameLength(subStructure,data,parent,parentIndex) - } - } - return "".length - - }else if (structure instanceof api.ODCheckerTypeSwitchStructure){ - if (typeof data == "boolean" && structure.options.boolean) return getArrayPreviewStructureNameLength(structure.options.boolean,data,parent,parentIndex) - else if (typeof data == "number" && structure.options.number) return getArrayPreviewStructureNameLength(structure.options.number,data,parent,parentIndex) - else if (typeof data == "string" && structure.options.string) return getArrayPreviewStructureNameLength(structure.options.string,data,parent,parentIndex) - else if (typeof data == "object" && !Array.isArray(data) && data && structure.options.object) return getArrayPreviewStructureNameLength(structure.options.object,data,parent,parentIndex) - else if (Array.isArray(data) && structure.options.array) return getArrayPreviewStructureNameLength(structure.options.array,data,parent,parentIndex) - else if (data === null && structure.options.null) return getArrayPreviewStructureNameLength(structure.options.null,data,parent,parentIndex) - else return "".length - }else return "".length -} - -function getArrayPreviewFromStructure(structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object,parentIndex:string|number,nameLength:number): string { - if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") return data.toString() - else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") return data.toString() - else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") return data - else if (structure instanceof api.ODCheckerNullStructure && data === null) return "Null" - else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) return "Array" - else if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data){ - const additionalKeys = (structure.options.cliDisplayAdditionalKeysInParentArray ?? []).map((key) => key+": "+data[key].toString()).join(", ") - if (!structure.options.cliDisplayKeyInParentArray) return "Object" - else return data[structure.options.cliDisplayKeyInParentArray].toString().padEnd(nameLength+5," ")+ansis.gray(additionalKeys.length > 0 ? "("+additionalKeys+")" : "") - - }else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data){ - const subStructure = structure.options.checker - if (!subStructure) return "" - return getArrayPreviewFromStructure(subStructure,data,parent,parentIndex,nameLength) - - }else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data){ - for (const objectTemplate of (structure.options.objects ?? [])){ - if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){ - //object template matches data - const subStructure = objectTemplate.checker - return getArrayPreviewFromStructure(subStructure,data,parent,parentIndex,nameLength) - } - } - return "" - - }else if (structure instanceof api.ODCheckerTypeSwitchStructure){ - if (typeof data == "boolean" && structure.options.boolean) return getArrayPreviewFromStructure(structure.options.boolean,data,parent,parentIndex,nameLength) - else if (typeof data == "number" && structure.options.number) return getArrayPreviewFromStructure(structure.options.number,data,parent,parentIndex,nameLength) - else if (typeof data == "string" && structure.options.string) return getArrayPreviewFromStructure(structure.options.string,data,parent,parentIndex,nameLength) - else if (typeof data == "object" && !Array.isArray(data) && data && structure.options.object) return getArrayPreviewFromStructure(structure.options.object,data,parent,parentIndex,nameLength) - else if (Array.isArray(data) && structure.options.array) return getArrayPreviewFromStructure(structure.options.array,data,parent,parentIndex,nameLength) - else if (data === null && structure.options.null) return getArrayPreviewFromStructure(structure.options.null,data,parent,parentIndex,nameLength) - else return "" - }else return "" -} - -async function chooseAdditionConfigStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - if (structure instanceof api.ODCheckerObjectStructure) await renderAdditionConfigObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerBooleanStructure) await renderAdditionConfigBooleanStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerNumberStructure) await renderAdditionConfigNumberStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerStringStructure) await renderAdditionConfigStringStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerNullStructure) await renderAdditionConfigNullStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerEnabledObjectStructure) await renderAdditionConfigEnabledObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerObjectSwitchStructure) await renderAdditionConfigObjectSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerArrayStructure) await renderAdditionConfigArrayStructureSelector(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else if (structure instanceof api.ODCheckerTypeSwitchStructure) await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - else await backFn() -} - -async function renderAdditionConfigObjectStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerObjectStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],localData:object={}){ - const children = structure.options.children ?? [] - const skipKeys = (structure.options.cliInitSkipKeys ?? []) - //add skipped properties - for (const key of skipKeys){ - const childStructure = children.find((c) => c.key == key) - if (childStructure){ - const defaultValue = childStructure.checker.options.cliInitDefaultValue - if (childStructure.checker instanceof api.ODCheckerBooleanStructure) localData[key] = (typeof defaultValue == "boolean" ? defaultValue : false) - else if (childStructure.checker instanceof api.ODCheckerNumberStructure) localData[key] = (typeof defaultValue == "number" ? defaultValue : 0) - else if (childStructure.checker instanceof api.ODCheckerStringStructure) localData[key] = (typeof defaultValue == "string" ? defaultValue : "") - else if (childStructure.checker instanceof api.ODCheckerNullStructure) localData[key] = (defaultValue === null ? defaultValue : null) - else if (childStructure.checker instanceof api.ODCheckerArrayStructure) localData[key] = (Array.isArray(defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : []) - else if (childStructure.checker instanceof api.ODCheckerObjectStructure) localData[key] = ((typeof defaultValue == "object" && !Array.isArray(defaultValue) && defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : {}) - else if (childStructure.checker instanceof api.ODCheckerObjectSwitchStructure) localData[key] = ((typeof defaultValue == "object" && !Array.isArray(defaultValue) && defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : {}) - else if (childStructure.checker instanceof api.ODCheckerEnabledObjectStructure) localData[key] = ((typeof defaultValue == "object" && !Array.isArray(defaultValue) && defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : {}) - else if (childStructure.checker instanceof api.ODCheckerTypeSwitchStructure && typeof defaultValue != "undefined") localData[key] = JSON.parse(JSON.stringify(defaultValue)) - else throw new api.ODSystemError("OT CLI => Object skip key has an invalid checker structure! key: "+key) - } - } - - //add properties that need to be configured - const configChildren = children.filter((c) => !skipKeys.includes(c.key)).map((c) => {return {key:c.key,checker:c.checker}}) - await configureAdditionObjectProperties(checker,configChildren,0,localData,[...path,parentIndex],(typeof parentIndex == "number") ? [...localPath] : [...localPath,parentIndex],async () => { - //go back to previous screen - await backFn() - },async () => { - //finish setup - terminal.bold.blue("\n\nโœ… Variable saved succesfully!") - await utilities.timer(400) - await nextFn(localData) - }) -} - -async function configureAdditionObjectProperties(checker:api.ODChecker,children:{key:string,checker:api.ODCheckerStructure}[],currentIndex:number,localData:object,path:(string|number)[],localPath:(string|number)[],backFn:(() => api.ODPromiseVoid),nextFn:(() => api.ODPromiseVoid)){ - if (children.length < 1) return await nextFn() - - const child = children[currentIndex] - await chooseAdditionConfigStructure(checker,async () => { - if (children[currentIndex-1]) await configureAdditionObjectProperties(checker,children,currentIndex-1,localData,path,localPath,backFn,nextFn) - else await backFn() - },async (data) => { - localData[child.key] = data - if (children[currentIndex+1]) await configureAdditionObjectProperties(checker,children,currentIndex+1,localData,path,localPath,backFn,nextFn) - else await nextFn() - },child.checker,localData,child.key,path,localPath) -} - -async function renderAdditionConfigEnabledObjectStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerEnabledObjectStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - const enabledProperty = structure.options.property - const enabledValue = structure.options.enabledValue - const subStructure = structure.options.checker - if (!enabledProperty || !subStructure || !subStructure.options.children) return await backFn() - - let propertyStructure: api.ODCheckerBooleanStructure|api.ODCheckerNumberStructure|api.ODCheckerStringStructure - if (typeof enabledValue == "string") propertyStructure = new api.ODCheckerStringStructure("opendiscord:CLI-checker-enabled-object-structure",{}) - else if (typeof enabledValue == "number") propertyStructure = new api.ODCheckerNumberStructure("opendiscord:CLI-checker-enabled-object-structure",{}) - else if (typeof enabledValue == "boolean") propertyStructure = new api.ODCheckerBooleanStructure("opendiscord:CLI-checker-enabled-object-structure",{}) - else throw new Error("OT CLI => enabled object structure has an invalid type of enabledProperty. It must be a primitive boolean/number/string.") - - const localData = {} - await chooseAdditionConfigStructure(checker,backFn,async (data) => { - if (data === enabledValue) await renderAdditionConfigObjectStructure(checker,async () => {await renderAdditionConfigEnabledObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,subStructure,parent,parentIndex,path,localPath,localData) - else{ - localData[enabledProperty] = data - //copy old object checker to new object checker => all options get de-referenced (this is needed for the new object skip keys are temporary) - const newStructure = new api.ODCheckerObjectStructure(subStructure.id,{children:[]}) - - //copy all options over to the new checker - newStructure.options.children = [...subStructure.options.children] - newStructure.options.cliInitSkipKeys = subStructure.options.children.map((child) => child.key) - for (const key of Object.keys(subStructure.options)){ - if (key != "children" && key != "cliInitSkipKeys") newStructure.options[key] = subStructure.options[key] - } - - //adds all properties to object as "skipKeys", then continues to next function - await renderAdditionConfigObjectStructure(checker,async () => {await renderAdditionConfigEnabledObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,newStructure,parent,parentIndex,path,localPath,localData) - await nextFn(localData) - } - },propertyStructure,localData,enabledProperty,[...path,parentIndex],[...localPath,parentIndex]) -} - -async function renderAdditionConfigObjectSwitchStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerObjectSwitchStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("What type of object would you like to add?\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - const answer = await terminal.singleColumnMenu(structure.options.objects.map((obj) => obj.name),{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - const objectTemplate = structure.options.objects[answer.selectedIndex] - - //copy old object checker to new object checker => all options get de-referenced (this is needed for the new object switch properties which are temporary) - const oldStructure = objectTemplate.checker - const newStructure = new api.ODCheckerObjectStructure(oldStructure.id,{children:[]}) - - //copy all options over to the new checker - newStructure.options.children = [...oldStructure.options.children] - newStructure.options.cliInitSkipKeys = [...(oldStructure.options.cliInitSkipKeys ?? [])] - for (const key of Object.keys(oldStructure.options)){ - if (key != "children" && key != "cliInitSkipKeys") newStructure.options[key] = oldStructure.options[key] - } - - //add the keys of the object switch properties to the 'cliInitSkipKeys' because they need to be skipped. - objectTemplate.properties.map((p) => p.key).forEach((p) => { - if (!newStructure.options.cliInitSkipKeys) newStructure.options.cliInitSkipKeys = [p] - else if (!newStructure.options.cliInitSkipKeys.includes(p)) newStructure.options.cliInitSkipKeys.push(p) - }) - - //add structure checkers for all properties - for (const prop of objectTemplate.properties){ - if (!newStructure.options.children.find((child) => child.key === prop.key)){ - if (typeof prop.value == "string") newStructure.options.children.unshift({key:prop.key,optional:false,priority:1,checker:new api.ODCheckerStringStructure("opendiscord:CLI-checker-object-switch-structure",{cliInitDefaultValue:prop.value})}) - else if (typeof prop.value == "number") newStructure.options.children.unshift({key:prop.key,optional:false,priority:1,checker:new api.ODCheckerNumberStructure("opendiscord:CLI-checker-object-switch-structure",{cliInitDefaultValue:prop.value})}) - else if (typeof prop.value == "boolean") newStructure.options.children.unshift({key:prop.key,optional:false,priority:1,checker:new api.ODCheckerBooleanStructure("opendiscord:CLI-checker-object-switch-structure",{cliInitDefaultValue:prop.value})}) - } - } - - await chooseAdditionConfigStructure(checker,backFn,nextFn,newStructure,parent,parentIndex,path,localPath) -} - -async function renderAdditionConfigBooleanStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerBooleanStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the boolean property "+ansis.blue("\""+parentIndex+"\"") : "boolean property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.singleColumnMenu(["false (Disabled)","true (Enabled)"],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run config checker - const newValue = (answer.selectedIndex == 0) ? false : true - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - terminal.bold.blue("\n\nโœ… Variable saved succesfully!") - await utilities.timer(400) - await nextFn(newValue) - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\nโŒ Variable is invalid! Please try again!") - terminal.gray("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderAdditionConfigBooleanStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - } -} - -async function renderAdditionConfigNumberStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerNumberStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],prefillValue?:string){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the number property "+ansis.blue("\""+parentIndex+"\"") : "number property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n")) - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.inputField({ - default:prefillValue, - style:terminal.cyan, - cancelable:true - }).promise - - if (typeof answer != "string") return await backFn() - - //run config checker - const newValue = Number(answer.replaceAll(",",".")) - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - terminal.bold.blue("\n\nโœ… Variable saved succesfully!") - await utilities.timer(400) - await nextFn(newValue) - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\nโŒ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderAdditionConfigNumberStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath,answer) - } -} - -async function renderAdditionConfigStringStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerStringStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],prefillValue?:string){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the string property "+ansis.blue("\""+parentIndex+"\"") : "string property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n")) - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const customExtraOptions = (structure instanceof api.ODCheckerCustomStructure_DiscordId) ? structure.extraOptions : undefined - const customAutocompleteFunc = structure.options.cliAutocompleteFunc ? await structure.options.cliAutocompleteFunc() : null - const autocompleteList = ((customAutocompleteFunc ?? structure.options.cliAutocompleteList) ?? customExtraOptions) ?? structure.options.choices - const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = { - style:terminal.white, - selectedStyle:terminal.bgBlue.white - } - - const input = terminal.inputField({ - default:prefillValue, - style:terminal.cyan, - hintStyle:terminal.gray, - cancelable:false, - autoComplete:autocompleteList, - autoCompleteHint:(!!autocompleteList), - autoCompleteMenu:(autocompleteList) ? autoCompleteMenuOpts as Terminal.Autocompletion : false - }) - - terminal.on("key",async (name:string,matches:string[],data:object) => { - if (name == "ESCAPE"){ - terminal.removeListener("key","cli-render-string-structure-add") - input.abort() - await backFn() - } - },({id:"cli-render-string-structure-add"} as any)) - - const answer = await input.promise - terminal.removeListener("key","cli-render-string-structure-add") - if (typeof answer != "string") return - - //run config checker - const newValue = answer.replaceAll("\\n","\n") - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - terminal.bold.blue("\n\nโœ… Variable saved succesfully!") - await utilities.timer(400) - await nextFn(newValue) - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\nโŒ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderAdditionConfigStringStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath,answer) - } -} - -async function renderAdditionConfigNullStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerNullStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the null property "+ansis.blue("\""+parentIndex+"\"") : "null property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const answer = await terminal.singleColumnMenu(["null"],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run config checker - const newValue = null - const newPath = [...path] - newPath.shift() - checker.messages = [] //manually clear previous messages - const isDataValid = structure.check(checker,newValue,newPath) - - if (isDataValid){ - terminal.bold.blue("\n\nโœ… Variable saved succesfully!") - await utilities.timer(400) - await nextFn(newValue) - }else{ - const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n") - terminal.bold.blue("\n\nโŒ Variable is invalid! Please try again!") - terminal.red("\n"+messages) - await utilities.timer(1000+(2000*checker.messages.length)) - await renderAdditionConfigNullStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath) - } -} - -async function renderAdditionConfigArrayStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerArrayStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],localData:any[]=[]){ - renderHeader([...path,parentIndex]) - terminal(ansis.bold.green("Please select what you would like to do with the new array.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - if (!structure.options.propertyChecker) return await backFn() - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const propertyName = structure.options.cliDisplayPropertyName ?? "index" - const answer = await terminal.singleColumnMenu(localData.length < 1 ? [ansis.magenta("-> Continue to next variable"),"Add "+propertyName] : [ - ansis.magenta("-> Continue to next variable"), - "Add "+propertyName, - "Edit "+propertyName, - "Move "+propertyName, - "Remove "+propertyName, - "Duplicate "+propertyName, - - ],{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - const backFnFunc = async () => {await renderAdditionConfigArrayStructureSelector(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath,localData)} - - if (answer.canceled) return await backFn() - if (answer.selectedIndex == 0) await nextFn(localData) - else if (answer.selectedIndex == 1) await chooseAdditionConfigStructure(checker,backFnFunc,async (newData) => { - localData[localData.length] = newData - await backFnFunc() - },structure.options.propertyChecker,localData,localData.length,path,[]) - else if (answer.selectedIndex == 2) await renderConfigArrayStructureEditSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path) - else if (answer.selectedIndex == 3) await renderconfigArrayStructureMoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path) - else if (answer.selectedIndex == 4) await renderconfigArrayStructureRemoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path) - else if (answer.selectedIndex == 5) await renderConfigArrayStructureDuplicateSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path) -} - -async function renderAdditionConfigTypeSwitchStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerTypeSwitchStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ - renderHeader(path) - terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the property "+ansis.blue("\""+parentIndex+"\"") : "property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) - - terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n") - terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n") - - const actionsList: string[] = [] - if (structure.options.boolean) actionsList.push("Create as boolean") - if (structure.options.string) actionsList.push("Create as string") - if (structure.options.number) actionsList.push("Create as number") - if (structure.options.object) actionsList.push("Create as object") - if (structure.options.array) actionsList.push("Create as array/list") - if (structure.options.null) actionsList.push("Create as null") - - const answer = await terminal.singleColumnMenu(actionsList,{ - leftPadding:"> ", - style:terminal.cyan, - selectedStyle:terminal.bgDefaultColor.bold, - submittedStyle:terminal.bgBlue, - extraLines:2, - cancelable:true - }).promise - - if (answer.canceled) return await backFn() - - //run selected structure editor (untested) - if (answer.selectedText.startsWith("Create as boolean") && structure.options.boolean) await renderAdditionConfigBooleanStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.boolean,parent,parentIndex,path,localPath) - else if (answer.selectedText.startsWith("Create as string") && structure.options.string) await renderAdditionConfigStringStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.string,parent,parentIndex,path,localPath) - else if (answer.selectedText.startsWith("Create as number") && structure.options.number) await renderAdditionConfigNumberStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.number,parent,parentIndex,path,localPath) - else if (answer.selectedText.startsWith("Create as object") && structure.options.object) await renderAdditionConfigObjectStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.object,parent,parentIndex,path,localPath) - else if (answer.selectedText.startsWith("Create as array/list") && structure.options.array) await renderAdditionConfigArrayStructureSelector(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.array,parent,parentIndex,path,localPath) - else if (answer.selectedText.startsWith("Create as null") && structure.options.null) await renderAdditionConfigNullStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.null,parent,parentIndex,path,localPath) -} \ No newline at end of file diff --git a/src/core/cli/quickSetup.ts b/src/core/cli/quickSetup.ts index 22a44d5..408694b 100644 --- a/src/core/cli/quickSetup.ts +++ b/src/core/cli/quickSetup.ts @@ -1,16 +1,11 @@ -import {opendiscord, api, utilities} from "../../index" -import {Terminal, terminal} from "terminal-kit" +import {opendiscord, api, utilities} from "../../index.js" +import * as cli from "@open-discord-bots/framework/cli" +import terminalKit from "terminal-kit" import ansis from "ansis" import * as discord from "discord.js" -import crypto from "crypto" -import {renderHeader, terminate} from "./cli" +import { headerOpts } from "./cli.js" -function generateUniqueIdFromName(name:string){ - //id only allows a-z, 0-9 & dash characters (& replace spaces with dashes) - const filteredChars = name.toLowerCase().replaceAll(" ","-").split("").filter((ch) => /^[a-zA-Z0-9-]{1}$/.test(ch)) - const randomSuffix = "-"+crypto.randomBytes(4).toString("hex") - return filteredChars.join("")+randomSuffix -} +const terminal = terminalKit.terminal interface ODQuickSetupVariables { client?:api.ODClientManager, @@ -20,7 +15,7 @@ interface ODQuickSetupVariables { language?:string, slashCommands?:boolean, textCommands?:boolean, - status?:api.ODJsonConfig_DefaultStatusType, + status?:api.ODGeneralJsonConfig_Status, logChannel?:string|null, ticketCategory?:string|null, ticketOptions:({ @@ -30,7 +25,7 @@ interface ODQuickSetupVariables { buttonColor:api.ODValidButtonColor, buttonEmoji:string|null, channelPrefix:string, - channelSuffix:api.ODJsonConfig_DefaultOptionTicketChannelType["suffix"] + channelSuffix:api.ODOptionsJsonConfig_TicketOptionChannelSettings["suffix"] }|null)[], optionIdStorage:string[], autocloseHours?:number|null, @@ -38,7 +33,7 @@ interface ODQuickSetupVariables { globalUserLimit?:number|null, removeParticipantsOnClose?:boolean, ticketMessageLayout?:"embed"|"text"|null, - emojiStyle?:api.ODJsonConfig_DefaultSystem["emojiStyle"], + emojiStyle?:api.ODGeneralJsonConfig_TicketSystem["emojiStyle"], panelName?:string, panelDescription?:string, panelDropdown?:boolean, @@ -49,7 +44,7 @@ interface ODQuickSetupVariables { const stepCount = (count:number) => "(Step "+count+"/24) " const quickSetupStorage: ODQuickSetupVariables = {ticketOptions:[],optionIdStorage:[]} -const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = { +const autoCompleteMenuOpts: terminalKit.Terminal.SingleLineMenuOptions = { style:terminal.white, selectedStyle:terminal.bgBlue.white } @@ -113,7 +108,7 @@ function quickSetupRequiresReset(): boolean { } async function renderQuickSetupWarning(backFn:() => api.ODPromiseVoid) { - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Warning") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Warning") terminal.bold(ansis.yellow("WARNING! ")+ansis.red("By using the 'Quick Setup' feature, your current config will be completely resetted!")) terminal.gray("\nAre you sure you want to continue?\n") @@ -135,7 +130,7 @@ async function renderQuickSetupWarning(backFn:() => api.ODPromiseVoid) { } async function renderQuickSetupWelcome(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Introduction") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Introduction") terminal.bold.underline.blue("Open Ticket: Quick Setup\n") terminal.gray([ @@ -165,7 +160,7 @@ async function renderQuickSetupWelcome(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupDevPortal(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Discord Bot & Developer Portal") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Discord Bot & Developer Portal") terminal.bold.blue(stepCount(1)+"Have you already created a Discord bot you can use for Open Ticket?\n") @@ -189,7 +184,7 @@ async function renderQuickSetupDevPortal(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupDevPortalGuide(variation:0|1,backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Discord Bot & Developer Portal") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Discord Bot & Developer Portal") if (variation == 0){ terminal.bold.blue(stepCount(1.1)+"You've mentioned that you don't know how to create a Discord bot.\n\n") @@ -238,10 +233,10 @@ async function quickSetupLogin(token:string): Promise } async function renderQuickSetupBotToken(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Bot Token") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Bot Token") terminal.bold.blue(stepCount(2)+"Please insert the token of your discord bot.\n") - terminal.gray("This is used to configure the bot and is then stored securely in the './config/general.json' file.\n\n> ") + terminal.gray("This is used to configure the bot and is then stored securely in the './config/general.jsonc' file.\n\n> ") const answer = await terminal.inputField({ style:terminal.white, @@ -275,7 +270,7 @@ async function renderQuickSetupServer(backFn:() => api.ODPromiseVoid){ const {client} = quickSetupStorage if (!client) return - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Discord Server") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Discord Server") terminal.bold.blue(stepCount(3)+"Please select a Discord Server to use.\n") terminal.gray("The bot will only work in this server.\n\n") @@ -305,7 +300,7 @@ async function renderQuickSetupAdminRoles(selectedAdmins:string[],backFn:() => a const {client,guild} = quickSetupStorage if (!client || !guild) return - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Admin Roles") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Admin Roles") terminal.bold.blue(stepCount(4)+"Please select all 'Global Admins' roles to use.\n") terminal.gray("Users with one of these roles will be able to access & interact with all tickets.\n\n") @@ -341,7 +336,7 @@ async function renderQuickSetupColorPicker(backFn:() => api.ODPromiseVoid){ const {client,guild,globalAdmins} = quickSetupStorage if (!client || !guild || !globalAdmins) return - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Main Color") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Main Color") terminal.bold.blue(stepCount(5)+"Please insert a valid hex-color to use in all embeds.\n") terminal.gray("You can also choose from existing presets. (e.g. red, green, blue, ...)\n\n> ") @@ -352,7 +347,7 @@ async function renderQuickSetupColorPicker(backFn:() => api.ODPromiseVoid){ cancelable:true, autoComplete:Array.from(presetColors.keys()), autoCompleteHint:true, - autoCompleteMenu:autoCompleteMenuOpts as Terminal.Autocompletion + autoCompleteMenu:autoCompleteMenuOpts as terminalKit.Terminal.Autocompletion }).promise if (typeof answer != "string") return await backFn() @@ -375,7 +370,7 @@ async function renderQuickSetupColorPicker(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupLanguage(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Language") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Language") terminal.bold.blue(stepCount(6)+"What language would you like to use in the bot?\n") terminal.gray("View a list of available languages here: https://otgithub.dj-dj.be#-translators\n\n> ") @@ -384,14 +379,14 @@ async function renderQuickSetupLanguage(backFn:() => api.ODPromiseVoid){ style:terminal.white, hintStyle:terminal.gray, cancelable:true, - autoComplete:opendiscord.defaults.getDefault("languageList"), + autoComplete:opendiscord.sharedFuses.getFuse("languageList"), autoCompleteHint:true, - autoCompleteMenu:autoCompleteMenuOpts as Terminal.Autocompletion + autoCompleteMenu:autoCompleteMenuOpts as terminalKit.Terminal.Autocompletion }).promise if (typeof answer != "string") return await backFn() else{ - if (!opendiscord.defaults.getDefault("languageList").includes(answer.toLowerCase())){ + if (!opendiscord.sharedFuses.getFuse("languageList").includes(answer.toLowerCase())){ terminal.red.bold("\n\nโŒ Please insert an available language from the list. (TIP: use tab for autocomplete)\n") await utilities.timer(2000) return await renderQuickSetupLanguage(backFn) @@ -403,7 +398,7 @@ async function renderQuickSetupLanguage(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupCommandTypes(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Command Types") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Command Types") terminal.bold.blue(stepCount(7)+"Would you like to use slash commands, text commands or both?\n") terminal.gray("Slash commands are recommended.\n\n") @@ -436,7 +431,7 @@ async function renderQuickSetupCommandTypes(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupStatusType(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Status Type") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Status Type") terminal.bold.blue(stepCount(8)+"Please select the type of status you want to use.\n") terminal.gray("The status will be shown below the bot name in the userlist.\n\n") @@ -471,7 +466,7 @@ async function renderQuickSetupStatusText(backFn:() => api.ODPromiseVoid){ const {status} = quickSetupStorage if (!status) return - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Status Text") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Status Text") terminal.bold.blue(stepCount(8.1)+"What text would you like to display in the status?\n") terminal.gray("This will be appended after the type you have chosen in the previous question.\n\n> ") @@ -494,7 +489,7 @@ async function renderQuickSetupLogs(backFn:() => api.ODPromiseVoid){ const {client,guild} = quickSetupStorage if (!client || !guild) return - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Channel Logs") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Channel Logs") terminal.bold.blue(stepCount(9)+"Please select the 'Text Channel' to use for logs.\n") terminal.gray("All logs of the bot will be sent here. Make sure only admins can access this channel.\n\n") @@ -531,7 +526,7 @@ async function renderQuickSetupTicketCategory(backFn:() => api.ODPromiseVoid){ const {client,guild} = quickSetupStorage if (!client || !guild) return - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Category") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Category") terminal.bold.blue(stepCount(10)+"Please select which 'Category' you would like tickets to be created in.\n") terminal.gray("When no category is selected, tickets will appear at the top of the channel list.\n\n") @@ -564,7 +559,7 @@ async function renderQuickSetupTicketCategory(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupTicketCount(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration") terminal.bold.blue(stepCount(11)+"How many ticket options/types would you like to create?\n") terminal.gray("You can always add more ticket options/types in the config afterwards.\n\n") @@ -601,7 +596,7 @@ async function renderQuickSetupTicketCount(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupCreateTicketName(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the name of this ticket option.\n") terminal.gray("Recommendation: Clean, short, obvious name, not more than ยฑ30 characters.\n\n> ") @@ -635,7 +630,7 @@ async function renderQuickSetupCreateTicketName(ticketIndex:number,requiredTicke } async function renderQuickSetupCreateTicketDescription(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the description of this ticket option.\n") terminal.gray("Recommendation: Use '\\n' (backslash-n) for a newline.\n\n> ") @@ -655,7 +650,7 @@ async function renderQuickSetupCreateTicketDescription(ticketIndex:number,requir } async function renderQuickSetupCreateTicketButtonType(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) How would you like to display the ticket name in the button/dropdown?\n") terminal.gray("You will be able to choose between dropdown/buttons when configuring panels.\n\n") @@ -686,7 +681,7 @@ async function renderQuickSetupCreateTicketButtonType(ticketIndex:number,require } async function renderQuickSetupCreateTicketButtonEmoji(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the button emoji of this ticket option.\n") terminal.gray("Only 1 emoji allowed. Tip: Insert custom emoji's via the following syntax: <:12345678910:emoji_name>\n\n> ") @@ -714,7 +709,7 @@ async function renderQuickSetupCreateTicketButtonEmoji(ticketIndex:number,requir } async function renderQuickSetupCreateTicketButtonColor(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) What color would you like the button to be?\n") terminal.gray("This will not apply when choosing 'dropdown' mode in the panel configuration.\n\n") @@ -742,7 +737,7 @@ async function renderQuickSetupCreateTicketButtonColor(ticketIndex:number,requir } async function renderQuickSetupCreateTicketChannelPrefix(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the channel prefix of this ticket option.\n") terminal.gray("Examples: 'ticket-', 'question-', 'test-channel-', ...\n\n> ") @@ -766,7 +761,7 @@ async function renderQuickSetupCreateTicketChannelPrefix(ticketIndex:number,requ } async function renderQuickSetupCreateTicketChannelSuffix(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please select the channel suffix mode of this ticket option.\n") terminal.gray("The suffix is appended after the prefix and will be generated on ticket creation.\n\n") @@ -806,7 +801,7 @@ async function renderQuickSetupCreateTicketChannelSuffix(ticketIndex:number,requ } async function renderQuickSetupAutoclose(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Autoclose") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Autoclose") terminal.bold.blue(stepCount(12)+"Would you like to enable autoclosing tickets?\n") terminal.gray("Applies to all created tickets. You can always change/disable autoclose per ticket-option in the config afterwards.\n\n") @@ -839,7 +834,7 @@ async function renderQuickSetupAutoclose(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupCooldown(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Cooldown") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Cooldown") terminal.bold.blue(stepCount(13)+"Would you like to enable ticket creation cooldown?\n") terminal.gray("Applies to all created tickets. You can always change/disable cooldown per ticket-option in the config afterwards.\n\n") @@ -873,7 +868,7 @@ async function renderQuickSetupCooldown(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupLimits(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Limits") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Limits") terminal.bold.blue(stepCount(14)+"Would you like to enable user ticket creation limits?\n") terminal.gray("Applies to all created tickets. You can always change/disable limits globally or per ticket-option in the config afterwards.\n\n") @@ -904,7 +899,7 @@ async function renderQuickSetupLimits(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupCloseParticipants(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Close Configuration") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Close Configuration") terminal.bold.blue(stepCount(15)+"Would you like to remove all ticket participants when closing the ticket?\n") terminal.gray("When a ticket is closed, only admins can read/write in the ticket. Reopen ticket to restore read/write perms.\n\n") @@ -929,7 +924,7 @@ async function renderQuickSetupCloseParticipants(backFn:() => api.ODPromiseVoid) } async function renderQuickSetupTicketMessageLayout(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Message Configuration") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Message Configuration") terminal.bold.blue(stepCount(16)+"How would you like the (initial) ticket message to be displayed?\n") terminal.gray("This message is sent by the bot when creating a ticket and contains buttons like closing, claiming & deleting.\n\n") @@ -955,7 +950,7 @@ async function renderQuickSetupTicketMessageLayout(backFn:() => api.ODPromiseVoi } async function renderQuickSetupEmojiStyle(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Emoji Style") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Emoji Style") terminal.bold.blue(stepCount(17)+"How would you like emojis to be displayed in messages?\n") terminal.gray("This will affect emojis in all messages of the bot, but does not apply to buttons & dropdowns.\n\n") @@ -982,7 +977,7 @@ async function renderQuickSetupEmojiStyle(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupPanelName(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Panel Name") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Panel Name") terminal.bold.blue(stepCount(18)+"Please insert the name of the ticket panel.\n") terminal.gray("This will be shown as the title of the panel message where all tickets are located.\n\n> ") @@ -1005,7 +1000,7 @@ async function renderQuickSetupPanelName(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupPanelDescription(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Panel Description") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Panel Description") terminal.bold.blue(stepCount(19)+"Please insert the description of the ticket panel.\n") terminal.gray("Shown below the title. Can be used to explain some info/rules about the ticket system.\n\n> ") @@ -1024,7 +1019,7 @@ async function renderQuickSetupPanelDescription(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupPanelDropdown(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Panel Mode") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Panel Mode") terminal.bold.blue(stepCount(20)+"Do you want to show the tickets as buttons or a dropdown?\n") terminal.gray("Dropdown doesn't support colors and cannot contain option types other than 'tickets' (e.g. website/url or reaction roles).\n\n") @@ -1049,7 +1044,7 @@ async function renderQuickSetupPanelDropdown(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupPanelLayout(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Panel Layout") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Panel Layout") terminal.bold.blue(stepCount(21)+"How would you like the panel message to be displayed?\n") terminal.gray("Most of the time embeds are used. But for a simpler solution, you can choose the text layout.\n\n") @@ -1074,7 +1069,7 @@ async function renderQuickSetupPanelLayout(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupPanelDescribeOptions(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Panel Option Descriptions") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Panel Option Descriptions") terminal.bold.blue(stepCount(22)+"Would you like the panel to have auto-generated (ticket-)option descriptions?\n") terminal.gray("It will use the 'name' & 'description' of each ticket option and displays it below the panel description.\n\n") @@ -1101,7 +1096,7 @@ async function renderQuickSetupPanelDescribeOptions(backFn:() => api.ODPromiseVo } async function renderQuickSetupPanelMaxTicketsWarning(backFn:() => api.ODPromiseVoid){ - renderHeader("โฑ๏ธ Open Ticket Quick Setup: Ticket Close Configuration") + cli.renderHeader(headerOpts,"โฑ๏ธ Open Ticket Quick Setup: Ticket Close Configuration") terminal.bold.blue(stepCount(23)+"Would you like to show the maximum amount of tickets a user can create in the panel?\n") terminal.gray("This will show the amount of tickets a user can create at the same time when limits are enabled.\n\n") @@ -1126,7 +1121,7 @@ async function renderQuickSetupPanelMaxTicketsWarning(backFn:() => api.ODPromise } async function renderQuickSetupReady(backFn:() => api.ODPromiseVoid){ - renderHeader("๐Ÿ˜Ž Open Ticket Quick Setup: Overview") + cli.renderHeader(headerOpts,"๐Ÿ˜Ž Open Ticket Quick Setup: Overview") terminal.bold.blue(stepCount(24)+"This is the overview of your ticket bot configuration!\n") terminal.gray("Press 'Enter' to save the result to the config.\n\n") @@ -1171,7 +1166,7 @@ async function renderQuickSetupReady(backFn:() => api.ODPromiseVoid){ } async function renderQuickSetupFinished(){ - renderHeader("โœ… Open Ticket Quick Setup: Ready") + cli.renderHeader(headerOpts,"โœ… Open Ticket Quick Setup: Ready") terminal.bold.green("The config has been saved succesfully and the bot is now ready for usage!\n") terminal.gray("Press 'Enter' to exit the Quick Setup CLI.\n\n") @@ -1203,18 +1198,14 @@ async function renderQuickSetupFinished(){ }).promise //stop CLI - return await terminate() + return await cli.terminate(headerOpts) } async function saveQuickSetupConfig(){ //GENERAL CONFIG const generalConfig = opendiscord.configs.get("opendiscord:general") - const generalConfigData: api.ODJsonConfig_DefaultGeneralData = { - _INFO:{ - support:"https://otdocs.dj-dj.be", - discord:"https://discord.dj-dj.be", - version:"open-ticket-"+opendiscord.versions.get("opendiscord:version").toString() - }, + const generalConfigData: api.ODGeneralJsonConfig_GeneralData = { + _CONFIG_VERSION:"open-ticket-"+opendiscord.versions.get("opendiscord:version").toString(), token:quickSetupStorage.client?.token ?? "", tokenFromENV:false, @@ -1229,8 +1220,29 @@ async function saveQuickSetupConfig(){ textCommands:quickSetupStorage.textCommands ?? false, status:quickSetupStorage.status ?? {enabled:false,mode:"online",type:"custom",text:"",state:""}, + logs:{ + enabled:(typeof quickSetupStorage.logChannel == "string"), + channel:quickSetupStorage.logChannel ?? "", + logMessages:{ + creation:{dm:true,logs:true}, + closing:{dm:true,logs:true}, + deleting:{dm:true,logs:true}, + reopening:{dm:false,logs:true}, + claiming:{dm:false,logs:true}, + pinning:{dm:false,logs:true}, + adding:{dm:false,logs:true}, + removing:{dm:false,logs:true}, + renaming:{dm:false,logs:true}, + moving:{dm:true,logs:true}, + blacklisting:{dm:true,logs:true}, + transferring:{dm:true,logs:true}, + topicChange:{dm:false,logs:true}, + priorityChange:{dm:false,logs:true}, + reactionRole:{dm:false,logs:true} + } + }, - system:{ + ticketSystem:{ preferSlashOverText:quickSetupStorage.slashCommands ?? false, sendErrorOnUnknownCommand:true, questionFieldsInCodeBlock:true, @@ -1241,10 +1253,11 @@ async function saveQuickSetupConfig(){ alwaysShowReason:false, emojiStyle:quickSetupStorage.emojiStyle ?? "before", pinEmoji:"๐Ÿ“Œ", + closeEmoji:"๐Ÿ”’", - replyOnTicketCreation:false, + replyOnTicketCreation:true, replyOnReactionRole:true, - askPriorityOnTicketCreation:false, + askPriorityOnTicketCreation:true, removeParticipantsOnClose:quickSetupStorage.removeParticipantsOnClose ?? false, disableAutocloseAfterReopen:true, autodeleteRequiresClosedTicket:true, @@ -1252,7 +1265,7 @@ async function saveQuickSetupConfig(){ allowCloseBeforeMessage:false, allowCloseBeforeAdminMessage:true, useTranslatedConfigChecker:true, - pinFirstTicketMessage:false, + pinFirstTicketMessage:true, enableTicketClaimButtons:true, enableTicketCloseButtons:true, @@ -1260,11 +1273,7 @@ async function saveQuickSetupConfig(){ enableTicketDeleteButtons:true, enableTicketActionWithReason:true, enableDeleteWithoutTranscript:true, - - logs:{ - enabled:(typeof quickSetupStorage.logChannel == "string"), - channel:quickSetupStorage.logChannel ?? "" - }, + enableCreateTicketForOtherUser:true, limits:{ enabled:(typeof quickSetupStorage.globalUserLimit == "number"), @@ -1283,49 +1292,41 @@ async function saveQuickSetupConfig(){ showCreator:false, showParticipants:false }, - - permissions:{ - help:"everyone", - panel:"admin", - ticket:"everyone", - close:"admin", - delete:"admin", - reopen:"admin", - claim:"admin", - unclaim:"admin", - pin:"admin", - unpin:"admin", - move:"admin", - rename:"admin", - add:"admin", - remove:"admin", - blacklist:"admin", - stats:"everyone", - clear:"admin", - autoclose:"admin", - autodelete:"admin", - transfer:"admin", - topic:"admin", - priority:"admin", + + closedCategory:{ + enabled:false, + categoryId:"" }, - - messages:{ - creation:{dm:true,logs:true}, - closing:{dm:true,logs:true}, - deleting:{dm:true,logs:true}, - reopening:{dm:false,logs:true}, - claiming:{dm:false,logs:true}, - pinning:{dm:false,logs:true}, - adding:{dm:false,logs:true}, - removing:{dm:false,logs:true}, - renaming:{dm:false,logs:true}, - moving:{dm:true,logs:true}, - blacklisting:{dm:true,logs:true}, - transferring:{dm:true,logs:true}, - topicChange:{dm:false,logs:true}, - priorityChange:{dm:false,logs:true}, - reactionRole:{dm:false,logs:true} - } + backupCategory:{ + enabled:false, + categoryId:"" + }, + claimedCategories:[], + }, + permissions:{ + help:"everyone", + panel:"admin", + ticket:"everyone", + close:"admin", + delete:"admin", + reopen:"admin", + claim:"admin", + unclaim:"admin", + pin:"admin", + unpin:"admin", + move:"admin", + rename:"admin", + add:"admin", + remove:"admin", + blacklist:"admin", + stats:"everyone", + clear:"admin", + autoclose:"admin", + autodelete:"admin", + transfer:"admin", + topic:"admin", + priority:"admin", + transcripts:"admin" } } generalConfig.data = generalConfigData @@ -1333,14 +1334,15 @@ async function saveQuickSetupConfig(){ //QUESTIONS CONFIG => no configuration needed (coming soonTM) const questionsConfig = opendiscord.configs.get("opendiscord:questions") - const questionsConfigData: api.ODJsonConfig_DefaultQuestionsData = [ + const questionsConfigData: api.ODQuestionsJsonConfig_QuestionsData = [ { id:"example-question-1", name:"Example Question 1", + description:"This is a short text input question.", type:"short", - required:true, - placeholder:"Insert your short answer here!", + + placeholder:"Insert answer...", length:{ enabled:false, min:0, @@ -1350,15 +1352,69 @@ async function saveQuickSetupConfig(){ { id:"example-question-2", name:"Example Question 2", + description:"This is a paragraph text input question.", type:"paragraph", - required:false, - placeholder:"Insert your long answer here!", + + placeholder:"Insert answer...", length:{ enabled:false, min:0, max:1000 } + }, + { + id:"example-question-3", + name:"Example Question 3", + description:"This is a dropdown question.", + type:"dropdown", + required:false, + + placeholder:"Choose your answer...", + choices:[ + {title:"Choice A",description:"Apple",emoji:"๐ŸŽ"}, + {title:"Choice B",description:"Banana",emoji:"๐ŸŒ"}, + {title:"Choice C",description:"Orange",emoji:"๐ŸŠ"}, + {title:"Choice D",description:"Kiwi",emoji:"๐Ÿฅ"} + ] + }, + { + id:"example-question-4", + name:"Example Question 4", + description:"This is a radio select question.", + type:"radio-select", + required:true, + + choices:[ + {title:"Choice A",description:"Up",selectedByDefault:false}, + {title:"Choice B",description:"Down",selectedByDefault:false}, + {title:"Choice C",description:"Left",selectedByDefault:false}, + {title:"Choice D",description:"Right",selectedByDefault:false} + ] + }, + { + id:"example-question-5", + name:"Example Question 5", + description:"This is a checkbox select question.", + type:"checkbox-select", + required:true, + + limits:{ + enabled:false, + min:0, + max:10 + }, + choices:[ + {title:"Choice A",description:"Happiness",selectedByDefault:false}, + {title:"Choice B",description:"Anger",selectedByDefault:false}, + {title:"Choice C",description:"Sadness",selectedByDefault:false}, + {title:"Choice D",description:"Fear",selectedByDefault:false} + ] + }, + { + id:"example-text-display", + type:"text-display", + textContents:"This is a text display. It isn't a question, but allows you to display a text, explaination or details." } ] questionsConfig.data = questionsConfigData @@ -1366,8 +1422,8 @@ async function saveQuickSetupConfig(){ //OPTIONS CONFIG const optionsConfig = opendiscord.configs.get("opendiscord:options") - const optionsConfigData: api.ODJsonConfig_DefaultOptionsData = quickSetupStorage.ticketOptions.filter((ticket) => ticket !== null).map((ticket) => { - const id = generateUniqueIdFromName(ticket.name) + const optionsConfigData: api.ODOptionsJsonConfig_OptionsData = quickSetupStorage.ticketOptions.filter((ticket) => ticket !== null).map((ticket) => { + const id = cli.generateUniqueIdFromName(ticket.name) quickSetupStorage.optionIdStorage.push(id) return { @@ -1391,9 +1447,6 @@ async function saveQuickSetupConfig(){ prefix:ticket.channelPrefix, suffix:ticket.channelSuffix, category:quickSetupStorage.ticketCategory ?? "", - closedCategory:"", - backupCategory:"", - claimedCategory:[], topic:ticket.description }, @@ -1464,9 +1517,9 @@ async function saveQuickSetupConfig(){ //PANELS CONFIG const panelsConfig = opendiscord.configs.get("opendiscord:panels") - const panelsConfigData: api.ODJsonConfig_DefaultPanelsData = [ + const panelsConfigData: api.ODPanelsJsonConfig_PanelsData = [ { - id:generateUniqueIdFromName(quickSetupStorage.panelName ?? "ticket-panel"), + id:cli.generateUniqueIdFromName(quickSetupStorage.panelName ?? "ticket-panel"), name:quickSetupStorage.panelName ?? "Ticket Panel", dropdown:quickSetupStorage.panelDropdown ?? false, options:quickSetupStorage.optionIdStorage, @@ -1489,6 +1542,7 @@ async function saveQuickSetupConfig(){ }, settings:{ dropdownPlaceholder:"Open a ticket", + maximumButtonsPerRow:5, enableMaxTicketsWarningInText:(quickSetupStorage.panelLayout == "text" && (quickSetupStorage.panelMaxTicketsWarning ?? false)), enableMaxTicketsWarningInEmbed:(quickSetupStorage.panelLayout == "embed" && (quickSetupStorage.panelMaxTicketsWarning ?? false)), @@ -1506,7 +1560,7 @@ async function saveQuickSetupConfig(){ //TRANSCRIPTS CONFIG => no configuration needed (coming soonTM) const transcriptsConfig = opendiscord.configs.get("opendiscord:transcripts") - const transcriptsConfigData: api.ODJsonConfig_DefaultTranscriptsData = { + const transcriptsConfigData: api.ODTranscriptsJsonConfig_TranscriptsData = { general:{ enabled:(typeof quickSetupStorage.logChannel == "string"), diff --git a/src/core/main.ts b/src/core/main.ts new file mode 100644 index 0000000..9ba8b41 --- /dev/null +++ b/src/core/main.ts @@ -0,0 +1,137 @@ +/////////////////////////////////////// +//OPEN TICKET MAIN MODULE +/////////////////////////////////////// +import * as api from "./api.js" +import * as utilities from "@open-discord-bots/framework/utilities" + +export class ODOpenTicketMain extends api.ODMain { + declare versions: api.ODMappedVersionManager + declare events: api.ODMappedEventManager + + declare plugins: api.ODMappedPluginManager + declare flags: api.ODMappedFlagManager + declare progressbars: api.ODMappedProgressBarManager + declare configs: api.ODMappedConfigManager + declare databases: api.ODMappedDatabaseManager + declare sessions: api.ODMappedSessionManager + declare languages: api.ODMappedLanguageManager + + declare checkers: api.ODMappedCheckerManager + declare builders: api.ODMappedBuilderManager + declare components: api.ODMappedComponentManager + declare responders: api.ODMappedResponderManager + declare actions: api.ODMappedActionManager + declare verifybars: api.ODMappedVerifyBarManager + declare permissions: api.ODMappedPermissionManager + declare cooldowns: api.ODMappedCooldownManager + declare helpmenu: api.ODMappedHelpMenuManager + declare statistics: api.ODMappedStatisticManager + declare code: api.ODMappedCodeManager + declare posts: api.ODMappedPostManager + declare states: api.ODMappedStateManager + + declare client: api.ODMappedClientManager + declare livestatus: api.ODMappedLiveStatusManager + declare startscreen: api.ODMappedStartScreenManager + + ///////////////////// + //// OPEN TICKET //// + ///////////////////// + + /**Open Ticket specific fuses. With these fuses/switches, you can turn off "default behaviours" from the bot. Useful for replacing default behaviour with a custom implementation. */ + fuses: api.ODFuseManager + /**The manager that manages all the data of questions in the bot. (these are used in options & tickets) */ + questions: api.ODQuestionManager + /**The manager that manages all the data of options in the bot. (these are used for panels, ticket creation, reaction roles) */ + options: api.ODOptionManager + /**The manager that manages all the data of panels in the bot. (panels contain the options) */ + panels: api.ODPanelManager + /**The manager that manages all tickets in the bot. (here, you can get & edit a lot of data from tickets) */ + tickets: api.ODTicketManager + /**The manager that manages the ticket blacklist. (people who are blacklisted can't create a ticket) */ + blacklist: api.ODBlacklistManager + /**The manager that manages the ticket transcripts. (both the history & compilers) */ + transcripts: api.ODMappedTranscriptManager + /**The manager that manages all reaction roles in the bot. (here, you can add additional data to roles) */ + roles: api.ODRoleManager + /**The manager that manages all priority levels in the bot. (register/edit ticket priority levels) */ + priorities: api.ODMappedPriorityManager + + constructor(){ + const version = api.ODVersion.fromString("opendiscord:version","v4.2.0") + const debugfile = new api.ODDebugFileManager("./","otdebug.txt",5000,version) + const console = new api.ODConsoleManager(100,debugfile) + const debug = new api.ODDebugger(console) + const client = new api.ODMappedClientManager(debug) + const livestatus = new api.ODMappedLiveStatusManager(debug,console) + const permissions = new api.ODMappedPermissionManager(debug,client,true) + + super({ + versions:new api.ODMappedVersionManager(), + debugfile,console,debug, + events:new api.ODMappedEventManager(debug), + processStartupDate:new Date(), + readyStartupDate:null, + + plugins:new api.ODMappedPluginManager(debug), + flags:new api.ODMappedFlagManager(debug), + progressbars:new api.ODMappedProgressBarManager(debug), + configs:new api.ODMappedConfigManager(debug), + databases:new api.ODMappedDatabaseManager(debug), + sessions:new api.ODMappedSessionManager(debug), + languages:new api.ODMappedLanguageManager(debug,false), + + checkers:new api.ODMappedCheckerManager(debug, + new api.ODCheckerStorage(), + new api.ODDefaultCheckerRenderer("#f8ba00","https://discord.dj-dj.be","https://otdocs.dj-dj.be"), + new api.ODMappedCheckerTranslationRegister(), + new api.ODMappedCheckerFunctionManager(debug) + ), + builders:new api.ODMappedBuilderManager(debug), + components:new api.ODMappedComponentManager(debug), + client, + responders:new api.ODMappedResponderManager(debug,client), + actions:new api.ODMappedActionManager(debug), + verifybars:new api.ODMappedVerifyBarManager(debug), + permissions, + cooldowns:new api.ODMappedCooldownManager(debug), + helpmenu:new api.ODMappedHelpMenuManager(debug), + statistics:new api.ODMappedStatisticManager(debug), + code:new api.ODMappedCodeManager(debug), + posts:new api.ODMappedPostManager(debug), + states:new api.ODMappedStateManager(debug), + + sharedFuses:utilities.sharedFuses, + env:new api.ODEnvHelper(), + livestatus, + startscreen:new api.ODMappedStartScreenManager(debug,livestatus), + },"openticket") + + this.livestatus.useMain(this) + this.versions.add(api.ODVersion.fromString("opendiscord:version","v4.2.0")) + this.versions.add(api.ODVersion.fromString("opendiscord:transcripts","v2.1.0")) + + //OPEN TICKET + this.fuses = new api.ODFuseManager({ + priorityLoading:true, + questionLoading:true, + optionLoading:true, + panelLoading:true, + ticketLoading:true, + roleLoading:true, + blacklistLoading:true, + transcriptCompilerLoading:true, + transcriptHistoryLoading:true, + autocloseCheckInterval:300000, //5 minutes + autodeleteCheckInterval:300000 //5 minutes + }) + this.questions = new api.ODQuestionManager(debug) + this.options = new api.ODOptionManager(debug) + this.panels = new api.ODPanelManager(debug) + this.tickets = new api.ODTicketManager(debug,client) + this.blacklist = new api.ODBlacklistManager(debug) + this.transcripts = new api.ODMappedTranscriptManager(debug,this.tickets,client,permissions) + this.roles = new api.ODRoleManager(debug) + this.priorities = new api.ODMappedPriorityManager(debug) + } +} \ No newline at end of file diff --git a/src/core/api/defaults/action.ts b/src/core/mappings/action.ts similarity index 65% rename from src/core/api/defaults/action.ts rename to src/core/mappings/action.ts index c86269b..b7a3478 100644 --- a/src/core/api/defaults/action.ts +++ b/src/core/mappings/action.ts @@ -1,173 +1,152 @@ /////////////////////////////////////// -//DEFAULT ACTION MODULE +//OPEN TICKET ACTION MAPPINGS /////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODAction, ODActionManager } from "../modules/action" -import { ODWorkerManager_Default } from "./worker" +import * as api from "@open-discord-bots/framework/api" import * as discord from "discord.js" -import { ODRoleOption, ODTicketOption } from "../openticket/option" -import { ODTicket, ODTicketClearFilter } from "../openticket/ticket" -import { ODTranscriptCompiler, ODTranscriptCompilerCompileResult } from "../openticket/transcript" -import { ODMessageBuildSentResult } from "../modules/builder" -import { ODRole, ODRoleUpdateMode, ODRoleUpdateResult } from "../openticket/role" -import { ODPriorityLevel } from "../openticket/priority" +import { ODRoleOption, ODTicketOption } from "../api/option.js" +import { ODTicket, ODTicketClearFilter } from "../api/ticket.js" +import { ODTranscriptCompiler, ODTranscriptCompilerCompileResult } from "../api/transcript.js" +import { ODRole, ODRoleUpdateMode, ODRoleUpdateResult } from "../api/role.js" +import { ODPriorityLevel } from "../api/priority.js" -/**## ODActionManagerIds_Default `interface` - * This interface is a list of ids available in the `ODActionManager_Default` class. +/**## ODActionManagerIdMappings `interface` + * A list of all available IDs in the default `ODActionManager` class in `opendiscord`. * It's used to generate typescript declarations for this class. */ -export interface ODActionManagerIds_Default { +export interface ODActionManagerIdMappings extends api.ODActionManagerIdConstraint { "opendiscord:create-ticket-permissions":{ - source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other", + origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other", params:{guild:discord.Guild,user:discord.User,option:ODTicketOption}, result:{valid:boolean,reason:"blacklist"|"cooldown"|"global-limit"|"global-user-limit"|"option-limit"|"option-user-limit"|"custom"|null,cooldownUntil?:Date,customReason?:string}, workers:"opendiscord:check-blacklist"|"opendiscord:check-cooldown"|"opendiscord:check-global-limits"|"opendiscord:check-option-limits"|"opendiscord:valid" }, "opendiscord:create-transcript":{ - source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other", + origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket}, - result:{compiler:ODTranscriptCompiler, success:boolean, result:ODTranscriptCompilerCompileResult, errorReason:string|null, pendingMessage:ODMessageBuildSentResult|null, initData:object|null, participants:{user:discord.User,role:"creator"|"participant"|"admin"}[]}, + result:{compiler:ODTranscriptCompiler, success:boolean, result:ODTranscriptCompilerCompileResult, errorReason:string|null, pendingMessage:api.ODResponderSendResult|null, initData:object|null, participants:{user:discord.User,role:"creator"|"participant"|"admin"}[]}, workers:"opendiscord:select-compiler"|"opendiscord:init-transcript"|"opendiscord:compile-transcript"|"opendiscord:ready-transcript"|"opendiscord:logs" }, "opendiscord:create-ticket":{ - source:"panel-button"|"panel-dropdown"|"slash"|"text"|"other", + origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other", params:{guild:discord.Guild,user:discord.User,option:ODTicketOption,answers:{id:string,name:string,type:"short"|"paragraph",value:string|null}[]}, result:{channel:discord.GuildTextBasedChannel,ticket:ODTicket}, workers:"opendiscord:create-ticket"|"opendiscord:send-ticket-message"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:close-ticket":{ - source:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"other", + origin:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,allowCategoryChange?:boolean}, result:{}, workers:"opendiscord:close-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:delete-ticket":{ - source:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other", + origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,withoutTranscript:boolean}, result:{}, workers:"opendiscord:delete-ticket"|"opendiscord:discord-logs"|"opendiscord:delete-channel"|"opendiscord:logs" }, "opendiscord:reopen-ticket":{ - source:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"other", + origin:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,allowCategoryChange?:boolean}, result:{}, workers:"opendiscord:reopen-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:claim-ticket":{ - source:"slash"|"text"|"ticket-message"|"unclaim-message"|"other", + origin:"slash"|"text"|"ticket-message"|"unclaim-message"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,allowCategoryChange?:boolean}, result:{}, workers:"opendiscord:claim-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:unclaim-ticket":{ - source:"slash"|"text"|"ticket-message"|"claim-message"|"other", + origin:"slash"|"text"|"ticket-message"|"claim-message"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,allowCategoryChange?:boolean}, result:{}, workers:"opendiscord:unclaim-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:pin-ticket":{ - source:"slash"|"text"|"ticket-message"|"unpin-message"|"other", + origin:"slash"|"text"|"ticket-message"|"unpin-message"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean}, result:{}, workers:"opendiscord:pin-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:unpin-ticket":{ - source:"slash"|"text"|"ticket-message"|"pin-message"|"other", + origin:"slash"|"text"|"ticket-message"|"pin-message"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean}, result:{}, workers:"opendiscord:unpin-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:rename-ticket":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,data:string}, result:{}, workers:"opendiscord:rename-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:move-ticket":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,data:ODTicketOption}, result:{}, workers:"opendiscord:move-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:add-ticket-user":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,data:discord.User}, result:{}, workers:"opendiscord:add-ticket-user"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:remove-ticket-user":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,sendMessage:boolean,data:discord.User}, result:{}, workers:"opendiscord:remove-ticket-user"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:reaction-role":{ - source:"panel-button"|"other", + origin:"panel-button"|"other", params:{guild:discord.Guild,user:discord.User,option:ODRoleOption,overwriteMode:ODRoleUpdateMode|null}, result:{result:ODRoleUpdateResult[],role:ODRole}, workers:"opendiscord:reaction-role"|"opendiscord:logs" }, "opendiscord:clear-tickets":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:ODTicket[]}, result:{list:string[]}, workers:"opendiscord:clear-tickets"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:update-ticket-topic":{ - source:"slash"|"text"|"ticket-action"|"other", + origin:"slash"|"text"|"ticket-action"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,newTopic:string|null,sendMessage:boolean}, result:{}, workers:"opendiscord:update-ticket-topic"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:update-ticket-priority":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,newPriority:ODPriorityLevel,reason:string|null,sendMessage:boolean}, result:{}, workers:"opendiscord:update-ticket-priority"|"opendiscord:discord-logs"|"opendiscord:logs" }, "opendiscord:transfer-ticket":{ - source:"slash"|"text"|"other", + origin:"slash"|"text"|"other", params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,newCreator:discord.User,reason:string|null,sendMessage:boolean}, result:{}, workers:"opendiscord:transfer-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" }, + "opendiscord:calculate-ticket-category":{ + origin:"create-ticket"|"close-ticket"|"reopen-ticket"|"claim-ticket"|"unclaim-ticket"|"move-ticket"|"other", + params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel|null,user:discord.User,option:ODTicketOption,ticket:ODTicket|null,currentCategoryId:string|null}, + result:{newCategoryId:string|null,newCategoryMode:string|null,newCategory:discord.CategoryChannel|null,shouldChangeCategory:boolean}, + workers:"opendiscord:default-category"|"opendiscord:close-category"|"opendiscord:claim-category"|"opendiscord:backup-category" + }, + "opendiscord:calculate-ticket-name":{ + origin:"create-ticket"|"close-ticket"|"reopen-ticket"|"claim-ticket"|"unclaim-ticket"|"move-ticket"|"pin-ticket"|"unpin-ticket"|"rename-ticket"|"transfer-ticket"|"priority-change"|"other", + params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel|null,user:discord.User,option:ODTicketOption,ticket:ODTicket|null,currentChannelName:string|null}, + result:{newChannelName:string,newChannelSuffix:string,shouldChangeName:boolean}, + workers:"opendiscord:calculate-ticket-name" + }, } -/**## ODActionManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODActionManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.actions`! +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedActionManager `class + * A special class with types for the Open Ticket `ODActionManager` class. */ -export class ODActionManager_Default extends ODActionManager { - get(id:ActionId): ODAction_Default - get(id:ODValidId): ODAction|null - - get(id:ODValidId): ODAction|null { - return super.get(id) - } - - remove(id:ActionId): ODAction_Default - remove(id:ODValidId): ODAction|null - - remove(id:ODValidId): ODAction|null { - return super.remove(id) - } - - exists(id:keyof ODActionManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODAction_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODAction class. - * It doesn't add any extra features! - * - * This default class is made for the default `ODAction`'s! - */ -export class ODAction_Default extends ODAction { - declare workers: ODWorkerManager_Default -} \ No newline at end of file +export class ODMappedActionManager extends api.ODActionManager {} \ No newline at end of file diff --git a/src/core/mappings/base.ts b/src/core/mappings/base.ts new file mode 100644 index 0000000..73eb270 --- /dev/null +++ b/src/core/mappings/base.ts @@ -0,0 +1,25 @@ +/////////////////////////////////////// +//BASE MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODVersionManagerIdMappings `interface` + * A list of all available IDs in the default `ODVersionManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODVersionManagerIdMappings extends api.ODVersionManagerIdConstraint { + "opendiscord:version":api.ODVersion, + "opendiscord:last-version":api.ODVersion, + "opendiscord:api":api.ODVersion, + "opendiscord:transcripts":api.ODVersion, + "opendiscord:livestatus":api.ODVersion +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedVersionManager `class + * A special class with types for the Open Ticket `ODVersionManager` class. + */ +export class ODMappedVersionManager extends api.ODVersionManager {} \ No newline at end of file diff --git a/src/core/mappings/builder.ts b/src/core/mappings/builder.ts new file mode 100644 index 0000000..63dcfbe --- /dev/null +++ b/src/core/mappings/builder.ts @@ -0,0 +1,285 @@ +/////////////////////////////////////// +//OPEN TICKET BUILDER MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import { ODPermissionEmbedType } from "./permission.js" +import { ODTranscriptCompiler, ODTranscriptCompilerCompileResult } from "../api/transcript.js" +import { ODRoleOption, ODTicketOption, ODWebsiteOption } from "../api/option.js" +import { ODTicket, ODTicketClearFilter } from "../api/ticket.js" +import { ODRole, ODRoleUpdateResult } from "../api/role.js" +import { ODPriorityLevel } from "../api/priority.js" +import { ODPanel } from "../api/panel.js" +import * as discord from "discord.js" + +/**## ODButtonManagerIdMappings `interface` + * A list of all available IDs in the default `ODButtonManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODButtonManagerIdMappings extends api.ODButtonManagerIdConstraint { + "opendiscord:verifybar-button":{origin:"verifybar"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar,verifyButtonId:string,defaultButtonType:"โœ…"|"โŒ",useDefaultLabels:boolean,customLabel?:string,customColor?:api.ODValidButtonColor,customEmoji?:string},workers:"opendiscord:verifybar-button"}, + + "opendiscord:error-ticket-deprecated-transcript":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{},workers:"opendiscord:error-ticket-deprecated-transcript"}, + + "opendiscord:help-menu-previous":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-previous"}, + "opendiscord:help-menu-next":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-next"}, + "opendiscord:help-menu-page":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-page"} + "opendiscord:help-menu-switch":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-switch"}, + + "opendiscord:ticket-option":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODTicketOption},workers:"opendiscord:ticket-option"}, + "opendiscord:website-option":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODWebsiteOption},workers:"opendiscord:website-option"}, + "opendiscord:role-option":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODRoleOption},workers:"opendiscord:role-option"} + + "opendiscord:visit-ticket":{origin:"ticket-created"|"dm"|"logs"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:visit-ticket"}, + + "opendiscord:close-ticket":{origin:"ticket-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:close-ticket"}, + "opendiscord:delete-ticket":{origin:"ticket-message"|"close-message"|"autoclose-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:delete-ticket"}, + "opendiscord:reopen-ticket":{origin:"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:reopen-ticket"}, + "opendiscord:claim-ticket":{origin:"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:claim-ticket"}, + "opendiscord:unclaim-ticket":{origin:"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unclaim-ticket"}, + "opendiscord:pin-ticket":{origin:"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:pin-ticket"}, + "opendiscord:unpin-ticket":{origin:"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:unpin-ticket"}, + + "opendiscord:transcript-html-visit":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-visit"}, + "opendiscord:transcript-error-retry":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error-retry"}, + "opendiscord:transcript-error-continue":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error-continue"}, + + "opendiscord:clear-continue":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[],inProgress:boolean},workers:"opendiscord:clear-continue"}, +} + +/**## ODDropdownManagerIdMappings `interface` + * A list of all available IDs in the default `ODDropdownManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODDropdownManagerIdMappings extends api.ODDropdownManagerIdConstraint { + "opendiscord:panel-dropdown-tickets":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,options:ODTicketOption[]},workers:"opendiscord:panel-dropdown-tickets"} +} + +/**## ODFileManagerIdMappings `interface` + * A list of all available IDs in the default `ODFileManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODFileManagerIdMappings extends api.ODFileManagerIdConstraint { + "opendiscord:text-transcript":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,result:ODTranscriptCompilerCompileResult},workers:"opendiscord:text-transcript"} +} + +/**## ODEmbedManagerIdMappings `interface` + * A list of all available IDs in the default `ODEmbedManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODEmbedManagerIdMappings extends api.ODEmbedManagerIdConstraint { + "opendiscord:error":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:string,layout:"simple"|"advanced",customTitle?:string},workers:"opendiscord:error"}, + "opendiscord:error-option-missing":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorMissingOption},workers:"opendiscord:error-option-missing"}, + "opendiscord:error-option-invalid":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorInvalidOption},workers:"opendiscord:error-option-invalid"}, + "opendiscord:error-unknown-command":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorUnknownCommand},workers:"opendiscord:error-unknown-command"}, + "opendiscord:error-no-permissions":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,permissions:ODPermissionEmbedType[]},workers:"opendiscord:error-no-permissions"}, + "opendiscord:error-no-permissions-cooldown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,until?:Date},workers:"opendiscord:error-no-permissions-cooldown"}, + "opendiscord:error-no-permissions-blacklisted":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-no-permissions-blacklisted"}, + "opendiscord:error-no-permissions-limits":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,limit:"global"|"global-user"|"option"|"option-user"},workers:"opendiscord:error-no-permissions-limits"}, + "opendiscord:error-responder-timeout":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-responder-timeout"}, + "opendiscord:error-ticket-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-unknown"}, + "opendiscord:error-ticket-deprecated":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-deprecated"}, + "opendiscord:error-option-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-option-unknown"}, + "opendiscord:error-panel-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-panel-unknown"}, + "opendiscord:error-not-in-guild":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-not-in-guild"}, + "opendiscord:error-channel-rename":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-rename"|"ticket-move"|"ticket-priority"|"ticket-transfer"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, + "opendiscord:error-channel-category":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-create"|"ticket-close"|"ticket-reopen"|"ticket-claim"|"ticket-unclaim"|"ticket-move"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalCategory:string,newCategory:string},workers:"opendiscord:error-channel-category"}, + "opendiscord:error-ticket-busy":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-busy"}, + + "opendiscord:help-menu":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu"}, + + "opendiscord:stats-global":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:stats-global"}, + "opendiscord:stats-ticket":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:ODTicket},workers:"opendiscord:stats-ticket"}, + "opendiscord:stats-user":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:discord.User},workers:"opendiscord:stats-user"|"opendiscord:easter-egg"}, + "opendiscord:stats-reset":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,reason:string|null},workers:"opendiscord:stats-reset"}, + "opendiscord:stats-ticket-unknown":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,id:string},workers:"opendiscord:stats-ticket-unknown"}, + + "opendiscord:panel":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel"}, + "opendiscord:ticket-created":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created"}, + "opendiscord:ticket-created-dm":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-dm"}, + "opendiscord:ticket-created-logs":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-logs"}, + "opendiscord:ticket-message":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-message"}, + "opendiscord:close-message":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:close-message"}, + "opendiscord:reopen-message":{origin:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:reopen-message"}, + "opendiscord:delete-message":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:delete-message"}, + "opendiscord:claim-message":{origin:"slash"|"text"|"ticket-message"|"unclaim-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:claim-message"}, + "opendiscord:unclaim-message":{origin:"slash"|"text"|"ticket-message"|"claim-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unclaim-message"}, + "opendiscord:pin-message":{origin:"slash"|"text"|"ticket-message"|"unpin-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:pin-message"}, + "opendiscord:unpin-message":{origin:"slash"|"text"|"ticket-message"|"pin-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unpin-message"}, + "opendiscord:rename-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:string},workers:"opendiscord:rename-message"}, + "opendiscord:move-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:ODTicketOption},workers:"opendiscord:move-message"}, + "opendiscord:add-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:add-message"}, + "opendiscord:remove-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:remove-message"}, + "opendiscord:ticket-action-dm":{origin:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-dm"}, + "opendiscord:ticket-action-logs":{origin:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-logs"}, + + "opendiscord:blacklist-view":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:blacklist-view"}, + "opendiscord:blacklist-get":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User},workers:"opendiscord:blacklist-get"}, + "opendiscord:blacklist-add":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-add"}, + "opendiscord:blacklist-remove":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-remove"} + "opendiscord:blacklist-dm":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-dm"}, + "opendiscord:blacklist-logs":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-logs"}, + + "opendiscord:transcript-text-ready":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{contents:string},null>,result:ODTranscriptCompilerCompileResult<{contents:string}>},workers:"opendiscord:transcript-text-ready"}, + "opendiscord:transcript-html-ready":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"}, + "opendiscord:transcript-html-progress":{origin:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"}, + "opendiscord:transcript-error":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error"}, + + "opendiscord:reaction-role":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"}, + "opendiscord:reaction-role-dm":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"}, + "opendiscord:reaction-role-logs":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-logs"}, + + "opendiscord:clear-verify-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[],inProgress:boolean},workers:"opendiscord:clear-verify-message"}, + "opendiscord:clear-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-message"}, + "opendiscord:clear-logs":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-logs"}, + + "opendiscord:autoclose-message":{origin:"timeout"|"leave"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autoclose-message"}, + "opendiscord:autodelete-message":{origin:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autodelete-message"}, + "opendiscord:autoclose-enable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autoclose-enable"}, + "opendiscord:autodelete-enable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autodelete-enable"}, + "opendiscord:autoclose-disable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autoclose-disable"}, + "opendiscord:autodelete-disable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autodelete-disable"}, + + "opendiscord:topic-set":{origin:"slash"|"text"|"ticket-action"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,topic:string},workers:"opendiscord:topic-set"}, + "opendiscord:priority-set":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel,reason:string|null},workers:"opendiscord:priority-set"}, + "opendiscord:priority-get":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel},workers:"opendiscord:priority-get"}, + "opendiscord:transfer-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,oldCreator:discord.User,newCreator:discord.User,reason:string|null},workers:"opendiscord:transfer-message"}, +} + +/**## ODMessageManagerIdMappings `interface` + * A list of all available IDs in the default `ODMessageManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODMessageManagerIdMappings extends api.ODMessageManagerIdConstraint { + "opendiscord:error":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:string,layout:"simple"|"advanced",customTitle?:string},workers:"opendiscord:error"}, + "opendiscord:error-option-missing":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorMissingOption},workers:"opendiscord:error-option-missing"}, + "opendiscord:error-option-invalid":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorInvalidOption},workers:"opendiscord:error-option-invalid"}, + "opendiscord:error-unknown-command":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,error:api.ODTextCommandErrorUnknownCommand},workers:"opendiscord:error-unknown-command"}, + "opendiscord:error-no-permissions":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,permissions:ODPermissionEmbedType[]},workers:"opendiscord:error-no-permissions"}, + "opendiscord:error-no-permissions-cooldown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,until?:Date},workers:"opendiscord:error-no-permissions-cooldown"}, + "opendiscord:error-no-permissions-blacklisted":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-no-permissions-blacklisted"}, + "opendiscord:error-no-permissions-limits":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,limit:"global"|"global-user"|"option"|"option-user"},workers:"opendiscord:error-no-permissions-limits"}, + "opendiscord:error-responder-timeout":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-responder-timeout"}, + "opendiscord:error-ticket-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-unknown"}, + "opendiscord:error-ticket-deprecated":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-deprecated"}, + "opendiscord:error-option-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-option-unknown"}, + "opendiscord:error-panel-unknown":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-panel-unknown"}, + "opendiscord:error-not-in-guild":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-not-in-guild"}, + "opendiscord:error-channel-rename":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-rename"|"ticket-move"|"ticket-priority"|"ticket-transfer"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, + "opendiscord:error-channel-category":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-create"|"ticket-close"|"ticket-reopen"|"ticket-claim"|"ticket-unclaim"|"ticket-move"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalCategory:string,newCategory:string},workers:"opendiscord:error-channel-category"}, + "opendiscord:error-ticket-busy":{origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-busy"}, + + "opendiscord:help-menu":{origin:"slash"|"text"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu"}, + + "opendiscord:stats-global":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:stats-global"}, + "opendiscord:stats-ticket":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:ODTicket},workers:"opendiscord:stats-ticket"}, + "opendiscord:stats-user":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,scopeData:discord.User},workers:"opendiscord:stats-user"|"opendiscord:easter-egg"}, + "opendiscord:stats-reset":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,reason:string|null},workers:"opendiscord:stats-reset"}, + "opendiscord:stats-ticket-unknown":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,id:string},workers:"opendiscord:stats-ticket-unknown"}, + + "opendiscord:panel":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel-layout"|"opendiscord:panel-components"}, + "opendiscord:panel-ready":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel-ready"}, + + "opendiscord:ticket-created":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created"}, + "opendiscord:ticket-created-dm":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-dm"}, + "opendiscord:ticket-created-logs":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-logs"}, + "opendiscord:ticket-message":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-message-layout"|"opendiscord:ticket-message-components"|"opendiscord:ticket-message-disable-components"}, + "opendiscord:close-message":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"autoclose"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:close-message"}, + "opendiscord:reopen-message":{origin:"slash"|"text"|"ticket-message"|"close-message"|"autoclose-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:reopen-message"}, + "opendiscord:delete-message":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:delete-message"}, + "opendiscord:claim-message":{origin:"slash"|"text"|"ticket-message"|"unclaim-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:claim-message"}, + "opendiscord:unclaim-message":{origin:"slash"|"text"|"ticket-message"|"claim-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unclaim-message"}, + "opendiscord:pin-message":{origin:"slash"|"text"|"ticket-message"|"unpin-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:pin-message"}, + "opendiscord:unpin-message":{origin:"slash"|"text"|"ticket-message"|"pin-message"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:unpin-message"}, + "opendiscord:rename-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:string},workers:"opendiscord:rename-message"}, + "opendiscord:move-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:ODTicketOption},workers:"opendiscord:move-message"}, + "opendiscord:add-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:add-message"}, + "opendiscord:remove-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null,data:discord.User},workers:"opendiscord:remove-message"}, + "opendiscord:ticket-action-dm":{origin:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-dm"}, + "opendiscord:ticket-action-logs":{origin:"slash"|"text"|"ticket-message"|"close-message"|"reopen-message"|"delete-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message"|"autoclose-message"|"autoclose"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"close"|"reopen"|"delete"|"claim"|"unclaim"|"pin"|"unpin"|"rename"|"move"|"add"|"remove",ticket:ODTicket,reason:string|null,additionalData:null|string|discord.User|ODTicketOption},workers:"opendiscord:ticket-action-logs"}, + + "opendiscord:blacklist-view":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:blacklist-view"}, + "opendiscord:blacklist-get":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User},workers:"opendiscord:blacklist-get"}, + "opendiscord:blacklist-add":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-add"}, + "opendiscord:blacklist-remove":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,data:discord.User,reason:string|null},workers:"opendiscord:blacklist-remove"}, + "opendiscord:blacklist-dm":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-dm"}, + "opendiscord:blacklist-logs":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,mode:"add"|"remove",data:discord.User,reason:string|null},workers:"opendiscord:blacklist-logs"}, + + "opendiscord:transcript-text-ready":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{contents:string},null>,result:ODTranscriptCompilerCompileResult<{contents:string}>},workers:"opendiscord:transcript-text-ready"}, + "opendiscord:transcript-html-ready":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"}, + "opendiscord:transcript-html-progress":{origin:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"}, + "opendiscord:transcript-error":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error"}, + + "opendiscord:reaction-role":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"}, + "opendiscord:reaction-role-dm":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"}, + "opendiscord:reaction-role-logs":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-logs"}, + + "opendiscord:clear-verify-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[],inProgress:boolean},workers:"opendiscord:clear-verify-message"}, + "opendiscord:clear-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-message"}, + "opendiscord:clear-logs":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,filter:ODTicketClearFilter,list:string[]},workers:"opendiscord:clear-logs"}, + + "opendiscord:autoclose-message":{origin:"timeout"|"leave"|"verifybar"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autoclose-message"}, + "opendiscord:autodelete-message":{origin:"timeout"|"leave"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:autodelete-message"}, + "opendiscord:autoclose-enable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autoclose-enable"}, + "opendiscord:autodelete-enable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,time:number,reason:string|null},workers:"opendiscord:autodelete-enable"}, + "opendiscord:autoclose-disable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autoclose-disable"}, + "opendiscord:autodelete-disable":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,reason:string|null},workers:"opendiscord:autodelete-disable"}, + + "opendiscord:topic-set":{origin:"slash"|"text"|"ticket-action"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,topic:string},workers:"opendiscord:topic-set"}, + "opendiscord:priority-set":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel,reason:string|null},workers:"opendiscord:priority-set"}, + "opendiscord:priority-get":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel},workers:"opendiscord:priority-get"}, + "opendiscord:transfer-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,oldCreator:discord.User,newCreator:discord.User,reason:string|null},workers:"opendiscord:transfer-message"}, +} + +/**## ODModalManagerIdMappings `interface` + * A list of all available IDs in the default `ODModalManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODModalManagerIdMappings extends api.ODModalManagerIdConstraint { + "opendiscord:ticket-questions":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,option:ODTicketOption},workers:"opendiscord:ticket-questions"} + "opendiscord:close-ticket-reason":{origin:"ticket-message"|"reopen-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:close-ticket-reason"} + "opendiscord:reopen-ticket-reason":{origin:"ticket-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:reopen-ticket-reason"} + "opendiscord:delete-ticket-reason":{origin:"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:delete-ticket-reason"} + "opendiscord:claim-ticket-reason":{origin:"ticket-message"|"unclaim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:claim-ticket-reason"} + "opendiscord:unclaim-ticket-reason":{origin:"ticket-message"|"claim-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:unclaim-ticket-reason"} + "opendiscord:pin-ticket-reason":{origin:"ticket-message"|"unpin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:pin-ticket-reason"} + "opendiscord:unpin-ticket-reason":{origin:"ticket-message"|"pin-message"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket,message:discord.Message},workers:"opendiscord:unpin-ticket-reason"} +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedButtonManager `class + * A special class with types for the Open Ticket `ODButtonManager` class. + */ +export class ODMappedButtonManager extends api.ODButtonManager {} + +/**## ODMappedDropdownManager `class + * A special class with types for the Open Ticket `ODDropdownManager` class. + */ +export class ODMappedDropdownManager extends api.ODDropdownManager {} + +/**## ODMappedFileManager `class + * A special class with types for the Open Ticket `ODFileManager` class. + */ +export class ODMappedFileManager extends api.ODFileManager {} + +/**## ODMappedEmbedManager `class + * A special class with types for the Open Ticket `ODEmbedManager` class. + */ +export class ODMappedEmbedManager extends api.ODEmbedManager {} + +/**## ODMappedMessageManager `class + * A special class with types for the Open Ticket `ODMessageManager` class. + */ +export class ODMappedMessageManager extends api.ODMessageManager {} + +/**## ODMappedModalManager `class + * A special class with types for the Open Ticket `ODModalManager` class. + */ +export class ODMappedModalManager extends api.ODModalManager {} + +/**## ODMappedBuilderManager `class + * A special class with types for the Open Ticket `ODBuilderManager` class. + */ +export class ODMappedBuilderManager extends api.ODBuilderManager {} \ No newline at end of file diff --git a/src/core/mappings/checker.ts b/src/core/mappings/checker.ts new file mode 100644 index 0000000..a676744 --- /dev/null +++ b/src/core/mappings/checker.ts @@ -0,0 +1,149 @@ +/////////////////////////////////////// +//OPEN TICKET CONFIG CHECKER MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODBCheckerManagerIdMappings `interface` + * A list of all available IDs in the default `ODBCheckerManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODCheckerManagerIdMappings extends api.ODCheckerManagerIdConstraint { + "opendiscord:general":api.ODChecker, + "opendiscord:questions":api.ODChecker, + "opendiscord:options":api.ODChecker, + "opendiscord:panels":api.ODChecker, + "opendiscord:transcripts":api.ODChecker +} + +/**## ODCheckerTranslationRegisterOtherIdMappings `type` + * A list of all available IDs in the default `ODCheckerTranslationRegister` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export type ODCheckerTranslationRegisterOtherIdMappings = ( + "opendiscord:header-openticket"| + "opendiscord:header-configchecker"| + "opendiscord:header-description"| + "opendiscord:type-error"| + "opendiscord:type-warning"| + "opendiscord:type-info"| + "opendiscord:data-path"| + "opendiscord:data-docs"| + "opendiscord:data-message"| + "opendiscord:compact-information"| + "opendiscord:footer-error"| + "opendiscord:footer-warning"| + "opendiscord:footer-support" +) + +/**## ODCheckerTranslationRegisterMessageIdMappings `type` + * A list of all available IDs in the default `ODCheckerTranslationRegister` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export type ODCheckerTranslationRegisterMessageIdMappings = ( + "opendiscord:invalid-type"| + "opendiscord:property-missing"| + "opendiscord:property-optional"| + "opendiscord:object-disabled"| + "opendiscord:null-invalid"| + "opendiscord:switch-invalid-type"| + "opendiscord:object-switch-invalid-type"| + + "opendiscord:string-too-short"| + "opendiscord:string-too-long"| + "opendiscord:string-length-invalid"| + "opendiscord:string-starts-with"| + "opendiscord:string-ends-with"| + "opendiscord:string-contains"| + "opendiscord:string-inverted-contains"| + "opendiscord:string-choices"| + "opendiscord:string-lowercase"| + "opendiscord:string-uppercase"| + "opendiscord:string-special-characters"| + "opendiscord:string-no-spaces"| + "opendiscord:string-regex"| + "opendiscord:string-capital-word"| + "opendiscord:string-capital-sentence"| + "opendiscord:string-punctuation"| + + "opendiscord:number-nan"| + "opendiscord:number-too-short"| + "opendiscord:number-too-long"| + "opendiscord:number-length-invalid"| + "opendiscord:number-too-small"| + "opendiscord:number-too-large"| + "opendiscord:number-not-equal"| + "opendiscord:number-step"| + "opendiscord:number-step-offset"| + "opendiscord:number-starts-with"| + "opendiscord:number-ends-with"| + "opendiscord:number-contains"| + "opendiscord:number-inverted-contains"| + "opendiscord:number-choices"| + "opendiscord:number-float"| + "opendiscord:number-negative"| + "opendiscord:number-positive"| + "opendiscord:number-zero"| + + "opendiscord:boolean-true"| + "opendiscord:boolean-false"| + + "opendiscord:array-empty-disabled"| + "opendiscord:array-empty-required"| + "opendiscord:array-too-short"| + "opendiscord:array-too-long"| + "opendiscord:array-length-invalid"| + "opendiscord:array-invalid-types"| + "opendiscord:array-double"| + + "opendiscord:discord-invalid-id"| + "opendiscord:discord-invalid-id-options"| + "opendiscord:discord-invalid-token"| + "opendiscord:color-invalid"| + "opendiscord:emoji-too-short"| + "opendiscord:emoji-too-long"| + "opendiscord:emoji-custom"| + "opendiscord:emoji-invalid"| + "opendiscord:url-invalid"| + "opendiscord:url-invalid-http"| + "opendiscord:url-invalid-protocol"| + "opendiscord:url-invalid-hostname"| + "opendiscord:url-invalid-extension"| + "opendiscord:url-invalid-path"| + "opendiscord:id-not-unique"| + "opendiscord:id-non-existent"| + + "opendiscord:invalid-language"| + "opendiscord:invalid-button"| + "opendiscord:unused-option"| + "opendiscord:unused-question"| + "opendiscord:dropdown-option" +) + +/**## ODCheckerFunctionManagerIdMappings `type` + * A list of all available IDs in the default `ODCheckerFunctionManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODCheckerFunctionManagerIdMappings extends api.ODCheckerFunctionManagerIdConstraint { + "opendiscord:unused-options":api.ODCheckerFunction, + "opendiscord:unused-questions":api.ODCheckerFunction, + "opendiscord:dropdown-options":api.ODCheckerFunction +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedCheckerManager `class + * A special class with types for the Open Ticket `ODCheckerManager` class. + */ +export class ODMappedCheckerManager extends api.ODCheckerManager {} + +/**## ODMappedCheckerFunctionManager `class + * A special class with types for the Open Ticket `ODCheckerFunctionManager` class. + */ +export class ODMappedCheckerFunctionManager extends api.ODCheckerFunctionManager {} + +/**## ODMappedCheckerTranslationRegister `class + * A special class with types for the Open Ticket `ODCheckerTranslationRegister` class. + */ +export class ODMappedCheckerTranslationRegister extends api.ODCheckerTranslationRegister {} \ No newline at end of file diff --git a/src/core/mappings/client.ts b/src/core/mappings/client.ts new file mode 100644 index 0000000..4d37d8a --- /dev/null +++ b/src/core/mappings/client.ts @@ -0,0 +1,103 @@ +/////////////////////////////////////// +//OPEN TICKET CLIENT MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODSlashCommandManagerIdMappings `interface` + * A list of all available IDs in the default `ODSlashCommandManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODSlashCommandManagerIdMappings extends api.ODSlashCommandManagerIdConstraint { + "opendiscord:help":api.ODSlashCommand, + "opendiscord:panel":api.ODSlashCommand, + "opendiscord:ticket":api.ODSlashCommand, + "opendiscord:close":api.ODSlashCommand, + "opendiscord:delete":api.ODSlashCommand, + "opendiscord:reopen":api.ODSlashCommand, + "opendiscord:claim":api.ODSlashCommand, + "opendiscord:unclaim":api.ODSlashCommand, + "opendiscord:pin":api.ODSlashCommand, + "opendiscord:unpin":api.ODSlashCommand, + "opendiscord:move":api.ODSlashCommand, + "opendiscord:rename":api.ODSlashCommand, + "opendiscord:add":api.ODSlashCommand, + "opendiscord:remove":api.ODSlashCommand, + "opendiscord:blacklist":api.ODSlashCommand, + "opendiscord:stats":api.ODSlashCommand, + "opendiscord:clear":api.ODSlashCommand, + "opendiscord:autoclose":api.ODSlashCommand, + "opendiscord:autodelete":api.ODSlashCommand, + "opendiscord:topic":api.ODSlashCommand, + "opendiscord:priority":api.ODSlashCommand, + "opendiscord:transfer":api.ODSlashCommand, +} + +/**## ODTextCommandManagerIdMappings `interface` + * A list of all available IDs in the default `ODTextCommandManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTextCommandManagerIdMappings extends api.ODTextCommandManagerIdConstraint { + "opendiscord:dump":api.ODTextCommand, + "opendiscord:help":api.ODTextCommand, + "opendiscord:panel":api.ODTextCommand, + "opendiscord:close":api.ODTextCommand, + "opendiscord:delete":api.ODTextCommand, + "opendiscord:reopen":api.ODTextCommand, + "opendiscord:claim":api.ODTextCommand, + "opendiscord:unclaim":api.ODTextCommand, + "opendiscord:pin":api.ODTextCommand, + "opendiscord:unpin":api.ODTextCommand, + "opendiscord:move":api.ODTextCommand, + "opendiscord:rename":api.ODTextCommand, + "opendiscord:add":api.ODTextCommand, + "opendiscord:remove":api.ODTextCommand, + "opendiscord:blacklist-view":api.ODTextCommand, + "opendiscord:blacklist-add":api.ODTextCommand, + "opendiscord:blacklist-remove":api.ODTextCommand, + "opendiscord:blacklist-get":api.ODTextCommand, + "opendiscord:stats-global":api.ODTextCommand, + "opendiscord:stats-reset":api.ODTextCommand, + "opendiscord:stats-ticket":api.ODTextCommand, + "opendiscord:stats-user":api.ODTextCommand, + "opendiscord:clear":api.ODTextCommand, + "opendiscord:autoclose-disable":api.ODTextCommand, + "opendiscord:autoclose-enable":api.ODTextCommand, + "opendiscord:autodelete-disable":api.ODTextCommand, + "opendiscord:autodelete-enable":api.ODTextCommand, + "opendiscord:topic-set":api.ODTextCommand, + "opendiscord:priority-set":api.ODTextCommand, + "opendiscord:priority-get":api.ODTextCommand, + "opendiscord:transfer":api.ODTextCommand, +} + +/**## ODContextMenuManagerIdMappings `interface` + * A list of all available IDs in the default `ODContextMenuManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODContextMenuManagerIdMappings extends api.ODContextMenuManagerIdConstraint { + //"opendiscord:test-menu":ODContextMenu +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedClientManager `class + * A special class with types for the Open Ticket `ODClientManager` class. + */ +export class ODMappedClientManager extends api.ODClientManager {} + +/**## ODMappedSlashCommandManager `class + * A special class with types for the Open Ticket `ODSlashCommandManager` class. + */ +export class ODMappedSlashCommandManager extends api.ODSlashCommandManager {} + +/**## ODMappedTextCommandManager `class + * A special class with types for the Open Ticket `ODTextCommandManager` class. + */ +export class ODMappedTextCommandManager extends api.ODTextCommandManager {} + +/**## ODMappedContextMenuManager `class + * A special class with types for the Open Ticket `ODContextMenuManager` class. + */ +export class ODMappedContextMenuManager extends api.ODContextMenuManager {} diff --git a/src/core/mappings/code.ts b/src/core/mappings/code.ts new file mode 100644 index 0000000..c7d5cc1 --- /dev/null +++ b/src/core/mappings/code.ts @@ -0,0 +1,36 @@ +/////////////////////////////////////// +//OPEN TICKET CODE MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODCodeManagerIdMappings `interface` + * A list of all available IDs in the default `ODCodeManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODCodeManagerIdMappings extends api.ODCodeManagerIdConstraint { + "opendiscord:command-error-handling":api.ODCode, + "opendiscord:start-listening-interactions":api.ODCode, + "opendiscord:panel-database-cleaner":api.ODCode, + "opendiscord:suffix-database-cleaner":api.ODCode, + "opendiscord:option-database-cleaner":api.ODCode, + "opendiscord:user-database-cleaner":api.ODCode, + "opendiscord:ticket-database-cleaner":api.ODCode, + "opendiscord:panel-auto-update":api.ODCode, + "opendiscord:ticket-saver":api.ODCode, + "opendiscord:blacklist-saver":api.ODCode, + "opendiscord:auto-role-on-join":api.ODCode, + "opendiscord:autoclose-timeout":api.ODCode, + "opendiscord:autoclose-leave":api.ODCode, + "opendiscord:autodelete-timeout":api.ODCode, + "opendiscord:autodelete-leave":api.ODCode, + "opendiscord:ticket-anti-busy":api.ODCode, +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedCodeManager `class + * A special class with types for the Open Ticket `ODCodeManager` class. + */ +export class ODMappedCodeManager extends api.ODCodeManager {} \ No newline at end of file diff --git a/src/core/mappings/component.ts b/src/core/mappings/component.ts new file mode 100644 index 0000000..db313c6 --- /dev/null +++ b/src/core/mappings/component.ts @@ -0,0 +1,72 @@ +/////////////////////////////////////// +//OPEN TICKET COMPONENT MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import * as discord from "discord.js" + +/**## ODSharedComponentManagerIdMappings `interface` + * A list of all available IDs in the default `ODSharedComponentManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODSharedComponentManagerIdMappings extends api.ODComponentManagerIdConstraint { + //"opendiscord:example-component":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:example-component"}, +} + +/**## ODMessageComponentManagerIdMappings `interface` + * A list of all available IDs in the default `ODMessageComponentManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODMessageComponentManagerIdMappings extends api.ODComponentManagerIdConstraint { + //"opendiscord:example-message":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:example-message"}, +} + +/**## ODModalComponentManagerIdMappings `interface` + * A list of all available IDs in the default `ODModalComponentManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODModalComponentManagerIdMappings extends api.ODComponentManagerIdConstraint { + //"opendiscord:example-modal":{origin:"slash"|"text"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:example-modal"}, +} + +/**## ODComponentModifierManagerIdMappings `interface` + * A list of all available IDs in the default `ODComponentModifierManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODComponentModifierManagerIdMappings extends api.ODComponentModifierManagerIdConstraint { + "opendiscord:close-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"reopen-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:reopen-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"close-message"|"autoclose-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:delete-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"close-message"|"autoclose-message"|"reopen-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:claim-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"unclaim-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:unclaim-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"claim-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:pin-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"unpin-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, + "opendiscord:unpin-ticket-verifybar":api.ODMessageComponentModifier<"ticket-message"|"pin-message",{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,verifybar:api.ODVerifyBar},string>, +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedSharedComponentManager `class + * A special class with types for the Open Ticket `ODSharedComponentManager` class. + */ +export class ODMappedSharedComponentManager extends api.ODSharedComponentManager {} + +/**## ODMappedMessageComponentManager `class + * A special class with types for the Open Ticket `ODMessageComponentManager` class. + */ +export class ODMappedMessageComponentManager extends api.ODMessageComponentManager {} + +/**## ODMappedModalComponentManager `class + * A special class with types for the Open Ticket `ODModalComponentManager` class. + */ +export class ODMappedModalComponentManager extends api.ODModalComponentManager {} + +/**## ODMappedComponentModifierManager `class + * A special class with types for the Open Ticket `ODComponentModifierManager` class. + */ +export class ODMappedComponentModifierManager extends api.ODComponentModifierManager {} + +/**## ODMappedComponentManager `class + * A special class with types for the Open Ticket `ODBuilderManager` class. + */ +export class ODMappedComponentManager extends api.ODComponentManager {} \ No newline at end of file diff --git a/src/core/api/defaults/config.ts b/src/core/mappings/config.ts similarity index 56% rename from src/core/api/defaults/config.ts rename to src/core/mappings/config.ts index 69a1850..4f2114b 100644 --- a/src/core/api/defaults/config.ts +++ b/src/core/mappings/config.ts @@ -1,130 +1,74 @@ /////////////////////////////////////// -//DEFAULT CONFIG MODULE +//OPEN TICKET CONFIG MAPPINGS /////////////////////////////////////// -import { ODValidButtonColor, ODValidId } from "../modules/base" +import * as api from "@open-discord-bots/framework/api" import * as discord from "discord.js" -import { ODConfigManager, ODConfig, ODJsonConfig } from "../modules/config" -import { ODClientActivityMode, ODClientActivityType } from "../modules/client" -import { ODRoleUpdateMode } from "../openticket/role" +import { ODRoleUpdateMode } from "../api/role.js" -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW CONFIG VARIABLES? - * - Make the change to the config file in (./config/) and be aware of the following things: - * - The variable has a clear name and its function is obvious. - * - The variable is in the correct position/category of the config. - * - The variable contains a default placeholder to suggest the contents. - * - If there's a (./devconfig/), also modify this file. - * - Register the config in loadAllConfigs() in (./src/data/framework/configLoader.ts) - * - The variable should be added to the "formatters" in the correct position. - * - Add autocomplete for the variable in ODJsonConfig_Default... in (./src/core/api/defaults/config.ts) - * - Add the variable to the config checker in (./src/data/framework/checkerLoader.ts) - * - Make sure the variable is compatible with the Interactive Setup CLI. - * - The variable should be added by the migration manager (./src/core/startup/migration.ts) when missing. - * - Update the Open Ticket Documentation. - * - * IF VARIABLE IS FROM questions.json, options.json OR panels.json: - * - Check (./src/data/openticket/...) for loading/unloading of data. - * - Check (./src/actions/createTicket.ts) and related files. - * - Check (./src/builders), (./src/actions), (./src/data) & (./src/commands) in general in the areas that were changed. - */ - -/**## ODConfigManagerIds_Default `interface` - * This interface is a list of ids available in the `ODConfigManager_Default` class. +/**## ODConfigManagerIdMappings `interface` + * A list of all available IDs in the default `ODConfigManager` class in `opendiscord`. * It's used to generate typescript declarations for this class. */ -export interface ODConfigManagerIds_Default { - "opendiscord:general":ODJsonConfig_DefaultGeneral, - "opendiscord:questions":ODJsonConfig_DefaultQuestions, - "opendiscord:options":ODJsonConfig_DefaultOptions, - "opendiscord:panels":ODJsonConfig_DefaultPanels, - "opendiscord:transcripts":ODJsonConfig_DefaultTranscripts +export interface ODConfigManagerIdMappings extends api.ODConfigManagerIdConstraint { + "opendiscord:general":ODGeneralJsonCommentsConfig, + "opendiscord:questions":ODQuestionsJsonCommentsConfig, + "opendiscord:options":ODOptionsJsonCommentsConfig, + "opendiscord:panels":ODPanelsJsonCommentsConfig, + "opendiscord:transcripts":ODTranscriptsJsonCommentsConfig } -/**## ODConfigManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODConfigManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.configs`! - */ -export class ODConfigManager_Default extends ODConfigManager { - get(id:ConfigId): ODConfigManagerIds_Default[ConfigId] - get(id:ODValidId): ODConfig|null - - get(id:ODValidId): ODConfig|null { - return super.get(id) - } - - remove(id:ConfigId): ODConfigManagerIds_Default[ConfigId] - remove(id:ODValidId): ODConfig|null - - remove(id:ODValidId): ODConfig|null { - return super.remove(id) - } +/////////////////////////////////////// +// CONFIG STRUCTURES, VALUES & TYPES +// --> general.jsonc +/////////////////////////////////////// - exists(id:keyof ODConfigManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } -} - -/**## ODJsonConfig_DefaultStatusType `interface` - * This interface is an object which has all properties for the status object in the `general.json` config! +/**## ODGeneralJsonConfig_Status `interface` + * This interface is an object which has all properties for the status object in the `general.jsonc` config! */ -export interface ODJsonConfig_DefaultStatusType { +export interface ODGeneralJsonConfig_Status { /**Is the status enabled? */ enabled:boolean, /**The type of status (e.g. playing, listening, custom, ...) */ - type:Exclude, + type:Exclude, /**The mode/status of the bot (e.g. online, invisible, idle, do not disturb) */ - mode:ODClientActivityMode + mode:api.ODClientActivityMode /**The text for the status. */ text:string, /**Additional text for the status. (visible below 'text') */ state:string, } -/**## ODJsonConfig_DefaultMessageSettingsType `interface` - * This interface is an object which has all properties for the "system"."messages".... object in the `general.json` config! +/**## ODGeneralJsonConfig_MessageSettings `interface` + * This interface is an object which has all properties for the "system"."messages".... object in the `general.jsonc` config! */ -export interface ODJsonConfig_DefaultMessageSettingsType { +export interface ODGeneralJsonConfig_MessageSettings { /**Enable sending DM logs to the ticket creator for this action. */ dm:boolean, /**Enable sending logsto the log channel for this action. */ logs:boolean } -/**## ODJsonConfig_DefaultCmdPermissionSettingsType `type` - * This type is a collection of command permission settings for the "system"."permissions".... object in the `general.json` config! +/**## ODGeneralJsonConfig_CmdPermissionSettingsType `type` + * This type is a collection of command permission settings for the "system"."permissions".... object in the `general.jsonc` config! */ -export type ODJsonConfig_DefaultCmdPermissionSettingsType = "admin"|"everyone"|"none"|string +export type ODGeneralJsonConfig_CmdPermissionSettingsType = "admin"|"everyone"|"none"|string -/**## ODJsonConfig_DefaultInfo `interface` - * This object contains a few URLs and metadata for the config. - */ -export interface ODJsonConfig_DefaultInfo { - /**A link to the Open Ticket documentation. */ - support:string, - /**A link to the DJdj Development discord server. */ - discord:string, - /**The version of Open Ticket this config is compatible with. */ - version:string -} - -/**## ODJsonConfig_DefaultSystemLogs `interface` +/**## ODGeneralJsonConfig_SystemLogs `interface` * All settings related to the log channel. */ -export interface ODJsonConfig_DefaultSystemLogs { +export interface ODGeneralJsonConfig_SystemLogs { /**Enable logging. Individual actions should still be added via the `"system"."messages"..."logs"` */ enabled:boolean, /**The channel to send logs to. */ - channel:string + channel:string, + /**Configure dm & log messages for all Open Ticket commands & actions. */ + logMessages:ODGeneralJsonConfig_LogMessages } -/**## ODJsonConfig_DefaultSystemLimits `interface` +/**## ODGeneralJsonConfig_SystemLimits `interface` * All settings related to global ticket limits. */ -export interface ODJsonConfig_DefaultSystemLimits { +export interface ODGeneralJsonConfig_SystemLimits { /**Enable global ticket limits. */ enabled:boolean, /**The maximum amount of tickets that are allowed in the server at the same time. */ @@ -133,10 +77,10 @@ export interface ODJsonConfig_DefaultSystemLimits { userMaximum:number } -/**## ODJsonConfig_DefaultSystemChannelTopic `interface` +/**## ODGeneralJsonConfig_SystemChannelTopic `interface` * All global channel topic settings. */ -export interface ODJsonConfig_DefaultSystemChannelTopic { +export interface ODGeneralJsonConfig_SystemChannelTopic { /**Show the option name in the channel topic. */ showOptionName:boolean, /**Show the option description in the channel topic. */ @@ -157,59 +101,60 @@ export interface ODJsonConfig_DefaultSystemChannelTopic { showParticipants:boolean } -/**## ODJsonConfig_DefaultSystemPermissions `interface` +/**## ODGeneralJsonConfig_SystemPermissions `interface` * Configure permissions for all Open Ticket commands & actions. */ -export interface ODJsonConfig_DefaultSystemPermissions { - help:ODJsonConfig_DefaultCmdPermissionSettingsType, - panel:ODJsonConfig_DefaultCmdPermissionSettingsType, - ticket:ODJsonConfig_DefaultCmdPermissionSettingsType, - close:ODJsonConfig_DefaultCmdPermissionSettingsType, - delete:ODJsonConfig_DefaultCmdPermissionSettingsType, - reopen:ODJsonConfig_DefaultCmdPermissionSettingsType, - claim:ODJsonConfig_DefaultCmdPermissionSettingsType, - unclaim:ODJsonConfig_DefaultCmdPermissionSettingsType, - pin:ODJsonConfig_DefaultCmdPermissionSettingsType, - unpin:ODJsonConfig_DefaultCmdPermissionSettingsType, - move:ODJsonConfig_DefaultCmdPermissionSettingsType, - rename:ODJsonConfig_DefaultCmdPermissionSettingsType, - add:ODJsonConfig_DefaultCmdPermissionSettingsType, - remove:ODJsonConfig_DefaultCmdPermissionSettingsType, - blacklist:ODJsonConfig_DefaultCmdPermissionSettingsType, - stats:ODJsonConfig_DefaultCmdPermissionSettingsType, - clear:ODJsonConfig_DefaultCmdPermissionSettingsType, - autoclose:ODJsonConfig_DefaultCmdPermissionSettingsType, - autodelete:ODJsonConfig_DefaultCmdPermissionSettingsType, - transfer:ODJsonConfig_DefaultCmdPermissionSettingsType, - topic:ODJsonConfig_DefaultCmdPermissionSettingsType, - priority:ODJsonConfig_DefaultCmdPermissionSettingsType, +export interface ODGeneralJsonConfig_SystemPermissions { + help:ODGeneralJsonConfig_CmdPermissionSettingsType, + panel:ODGeneralJsonConfig_CmdPermissionSettingsType, + ticket:ODGeneralJsonConfig_CmdPermissionSettingsType, + close:ODGeneralJsonConfig_CmdPermissionSettingsType, + delete:ODGeneralJsonConfig_CmdPermissionSettingsType, + reopen:ODGeneralJsonConfig_CmdPermissionSettingsType, + claim:ODGeneralJsonConfig_CmdPermissionSettingsType, + unclaim:ODGeneralJsonConfig_CmdPermissionSettingsType, + pin:ODGeneralJsonConfig_CmdPermissionSettingsType, + unpin:ODGeneralJsonConfig_CmdPermissionSettingsType, + move:ODGeneralJsonConfig_CmdPermissionSettingsType, + rename:ODGeneralJsonConfig_CmdPermissionSettingsType, + add:ODGeneralJsonConfig_CmdPermissionSettingsType, + remove:ODGeneralJsonConfig_CmdPermissionSettingsType, + blacklist:ODGeneralJsonConfig_CmdPermissionSettingsType, + stats:ODGeneralJsonConfig_CmdPermissionSettingsType, + clear:ODGeneralJsonConfig_CmdPermissionSettingsType, + autoclose:ODGeneralJsonConfig_CmdPermissionSettingsType, + autodelete:ODGeneralJsonConfig_CmdPermissionSettingsType, + transfer:ODGeneralJsonConfig_CmdPermissionSettingsType, + topic:ODGeneralJsonConfig_CmdPermissionSettingsType, + priority:ODGeneralJsonConfig_CmdPermissionSettingsType, + transcripts:ODGeneralJsonConfig_CmdPermissionSettingsType, } -/**## ODJsonConfig_DefaultSystemMessages `interface` +/**## ODGeneralJsonConfig_LogMessages `interface` * Configure dm & log messages for all Open Ticket commands & actions. */ -export interface ODJsonConfig_DefaultSystemMessages { - creation:ODJsonConfig_DefaultMessageSettingsType, - closing:ODJsonConfig_DefaultMessageSettingsType, - deleting:ODJsonConfig_DefaultMessageSettingsType, - reopening:ODJsonConfig_DefaultMessageSettingsType, - claiming:ODJsonConfig_DefaultMessageSettingsType, - pinning:ODJsonConfig_DefaultMessageSettingsType, - adding:ODJsonConfig_DefaultMessageSettingsType, - removing:ODJsonConfig_DefaultMessageSettingsType, - renaming:ODJsonConfig_DefaultMessageSettingsType, - moving:ODJsonConfig_DefaultMessageSettingsType, - blacklisting:ODJsonConfig_DefaultMessageSettingsType, - transferring:ODJsonConfig_DefaultMessageSettingsType, - topicChange:ODJsonConfig_DefaultMessageSettingsType, - priorityChange:ODJsonConfig_DefaultMessageSettingsType, - reactionRole:ODJsonConfig_DefaultMessageSettingsType, +export interface ODGeneralJsonConfig_LogMessages { + creation:ODGeneralJsonConfig_MessageSettings, + closing:ODGeneralJsonConfig_MessageSettings, + deleting:ODGeneralJsonConfig_MessageSettings, + reopening:ODGeneralJsonConfig_MessageSettings, + claiming:ODGeneralJsonConfig_MessageSettings, + pinning:ODGeneralJsonConfig_MessageSettings, + adding:ODGeneralJsonConfig_MessageSettings, + removing:ODGeneralJsonConfig_MessageSettings, + renaming:ODGeneralJsonConfig_MessageSettings, + moving:ODGeneralJsonConfig_MessageSettings, + blacklisting:ODGeneralJsonConfig_MessageSettings, + transferring:ODGeneralJsonConfig_MessageSettings, + topicChange:ODGeneralJsonConfig_MessageSettings, + priorityChange:ODGeneralJsonConfig_MessageSettings, + reactionRole:ODGeneralJsonConfig_MessageSettings, } -/**## ODJsonConfig_DefaultSystem `interface` +/**## ODGeneralJsonConfig_TicketSystem `interface` * All settings related to the ticket system. */ -export interface ODJsonConfig_DefaultSystem { +export interface ODGeneralJsonConfig_TicketSystem { /**Prefer slash-commands over text-commands when displaying them in menu's and messages. */ preferSlashOverText:boolean, /**Reply with "unknown command" when the prefix is used without a valid command. */ @@ -230,6 +175,8 @@ export interface ODJsonConfig_DefaultSystem { emojiStyle:"before"|"after"|"double"|"disabled", /**The emoji used when pinning tickets. This is '๐Ÿ“Œ' by default. */ pinEmoji:string, + /**The emoji used when closing tickets. This is '๐Ÿ”’' by default. */ + closeEmoji:string, /**Reply with an ephemeral message when a ticket is created. */ replyOnTicketCreation:boolean, @@ -266,29 +213,40 @@ export interface ODJsonConfig_DefaultSystem { enableTicketActionWithReason:boolean, /**Enable/disable the delete without transcript feature (button & /delete command). */ enableDeleteWithoutTranscript:boolean, - - /**All settings related to the log channel. */ - logs:ODJsonConfig_DefaultSystemLogs, + /**Enable/disable creating tickets for other users with /ticket . (ADMIN ONLY) */ + enableCreateTicketForOtherUser:boolean, /**All settings related to global ticket limits. */ - limits:ODJsonConfig_DefaultSystemLimits, + limits:ODGeneralJsonConfig_SystemLimits, /**All global channel topic settings. */ - channelTopic:ODJsonConfig_DefaultSystemChannelTopic, + channelTopic:ODGeneralJsonConfig_SystemChannelTopic, - /**Configure permissions for all Open Ticket commands & actions. */ - permissions:ODJsonConfig_DefaultSystemPermissions, - - /**Configure dm & log messages for all Open Ticket commands & actions. */ - messages:ODJsonConfig_DefaultSystemMessages + /**Move closed tickets to this channel category. */ + closedCategory:{ + enabled:boolean + categoryId:string + }, + /**Create tickets in this channel category when the original category is full (max 50 channels). */ + backupCategory:{ + enabled:boolean + categoryId:string + }, + /**Move claimed tickets to the matching channel category of the user that claimed the ticket. */ + claimedCategories:{ + /**The user who claimed the ticket. */ + user:string, + /**The category to move the ticket to. */ + category:string + }[], } -/**## ODJsonConfig_DefaultGeneralData `interface` - * All contents of the `general.json` config file. +/**## ODGeneralJsonConfig_GeneralData `interface` + * All contents of the `general.jsonc` config file. */ -export interface ODJsonConfig_DefaultGeneralData { +export interface ODGeneralJsonConfig_GeneralData { /**This object contains a few URLs and metadata for the config. */ - _INFO:ODJsonConfig_DefaultInfo, + _CONFIG_VERSION:string, /**The token of the bot. (Empty when using `tokenFromENV`) */ token:string, @@ -312,26 +270,27 @@ export interface ODJsonConfig_DefaultGeneralData { textCommands:boolean, /**All settings related to the status of the bot. */ - status:ODJsonConfig_DefaultStatusType, - + status:ODGeneralJsonConfig_Status, + /**All settings related to the ticket system. */ - system:ODJsonConfig_DefaultSystem + ticketSystem:ODGeneralJsonConfig_TicketSystem, + + /**Configure permissions for all Open Ticket commands & actions. */ + permissions:ODGeneralJsonConfig_SystemPermissions, + + /**All settings related to the log channel. */ + logs:ODGeneralJsonConfig_SystemLogs, } -/**## ODJsonConfig_DefaultGeneral `default_class` - * This is a special class that adds type definitions & typescript to the ODJsonConfig class. - * It doesn't add any extra features! - * - * This default class is made for the `general.json` config! - */ -export class ODJsonConfig_DefaultGeneral extends ODJsonConfig { - declare data: ODJsonConfig_DefaultGeneralData -} +/////////////////////////////////////// +// CONFIG STRUCTURES, VALUES & TYPES +// --> options.jsonc +/////////////////////////////////////// -/**## ODJsonConfig_DefaultOptionType `interface` - * This interface is an object which has all basic properties for options in the `options.json` config! +/**## ODOptionsJsonConfig_BaseOption `interface` + * The basic properties for options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionType { +export interface ODOptionsJsonConfig_BaseOption { /**The id of this option. */ id:string, /**The name of this option. */ @@ -339,7 +298,7 @@ export interface ODJsonConfig_DefaultOptionType { /**The description of this option. */ description:string, /**The type of this option. This type also determines the other option-specific variables. */ - type:"ticket"|"website"|"role", + type:"ticket"|"website"|"role"|"sub-panel", /**All settings related to the button for the 3 option types. */ button:{ /**The emoji of the button. (can also be empty) */ @@ -349,22 +308,22 @@ export interface ODJsonConfig_DefaultOptionType { } } -/**## ODJsonConfig_DefaultOptionButtonSettingsType `interface` - * This interface is an object which has all button settings for ticket & reaction role options in the `options.json` config! +/**## ODOptionsJsonConfig_OptionButtonSettings `interface` + * The button settings for ticket, sub-panel & reaction role options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionButtonSettingsType { +export interface ODOptionsJsonConfig_OptionButtonSettings { /**The emoji of the button. (can also be empty) */ emoji:string, /**The label of the button (can also be empty) */ label:string, /**The color of the button (not available in options with the 'website' type!) */ - color:ODValidButtonColor + color:api.ODValidButtonColor } -/**## ODJsonConfig_DefaultOptionEmbedSettingsType `interface` - * This interface is an object which has all message embed settings for ticket options in the `options.json` config! +/**## ODOptionsJsonConfig_TicketOptionEmbedSettings `interface` + * The message embed settings for ticket options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionEmbedSettingsType { +export interface ODOptionsJsonConfig_TicketOptionEmbedSettings { /**Is this embed enabled? */ enabled:boolean, /**The title of the embed. */ @@ -390,10 +349,10 @@ export interface ODJsonConfig_DefaultOptionEmbedSettingsType { timestamp:boolean } -/**## ODJsonConfig_DefaultOptionPingSettingsType `interface` - * This interface is an object which has all message ping settings for ticket options in the `options.json` config! +/**## ODOptionsJsonConfig_TicketOptionPingSettings `interface` + * The message ping settings for ticket options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionPingSettingsType { +export interface ODOptionsJsonConfig_TicketOptionPingSettings { /**Ping `@here`. */ "@here":boolean, /**Ping `@everyone`. */ @@ -402,47 +361,36 @@ export interface ODJsonConfig_DefaultOptionPingSettingsType { custom:string[] } -/**## ODJsonConfig_DefaultOptionTicketChannelType `interface` +/**## ODOptionsJsonConfig_TicketOptionChannelSettings `interface` * All settings related to the ticket channel itself in a ticket option. */ -export interface ODJsonConfig_DefaultOptionTicketChannelType { +export interface ODOptionsJsonConfig_TicketOptionChannelSettings { /**The prefix used in the name of this ticket channel. */ prefix:string, /**The type of suffix used in the name of this ticket channel. */ suffix:"user-name"|"user-nickname"|"user-id"|"random-number"|"random-hex"|"counter-dynamic"|"counter-fixed", /**An optional discord category id to create this ticket in. */ category:string, - /**An optional discord category id to move this ticket to when closed. */ - closedCategory:string, - /**An optional discord category id to create this ticket in when the primary one is full (max. 50 tickets). */ - backupCategory:string, - /**A list of discord category ids to move this ticket to when claimed by a specific user. */ - claimedCategory:{ - /**The user which claimed the ticket. */ - user:string, - /**The category to move the ticket to when claimed by this user. */ - category:string - }[], /**The channel topic shown at the top of the channel in discord. */ topic:string } -/**## ODJsonConfig_DefaultOptionTicketType `interface` - * This interface is an object which has all ticket properties for options in the `options.json` config! +/**## ODOptionsJsonConfig_TicketOption `interface` + * All properties for ticket options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionTicketType extends ODJsonConfig_DefaultOptionType { +export interface ODOptionsJsonConfig_TicketOption extends ODOptionsJsonConfig_BaseOption { type:"ticket", - button:ODJsonConfig_DefaultOptionButtonSettingsType, + button:ODOptionsJsonConfig_OptionButtonSettings, /**A list of discord role ids which are able to access this ticket type & use commands. */ ticketAdmins:string[], /**A list of discord role ids which are able to access this ticket type but can't write in the chat. */ readonlyAdmins:string[], /**When enabled, blacklisted users can still create this ticket type. (used for appeals, etc) */ allowCreationByBlacklistedUsers:boolean, - /**A list of valid question ids from the `questions.json` config. */ + /**A list of valid question ids from the `questions.jsonc` config. */ questions:string[], /**All settings related to the ticket channel itself. */ - channel:ODJsonConfig_DefaultOptionTicketChannelType, + channel:ODOptionsJsonConfig_TicketOptionChannelSettings, /**All settings related to the message sent in DM to the creator when the ticket is created. */ dmMessage:{ /**Enable this message. */ @@ -450,7 +398,7 @@ export interface ODJsonConfig_DefaultOptionTicketType extends ODJsonConfig_Defau /**The raw text contents of this message. (empty for embed only) */ text:string, /**The embed of this message. */ - embed:ODJsonConfig_DefaultOptionEmbedSettingsType + embed:ODOptionsJsonConfig_TicketOptionEmbedSettings }, /**All settings related to the message sent in the ticket channel when the ticket is created. */ ticketMessage:{ @@ -459,9 +407,9 @@ export interface ODJsonConfig_DefaultOptionTicketType extends ODJsonConfig_Defau /**The raw text contents of this message. (empty for embed only) */ text:string, /**The embed of this message. */ - embed:ODJsonConfig_DefaultOptionEmbedSettingsType, + embed:ODOptionsJsonConfig_TicketOptionEmbedSettings, /**Additional ping/mention settings for this ticket channel. */ - ping:ODJsonConfig_DefaultOptionPingSettingsType + ping:ODOptionsJsonConfig_TicketOptionPingSettings }, /**All settings related to autoclosing this ticket type. */ autoclose:{ @@ -510,21 +458,21 @@ export interface ODJsonConfig_DefaultOptionTicketType extends ODJsonConfig_Defau } } -/**## ODJsonConfig_DefaultOptionWebsiteType `interface` - * This interface is an object which has all website properties for options in the `options.json` config! +/**## ODOptionsJsonConfig_WebsiteOption `interface` + * All properties for website options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionWebsiteType extends ODJsonConfig_DefaultOptionType { +export interface ODOptionsJsonConfig_WebsiteOption extends ODOptionsJsonConfig_BaseOption { type:"website", /**The URL this button will point to. */ url:string } -/**## ODJsonConfig_DefaultOptionRoleType `interface` - * This interface is an object which has all reaction role properties for options in the `options.json` config! +/**## ODOptionsJsonConfig_RoleOption `interface` + * All properties for reaction-role options in the `options.jsonc` config! */ -export interface ODJsonConfig_DefaultOptionRoleType extends ODJsonConfig_DefaultOptionType { +export interface ODOptionsJsonConfig_RoleOption extends ODOptionsJsonConfig_BaseOption { type:"role", - button:ODJsonConfig_DefaultOptionButtonSettingsType, + button:ODOptionsJsonConfig_OptionButtonSettings, /**All roles which will be affected by this button. */ roles:string[], /**The mode determines what will happen with the affected roles on the user. */ @@ -535,25 +483,30 @@ export interface ODJsonConfig_DefaultOptionRoleType extends ODJsonConfig_Default addOnMemberJoin:boolean } -/**## ODJsonConfig_DefaultOptionsData `type` - * All contents of the `options.json` config file. +/**## ODOptionsJsonConfig_SubPanelOption `interface` + * All properties for sub-panel options in the `options.jsonc` config! */ -export type ODJsonConfig_DefaultOptionsData = (ODJsonConfig_DefaultOptionTicketType|ODJsonConfig_DefaultOptionWebsiteType|ODJsonConfig_DefaultOptionRoleType)[] - -/**## ODJsonConfig_DefaultOptions `default_class` - * This is a special class that adds type definitions & typescript to the ODJsonConfig class. - * It doesn't add any extra features! - * - * This default class is made for the `options.json` config! - */ -export class ODJsonConfig_DefaultOptions extends ODJsonConfig { - declare data: ODJsonConfig_DefaultOptionsData +export interface ODOptionsJsonConfig_SubPanelOption extends ODOptionsJsonConfig_BaseOption { + type:"sub-panel", + button:ODOptionsJsonConfig_OptionButtonSettings, + /**The panel ID of the sub-panel to show when the button is clicked. */ + subPanelId:string } -/**## ODJsonConfig_DefaultPanelEmbedSettingsType `interface` - * This interface is an object which has all message embed settings for panels in the `panels.json` config! +/**## ODOptionsJsonConfig_OptionsData `type` + * All contents of the `options.jsonc` config file. */ -export interface ODJsonConfig_DefaultPanelEmbedSettingsType { +export type ODOptionsJsonConfig_OptionsData = (ODOptionsJsonConfig_TicketOption|ODOptionsJsonConfig_WebsiteOption|ODOptionsJsonConfig_RoleOption|ODOptionsJsonConfig_SubPanelOption)[] + +/////////////////////////////////////// +// CONFIG STRUCTURES, VALUES & TYPES +// --> panels.jsonc +/////////////////////////////////////// + +/**## ODPanelsJsonConfig_PanelEmbedSettings `interface` + * This interface is an object which has all message embed settings for panels in the `panels.jsonc` config! + */ +export interface ODPanelsJsonConfig_PanelEmbedSettings { /**Is this embed enabled? */ enabled:boolean, /**The title of the embed. */ @@ -585,12 +538,14 @@ export interface ODJsonConfig_DefaultPanelEmbedSettingsType { timestamp:boolean } -/**## ODJsonConfig_DefaultPanelSettingsType `interface` +/**## ODPanelsJsonConfig_PanelSettings `interface` * This interface is a collection of additional settings for extra customisation in a panel. */ -export interface ODJsonConfig_DefaultPanelSettingsType { +export interface ODPanelsJsonConfig_PanelSettings { /**The placeholder used in the dropdown when enabled. */ dropdownPlaceholder:string, + /**The maximum amount of option buttons before starting a new row. */ + maximumButtonsPerRow:number /**Enable a max tickets warning in the text contents. */ enableMaxTicketsWarningInText:boolean, @@ -609,46 +564,41 @@ export interface ODJsonConfig_DefaultPanelSettingsType { describeOptionsInEmbedDescription:boolean } -/**## ODJsonConfig_DefaultPanelType `interface` - * This interface is an object which has all properties for panels in the `panels.json` config! +/**## ODPanelsJsonConfig_Panel `interface` + * This interface is an object which has all properties for panels in the `panels.jsonc` config! */ -export interface ODJsonConfig_DefaultPanelType { +export interface ODPanelsJsonConfig_Panel { /**The id of this panel. */ id:string, /**The name of this panel. */ name:string, /**When enabled, the panel uses a dropdown instead of buttons. */ dropdown:boolean, - /**A list of valid options ids from the `options.json` config. */ + /**A list of valid options ids from the `options.jsonc` config. */ options:string[], /**The raw text contents of this panel. (empty for embed only) */ text:string, /**The embed of this panel. */ - embed:ODJsonConfig_DefaultPanelEmbedSettingsType, + embed:ODPanelsJsonConfig_PanelEmbedSettings, /**A collection of additional settings for extra customisation in a panel. */ - settings:ODJsonConfig_DefaultPanelSettingsType + settings:ODPanelsJsonConfig_PanelSettings } -/**## ODJsonConfig_DefaultPanelsData `type` - * All contents of the `panels.json` config file. +/**## ODPanelsJsonConfig_PanelsData `type` + * All contents of the `panels.jsonc` config file. */ -export type ODJsonConfig_DefaultPanelsData = ODJsonConfig_DefaultPanelType[] +export type ODPanelsJsonConfig_PanelsData = ODPanelsJsonConfig_Panel[] -/**## ODJsonConfig_DefaultPanels `default_class` - * This is a special class that adds type definitions & typescript to the ODJsonConfig class. - * It doesn't add any extra features! - * - * This default class is made for the `panels.json` config! - */ -export class ODJsonConfig_DefaultPanels extends ODJsonConfig { - declare data: ODJsonConfig_DefaultPanelsData -} +/////////////////////////////////////// +// CONFIG STRUCTURES, VALUES & TYPES +// --> questions.jsonc +/////////////////////////////////////// -/**## ODJSonConfig_DefaultQuestionLengthSettings `interface` +/**## ODQuestionsJsonConfig_TextLengthLimits `interface` * This interface is a collection of settings related to length validation in a question. */ -export interface ODJSonConfig_DefaultQuestionLengthSettings { +export interface ODQuestionsJsonConfig_TextLengthLimits { /**Enable text length verification. */ enabled:boolean, /**The minimum text input length. */ @@ -657,63 +607,144 @@ export interface ODJSonConfig_DefaultQuestionLengthSettings { max:number } -/**## ODJsonConfig_DefaultShortQuestionType `interface` - * This interface is an object which has all properties for short questions in the `questions.json` config! +/**## ODQuestionsJsonConfig_CheckboxLimits `interface` + * The required amount of checkboxes validation in a question. */ -export interface ODJsonConfig_DefaultShortQuestionType { +export interface ODQuestionsJsonConfig_CheckboxLimits { + /**Enable checkbox limits. */ + enabled:boolean, + /**The minimum amount of selected checkboxes. */ + min:number, + /**The maximum amount of selected checkboxes. */ + max:number +} + +/**## ODQuestionsJsonConfig_DropdownChoice `interface` + * A dropdown choice used in `ODQuestionsJsonConfig_DropdownQuestion` + */ +export interface ODQuestionsJsonConfig_DropdownChoice { + /**The title of the choice. */ + title:string, + /**The optional description of the choice. (Leave empty for none) */ + description:string, + /**The optional emoji of the choice. (Leave empty for none) */ + emoji:string +} + +/**## ODQuestionsJsonConfig_RadioCheckboxChoice `interface` + * A radio/checkbox choice used in `ODQuestionsJsonConfig_RadioSelectQuestion` & `ODQuestionsJsonConfig_CheckboxSelectQuestion` + */ +export interface ODQuestionsJsonConfig_RadioCheckboxChoice { + /**The title of the choice. */ + title:string, + /**The optional description of the choice. (Leave empty for none) */ + description:string, + /**Is this choice selected by default? */ + selectedByDefault:boolean +} + +/**## ODQuestionsJsonConfig_BaseQuestion `interface` + * This interface is an object which has all universal properties for questions in the `questions.jsonc` config! + */ +export interface ODQuestionsJsonConfig_BaseQuestion { /**The id of this question. */ id:string, /**The name of this question. */ name:string, + /**The description of this question. (Leave empty for none) */ + description:string, /**The type of this question. */ + type:"short"|"paragraph"|"text-display"|"dropdown"|"radio-select"|"checkbox-select", + /**Is this question required? */ + required:boolean, +} + +/**## ODQuestionsJsonConfig_TextDisplayQuestion `interface` + * All properties for a text-display in the `questions.jsonc` config! + */ +export interface ODQuestionsJsonConfig_TextDisplayQuestion { + /**The id of this text-display. */ + id:string, + /**The type of this question. */ + type:"text-display", + /**The text contents to show in the modal. */ + textContents:string +} + +/**## ODQuestionsJsonConfig_ShortQuestion `interface` + * All properties for short questions in the `questions.jsonc` config! + */ +export interface ODQuestionsJsonConfig_ShortQuestion extends ODQuestionsJsonConfig_BaseQuestion { type:"short", - - /**Is this question required? */ - required:boolean, /**A placeholder for the question. */ placeholder:string, /**A collection of settings related to length validation in a question. */ - length:ODJSonConfig_DefaultQuestionLengthSettings + length:ODQuestionsJsonConfig_TextLengthLimits } -/**## ODJsonConfig_DefaultParagraphQuestionType `interface` - * This interface is an object which has all properties for paragraph questions in the `questions.json` config! +/**## ODQuestionsJsonConfig_ParagraphQuestion `interface` + * All properties for paragraph questions in the `questions.jsonc` config! */ -export interface ODJsonConfig_DefaultParagraphQuestionType { - /**The id of this question. */ - id:string, - /**The name of this question. */ - name:string, - /**The type of this question. */ +export interface ODQuestionsJsonConfig_ParagraphQuestion extends ODQuestionsJsonConfig_BaseQuestion { type:"paragraph", - - /**Is this question required? */ - required:boolean, /**A placeholder for the question. */ placeholder:string, /**A collection of settings related to length validation in a question. */ - length:ODJSonConfig_DefaultQuestionLengthSettings + length:ODQuestionsJsonConfig_TextLengthLimits } -/**## ODJsonConfig_DefaultQuestionsData `type` - * All contents of the `questions.json` config file. +/**## ODQuestionsJsonConfig_DropdownQuestion `interface` + * All properties for dropdown questions in the `questions.jsonc` config! */ -export type ODJsonConfig_DefaultQuestionsData = (ODJsonConfig_DefaultShortQuestionType|ODJsonConfig_DefaultParagraphQuestionType)[] - -/**## ODJsonConfig_DefaultQuestions `default_class` - * This is a special class that adds type definitions & typescript to the ODJsonConfig class. - * It doesn't add any extra features! - * - * This default class is made for the `questions.json` config! - */ -export class ODJsonConfig_DefaultQuestions extends ODJsonConfig { - declare data: ODJsonConfig_DefaultQuestionsData +export interface ODQuestionsJsonConfig_DropdownQuestion extends ODQuestionsJsonConfig_BaseQuestion { + type:"dropdown", + /**A placeholder for the dropdown. */ + placeholder:string, + /**A list of maximum 25 dropdown choices. */ + choices:ODQuestionsJsonConfig_DropdownChoice[] } -/**## ODJsonConfig_DefaultTranscriptsTextLayout `interface` +/**## ODQuestionsJsonConfig_RadioSelectQuestion `interface` + * All properties for radio select questions in the `questions.jsonc` config! + */ +export interface ODQuestionsJsonConfig_RadioSelectQuestion extends ODQuestionsJsonConfig_BaseQuestion { + type:"radio-select", + /**A list of minimum 2, maximum 10 radio choices. */ + choices:ODQuestionsJsonConfig_RadioCheckboxChoice[] +} + +/**## ODQuestionsJsonConfig_CheckboxSelectQuestion `interface` + * All properties for checkbox select questions in the `questions.jsonc` config! + */ +export interface ODQuestionsJsonConfig_CheckboxSelectQuestion extends ODQuestionsJsonConfig_BaseQuestion { + type:"checkbox-select", + /**Verify the checked amount of checkboxes with a minimum & maximum. */ + limits:ODQuestionsJsonConfig_CheckboxLimits + /**A list of minimum 1, maximum 10 checkbox choices. */ + choices:ODQuestionsJsonConfig_RadioCheckboxChoice[] +} + +/**## ODQuestionsJsonConfig_QuestionsData `type` + * All contents of the `questions.jsonc` config file. + */ +export type ODQuestionsJsonConfig_QuestionsData = ( + ODQuestionsJsonConfig_ShortQuestion| + ODQuestionsJsonConfig_ParagraphQuestion| + ODQuestionsJsonConfig_TextDisplayQuestion| + ODQuestionsJsonConfig_DropdownQuestion| + ODQuestionsJsonConfig_RadioSelectQuestion| + ODQuestionsJsonConfig_CheckboxSelectQuestion +)[] + +/////////////////////////////////////// +// CONFIG STRUCTURES, VALUES & TYPES +// --> transcripts.jsonc +/////////////////////////////////////// + +/**## ODTranscriptsJsonConfig_TranscriptsTextLayout `interface` * This interface contains the layout of the text transcripts. */ -export interface ODJsonConfig_DefaultTranscriptsTextLayout { +export interface ODTranscriptsJsonConfig_TranscriptsTextLayout { /**The layout/complexity of the text transcripts. */ layout:"simple"|"normal"|"detailed", /**Include stats in the transcript. */ @@ -733,10 +764,10 @@ export interface ODJsonConfig_DefaultTranscriptsTextLayout { customFileName:string } -/**## ODJsonConfig_DefaultTranscriptsHtmlLayout `interface` +/**## ODTranscriptsJsonConfig_TranscriptsHtmlLayout `interface` * This interface contains the layout of the HTML transcripts. */ -export interface ODJsonConfig_DefaultTranscriptsHtmlLayout { +export interface ODTranscriptsJsonConfig_TranscriptsHtmlLayout { /**Settings related to the background. */ background:{ /**Enable a custom background. */ @@ -781,10 +812,10 @@ export interface ODJsonConfig_DefaultTranscriptsHtmlLayout { } } -/**## ODJsonConfig_DefaultTranscriptsData `interface` - * All contents of the `transcripts.json` config file. +/**## ODTranscriptsJsonConfig_TranscriptsData `interface` + * All contents of the `transcripts.jsonc` config file. */ -export interface ODJsonConfig_DefaultTranscriptsData { +export interface ODTranscriptsJsonConfig_TranscriptsData { /**All general settings related to transcripts. */ general:{ /**Are transcripts enabled? */ @@ -816,18 +847,41 @@ export interface ODJsonConfig_DefaultTranscriptsData { includeTicketStats:boolean }, /**The layout of the text transcripts. */ - textTranscriptStyle:ODJsonConfig_DefaultTranscriptsTextLayout, + textTranscriptStyle:ODTranscriptsJsonConfig_TranscriptsTextLayout, /**The layout of the HTML transcripts. */ - htmlTranscriptStyle:ODJsonConfig_DefaultTranscriptsHtmlLayout + htmlTranscriptStyle:ODTranscriptsJsonConfig_TranscriptsHtmlLayout } +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// -/**## ODJsonConfig_DefaultTranscripts `default_class` - * This is a special class that adds type definitions & typescript to the ODJsonConfig class. - * It doesn't add any extra features! - * - * This default class is made for the `transcripts.json` config! +/**## ODMappedConfigManager `class + * A special class with types for the Open Ticket `ODConfigManager` class. */ -export class ODJsonConfig_DefaultTranscripts extends ODJsonConfig { - declare data: ODJsonConfig_DefaultTranscriptsData -} \ No newline at end of file +export class ODMappedConfigManager extends api.ODConfigManager {} + +/**## ODGeneralJsonCommentsConfig `class + * A special class with types for the Open Ticket `config/general.jsonc` config file + */ +export class ODGeneralJsonCommentsConfig extends api.ODJsonCommentsConfig {} + +/**## ODQuestionsJsonCommentsConfig `class + * A special class with types for the Open Ticket `config/questions.jsonc` config file + */ +export class ODQuestionsJsonCommentsConfig extends api.ODJsonCommentsConfig {} + +/**## ODOptionsJsonCommentsConfig `class + * A special class with types for the Open Ticket `config/options.jsonc` config file + */ +export class ODOptionsJsonCommentsConfig extends api.ODJsonCommentsConfig {} + +/**## ODPanelsJsonCommentsConfig `class + * A special class with types for the Open Ticket `config/panels.jsonc` config file + */ +export class ODPanelsJsonCommentsConfig extends api.ODJsonCommentsConfig {} + +/**## ODTranscriptsJsonCommentsConfig `class + * A special class with types for the Open Ticket `config/transcripts.jsonc` config file + */ +export class ODTranscriptsJsonCommentsConfig extends api.ODJsonCommentsConfig {} \ No newline at end of file diff --git a/src/core/mappings/console.ts b/src/core/mappings/console.ts new file mode 100644 index 0000000..362d0bb --- /dev/null +++ b/src/core/mappings/console.ts @@ -0,0 +1,21 @@ +/////////////////////////////////////// +//OPEN TICKET CONSOLE MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODLiveStatusManagerIdMappings `interface` + * A list of all available IDs in the default `ODLiveStatusManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODLiveStatusManagerIdMappings extends api.ODLiveStatusManagerIdConstraint { + "opendiscord:default-djdj-dev":api.ODLiveStatusUrlSource +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedLiveStatusManager `class + * A special class with types for the Open Ticket `ODLiveStatusManager` class. + */ +export class ODMappedLiveStatusManager extends api.ODLiveStatusManager {} \ No newline at end of file diff --git a/src/core/mappings/cooldown.ts b/src/core/mappings/cooldown.ts new file mode 100644 index 0000000..49cc921 --- /dev/null +++ b/src/core/mappings/cooldown.ts @@ -0,0 +1,21 @@ +/////////////////////////////////////// +//OPEN TICKET COOLDOWN MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODCooldownManagerIdMappings `interface` + * A list of all available IDs in the default `ODCooldownManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODCooldownManagerIdMappings extends api.ODCooldownManagerIdConstraint { + //"opendiscord:cooldown":api.ODCooldown +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedCooldownManager `class + * A special class with types for the Open Ticket `ODCooldownManager` class. + */ +export class ODMappedCooldownManager extends api.ODCooldownManager {} \ No newline at end of file diff --git a/src/core/mappings/database.ts b/src/core/mappings/database.ts new file mode 100644 index 0000000..11ad62d --- /dev/null +++ b/src/core/mappings/database.ts @@ -0,0 +1,98 @@ +/////////////////////////////////////// +//OPEN TICKET DATABASE MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import { ODTicketJson } from "../api/ticket.js" +import { ODOptionJson } from "../api/option.js" + +/**## ODDatabaseManagerIdMappings `interface` + * A list of all available IDs in the default `ODDatabaseManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODDatabaseManagerIdMappings extends api.ODDatabaseManagerIdConstraint { + "opendiscord:global":ODGlobalDatabase, + "opendiscord:stats":ODStatsDatabase, + "opendiscord:tickets":ODTicketsDatabase, + "opendiscord:users":ODUsersDatabase, + "opendiscord:options":ODOptionsDatabase, + "opendiscord:message-states":ODMessageStatesDatabase, +} + +///////////////////////////////////////// +// DATABASE MAPPINGS, CATEGORIES & TYPES +///////////////////////////////////////// + +/**## ODGlobalDatabaseIdMappings `interface` + * A list of all available IDs in the default `ODGlobalDatabase` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODGlobalDatabaseIdMappings extends api.ODDatabaseIdConstraint { + "opendiscord:panel-message":string, + "opendiscord:panel-update":string, + "opendiscord:option-suffix-counter":number, + "opendiscord:option-suffix-history":string[], + "opendiscord:last-version":string +} + +/**## ODTicketsDatabaseIdMappings `interface` + * A list of all available IDs in the default `ODTicketsDatabase` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTicketsDatabaseIdMappings extends api.ODDatabaseIdConstraint { + "opendiscord:ticket":ODTicketJson +} + +/**## ODUsersDatabaseIdMappings `interface` + * A list of all available IDs in the default `ODUsersDatabase` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODUsersDatabaseIdMappings extends api.ODDatabaseIdConstraint { + "opendiscord:blacklist":ODTicketJson +} + +/**## ODOptionsDatabaseIdMappings `interface` + * A list of all available IDs in the default `ODOptionsDatabase` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODOptionsDatabaseIdMappings extends api.ODDatabaseIdConstraint { + "opendiscord:used-option":ODOptionJson +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedDatabaseManager `class + * A special class with types for the Open Ticket `ODDatabaseManager` class. + */ +export class ODMappedDatabaseManager extends api.ODDatabaseManager {} + +/**## ODGlobalDatabase `class + * A special class with types for the Open Ticket `database/global.json` database file + */ +export class ODGlobalDatabase extends api.ODFormattedJsonDatabase {} + +/**## ODStatsDatabase `class + * A special class with types for the Open Ticket `database/stats.json` database file + */ +export class ODStatsDatabase extends api.ODFormattedJsonDatabase {} + +/**## ODTicketsDatabase `class + * A special class with types for the Open Ticket `database/tickets.json` database file + */ +export class ODTicketsDatabase extends api.ODFormattedJsonDatabase {} + +/**## ODUsersDatabase `class + * A special class with types for the Open Ticket `database/users.json` database file + */ +export class ODUsersDatabase extends api.ODFormattedJsonDatabase {} + +/**## ODOptionsDatabase `class + * A special class with types for the Open Ticket `database/options.json` database file + */ +export class ODOptionsDatabase extends api.ODFormattedJsonDatabase {} + +/**## ODMessageStatesDatabase `class + * A special class with types for the Open Ticket `database/states.json` database file + */ +export class ODMessageStatesDatabase extends api.ODFormattedJsonDatabase {} \ No newline at end of file diff --git a/src/core/mappings/event.ts b/src/core/mappings/event.ts new file mode 100644 index 0000000..0aaf988 --- /dev/null +++ b/src/core/mappings/event.ts @@ -0,0 +1,361 @@ +/////////////////////////////////////// +//OPEN TICKET EVENT MAPPINGS +/////////////////////////////////////// + +//BASE MAPPINGSS +import * as api from "@open-discord-bots/framework/api" +import * as discord from "discord.js" + +//OPEN TICKET MAPPINGS +import { ODMappedPluginClassManager, ODMappedPluginManager } from "./plugin.js" +import { ODMappedConfigManager} from "./config.js" +import { ODMappedDatabaseManager } from "./database.js" +import { ODMappedFlagManager } from "./flag.js" +import { ODMappedSessionManager } from "./session.js" +import { ODMappedLanguageManager } from "./language.js" +import { ODMappedCheckerFunctionManager, ODMappedCheckerManager, ODMappedCheckerTranslationRegister } from "./checker.js" +import { ODMappedClientManager, ODMappedContextMenuManager, ODMappedSlashCommandManager, ODMappedTextCommandManager } from "./client.js" +import { ODMappedBuilderManager, ODMappedButtonManager, ODMappedDropdownManager, ODMappedEmbedManager, ODMappedFileManager, ODMappedMessageManager, ODMappedModalManager } from "./builder.js" +import { ODMappedAutocompleteResponderManager, ODMappedButtonResponderManager, ODMappedCommandResponderManager, ODMappedContextMenuResponderManager, ODMappedDropdownResponderManager, ODMappedModalResponderManager, ODMappedResponderManager } from "./responder.js" +import { ODMappedActionManager } from "./action.js" +import { ODMappedPermissionManager } from "./permission.js" +import { ODMappedHelpMenuManager } from "./helpmenu.js" +import { ODMappedStatisticManager } from "./statistic.js" +import { ODMappedCodeManager } from "./code.js" +import { ODMappedCooldownManager } from "./cooldown.js" +import { ODMappedPostManager } from "./post.js" +import { ODMappedVerifyBarManager } from "./verifybar.js" +import { ODMappedStartScreenManager } from "./startscreen.js" +import { ODMappedLiveStatusManager } from "./console.js" +import { ODMappedProgressBarManager, ODMappedProgressBarRendererManager } from "./progressbar.js" +import { ODMappedComponentManager, ODMappedComponentModifierManager, ODMappedMessageComponentManager, ODMappedModalComponentManager, ODMappedSharedComponentManager } from "./component.js" +import { ODMappedStateManager } from "./state.js" + +//OPEN TICKET MAPPINGSS +import { ODOptionManager, ODTicketOption } from "../api/option.js" +import { ODPanel, ODPanelManager } from "../api/panel.js" +import { ODTicket, ODTicketClearFilter, ODTicketManager } from "../api/ticket.js" +import { ODQuestionManager } from "../api/question.js" +import { ODBlacklistManager } from "../api/blacklist.js" +import { ODMappedTranscriptManager } from "../api/transcript.js" +import { ODRole, ODRoleManager } from "../api/role.js" +import { ODMappedPriorityManager, ODPriorityLevel } from "../api/priority.js" + +/**## ODEventManagerIdMappings `interface` + * A list of all available IDs in the default `ODEventManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODEventManagerIdMappings extends api.ODEventManagerIdConstraint { + //error handling + "onErrorHandling": api.ODEvent<(error:Error, origin:NodeJS.UncaughtExceptionOrigin) => api.ODPromiseVoid> + "afterErrorHandling": api.ODEvent<(error:Error, origin:NodeJS.UncaughtExceptionOrigin, message:api.ODError) => api.ODPromiseVoid> + + //plugins + "afterPluginsLoaded": api.ODEvent<(plugins:ODMappedPluginManager) => api.ODPromiseVoid> + "onPluginClassLoad": api.ODEvent<(classes:ODMappedPluginClassManager, plugins:ODMappedPluginManager) => api.ODPromiseVoid> + "afterPluginClassesLoaded": api.ODEvent<(classes:ODMappedPluginClassManager, plugins:ODMappedPluginManager) => api.ODPromiseVoid> + + //flags + "onFlagLoad": api.ODEvent<(flags:ODMappedFlagManager) => api.ODPromiseVoid> + "afterFlagsLoaded": api.ODEvent<(flags:ODMappedFlagManager) => api.ODPromiseVoid> + "onFlagInit": api.ODEvent<(flags:ODMappedFlagManager) => api.ODPromiseVoid> + "afterFlagsInitiated": api.ODEvent<(flags:ODMappedFlagManager) => api.ODPromiseVoid> + + //progress bars + "onProgressBarRendererLoad": api.ODEvent<(renderers:ODMappedProgressBarRendererManager) => api.ODPromiseVoid> + "afterProgressBarRenderersLoaded": api.ODEvent<(renderers:ODMappedProgressBarRendererManager) => api.ODPromiseVoid> + "onProgressBarLoad": api.ODEvent<(progressbars:ODMappedProgressBarManager) => api.ODPromiseVoid> + "afterProgressBarsLoaded": api.ODEvent<(progressbars:ODMappedProgressBarManager) => api.ODPromiseVoid> + + //configs + "onConfigLoad": api.ODEvent<(configs:ODMappedConfigManager) => api.ODPromiseVoid> + "afterConfigsLoaded": api.ODEvent<(configs:ODMappedConfigManager) => api.ODPromiseVoid> + "onConfigInit": api.ODEvent<(configs:ODMappedConfigManager) => api.ODPromiseVoid> + "afterConfigsInitiated": api.ODEvent<(configs:ODMappedConfigManager) => api.ODPromiseVoid> + + //databases + "onDatabaseLoad": api.ODEvent<(databases:ODMappedDatabaseManager) => api.ODPromiseVoid> + "afterDatabasesLoaded": api.ODEvent<(databases:ODMappedDatabaseManager) => api.ODPromiseVoid> + "onDatabaseInit": api.ODEvent<(databases:ODMappedDatabaseManager) => api.ODPromiseVoid> + "afterDatabasesInitiated": api.ODEvent<(databases:ODMappedDatabaseManager) => api.ODPromiseVoid> + + //languages + "onLanguageLoad": api.ODEvent<(languages:ODMappedLanguageManager) => api.ODPromiseVoid> + "afterLanguagesLoaded": api.ODEvent<(languages:ODMappedLanguageManager) => api.ODPromiseVoid> + "onLanguageInit": api.ODEvent<(languages:ODMappedLanguageManager) => api.ODPromiseVoid> + "afterLanguagesInitiated": api.ODEvent<(languages:ODMappedLanguageManager) => api.ODPromiseVoid> + "onLanguageSelect": api.ODEvent<(languages:ODMappedLanguageManager) => api.ODPromiseVoid> + "afterLanguagesSelected": api.ODEvent<(main:api.ODLanguage|null, backup:api.ODLanguage|null, languages:ODMappedLanguageManager) => api.ODPromiseVoid> + + //sessions + "onSessionLoad": api.ODEvent<(languages:ODMappedSessionManager) => api.ODPromiseVoid> + "afterSessionsLoaded": api.ODEvent<(languages:ODMappedSessionManager) => api.ODPromiseVoid> + + //config checkers + "onCheckerLoad": api.ODEvent<(checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "afterCheckersLoaded": api.ODEvent<(checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "onCheckerFunctionLoad": api.ODEvent<(functions:ODMappedCheckerFunctionManager, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "afterCheckerFunctionsLoaded": api.ODEvent<(functions:ODMappedCheckerFunctionManager, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "onCheckerExecute": api.ODEvent<(checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "afterCheckersExecuted": api.ODEvent<(result:api.ODCheckerResult, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "onCheckerTranslationLoad": api.ODEvent<(translations:ODMappedCheckerTranslationRegister, enabled:boolean, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "afterCheckerTranslationsLoaded": api.ODEvent<(translations:ODMappedCheckerTranslationRegister, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "onCheckerRender": api.ODEvent<(renderer:api.ODCheckerRenderer, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "afterCheckersRendered": api.ODEvent<(renderer:api.ODCheckerRenderer, checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + "onCheckerQuit": api.ODEvent<(checkers:ODMappedCheckerManager) => api.ODPromiseVoid> + + //plugin loading before client + "onPluginBeforeClientLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeClientLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //client configuration + "onClientLoad": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "afterClientLoaded": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "onClientInit": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "afterClientInitiated": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "onClientReady": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "afterClientReady": api.ODEvent<(client:ODMappedClientManager) => api.ODPromiseVoid> + "onClientActivityLoad": api.ODEvent<(activity:api.ODClientActivityManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterClientActivityLoaded": api.ODEvent<(activity:api.ODClientActivityManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "onClientActivityInit": api.ODEvent<(activity:api.ODClientActivityManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterClientActivityInitiated": api.ODEvent<(activity:api.ODClientActivityManager, client:ODMappedClientManager) => api.ODPromiseVoid> + + //priority levels + "onPriorityLoad": api.ODEvent<(priorities:ODMappedPriorityManager) => api.ODPromiseVoid> + "afterPrioritiesLoaded": api.ODEvent<(priorities:ODMappedPriorityManager) => api.ODPromiseVoid> + + //client slash commands + "onSlashCommandLoad": api.ODEvent<(slash:ODMappedSlashCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterSlashCommandsLoaded": api.ODEvent<(slash:ODMappedSlashCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "onSlashCommandRegister": api.ODEvent<(slash:ODMappedSlashCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterSlashCommandsRegistered": api.ODEvent<(slash:ODMappedSlashCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + + //client context menus + "onContextMenuLoad": api.ODEvent<(menu:ODMappedContextMenuManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterContextMenusLoaded": api.ODEvent<(menu:ODMappedContextMenuManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "onContextMenuRegister": api.ODEvent<(menu:ODMappedContextMenuManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterContextMenusRegistered": api.ODEvent<(menu:ODMappedContextMenuManager, client:ODMappedClientManager) => api.ODPromiseVoid> + + //client text commands + "onTextCommandLoad": api.ODEvent<(text:ODMappedTextCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + "afterTextCommandsLoaded": api.ODEvent<(text:ODMappedTextCommandManager, client:ODMappedClientManager) => api.ODPromiseVoid> + + //states + "onStateLoad": api.ODEvent<(posts:ODMappedStateManager) => api.ODPromiseVoid> + "afterStatesLoaded": api.ODEvent<(posts:ODMappedStateManager) => api.ODPromiseVoid> + "onStateInit": api.ODEvent<(posts:ODMappedStateManager) => api.ODPromiseVoid> + "afterStatesInitiated": api.ODEvent<(posts:ODMappedStateManager) => api.ODPromiseVoid> + + //plugin loading before managers + "onPluginBeforeManagerLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeManagerLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //questions + "onQuestionLoad": api.ODEvent<(questions:ODQuestionManager) => api.ODPromiseVoid> + "afterQuestionsLoaded": api.ODEvent<(questions:ODQuestionManager) => api.ODPromiseVoid> + + //options + "onOptionLoad": api.ODEvent<(options:ODOptionManager) => api.ODPromiseVoid> + "afterOptionsLoaded": api.ODEvent<(options:ODOptionManager) => api.ODPromiseVoid> + + //panels + "onPanelLoad": api.ODEvent<(panels:ODPanelManager) => api.ODPromiseVoid> + "afterPanelsLoaded": api.ODEvent<(panels:ODPanelManager) => api.ODPromiseVoid> + "onPanelSpawn": api.ODEvent<(panel:ODPanel) => api.ODPromiseVoid> + "afterPanelSpawned": api.ODEvent<(panel:ODPanel) => api.ODPromiseVoid> + + //tickets + "onTicketLoad": api.ODEvent<(tickets:ODTicketManager) => api.ODPromiseVoid> + "afterTicketsLoaded": api.ODEvent<(tickets:ODTicketManager) => api.ODPromiseVoid> + + //ticket creation + "onTicketChannelCreation": api.ODEvent<(option:ODTicketOption, user:discord.User) => api.ODPromiseVoid> + "afterTicketChannelCreated": api.ODEvent<(option:ODTicketOption, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + "onTicketChannelDeletion": api.ODEvent<(ticket:ODTicket, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + "afterTicketChannelDeleted": api.ODEvent<(ticket:ODTicket, user:discord.User) => api.ODPromiseVoid> + "onTicketPermissionsCreated": api.ODEvent<(option:ODTicketOption, permissions:ODMappedPermissionManager, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + "afterTicketPermissionsCreated": api.ODEvent<(option:ODTicketOption, permissions:ODMappedPermissionManager, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + "onTicketMainMessageCreated": api.ODEvent<(ticket:ODTicket, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + "afterTicketMainMessageCreated": api.ODEvent<(ticket:ODTicket, message:discord.Message, channel:discord.GuildTextBasedChannel, user:discord.User) => api.ODPromiseVoid> + + //ticket actions + "onTicketCreate": api.ODEvent<(creator:discord.User) => api.ODPromiseVoid> + "afterTicketCreated": api.ODEvent<(ticket:ODTicket, creator:discord.User, channel:discord.GuildTextBasedChannel) => api.ODPromiseVoid> + "onTicketClose": api.ODEvent<(ticket:ODTicket, closer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketClosed": api.ODEvent<(ticket:ODTicket, closer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketReopen": api.ODEvent<(ticket:ODTicket, reopener:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketReopened": api.ODEvent<(ticket:ODTicket, reopener:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketDelete": api.ODEvent<(ticket:ODTicket, deleter:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketDeleted": api.ODEvent<(ticket:ODTicket, deleter:discord.User, reason:string|null) => api.ODPromiseVoid> + "onTicketMove": api.ODEvent<(ticket:ODTicket, mover:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketMoved": api.ODEvent<(ticket:ODTicket, mover:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketClaim": api.ODEvent<(ticket:ODTicket, claimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketClaimed": api.ODEvent<(ticket:ODTicket, claimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketUnclaim": api.ODEvent<(ticket:ODTicket, unclaimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketUnclaimed": api.ODEvent<(ticket:ODTicket, unclaimer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketPin": api.ODEvent<(ticket:ODTicket, pinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketPinned": api.ODEvent<(ticket:ODTicket, pinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketUnpin": api.ODEvent<(ticket:ODTicket, unpinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketUnpinned": api.ODEvent<(ticket:ODTicket, unpinner:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketUserAdd": api.ODEvent<(ticket:ODTicket, adder:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketUserAdded": api.ODEvent<(ticket:ODTicket, adder:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketUserRemove": api.ODEvent<(ticket:ODTicket, remover:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketUserRemoved": api.ODEvent<(ticket:ODTicket, remover:discord.User, user:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketRename": api.ODEvent<(ticket:ODTicket, renamer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "afterTicketRenamed": api.ODEvent<(ticket:ODTicket, renamer:discord.User, channel:discord.GuildTextBasedChannel, reason:string|null) => api.ODPromiseVoid> + "onTicketsClear": api.ODEvent<(tickets:ODTicket[], clearer:discord.User, channel:discord.GuildTextBasedChannel, filter:ODTicketClearFilter) => api.ODPromiseVoid> + "afterTicketsCleared": api.ODEvent<(tickets:ODTicket[], clearer:discord.User, channel:discord.GuildTextBasedChannel, filter:ODTicketClearFilter) => api.ODPromiseVoid> + "onTicketTopicChange": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldTopic:string, newTopic:string) => api.ODPromiseVoid> + "afterTicketTopicChanged": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldTopic:string, newTopic:string) => api.ODPromiseVoid> + "onTicketPriorityChange": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel, reason:string|null) => api.ODPromiseVoid> + "afterTicketPriorityChanged": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel, reason:string|null) => api.ODPromiseVoid> + "onTicketTransfer": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldCreator:discord.User, newCreator:discord.User, reason:string|null) => api.ODPromiseVoid> + "afterTicketTransferred": api.ODEvent<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldCreator:discord.User, newCreator:discord.User, reason:string|null) => api.ODPromiseVoid> + + //roles + "onRoleLoad": api.ODEvent<(roles:ODRoleManager) => api.ODPromiseVoid> + "afterRolesLoaded": api.ODEvent<(roles:ODRoleManager) => api.ODPromiseVoid> + "onRoleUpdate": api.ODEvent<(user:discord.User,role:ODRole) => api.ODPromiseVoid> + "afterRolesUpdated": api.ODEvent<(user:discord.User,role:ODRole) => api.ODPromiseVoid> + + //blacklist + "onBlacklistLoad": api.ODEvent<(blacklist:ODBlacklistManager) => api.ODPromiseVoid> + "afterBlacklistLoaded": api.ODEvent<(blacklist:ODBlacklistManager) => api.ODPromiseVoid> + + //transcripts + "onTranscriptCompilerLoad": api.ODEvent<(transcripts:ODMappedTranscriptManager) => api.ODPromiseVoid> + "afterTranscriptCompilersLoaded": api.ODEvent<(transcripts:ODMappedTranscriptManager) => api.ODPromiseVoid> + "onTranscriptHistoryLoad": api.ODEvent<(transcripts:ODMappedTranscriptManager) => api.ODPromiseVoid> + "afterTranscriptHistoryLoaded": api.ODEvent<(transcripts:ODMappedTranscriptManager) => api.ODPromiseVoid> + + //transcript creation + "onTranscriptCreate": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "afterTranscriptCreated": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "onTranscriptInit": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "afterTranscriptInitiated": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "onTranscriptCompile": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "afterTranscriptCompiled": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "onTranscriptReady": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + "afterTranscriptReady": api.ODEvent<(transcripts:ODMappedTranscriptManager,ticket:ODTicket,channel:discord.TextChannel,user:discord.User) => api.ODPromiseVoid> + + //plugin loading before builders + "onPluginBeforeBuilderLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeBuilderLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //builders + "onButtonBuilderLoad": api.ODEvent<(buttons:ODMappedButtonManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterButtonBuildersLoaded": api.ODEvent<(buttons:ODMappedButtonManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onDropdownBuilderLoad": api.ODEvent<(dropdowns:ODMappedDropdownManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterDropdownBuildersLoaded": api.ODEvent<(dropdowns:ODMappedDropdownManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onFileBuilderLoad": api.ODEvent<(files:ODMappedFileManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterFileBuildersLoaded": api.ODEvent<(files:ODMappedFileManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onEmbedBuilderLoad": api.ODEvent<(embeds:ODMappedEmbedManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterEmbedBuildersLoaded": api.ODEvent<(embeds:ODMappedEmbedManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onMessageBuilderLoad": api.ODEvent<(messages:ODMappedMessageManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterMessageBuildersLoaded": api.ODEvent<(messages:ODMappedMessageManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onModalBuilderLoad": api.ODEvent<(modals:ODMappedModalManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterModalBuildersLoaded": api.ODEvent<(modals:ODMappedModalManager, builders:ODMappedBuilderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + + //components + "onSharedComponentLoad": api.ODEvent<(shared:ODMappedSharedComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterSharedComponentsLoaded": api.ODEvent<(shared:ODMappedSharedComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onMessageComponentLoad": api.ODEvent<(shared:ODMappedMessageComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterMessageComponentsLoaded": api.ODEvent<(shared:ODMappedMessageComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onModalComponentLoad": api.ODEvent<(shared:ODMappedModalComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterModalComponentsLoaded": api.ODEvent<(shared:ODMappedModalComponentManager, components:ODMappedComponentManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onComponentModifierLoad": api.ODEvent<(modifiers:ODMappedComponentModifierManager, msgComponents:ODMappedMessageComponentManager, msgBuilders:ODMappedMessageManager) => api.ODPromiseVoid> + "afterComponentModifiersLoaded": api.ODEvent<(modifiers:ODMappedComponentModifierManager, msgComponents:ODMappedMessageComponentManager, msgBuilders:ODMappedMessageManager) => api.ODPromiseVoid> + + //plugin loading before responders + "onPluginBeforeResponderLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeResponderLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //responders + "onCommandResponderLoad": api.ODEvent<(commands:ODMappedCommandResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterCommandRespondersLoaded": api.ODEvent<(commands:ODMappedCommandResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onButtonResponderLoad": api.ODEvent<(buttons:ODMappedButtonResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterButtonRespondersLoaded": api.ODEvent<(buttons:ODMappedButtonResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onDropdownResponderLoad": api.ODEvent<(dropdowns:ODMappedDropdownResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterDropdownRespondersLoaded": api.ODEvent<(dropdowns:ODMappedDropdownResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onModalResponderLoad": api.ODEvent<(modals:ODMappedModalResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterModalRespondersLoaded": api.ODEvent<(modals:ODMappedModalResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onContextMenuResponderLoad": api.ODEvent<(menus:ODMappedContextMenuResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterContextMenuRespondersLoaded": api.ODEvent<(menus:ODMappedContextMenuResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "onAutocompleteResponderLoad": api.ODEvent<(autocomplete:ODMappedAutocompleteResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterAutocompleteRespondersLoaded": api.ODEvent<(autocomplete:ODMappedAutocompleteResponderManager, responders:ODMappedResponderManager, actions:ODMappedActionManager) => api.ODPromiseVoid> + + //plugin loading before finalizations + "onPluginBeforeFinalizationLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeFinalizationLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //actions + "onActionLoad": api.ODEvent<(actions:ODMappedActionManager) => api.ODPromiseVoid> + "afterActionsLoaded": api.ODEvent<(actions:ODMappedActionManager) => api.ODPromiseVoid> + + //verifybars + "onVerifyBarLoad": api.ODEvent<(verifybars:ODMappedVerifyBarManager) => api.ODPromiseVoid> + "afterVerifyBarsLoaded": api.ODEvent<(verifybars:ODMappedVerifyBarManager) => api.ODPromiseVoid> + + //permissions + "onPermissionLoad": api.ODEvent<(permissions:ODMappedPermissionManager) => api.ODPromiseVoid> + "afterPermissionsLoaded": api.ODEvent<(permissions:ODMappedPermissionManager) => api.ODPromiseVoid> + + //posts + "onPostLoad": api.ODEvent<(posts:ODMappedPostManager) => api.ODPromiseVoid> + "afterPostsLoaded": api.ODEvent<(posts:ODMappedPostManager) => api.ODPromiseVoid> + "onPostInit": api.ODEvent<(posts:ODMappedPostManager) => api.ODPromiseVoid> + "afterPostsInitiated": api.ODEvent<(posts:ODMappedPostManager) => api.ODPromiseVoid> + + //cooldowns + "onCooldownLoad": api.ODEvent<(cooldowns:ODMappedCooldownManager) => api.ODPromiseVoid> + "afterCooldownsLoaded": api.ODEvent<(cooldowns:ODMappedCooldownManager) => api.ODPromiseVoid> + "onCooldownInit": api.ODEvent<(cooldowns:ODMappedCooldownManager) => api.ODPromiseVoid> + "afterCooldownsInitiated": api.ODEvent<(cooldowns:ODMappedCooldownManager) => api.ODPromiseVoid> + + //help menu + "onHelpMenuCategoryLoad": api.ODEvent<(menu:ODMappedHelpMenuManager) => api.ODPromiseVoid> + "afterHelpMenuCategoriesLoaded": api.ODEvent<(menu:ODMappedHelpMenuManager) => api.ODPromiseVoid> + "onHelpMenuComponentLoad": api.ODEvent<(menu:ODMappedHelpMenuManager) => api.ODPromiseVoid> + "afterHelpMenuComponentsLoaded": api.ODEvent<(menu:ODMappedHelpMenuManager) => api.ODPromiseVoid> + + //stats + "onStatisticScopeLoad": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + "afterStatisticScopesLoaded": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + "onStatisticLoad": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + "afterStatisticsLoaded": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + "onStatisticInit": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + "afterStatisticsInitiated": api.ODEvent<(stats:ODMappedStatisticManager) => api.ODPromiseVoid> + + //plugin loading before code + "onPluginBeforeCodeLoad": api.ODEvent<() => api.ODPromiseVoid>, + "afterPluginBeforeCodeLoaded": api.ODEvent<() => api.ODPromiseVoid>, + + //code + "onCodeLoad": api.ODEvent<(code:ODMappedCodeManager) => api.ODPromiseVoid> + "afterCodeLoaded": api.ODEvent<(code:ODMappedCodeManager) => api.ODPromiseVoid> + "onCodeExecute": api.ODEvent<(code:ODMappedCodeManager) => api.ODPromiseVoid> + "afterCodeExecuted": api.ODEvent<(code:ODMappedCodeManager) => api.ODPromiseVoid> + + //livestatus + "onLiveStatusSourceLoad": api.ODEvent<(livestatus:ODMappedLiveStatusManager) => api.ODPromiseVoid> + "afterLiveStatusSourcesLoaded": api.ODEvent<(livestatus:ODMappedLiveStatusManager) => api.ODPromiseVoid> + + //startscreen + "onStartScreenLoad": api.ODEvent<(startscreen:ODMappedStartScreenManager) => api.ODPromiseVoid> + "afterStartScreensLoaded": api.ODEvent<(startscreen:ODMappedStartScreenManager) => api.ODPromiseVoid> + "onStartScreenRender": api.ODEvent<(startscreen:ODMappedStartScreenManager) => api.ODPromiseVoid> + "afterStartScreensRendered": api.ODEvent<(startscreen:ODMappedStartScreenManager) => api.ODPromiseVoid> + + //ready + "beforeReadyForUsage": api.ODEvent<() => api.ODPromiseVoid> + "onReadyForUsage": api.ODEvent<() => api.ODPromiseVoid> +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedEventManager `class + * A special class with types for the Open Ticket `ODEventManager` class. + */ +export class ODMappedEventManager extends api.ODEventManager {} \ No newline at end of file diff --git a/src/core/mappings/flag.ts b/src/core/mappings/flag.ts new file mode 100644 index 0000000..47b9bee --- /dev/null +++ b/src/core/mappings/flag.ts @@ -0,0 +1,36 @@ +/////////////////////////////////////// +//OPEN TICKET PROCESS MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODFlagManagerIdMappings `interface` + * A list of all available IDs in the default `ODFlagManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODFlagManagerIdMappings extends api.ODFlagManagerIdConstraint { + "opendiscord:no-migration":api.ODFlag, + "opendiscord:dev-config":api.ODFlag, + "opendiscord:dev-database":api.ODFlag, + "opendiscord:debug":api.ODFlag, + "opendiscord:crash":api.ODFlag, + "opendiscord:no-transcripts":api.ODFlag, + "opendiscord:no-checker":api.ODFlag, + "opendiscord:checker":api.ODFlag, + "opendiscord:no-easter":api.ODFlag, + "opendiscord:no-plugins":api.ODFlag, + "opendiscord:soft-plugins":api.ODFlag, + "opendiscord:force-slash-update":api.ODFlag, + "opendiscord:no-compile":api.ODFlag, + "opendiscord:compile-only":api.ODFlag, + "opendiscord:silent":api.ODFlag, + "opendiscord:cli":api.ODFlag, +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedFlagManager `class + * A special class with types for the Open Ticket `ODFlagManager` class. + */ +export class ODMappedFlagManager extends api.ODFlagManager {} \ No newline at end of file diff --git a/src/core/mappings/fuse.ts b/src/core/mappings/fuse.ts new file mode 100644 index 0000000..6e1ae00 --- /dev/null +++ b/src/core/mappings/fuse.ts @@ -0,0 +1,29 @@ +/////////////////////////////////////// +//OPEN TICKET FUSE MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +export interface ODOpenTicketFuseList { + /**Load the default Open Ticket ticket priority levels. */ + priorityLoading:boolean, + /**Load the default Open Ticket questions (from `config/questions.jsonc`) */ + questionLoading:boolean, + /**Load the default Open Ticket options (from `config/options.jsonc`) */ + optionLoading:boolean, + /**Load the default Open Ticket panels (from `config/panels.jsonc`) */ + panelLoading:boolean, + /**Load the default Open Ticket tickets (from `database/tickets.json`) */ + ticketLoading:boolean, + /**Load the default Open Ticket reaction roles (from `config/options.jsonc`) */ + roleLoading:boolean, + /**Load the default Open Ticket blacklist (from `database/users.json`) */ + blacklistLoading:boolean, + /**Load the default Open Ticket transcript compilers. */ + transcriptCompilerLoading:boolean, + /**Load the default Open Ticket transcript history (from `database/transcripts.jsonc`) */ + transcriptHistoryLoading:boolean, + /**The interval in milliseconds that are between autoclose timeout checkers. */ + autocloseCheckInterval:number, + /**The interval in milliseconds that are between autodelete timeout checkers. */ + autodeleteCheckInterval:number, +} \ No newline at end of file diff --git a/src/core/mappings/helpmenu.ts b/src/core/mappings/helpmenu.ts new file mode 100644 index 0000000..1b8ddfc --- /dev/null +++ b/src/core/mappings/helpmenu.ts @@ -0,0 +1,145 @@ +/////////////////////////////////////// +//OPEN TICKET HELP MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODHelpMenuManagerIdMappings `interface` + * A list of all available IDs in the default `ODHelpMenuManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODHelpMenuManagerIdMappings extends api.ODHelpMenuManagerIdConstraint { + "opendiscord:general":ODGeneralHelpMenuCategory, + "opendiscord:ticket-basic":ODBasicTicketHelpMenuCategory, + "opendiscord:ticket-advanced":ODAdvancedTicketHelpMenuCategory, + "opendiscord:ticket-user":ODUserTicketHelpMenuCategory, + "opendiscord:admin":ODAdminHelpMenuCategory, + "opendiscord:advanced":ODAdvancedHelpMenuCategory, + "opendiscord:extra":ODExtraHelpMenuCategory +} + +///////////////////////////////////////// +// HELP MENU MAPPINGS, CATEGORIES & TYPES +///////////////////////////////////////// + +/**## ODGeneralHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODGeneralHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODGeneralHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:help":api.ODHelpMenuCommandComponent, + "opendiscord:ticket":api.ODHelpMenuCommandComponent|null +} + +/**## ODBasicTicketHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODBasicTicketHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODBasicTicketHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:close":api.ODHelpMenuCommandComponent, + "opendiscord:delete":api.ODHelpMenuCommandComponent, + "opendiscord:reopen":api.ODHelpMenuCommandComponent +} + +/**## ODAdvancedTicketHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODAdvancedTicketHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODAdvancedTicketHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:pin":api.ODHelpMenuCommandComponent, + "opendiscord:unpin":api.ODHelpMenuCommandComponent, + "opendiscord:move":api.ODHelpMenuCommandComponent, + "opendiscord:rename":api.ODHelpMenuCommandComponent +} + +/**## ODUserTicketHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODUserTicketHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODUserTicketHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:claim":api.ODHelpMenuCommandComponent, + "opendiscord:unclaim":api.ODHelpMenuCommandComponent, + "opendiscord:add":api.ODHelpMenuCommandComponent, + "opendiscord:remove":api.ODHelpMenuCommandComponent, + "opendiscord:transfer":api.ODHelpMenuCommandComponent, +} + +/**## ODAdminHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODAdminHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODAdminHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:panel":api.ODHelpMenuCommandComponent, + "opendiscord:blacklist-view":api.ODHelpMenuCommandComponent, + "opendiscord:blacklist-add":api.ODHelpMenuCommandComponent, + "opendiscord:blacklist-remove":api.ODHelpMenuCommandComponent, + "opendiscord:blacklist-get":api.ODHelpMenuCommandComponent +} + +/**## ODAdvancedHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODAdvancedHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODAdvancedHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + "opendiscord:stats-global":api.ODHelpMenuCommandComponent, + "opendiscord:stats-reset":api.ODHelpMenuCommandComponent, + "opendiscord:stats-ticket":api.ODHelpMenuCommandComponent, + "opendiscord:stats-user":api.ODHelpMenuCommandComponent, + "opendiscord:autoclose-disable":api.ODHelpMenuCommandComponent, + "opendiscord:autoclose-enable":api.ODHelpMenuCommandComponent, + "opendiscord:autodelete-disable":api.ODHelpMenuCommandComponent, + "opendiscord:autodelete-enable":api.ODHelpMenuCommandComponent, + "opendiscord:topic-set":api.ODHelpMenuCommandComponent, + "opendiscord:priority-set":api.ODHelpMenuCommandComponent, +} + +/**## ODExtraHelpMenuCategoryIdMappings `interface` + * A list of all available IDs in the default `ODExtraHelpMenuCategory` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODExtraHelpMenuCategoryIdMappings extends api.ODHelpMenuCategoryIdConstraint { + //"opendiscord:help-component":api.ODHelpMenuCommandComponent +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedHelpMenuManager `class + * A special class with types for the Open Ticket `ODHelpMenuManager` class. + */ +export class ODMappedHelpMenuManager extends api.ODHelpMenuManager {} + +/**## ODGeneralHelpMenuCategory `class + * A special class with types for the Open Ticket `General Commands` help menu category. + */ +export class ODGeneralHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODBasicTicketHelpMenuCategory `class + * A special class with types for the Open Ticket `Basic Ticket Commands` help menu category. + */ +export class ODBasicTicketHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODAdvancedTicketHelpMenuCategory `class + * A special class with types for the Open Ticket `Advanced Ticket Commands` help menu category. + */ +export class ODAdvancedTicketHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODUserTicketHelpMenuCategory `class + * A special class with types for the Open Ticket `User ticket Commands` help menu category. + */ +export class ODUserTicketHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODAdminHelpMenuCategory `class + * A special class with types for the Open Ticket `Admin Commands` help menu category. + */ +export class ODAdminHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODAdvancedHelpMenuCategory `class + * A special class with types for the Open Ticket `Advanced Commands` help menu category. + */ +export class ODAdvancedHelpMenuCategory extends api.ODHelpMenuCategory {} + +/**## ODExtraHelpMenuCategory `class + * A special class with types for the Open Ticket `Extra Commands` help menu category. + */ +export class ODExtraHelpMenuCategory extends api.ODHelpMenuCategory {} \ No newline at end of file diff --git a/src/core/api/defaults/language.ts b/src/core/mappings/language.ts similarity index 80% rename from src/core/api/defaults/language.ts rename to src/core/mappings/language.ts index 64270e2..e5ff8c7 100644 --- a/src/core/api/defaults/language.ts +++ b/src/core/mappings/language.ts @@ -1,68 +1,60 @@ /////////////////////////////////////// -//DEFAULT LANGUAGE MODULE +//OPEN TICKET LANGUAGE MAPPINGS /////////////////////////////////////// -import { ODValidId } from "../modules/base" -import { ODLanguageManager, ODLanguage } from "../modules/language" +import * as api from "@open-discord-bots/framework/api" -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW LANGUAGES? - * - Add the file to (./languages/) and make sure the metadata is valid. - * - Register the language in loadAllLanguages() in (./src/data/framework/languageLoader.ts). - * - Add autocomplete for the language in ODLanguageManagerIds_Default in (./src/core/api/defaults/language.ts). - * - Update the language list in the README.md translator list. - * - Update the 2 language counters in the README.md features list. - * - Update the Open Ticket Documentation. - */ - -/**## ODLanguageManagerIds_Default `interface` - * This interface is a list of ids available in the `ODLanguageManager_Default` class. +/**## ODLanguageManagerIdMappings `interface` + * A list of all available IDs in the default `ODLanguageManager` class in `opendiscord`. * It's used to generate typescript declarations for this class. */ -export interface ODLanguageManagerIds_Default { - "opendiscord:custom":ODLanguage, - "opendiscord:english":ODLanguage, - "opendiscord:dutch":ODLanguage, - "opendiscord:portuguese":ODLanguage, - "opendiscord:czech":ODLanguage, - "opendiscord:german":ODLanguage, - "opendiscord:catalan":ODLanguage, - "opendiscord:hungarian":ODLanguage, - "opendiscord:spanish":ODLanguage, - "opendiscord:romanian":ODLanguage, - "opendiscord:ukrainian":ODLanguage, - "opendiscord:indonesian":ODLanguage, - "opendiscord:italian":ODLanguage, - "opendiscord:estonian":ODLanguage, - "opendiscord:finnish":ODLanguage, - "opendiscord:danish":ODLanguage, - "opendiscord:thai":ODLanguage, - "opendiscord:turkish":ODLanguage, - "opendiscord:french":ODLanguage, - "opendiscord:arabic":ODLanguage, - "opendiscord:hindi":ODLanguage, - "opendiscord:lithuanian":ODLanguage, - "opendiscord:polish":ODLanguage, - "opendiscord:latvian":ODLanguage, - "opendiscord:norwegian":ODLanguage, - "opendiscord:russian":ODLanguage, - "opendiscord:swedish":ODLanguage, - "opendiscord:vietnamese":ODLanguage, - "opendiscord:persian":ODLanguage, - "opendiscord:bengali":ODLanguage, - "opendiscord:greek":ODLanguage, - "opendiscord:japanese":ODLanguage, - "opendiscord:korean":ODLanguage, - "opendiscord:kurdish":ODLanguage, - "opendiscord:simplified-chinese":ODLanguage, - "opendiscord:slovenian":ODLanguage, - "opendiscord:tamil":ODLanguage, +export interface ODLanguageManagerIdMappings extends api.ODLanguageManagerIdConstraint { + "opendiscord:custom":api.ODLanguage, + "opendiscord:english":api.ODLanguage, + "opendiscord:dutch":api.ODLanguage, + "opendiscord:portuguese":api.ODLanguage, + "opendiscord:czech":api.ODLanguage, + "opendiscord:german":api.ODLanguage, + "opendiscord:catalan":api.ODLanguage, + "opendiscord:hungarian":api.ODLanguage, + "opendiscord:spanish":api.ODLanguage, + "opendiscord:romanian":api.ODLanguage, + "opendiscord:ukrainian":api.ODLanguage, + "opendiscord:indonesian":api.ODLanguage, + "opendiscord:italian":api.ODLanguage, + "opendiscord:estonian":api.ODLanguage, + "opendiscord:finnish":api.ODLanguage, + "opendiscord:danish":api.ODLanguage, + "opendiscord:thai":api.ODLanguage, + "opendiscord:turkish":api.ODLanguage, + "opendiscord:french":api.ODLanguage, + "opendiscord:arabic":api.ODLanguage, + "opendiscord:hindi":api.ODLanguage, + "opendiscord:lithuanian":api.ODLanguage, + "opendiscord:polish":api.ODLanguage, + "opendiscord:latvian":api.ODLanguage, + "opendiscord:norwegian":api.ODLanguage, + "opendiscord:russian":api.ODLanguage, + "opendiscord:swedish":api.ODLanguage, + "opendiscord:vietnamese":api.ODLanguage, + "opendiscord:persian":api.ODLanguage, + "opendiscord:bengali":api.ODLanguage, + "opendiscord:greek":api.ODLanguage, + "opendiscord:japanese":api.ODLanguage, + "opendiscord:korean":api.ODLanguage, + "opendiscord:kurdish":api.ODLanguage, + "opendiscord:simplified-chinese":api.ODLanguage, + "opendiscord:traditional-chinese":api.ODLanguage, + "opendiscord:slovenian":api.ODLanguage, + "opendiscord:tamil":api.ODLanguage, + "opendiscord:khmer ":api.ODLanguage, //ADD NEW LANGUAGES HERE!!! } -/**## ODLanguageManagerTranslations_Default `type` - * This interface is a list of ids available in the `ODLanguageManager_Default` class. +/**## ODLanguageManagerTranslationIdMappings `type` + * A list of all available translation IDs in the default `ODLanguageManager` class in `opendiscord`. * It's used to generate typescript declarations for this class. */ -export type ODLanguageManagerTranslations_Default = ( +export type ODLanguageManagerTranslationIdMappings = ( "checker.system.headerOpenTicket"| "checker.system.typeError"| "checker.system.typeWarning"| @@ -622,59 +614,11 @@ export type ODLanguageManagerTranslations_Default = ( "priorities.none" ) -/**## ODLanguageManager_Default `default_class` - * This is a special class that adds type definitions & typescript to the ODLanguageManager class. - * It doesn't add any extra features! - * - * This default class is made for the global variable `opendiscord.languages`! +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedLanguageManager `class + * A special class with types for the Open Ticket `ODLanguageManager` class. */ -export class ODLanguageManager_Default extends ODLanguageManager { - get(id:LanguageId): ODLanguageManagerIds_Default[LanguageId] - get(id:ODValidId): ODLanguage|null - - get(id:ODValidId): ODLanguage|null { - return super.get(id) - } - - remove(id:LanguageId): ODLanguageManagerIds_Default[LanguageId] - remove(id:ODValidId): ODLanguage|null - - remove(id:ODValidId): ODLanguage|null { - return super.remove(id) - } - - exists(id:keyof ODLanguageManagerIds_Default): boolean - exists(id:ODValidId): boolean - - exists(id:ODValidId): boolean { - return super.exists(id) - } - - getTranslation(id:ODLanguageManagerTranslations_Default): string - getTranslation(id:string): string|null - - getTranslation(id:string): string|null { - return super.getTranslation(id) - } - - setCurrentLanguage(id:keyof ODLanguageManagerIds_Default): void - setCurrentLanguage(id:ODValidId): void - - setCurrentLanguage(id:ODValidId): void { - return super.setCurrentLanguage(id) - } - - setBackupLanguage(id:keyof ODLanguageManagerIds_Default): void - setBackupLanguage(id:ODValidId): void - - setBackupLanguage(id:ODValidId): void { - return super.setBackupLanguage(id) - } - - getTranslationWithParams(id:ODLanguageManagerTranslations_Default, params:string[]): string - getTranslationWithParams(id:string, params:string[]): string|null - - getTranslationWithParams(id:string, params:string[]): string|null { - return super.getTranslationWithParams(id,params) - } -} \ No newline at end of file +export class ODMappedLanguageManager extends api.ODLanguageManager {} \ No newline at end of file diff --git a/src/core/mappings/permission.ts b/src/core/mappings/permission.ts new file mode 100644 index 0000000..050b225 --- /dev/null +++ b/src/core/mappings/permission.ts @@ -0,0 +1,34 @@ +/////////////////////////////////////// +//OPEN TICKET PERMISSION MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODPermissionManagerIdMappings `interface` + * A list of all available IDs in the default `ODPermissionManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODPermissionManagerIdMappings extends api.ODPermissionManagerIdConstraint { + //"opendiscord:test-permission":api.ODPermission +} + +/**## ODPermissionEmbedType `type` + * A collection of all types available in the `opendiscord:no-permissions` embed. + */ +export type ODPermissionEmbedType = ( + "developer"| + "owner"| + "admin"| + "moderator"| + "support"| + "member"| + "discord-administrator" +) + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedPermissionManager `class + * A special class with types for the Open Ticket `ODPermissionManager` class. + */ +export class ODMappedPermissionManager extends api.ODPermissionManager {} \ No newline at end of file diff --git a/src/core/mappings/plugin.ts b/src/core/mappings/plugin.ts new file mode 100644 index 0000000..ccf204d --- /dev/null +++ b/src/core/mappings/plugin.ts @@ -0,0 +1,34 @@ +/////////////////////////////////////// +//OPEN TICKET POST MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODPluginManagerIdMappings `interface` + * A list of all available IDs in the default `ODPluginManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODPluginManagerIdMappings extends api.ODPluginManagerIdConstraint { + //"opendiscord:example-plugin":api.ODPlugin +} + +/**## ODPluginClassManagerIdMappings `interface` + * A list of all available IDs in the default `ODPluginClassManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODPluginClassManagerIdMappings extends api.ODPluginClassManagerIdConstraint { + //"opendiscord:example-plugin":any +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedPluginManager `class + * A special class with types for the Open Ticket `ODPluginManager` class. + */ +export class ODMappedPluginManager extends api.ODPluginManager {} + +/**## ODMappedPluginClassManager `class + * A special class with types for the Open Ticket `ODPluginClassManager` class. + */ +export class ODMappedPluginClassManager extends api.ODPluginClassManager {} \ No newline at end of file diff --git a/src/core/mappings/post.ts b/src/core/mappings/post.ts new file mode 100644 index 0000000..a1abe2e --- /dev/null +++ b/src/core/mappings/post.ts @@ -0,0 +1,23 @@ +/////////////////////////////////////// +//OPEN TICKET POST MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import * as discord from "discord.js" + +/**## ODPostManagerIdMappings `interface` + * A list of all available IDs in the default `ODPostManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODPostManagerIdMappings extends api.ODPostManagerIdConstraint { + "opendiscord:logs":api.ODPost|null, + "opendiscord:transcripts":api.ODPost|null +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedPostManager `class + * A special class with types for the Open Ticket `ODPostManager` class. + */ +export class ODMappedPostManager extends api.ODPostManager {} \ No newline at end of file diff --git a/src/core/mappings/progressbar.ts b/src/core/mappings/progressbar.ts new file mode 100644 index 0000000..68a2a3f --- /dev/null +++ b/src/core/mappings/progressbar.ts @@ -0,0 +1,44 @@ +/////////////////////////////////////// +//OPEN TICKET PROGRESS BAR MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODProgressBarManagerIdMappings `interface` + * A list of all available IDs in the default `ODProgressBarManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODProgressBarManagerIdMappings extends api.ODProgressBarManagerIdConstraint { + "opendiscord:slash-command-remove":api.ODManualProgressBar, + "opendiscord:slash-command-create":api.ODManualProgressBar, + "opendiscord:slash-command-update":api.ODManualProgressBar, + "opendiscord:context-menu-remove":api.ODManualProgressBar, + "opendiscord:context-menu-create":api.ODManualProgressBar, + "opendiscord:context-menu-update":api.ODManualProgressBar, +} + +/**## ODProgressBarRendererManagerIdMappings `interface` + * A list of all available IDs in the default `ODProgressBarRendererManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODProgressBarRendererManagerIdMappings extends api.ODProgressBarRendererManagerIdConstraint { + "opendiscord:value-renderer":api.ODDefaultProgressBarRenderer, + "opendiscord:fraction-renderer":api.ODDefaultProgressBarRenderer, + "opendiscord:percentage-renderer":api.ODDefaultProgressBarRenderer, + "opendiscord:time-ms-renderer":api.ODDefaultProgressBarRenderer, + "opendiscord:time-sec-renderer":api.ODDefaultProgressBarRenderer, + "opendiscord:time-min-renderer":api.ODDefaultProgressBarRenderer, +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedProgressBarManager `class + * A special class with types for the Open Ticket `ODProgressBarManager` class. + */ +export class ODMappedProgressBarManager extends api.ODProgressBarManager {} + +/**## ODMappedProgressBarRendererManager `class + * A special class with types for the Open Ticket `ODProgressBarRendererManager` class. + */ +export class ODMappedProgressBarRendererManager extends api.ODProgressBarRendererManager {} \ No newline at end of file diff --git a/src/core/mappings/responder.ts b/src/core/mappings/responder.ts new file mode 100644 index 0000000..036c509 --- /dev/null +++ b/src/core/mappings/responder.ts @@ -0,0 +1,142 @@ +/////////////////////////////////////// +//OPEN TICKET RESPONDER MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODCommandResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODCommandResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODCommandResponderManagerIdMappings extends api.ODCommandResponderManagerIdConstraint { + "opendiscord:help":{origin:"slash"|"text",params:{},workers:"opendiscord:help"|"opendiscord:logs"}, + "opendiscord:stats":{origin:"slash"|"text",params:{},workers:"opendiscord:stats"|"opendiscord:logs"}, + "opendiscord:panel":{origin:"slash"|"text",params:{},workers:"opendiscord:panel"|"opendiscord:logs"}, + "opendiscord:ticket":{origin:"slash"|"text",params:{},workers:"opendiscord:ticket"|"opendiscord:logs"}, + "opendiscord:blacklist":{origin:"slash"|"text",params:{},workers:"opendiscord:blacklist"|"opendiscord:discord-logs"|"opendiscord:logs"}, + + "opendiscord:close":{origin:"slash"|"text",params:{},workers:"opendiscord:close"|"opendiscord:logs"}, + "opendiscord:reopen":{origin:"slash"|"text",params:{},workers:"opendiscord:reopen"|"opendiscord:logs"}, + "opendiscord:delete":{origin:"slash"|"text",params:{},workers:"opendiscord:delete"|"opendiscord:logs"}, + "opendiscord:claim":{origin:"slash"|"text",params:{},workers:"opendiscord:claim"|"opendiscord:logs"}, + "opendiscord:unclaim":{origin:"slash"|"text",params:{},workers:"opendiscord:unclaim"|"opendiscord:logs"}, + "opendiscord:pin":{origin:"slash"|"text",params:{},workers:"opendiscord:pin"|"opendiscord:logs"}, + "opendiscord:unpin":{origin:"slash"|"text",params:{},workers:"opendiscord:unpin"|"opendiscord:logs"}, + + "opendiscord:rename":{origin:"slash"|"text",params:{},workers:"opendiscord:rename"|"opendiscord:logs"}, + "opendiscord:move":{origin:"slash"|"text",params:{},workers:"opendiscord:move"|"opendiscord:logs"}, + "opendiscord:add":{origin:"slash"|"text",params:{},workers:"opendiscord:add"|"opendiscord:logs"}, + "opendiscord:remove":{origin:"slash"|"text",params:{},workers:"opendiscord:remove"|"opendiscord:logs"}, + "opendiscord:clear":{origin:"slash"|"text",params:{},workers:"opendiscord:clear"|"opendiscord:logs"}, + "opendiscord:topic":{origin:"slash"|"text",params:{},workers:"opendiscord:topic"|"opendiscord:logs"}, + "opendiscord:priority":{origin:"slash"|"text",params:{},workers:"opendiscord:priority"|"opendiscord:logs"}, + "opendiscord:transfer":{origin:"slash"|"text",params:{},workers:"opendiscord:transfer"|"opendiscord:logs"}, + + "opendiscord:autoclose":{origin:"slash"|"text",params:{},workers:"opendiscord:autoclose"|"opendiscord:logs"}, + "opendiscord:autodelete":{origin:"slash"|"text",params:{},workers:"opendiscord:autodelete"|"opendiscord:logs"}, +} + +/**## ODButtonResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODButtonResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODButtonResponderManagerIdMappings extends api.ODButtonResponderManagerIdConstraint { + "opendiscord:verifybar-button":{origin:"button",params:{},workers:"opendiscord:verifybar-button"}, + + "opendiscord:help-menu-switch":{origin:"button",params:{},workers:"opendiscord:update-help-menu"}, + "opendiscord:help-menu-previous":{origin:"button",params:{},workers:"opendiscord:update-help-menu"}, + "opendiscord:help-menu-next":{origin:"button",params:{},workers:"opendiscord:update-help-menu"}, + + "opendiscord:ticket-option":{origin:"button",params:{},workers:"opendiscord:ticket-option"}, + "opendiscord:role-option":{origin:"button",params:{},workers:"opendiscord:role-option"}, + + "opendiscord:claim-ticket":{origin:"button",params:{},workers:"opendiscord:claim-ticket"}, + "opendiscord:unclaim-ticket":{origin:"button",params:{},workers:"opendiscord:unclaim-ticket"}, + "opendiscord:pin-ticket":{origin:"button",params:{},workers:"opendiscord:pin-ticket"}, + "opendiscord:unpin-ticket":{origin:"button",params:{},workers:"opendiscord:unpin-ticket"}, + "opendiscord:close-ticket":{origin:"button",params:{},workers:"opendiscord:close-ticket"}, + "opendiscord:reopen-ticket":{origin:"button",params:{},workers:"opendiscord:reopen-ticket"}, + "opendiscord:delete-ticket":{origin:"button",params:{},workers:"opendiscord:delete-ticket"}, + + "opendiscord:transcript-error-retry":{origin:"button",params:{},workers:"opendiscord:delete-ticket"|"opendiscord:logs"}, + "opendiscord:transcript-error-continue":{origin:"button",params:{},workers:"opendiscord:delete-ticket"|"opendiscord:logs"}, + "opendiscord:clear-continue":{origin:"button",params:{},workers:"opendiscord:clear-continue"}, +} + +/**## ODDropdownResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODDropdownResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODDropdownResponderManagerIdMappings extends api.ODDropdownResponderManagerIdConstraint { + "opendiscord:panel-dropdown-tickets":{origin:"dropdown",params:{},workers:"opendiscord:panel-dropdown-tickets"}, +} + +/**## ODModalResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODModalResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODModalResponderManagerIdMappings extends api.ODModalResponderManagerIdConstraint { + "opendiscord:ticket-questions":{origin:"modal",params:{},workers:"opendiscord:ticket-questions"}, + "opendiscord:close-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:close-ticket-reason"}, + "opendiscord:reopen-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:reopen-ticket-reason"}, + "opendiscord:delete-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:delete-ticket-reason"}, + "opendiscord:claim-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:claim-ticket-reason"}, + "opendiscord:unclaim-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:unclaim-ticket-reason"}, + "opendiscord:pin-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:pin-ticket-reason"}, + "opendiscord:unpin-ticket-reason":{origin:"modal",params:{},workers:"opendiscord:unpin-ticket-reason"}, +} + +/**## ODContextMenuResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODContextMenuResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODContextMenuResponderManagerIdMappings extends api.ODContextMenuResponderManagerIdConstraint { + //"opendiscord:example":{origin:"context-menu",params:{},workers:"opendiscord:example"}, +} + +/**## ODAutocompleteResponderManagerIdMappings `interface` + * A list of all available IDs in the default `ODAutocompleteResponderManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODAutocompleteResponderManagerIdMappings extends api.ODAutocompleteResponderManagerIdConstraint { + "opendiscord:panel-id":{origin:"autocomplete",params:{},workers:"opendiscord:panel-id"}, + "opendiscord:option-id":{origin:"autocomplete",params:{},workers:"opendiscord:option-id"} +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedCommandResponderManager `class + * A special class with types for the Open Ticket `ODCommandResponderManager` class. + */ +export class ODMappedCommandResponderManager extends api.ODCommandResponderManager {} + +/**## ODMappedButtonResponderManager `class + * A special class with types for the Open Ticket `ODButtonResponderManager` class. + */ +export class ODMappedButtonResponderManager extends api.ODButtonResponderManager {} + +/**## ODMappedDropdownResponderManager `class + * A special class with types for the Open Ticket `ODDropdownResponderManager` class. + */ +export class ODMappedDropdownResponderManager extends api.ODDropdownResponderManager {} + +/**## ODMappedModalResponderManager `class + * A special class with types for the Open Ticket `ODModalResponderManager` class. + */ +export class ODMappedModalResponderManager extends api.ODModalResponderManager {} + +/**## ODMappedContextMenuResponderManager `class + * A special class with types for the Open Ticket `ODContextMenuResponderManager` class. + */ +export class ODMappedContextMenuResponderManager extends api.ODContextMenuResponderManager {} + +/**## ODMappedAutocompleteResponderManager `class + * A special class with types for the Open Ticket `ODAutocompleteResponderManager` class. + */ +export class ODMappedAutocompleteResponderManager extends api.ODAutocompleteResponderManager {} + +/**## ODMappedResponderManager `class + * A special class with types for the Open Ticket `ODResponderManager` class. + */ +export class ODMappedResponderManager extends api.ODResponderManager {} \ No newline at end of file diff --git a/src/core/mappings/session.ts b/src/core/mappings/session.ts new file mode 100644 index 0000000..f9a9411 --- /dev/null +++ b/src/core/mappings/session.ts @@ -0,0 +1,21 @@ +/////////////////////////////////////// +//OPEN TICKET SESSION MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODSessionManagerIdMappings `interface` + * A list of all available IDs in the default `ODSessionManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODSessionManagerIdMappings extends api.ODSessionManagerIdConstraint { + //"opendiscord:example-session":api.ODSession +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedSessionManager `class + * A special class with types for the Open Ticket `ODSessionManager` class. + */ +export class ODMappedSessionManager extends api.ODSessionManager {} \ No newline at end of file diff --git a/src/core/mappings/startscreen.ts b/src/core/mappings/startscreen.ts new file mode 100644 index 0000000..1214828 --- /dev/null +++ b/src/core/mappings/startscreen.ts @@ -0,0 +1,28 @@ +/////////////////////////////////////// +//OPEN TICKET STARTSCREEN MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import { ODLiveStatusManagerIdMappings } from "./console.js" + +/**## ODStartScreenManagerIdMappings `interface` + * A list of all available IDs in the default `ODStartScreenManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODStartScreenManagerIdMappings extends api.ODStartScreenManagerIdConstraint { + "opendiscord:logo":api.ODStartScreenLogoComponent, + "opendiscord:header":api.ODStartScreenHeaderComponent, + "opendiscord:flags":api.ODStartScreenFlagsCategoryComponent, + "opendiscord:plugins":api.ODStartScreenPluginsCategoryComponent, + "opendiscord:stats":api.ODStartScreenPropertiesCategoryComponent, + "opendiscord:livestatus":api.ODStartScreenLiveStatusCategoryComponent>, + "opendiscord:logs":api.ODStartScreenCategoryComponent +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedStartScreenManager `class + * A special class with types for the Open Ticket `ODStartScreenManager` class. + */ +export class ODMappedStartScreenManager extends api.ODStartScreenManager {} \ No newline at end of file diff --git a/src/core/mappings/state.ts b/src/core/mappings/state.ts new file mode 100644 index 0000000..9136c41 --- /dev/null +++ b/src/core/mappings/state.ts @@ -0,0 +1,62 @@ +/////////////////////////////////////// +//OPEN TICKET STATE MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" +import * as discord from "discord.js" +import { ODTicketClearFilter } from "../api/ticket.js" + +/**## ODStateManagerIdMappings `interface` + * A list of all available IDs in the default `ODStateManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODStateManagerIdMappings extends api.ODStateManagerIdConstraint { + "opendiscord:interactive-message":ODInteractiveMessageState, + "opendiscord:clear-message":ODClearMessageState, +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedStateManager `class + * A special class with types for the Open Ticket `ODStateManager` class. + */ +export class ODMappedStateManager extends api.ODStateManager {} + +/**## ODInteractiveMessageState `class + * A special class with state types for interactive Open Ticket message. + */ +export class ODInteractiveMessageState extends api.ODState<{ + /**The method this message was generated with. */ + messageOrigin:"slash"|"text"|"button"|"dropdown"|"modal"|"other", + /**The type of message. Used when editing messages. */ + messageType:"ticket-message"|"close-message"|"reopen-message"|"autoclose-message"|"claim-message"|"unclaim-message"|"pin-message"|"unpin-message", + /**A reason this interactive message was generated. */ + messageReason?:string|null, + /**The original author of this interactive message. */ + messageAuthor?:string, + /**Additional data of this interactive message. */ + messageExtraData?:any, +},false,false> { + constructor(id:api.ODValidId,client:api.ODClientManager,database:api.ODDatabase){ + super(id,client,database,{}) + } +} + +/**## ODClearMessageState `class + * A special class with state types for the Open Ticket clear tickets message. + */ +export class ODClearMessageState extends api.ODState<{ + /**The method this message was generated with. */ + messageOrigin:"slash"|"text"|"other", + /**The clear filters. */ + clearFilter:ODTicketClearFilter, + /**The list of ticket channel names (e.g. `#ticket-1`) to be cleared. */ + clearChannelNameList:string[] +},false,true> { + constructor(id:api.ODValidId,client:api.ODClientManager,database:api.ODDatabase){ + super(id,client,database,{ + autodeleteOnRestart:true + }) + } +} \ No newline at end of file diff --git a/src/core/mappings/statistic.ts b/src/core/mappings/statistic.ts new file mode 100644 index 0000000..51b3f12 --- /dev/null +++ b/src/core/mappings/statistic.ts @@ -0,0 +1,143 @@ +/////////////////////////////////////// +//OPEN TICKET STATISTICS MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODStatisticManagerIdMappings `interface` + * A list of all available IDs in the default `ODStatisticManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODStatisticManagerIdMappings extends api.ODStatisticManagerIdConstraint { + "opendiscord:global":ODGlobalStatisticScope, + "opendiscord:system":ODSystemStatisticScope, + "opendiscord:user":ODUserStatisticScope, + "opendiscord:ticket":ODTicketStatisticScope, + "opendiscord:participants":ODParticipantsStatisticScope, + "opendiscord:messages":ODMessagesStatisticScope, +} + +///////////////////////////////////////// +// STATISTICS MAPPINGS, CATEGORIES & TYPES +///////////////////////////////////////// + +/**## ODGlobalStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODGlobalStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODGlobalStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:tickets-created":api.ODBaseStatistic, + "opendiscord:tickets-closed":api.ODBaseStatistic, + "opendiscord:tickets-deleted":api.ODBaseStatistic, + "opendiscord:tickets-reopened":api.ODBaseStatistic, + "opendiscord:tickets-autoclosed":api.ODBaseStatistic, + "opendiscord:tickets-autodeleted":api.ODBaseStatistic, + "opendiscord:tickets-claimed":api.ODBaseStatistic, + "opendiscord:tickets-pinned":api.ODBaseStatistic, + "opendiscord:tickets-moved":api.ODBaseStatistic, + "opendiscord:tickets-transferred":api.ODBaseStatistic, + "opendiscord:users-blacklisted":api.ODBaseStatistic, + "opendiscord:transcripts-created":api.ODBaseStatistic, + "opendiscord:ticket-volume":api.ODDynamicStatistic, + "opendiscord:average-tickets":api.ODDynamicStatistic, +} + +/**## ODSystemStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODSystemStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODSystemStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:startup-date":api.ODDynamicStatistic, + "opendiscord:system-uptime":api.ODDynamicStatistic, + "opendiscord:version":api.ODDynamicStatistic +} + +/**## ODUserStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODUserStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODUserStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:name":api.ODDynamicStatistic, + "opendiscord:role":api.ODDynamicStatistic, + "opendiscord:tickets-created":api.ODBaseStatistic, + "opendiscord:tickets-closed":api.ODBaseStatistic, + "opendiscord:tickets-deleted":api.ODBaseStatistic, + "opendiscord:tickets-reopened":api.ODBaseStatistic, + "opendiscord:tickets-claimed":api.ODBaseStatistic, + "opendiscord:tickets-pinned":api.ODBaseStatistic, + "opendiscord:tickets-moved":api.ODBaseStatistic, + "opendiscord:tickets-transferred":api.ODBaseStatistic, + "opendiscord:users-blacklisted":api.ODBaseStatistic, + "opendiscord:transcripts-created":api.ODBaseStatistic, + "opendiscord:current-tickets":api.ODDynamicStatistic, +} + +/**## ODTicketStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODTicketStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTicketStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:name":api.ODDynamicStatistic, + "opendiscord:status":api.ODDynamicStatistic, + "opendiscord:claimed":api.ODDynamicStatistic, + "opendiscord:pinned":api.ODDynamicStatistic, + "opendiscord:creation-date":api.ODDynamicStatistic, + "opendiscord:creator":api.ODDynamicStatistic, + "opendiscord:ticket-age":api.ODDynamicStatistic, + "opendiscord:response-time":api.ODDynamicStatistic, + "opendiscord:resolution-time":api.ODDynamicStatistic, +} + +/**## ODParticipantsStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODParticipantsStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODParticipantsStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:participants":api.ODDynamicStatistic +} + +/**## ODMessagesStatisticScopeIdMappings `interface` + * A list of all available IDs in the default `ODMessagesStatisticScope` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODMessagesStatisticScopeIdMappings extends api.ODStatisticScopeIdConstraint { + "opendiscord:count":api.ODDynamicStatistic +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedStatisticManager `class + * A special class with types for the Open Ticket `ODStatisticManager` class. + */ +export class ODMappedStatisticManager extends api.ODStatisticManager {} + +/**## ODGlobalStatisticScope `class + * A special class with types for the Open Ticket `Global` statistics category/scope. + */ +export class ODGlobalStatisticScope extends api.ODStatisticGlobalScope {} + +/**## ODSystemStatisticScope `class + * A special class with types for the Open Ticket `System` statistics category/scope. + */ +export class ODSystemStatisticScope extends api.ODStatisticGlobalScope {} + +/**## ODUserStatisticScope `class + * A special class with types for the Open Ticket `User` statistics category/scope. + */ +export class ODUserStatisticScope extends api.ODStatisticScope {} + +/**## ODTicketStatisticScope `class + * A special class with types for the Open Ticket `Ticket` statistics category/scope. + */ +export class ODTicketStatisticScope extends api.ODStatisticScope {} + +/**## ODParticipantsStatisticScope `class + * A special class with types for the Open Ticket `Participants` statistics category/scope. + */ +export class ODParticipantsStatisticScope extends api.ODStatisticScope {} + +/**## ODMessagesStatisticScope `class + * A special class with types for the Open Ticket `Messages` statistics category/scope. + */ +export class ODMessagesStatisticScope extends api.ODStatisticScope {} \ No newline at end of file diff --git a/src/core/mappings/verifybar.ts b/src/core/mappings/verifybar.ts new file mode 100644 index 0000000..267d422 --- /dev/null +++ b/src/core/mappings/verifybar.ts @@ -0,0 +1,37 @@ +/////////////////////////////////////// +//OPEN TICKET VERIFYBAR MAPPINGS +/////////////////////////////////////// +import * as api from "@open-discord-bots/framework/api" + +/**## ODVerifyBarManagerIdMappings `interface` + * A list of all available IDs in the default `ODVerifyBarManager` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODVerifyBarManagerIdMappings extends api.ODVerifyBarManagerIdConstraint { + "opendiscord:close-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:close-ticket">, + "opendiscord:reopen-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:reopen-ticket">, + "opendiscord:delete-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason"|"accept-without-transcript","opendiscord:delete-ticket">, + "opendiscord:claim-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:claim-ticket">, + "opendiscord:unclaim-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:unclaim-ticket">, + "opendiscord:pin-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:pin-ticket">, + "opendiscord:unpin-ticket":api.ODVerifyBar<"accept"|"cancel"|"accept-with-reason","opendiscord:unpin-ticket">, +} + +/**## ODVerifyButtonId `enum` + * Frequently used button ids in Open Ticket verify bars. + */ +export enum ODVerifyButtonId { + Cancel="cancel", + Accept="accept", + AcceptWithReason="accept-with-reason", + AcceptWithoutTranscript="accept-without-transcript" +} + +///////////////////////////// +////// MAPPED MANAGERS ////// +///////////////////////////// + +/**## ODMappedVerifyBarManager `class + * A special class with types for the Open Ticket `ODVerifyBarManager` class. + */ +export class ODMappedVerifyBarManager extends api.ODVerifyBarManager {} \ No newline at end of file diff --git a/src/core/startup/dump.ts b/src/core/startup/dump.ts deleted file mode 100644 index 5ec486b..0000000 --- a/src/core/startup/dump.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {opendiscord, api, utilities} from "../../index" -import * as discord from "discord.js" -import * as fs from "fs" - - -/** WHAT IS THIS?? - * This is the '!OPENTICKET:dump' command. - * It's a utility command which can only be used by the creator of Open Ticket or the owner of the bot. - * This command will send the `otdebug.txt` file in DM. It's not dangerous as the `otdebug.txt` file doesn't contain any sensitive data (only logs). - * - * WHY DOES IT EXIST?? - * This command can be used to quickly get the `otdebug.txt` file without having access to the hosting - * in case you're helping someone with setting up (or debugging) Open Ticket. - * - * CAN I DISABLE IT?? - * If you want to turn it off, you can always do it below this message! - */ - -///////// DISABLE DUMP COMMAND ///////// -const disableDumpCommand = false -//////////////////////////////////////// - -export const loadDumpCommand = () => { - if (disableDumpCommand) return - opendiscord.client.textCommands.add(new api.ODTextCommand("opendiscord:dump",{ - allowBots:false, - guildPermission:true, - dmPermission:true, - name:"dump", - prefix:"!OPENTICKET:" - })) - - opendiscord.client.textCommands.onInteraction("!OPENTICKET:","dump",async (msg) => { - if (msg.author.id == "779742674932072469" || opendiscord.permissions.hasPermissions("developer",await opendiscord.permissions.getPermissions(msg.author,msg.channel,null))){ - //user is bot owner OR creator of Open Ticket :) - opendiscord.log("Dumped otdebug.txt!","system",[ - {key:"user",value:msg.author.username}, - {key:"id",value:msg.author.id} - ]) - const debug = fs.readFileSync("./otdebug.txt") - - if (msg.channel.type != discord.ChannelType.GroupDM) msg.channel.send({content:"## The `otdebug.txt` dump is available!",files:[ - new discord.AttachmentBuilder(debug) - .setName("otdebug.txt") - .setDescription("The Open Ticket debug dump!") - ]}) - } - }) -} \ No newline at end of file diff --git a/src/core/startup/init.ts b/src/core/startup/init.ts deleted file mode 100644 index 18bb6e5..0000000 --- a/src/core/startup/init.ts +++ /dev/null @@ -1,281 +0,0 @@ -import * as fs from "fs" - -let tempErrors: string[] = [] -const tempError = () => { - if (tempErrors.length > 0){ - console.log("\n\n==============================\n[OPEN TICKET ERROR]: "+tempErrors.join("\n[OPEN TICKET ERROR]: ")+"\n==============================\n\n") - process.exit(1) - } - tempErrors = [] -} - -const nodev = process.versions.node.split(".") -if (Number(nodev[0]) < 18){ - tempErrors.push("Invalid node.js version. Open Ticket requires node.js v18 or above!") -} -tempError() - -const moduleInstalled = (id:string, throwError:boolean) => { - try{ - require.resolve(id) - return true - - }catch{ - if (throwError) tempErrors.push("npm module \""+id+"\" is not installed! Install it via 'npm install "+id+"'") - return false - } -} - -moduleInstalled("@discordjs/rest",true) -moduleInstalled("discord.js",true) -moduleInstalled("ansis",true) -moduleInstalled("formatted-json-stringify",true) -moduleInstalled("typescript",true) -moduleInstalled("terminal-kit",true) -tempError() - -//init API -import * as api from "../api/api" //import for local use -export * as api from "../api/api" //export to other parts of bot -import ansis from "ansis" //import ansis for usage in initialization - -export const opendiscord = new api.ODMain() -console.log("\n--------------------------- OPEN TICKET STARTUP ---------------------------") -opendiscord.log("Logging system activated!","system") -opendiscord.debug.debug("Using Node.js "+process.version+"!") - -try{ - const packageJson = JSON.parse(fs.readFileSync("./package.json").toString()) - opendiscord.debug.debug("Using discord.js "+packageJson.dependencies["discord.js"]+"!") - opendiscord.debug.debug("Using @discordjs/rest "+packageJson.dependencies["@discordjs/rest"]+"!") - opendiscord.debug.debug("Using ansis "+packageJson.dependencies["ansis"]+"!") - opendiscord.debug.debug("Using formatted-json-stringify "+packageJson.dependencies["formatted-json-stringify"]+"!") - opendiscord.debug.debug("Using terminal-kit "+packageJson.dependencies["terminal-kit"]+"!") - opendiscord.debug.debug("Using typescript "+packageJson.dependencies["typescript"]+"!") -}catch{ - opendiscord.debug.debug("Failed to fetch module versions!") -} - -const timer = (ms:number): Promise => { - return new Promise((resolve) => { - setTimeout(() => { - resolve() - },ms) - }) -} - -export interface ODUtilities { - /**## project `utility variable` - * This is the name of the project you are currently in. - * - * Developers can use this to create a multi-plugin compatible with all bots supporting the `open-discord` framework! - */ - project:"openticket" - /**## isBeta `utility variable` - * Check if you're running a beta version of Open Ticket. - */ - isBeta:boolean - /**## moduleInstalled `utility function` - * Use this function to check if an npm package is installed or not! - * @example utilities.moduleInstalled("discord.js") //check if discord.js is installed - */ - moduleInstalled(id:string): boolean - /**## timer `utility function` - * Use this to wait for a certain amount of milliseconds. This only works when using `await` - * @example await utilities.timer(1000) //wait 1sec - */ - timer(ms:number): Promise - /**## emojiTitle `utility function` - * Use this function to create a title with an emoji before/after the text. The style & divider are set in `opendiscord.defaults` - * @example utilities.emojiTitle("๐Ÿ“Ž","Links") //create a title with an emoji based on the bot emoji style - */ - emojiTitle(emoji:string, text:string): string - /**## runAsync `utility function` - * Use this function to run a snippet of code asyncronous without creating a separate function for it! - */ - runAsync(func:() => Promise): void - /**## timedAwait `utility function` - * Use this function to await a promise but reject after the certain timeout has been reached. - */ - timedAwait>(promise:ReturnValue, timeout:number, onError:(err:Error) => void): ReturnValue - /**## dateString `utility function` - * Use this function to create a short date string in the following format: `DD/MM/YYYY HH:MM:SS` - */ - dateString(date:Date): string - /**## asyncReplace `utility function` - * Same as `string.replace(search, value)` but with async compatibility - */ - asyncReplace(text:string, regex:RegExp, func:(value:string,...args:any[]) => Promise): Promise - /**## getLongestLength `utility function` - * Get the length of the longest string in the array. - */ - getLongestLength(text:string[]): number - /**## easterEggs `utility object` - * Object containing data for Open Ticket easter eggs. - */ - easterEggs: api.ODEasterEggs, - /**## ODVersionMigration `utility class` - * This class is used to manage data migration between Open Ticket versions. - * - * It shouldn't be used by plugins because this is an internal API feature! - */ - ODVersionMigration:new (version:api.ODVersion,func:() => void|Promise,afterInitFunc:() => void|Promise) => ODVersionMigration, - /**## ordinalNumber `utility function` - * Get a human readable ordinal number (e.g. 1st, 2nd, 3rd, 4th, ...) from a Javascript number. - */ - ordinalNumber(num:number): string, - /**## trimEmojis `utility function` - * Trim/remove all emoji's from a Javascript string. - */ - trimEmojis(text:string): string, -} - -/**## ODVersionMigration `utility class` - * This class is used to manage data migration between Open Ticket versions. - * - * It shouldn't be used by plugins because this is an internal API feature! - */ -export class ODVersionMigration { - /**The version to migrate data to */ - version: api.ODVersion - /**The migration function */ - #func: () => void|Promise - /**The migration function */ - #afterInitFunc: () => void|Promise - - constructor(version:api.ODVersion,func:() => void|Promise,afterInitFunc:() => void|Promise){ - this.version = version - this.#func = func - this.#afterInitFunc = afterInitFunc - } - /**Run this version migration as a plugin. Returns `false` when something goes wrong. */ - async migrate(): Promise { - try{ - await this.#func() - return true - }catch(err){ - process.emit("uncaughtException",err) - return false - } - } - /**Run this version migration as a plugin (after other plugins have loaded). Returns `false` when something goes wrong. */ - async migrateAfterInit(): Promise { - try{ - await this.#afterInitFunc() - return true - }catch(err){ - process.emit("uncaughtException",err) - return false - } - } -} - -export const utilities: ODUtilities = { - project:"openticket", - isBeta:false, - moduleInstalled:(id:string) => { - return moduleInstalled(id,false) - }, - timer, - emojiTitle(emoji:string, text:string){ - const style = opendiscord.defaults.getDefault("emojiTitleStyle") - const divider = opendiscord.defaults.getDefault("emojiTitleDivider") - - if (style == "disabled") return text - else if (style == "before") return emoji+divider+text - else if (style == "after") return text+divider+emoji - else if (style == "double") return emoji+divider+text+divider+emoji - else return text - }, - runAsync(func){ - func() - }, - timedAwait(promise:ReturnValue,timeout:number,onError:(err:Error) => void): ReturnValue { - let allowResolve = true - return new Promise(async (resolve,reject) => { - //set timeout & stop if it is before the promise resolved - setTimeout(() => { - allowResolve = false - reject("utilities.timedAwait() => Promise Timeout") - },timeout) - - //get promise result & return if not already rejected - try{ - const res = await promise - if (allowResolve) resolve(res) - }catch(err){ - onError(err) - } - return promise - }) as ReturnValue - }, - dateString(date): string { - return `${date.getDate()}/${date.getMonth()+1}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}` - }, - async asyncReplace(text,regex,func): Promise { - const promises: Promise[] = [] - text.replace(regex,(match,...args) => { - promises.push(func(match,...args)) - return match - }) - const data = await Promise.all(promises) - const result = text.replace(regex,(match) => { - const replaceResult = data.shift() - return replaceResult ?? match - }) - return result - }, - getLongestLength(texts:string[]): number { - return Math.max(...texts.map((t) => ansis.strip(t).length)) - }, - easterEggs:{ - /* THANK YOU TO ALL OUR CONTRIBUTORS!!! */ - creator:"779742674932072469", //DJj123dj - translators:[ - "779742674932072469", //DJj123dj - "574172558006681601", //Sanke - "540639725300613136", //Guillee.3 - "547231585368539136", //Mods HD - "664934139954331649", //SpyEye - "498055992962187264", //Redactado - "912052735950618705", //T0miiis - "366673202610569227", //johusens - "360780292853858306", //David.3 - "950611418389024809", //Sarcastic - "461603955517161473", //Maurizo - "465111430274875402", //The_Gamer - "586376952470831104", //Erxg - "226695254433202176", //Mkevas - "437695615095275520", //NoOneNook - "530047191222583307", //Anderskiy - "719072181631320145", //ToStam - "1172870906377408512", //Stragar - "1084794575945744445", //Sasanwm - "449613814049275905", //Benzorich - "905373133085741146", //Ronalds - "918504977369018408", //Palestinian - "807970841035145216", //Kornel0706 - "1198883915826475080", //Nova - "669988226819162133", //Danoglez - "1313597620996018271", //Fraden1 - "547809968145956884", //TsgIndrius - "264120132660363267", //Quiradon - "1272034143777329215", //NotMega - "LOREMIPSUM", //TODO - ] - }, - ODVersionMigration, - ordinalNumber(num:number){ - const i = Math.abs(Math.round(num)) - const cent = i % 100 - if (cent >= 10 && cent <= 20) return i+'th' - const dec = i % 10 - if (dec === 1) return i+'st' - if (dec === 2) return i+'nd' - if (dec === 3) return i+'rd' - return i+'th' - }, - trimEmojis(text){ - return text.replace(/(\p{Extended_Pictographic}(?:\uFE0F|\uFE0E)?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|\uFE0E)?)*)/gu,"") - }, -} \ No newline at end of file diff --git a/src/core/startup/manageMigration.ts b/src/core/startup/manageMigration.ts index cd0467b..5bd5eec 100644 --- a/src/core/startup/manageMigration.ts +++ b/src/core/startup/manageMigration.ts @@ -1,9 +1,24 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import fs from "fs" +import path from "path" + +/**Check if the no-migration flag is active. */ +function isMigrationAllowedFromFlag(){ + return (!process.argv.includes("--no-migration") && !process.argv.includes("-nm")) +} + +/**Read the global.json database raw to detect the last version of the bot. */ +function getRawLastVersion(){ + const isDevDatabase = process.argv.includes("--dev-database") || process.argv.includes("-dd") + const globalDatabaseLocation = path.join(process.cwd(),(isDevDatabase) ? "./devdatabase/global.json" : "./database/global.json") + const rawData: api.ODJsonDatabaseStructure = JSON.parse(fs.readFileSync(globalDatabaseLocation).toString()) + const lastVersion = rawData.find((d) => d.category == "opendiscord:last-version" && d.key == "opendiscord:version")?.value ?? null + return lastVersion as string|null +} /**Check if migration is required. Returns the last version used in the database. */ async function isMigrationRequired(): Promise { - const rawVersion = await opendiscord.databases.get("opendiscord:global").get("opendiscord:last-version","opendiscord:version") + const rawVersion = getRawLastVersion() if (!rawVersion) return false const version = api.ODVersion.fromString("opendiscord:last-version",rawVersion) if (opendiscord.versions.get("opendiscord:version").compare(version) == "higher"){ @@ -20,47 +35,6 @@ async function saveAllVersionsToDatabase(){ }) } -export const loadVersionMigrationSystem = async () => { - //ENTER MIGRATION CONTEXT - await preloadMigrationContext() - - const lastVersion = await isMigrationRequired() - - //save last version to database (OR set to current version if no migration is required) - opendiscord.versions.add(lastVersion ? lastVersion : api.ODVersion.fromString("opendiscord:last-version",opendiscord.versions.get("opendiscord:version").toString())) - - if (lastVersion && !opendiscord.flags.get("opendiscord:no-migration").value){ - //MIGRATION IS REQUIRED - opendiscord.log("Detected old data!","info") - opendiscord.log("Starting closed API context...","debug") - await utilities.timer(600) - opendiscord.log("Migrating data to new version...","debug") - await loadAllVersionMigrations(lastVersion) - opendiscord.log("Stopping closed API context...","debug") - await utilities.timer(400) - opendiscord.log("All data is now up to date!","info") - await utilities.timer(200) - console.log("---------------------------------------------------------------------") - } - saveAllVersionsToDatabase() - - //DEFAULT FLAGS - if (opendiscord.flags.exists("opendiscord:no-plugins") && opendiscord.flags.get("opendiscord:no-plugins").value) opendiscord.defaults.setDefault("pluginLoading",false) - if (opendiscord.flags.exists("opendiscord:soft-plugins") && opendiscord.flags.get("opendiscord:soft-plugins").value) opendiscord.defaults.setDefault("softPluginLoading",true) - if (opendiscord.flags.exists("opendiscord:crash") && opendiscord.flags.get("opendiscord:crash").value) opendiscord.defaults.setDefault("crashOnError",true) - if (opendiscord.flags.exists("opendiscord:force-slash-update") && opendiscord.flags.get("opendiscord:force-slash-update").value){ - opendiscord.defaults.setDefault("forceSlashCommandRegistration",true) - opendiscord.defaults.setDefault("forceContextMenuRegistration",true) - } - if (opendiscord.flags.exists("opendiscord:silent") && opendiscord.flags.get("opendiscord:silent").value) opendiscord.console.silent = true - - - //LEAVE MIGRATION CONTEXT - await unloadMigrationContext() - - return lastVersion -} - /**Initialize the migration context by loading the built-in flags, configs & databases. */ async function preloadMigrationContext(){ opendiscord.debug.debug("-- MIGRATION CONTEXT START --") @@ -73,6 +47,52 @@ async function preloadMigrationContext(){ opendiscord.debug.visible = true } +export async function loadVersionMigrationSystem(){ + const lastVersion = await isMigrationRequired() + + //save last version in version manager (OR set to current version if no migration is required) + opendiscord.versions.add(lastVersion ? lastVersion : api.ODVersion.fromString("opendiscord:last-version",opendiscord.versions.get("opendiscord:version").toString())) + + //MIGRATION IS REQUIRED + if (lastVersion && isMigrationAllowedFromFlag()){ + //BEFORE STARTUP MIGRATION + opendiscord.log("Detected old data!","info") + await loadBeforeStartupMigrations(lastVersion) + } + + //ENTER MIGRATION CONTEXT (must be separate for flags to work) + await preloadMigrationContext() + + if (lastVersion && isMigrationAllowedFromFlag()){ + //CONTEXT MIGRATION + opendiscord.log("Starting restricted API context...","debug") + await utilities.timer(600) + opendiscord.log("Migrating data to new version...","debug") + await loadContextMigrations(lastVersion) + opendiscord.log("Stopping restricted API context...","debug") + await utilities.timer(400) + opendiscord.log("All data is now up to date!","info") + await utilities.timer(200) + console.log("---------------------------------------------------------------------") + } + saveAllVersionsToDatabase() + + //SET FUSES & PROPERTIES OF SPECIAL FLAGS + if (opendiscord.flags.exists("opendiscord:no-plugins") && opendiscord.flags.get("opendiscord:no-plugins").value) opendiscord.sharedFuses.setFuse("pluginLoading",false) + if (opendiscord.flags.exists("opendiscord:soft-plugins") && opendiscord.flags.get("opendiscord:soft-plugins").value) opendiscord.sharedFuses.setFuse("softPluginLoading",true) + if (opendiscord.flags.exists("opendiscord:crash") && opendiscord.flags.get("opendiscord:crash").value) opendiscord.sharedFuses.setFuse("crashOnError",true) + if (opendiscord.flags.exists("opendiscord:force-slash-update") && opendiscord.flags.get("opendiscord:force-slash-update").value){ + opendiscord.sharedFuses.setFuse("forceSlashCommandRegistration",true) + opendiscord.sharedFuses.setFuse("forceContextMenuRegistration",true) + } + if (opendiscord.flags.exists("opendiscord:silent") && opendiscord.flags.get("opendiscord:silent").value) opendiscord.console.silent = true + + //LEAVE MIGRATION CONTEXT + await unloadMigrationContext() + + return lastVersion +} + /**Unload the migration context to start the bot normally. */ async function unloadMigrationContext(){ opendiscord.debug.visible = false @@ -98,8 +118,8 @@ function createMigrationBackup(){ else fs.cpSync("./database/","./.backup/database/",{force:true,recursive:true}) } -/**Execute all version migration functions which are handled in the restricted migration context. */ -async function loadAllVersionMigrations(lastVersion:api.ODVersion){ +/**Execute all version migration functions which are handled before any flags, configs or databases are loaded. */ +async function loadBeforeStartupMigrations(lastVersion:api.ODVersion){ const migrations = (await import("./migration.js")).migrations migrations.sort((a,b) => { const comparison = a.version.compare(b.version) @@ -114,10 +134,36 @@ async function loadAllVersionMigrations(lastVersion:api.ODVersion){ for (const migration of migrations){ if (migration.version.compare(lastVersion) == "higher"){ - const success = await migration.migrate() + const success = await migration.migrateBeforeStartup() if (success) opendiscord.log("Migrated data to "+migration.version.toString()+"!","debug",[ {key:"success",value:success ? "true" : "false"}, - {key:"afterInit",value:"false"} + {key:"type",value:"before-startup"} + ]) + else throw new api.ODSystemError("Migration Error: Unable to migrate database & config to the new version of the bot.") + } + } +} + +/**Execute all version migration functions which are handled in the restricted migration context. */ +async function loadContextMigrations(lastVersion:api.ODVersion){ + const migrations = (await import("./migration.js")).migrations + migrations.sort((a,b) => { + const comparison = a.version.compare(b.version) + if (comparison == "equal") return 0 + else if (comparison == "higher") return 1 + else return -1 + }) + if (migrations.length > 0){ + //create backup of config & database + createMigrationBackup() + } + + for (const migration of migrations){ + if (migration.version.compare(lastVersion) == "higher"){ + const success = await migration.migrateInContext() + if (success) opendiscord.log("Migrated data to "+migration.version.toString()+"!","debug",[ + {key:"success",value:success ? "true" : "false"}, + {key:"type",value:"restricted-context"} ]) else throw new api.ODSystemError("Migration Error: Unable to migrate database & config to the new version of the bot.") } @@ -125,7 +171,7 @@ async function loadAllVersionMigrations(lastVersion:api.ODVersion){ } /**Execute all version migration functions which are handled in the normal startup sequence. */ -export async function loadAllAfterInitVersionMigrations(lastVersion:api.ODVersion){ +export async function loadAfterStartupMigrations(lastVersion:api.ODVersion){ const migrations = (await import("./migration.js")).migrations migrations.sort((a,b) => { const comparison = a.version.compare(b.version) @@ -140,10 +186,10 @@ export async function loadAllAfterInitVersionMigrations(lastVersion:api.ODVersio for (const migration of migrations){ if (migration.version.compare(lastVersion) == "higher"){ - const success = await migration.migrateAfterInit() + const success = await migration.migrateAfterStartup() if (success) opendiscord.log("Migrated data to "+migration.version.toString()+"!","debug",[ {key:"success",value:success ? "true" : "false"}, - {key:"afterInit",value:"true"} + {key:"type",value:"after-startup"} ]) else throw new api.ODSystemError("Migration Error: Unable to migrate database & config to the new version of the bot.") } diff --git a/src/core/startup/migration.ts b/src/core/startup/migration.ts index 3ea5521..a62139f 100644 --- a/src/core/startup/migration.ts +++ b/src/core/startup/migration.ts @@ -1,147 +1,351 @@ -import {opendiscord, api, utilities} from "../../index" +import { opendiscord, api, utilities } from "../../index.js" +import fs from "fs" +import path from "path" export const migrations = [ //MIGRATE TO v4.0.0 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.0"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.0"),{}), //MIGRATE TO v4.0.1 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.1"),async () => {},async () => { - //AFTER INIT MIGRATION - - //add opendiscord:panel-message properties for all existing panels. - const globalDatabase = opendiscord.databases.get("opendiscord:global") - for (const panel of (await globalDatabase.getCategory("opendiscord:panel-update") ?? [])){ - globalDatabase.set("opendiscord:panel-message",panel.key,panel.value) + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.1"),{ + afterStartupMigrate:async () => { + //add opendiscord:panel-message properties for all existing panels. + const globalDatabase = opendiscord.databases.get("opendiscord:global") + for (const panel of (await globalDatabase.getCategory("opendiscord:panel-update") ?? [])){ + globalDatabase.set("opendiscord:panel-message",panel.key,panel.value) + } } }), //MIGRATE TO v4.0.2 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.2"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.2"),{}), //MIGRATE TO v4.0.3 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.3"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.3"),{}), //MIGRATE TO v4.0.4 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.4"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.4"),{}), //MIGRATE TO v4.0.5 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.5"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.5"),{}), //MIGRATE TO v4.0.6 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.6"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.6"),{}), //MIGRATE TO v4.0.7 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.7"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.0.7"),{}), //MIGRATE TO v4.1.0 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.0"),async () => {},async () => { - //AFTER INIT MIGRATION + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.0"),{ + afterStartupMigrate:async () => { + //migrate config + const generalConfig = opendiscord.configs.get("opendiscord:general") + const optionConfig = opendiscord.configs.get("opendiscord:options") - //migrate config - const generalConfig = opendiscord.configs.get("opendiscord:general") - const optionConfig = opendiscord.configs.get("opendiscord:options") + if (!generalConfig.data.status.state){ + //only migrate config when it hasn't been done manually by the user. - if (!generalConfig.data.status.state){ - //only migrate config when it hasn't been done manually by the user. + if (!generalConfig.data["_INFO"]) throw new api.ODSystemError("Couldn't find general.json '_INFO' category.") + generalConfig.data["_INFO"].version = "open-ticket-v4.1.0" - if (!generalConfig.data._INFO) throw new api.ODSystemError("Couldn't find general.json '_INFO' category.") - generalConfig.data._INFO.version = "open-ticket-v4.1.0" + if (!generalConfig.data.status) throw new api.ODSystemError("Couldn't find general.json 'status' category.") + generalConfig.data.status.mode = generalConfig.data.status["status"] ?? "online" + generalConfig.data.status.state = "" + delete generalConfig.data.status["status"] - if (!generalConfig.data.status) throw new api.ODSystemError("Couldn't find general.json 'status' category.") - generalConfig.data.status.mode = generalConfig.data.status["status"] ?? "online" - generalConfig.data.status.state = "" - delete generalConfig.data.status["status"] + if (!generalConfig.data["system"]) throw new api.ODSystemError("Couldn't find general.json 'system' category.") + generalConfig.data["system"].displayFieldsWithQuestions = false + generalConfig.data["system"].showGlobalAdminsInPanelRoles = false + generalConfig.data["system"].alwaysShowReason = false + generalConfig.data["system"].pinEmoji = "๐Ÿ“Œ" + generalConfig.data["system"].askPriorityOnTicketCreation = false + generalConfig.data["system"].disableAutocloseAfterReopen = true + generalConfig.data["system"].autodeleteRequiresClosedTicket = true + generalConfig.data["system"].adminOnlyDeleteWithoutTranscript = true + generalConfig.data["system"].allowCloseBeforeMessage = false + generalConfig.data["system"].allowCloseBeforeAdminMessage = true + generalConfig.data["system"].pinFirstTicketMessage = false - if (!generalConfig.data.system) throw new api.ODSystemError("Couldn't find general.json 'system' category.") - generalConfig.data.system.displayFieldsWithQuestions = false - generalConfig.data.system.showGlobalAdminsInPanelRoles = false - generalConfig.data.system.alwaysShowReason = false - generalConfig.data.system.pinEmoji = "๐Ÿ“Œ" - generalConfig.data.system.askPriorityOnTicketCreation = false - generalConfig.data.system.disableAutocloseAfterReopen = true - generalConfig.data.system.autodeleteRequiresClosedTicket = true - generalConfig.data.system.adminOnlyDeleteWithoutTranscript = true - generalConfig.data.system.allowCloseBeforeMessage = false - generalConfig.data.system.allowCloseBeforeAdminMessage = true - generalConfig.data.system.pinFirstTicketMessage = false - - generalConfig.data.system.channelTopic = { - showOptionName:true, - showOptionDescription:false, - showOptionTopic:true, - showPriority:false, - showClosed:true, - showClaimed:false, - showPinned:false, - showCreator:false, - showParticipants:false - } - - if (!generalConfig.data.system.permissions) throw new api.ODSystemError("Couldn't find general.json 'system.permissions' category.") - generalConfig.data.system.permissions.transfer = "admin" - generalConfig.data.system.permissions.topic = "admin" - generalConfig.data.system.permissions.priority = "admin" - - if (!generalConfig.data.system.messages) throw new api.ODSystemError("Couldn't find general.json 'system.messages' category.") - generalConfig.data.system.messages.transferring = {dm:false,logs:true} - generalConfig.data.system.messages.topicChange = {dm:false,logs:true} - generalConfig.data.system.messages.priorityChange = {dm:false,logs:true} - generalConfig.data.system.messages.reactionRole = generalConfig.data.system.messages["roleAdding"] ?? {dm:false,logs:true} - delete generalConfig.data.system.messages["roleAdding"] - delete generalConfig.data.system.messages["roleRemoving"] - - for (const option of optionConfig.data){ - if (option.type != "ticket") continue - option.channel.topic = option.channel["description"] ?? "" - delete option.channel["description"] - - option.slowMode = { - enabled:false, - slowModeSeconds:20 + generalConfig.data["system"].channelTopic = { + showOptionName:true, + showOptionDescription:false, + showOptionTopic:true, + showPriority:false, + showClosed:true, + showClaimed:false, + showPinned:false, + showCreator:false, + showParticipants:false } + + if (!generalConfig.data["system"].permissions) throw new api.ODSystemError("Couldn't find general.json 'system.permissions' category.") + generalConfig.data["system"].permissions.transfer = "admin" + generalConfig.data["system"].permissions.topic = "admin" + generalConfig.data["system"].permissions.priority = "admin" + + if (!generalConfig.data["system"].messages) throw new api.ODSystemError("Couldn't find general.json 'system.messages' category.") + generalConfig.data["system"].messages.transferring = {dm:false,logs:true} + generalConfig.data["system"].messages.topicChange = {dm:false,logs:true} + generalConfig.data["system"].messages.priorityChange = {dm:false,logs:true} + generalConfig.data["system"].messages.reactionRole = generalConfig.data["system"].messages["roleAdding"] ?? {dm:false,logs:true} + delete generalConfig.data["system"].messages["roleAdding"] + delete generalConfig.data["system"].messages["roleRemoving"] + + for (const option of optionConfig.data){ + if (option.type != "ticket") continue + option.channel.topic = option.channel["description"] ?? "" + delete option.channel["description"] + + option.slowMode = { + enabled:false, + slowModeSeconds:20 + } + } + + await generalConfig.save() + await optionConfig.save() } - await generalConfig.save() - await optionConfig.save() - } + //migrate database + const optionDatabase = opendiscord.databases.get("opendiscord:options") + const ticketDatabase = opendiscord.databases.get("opendiscord:tickets") - //migrate database - const optionDatabase = opendiscord.databases.get("opendiscord:options") - const ticketDatabase = opendiscord.databases.get("opendiscord:tickets") + for (const option of (await optionDatabase.getCategory("opendiscord:used-option") ?? [])){ + const optionData = option.value + + const topicData = optionData.data.find((d) => d.id == "opendiscord:channel-description") + if (topicData) topicData.id = "opendiscord:channel-topic" + if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-enabled")) optionData.data.push({id:"opendiscord:slowmode-enabled",value:false}) + if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-seconds")) optionData.data.push({id:"opendiscord:slowmode-seconds",value:20}) - for (const option of (await optionDatabase.getCategory("opendiscord:used-option") ?? [])){ - const optionData = option.value - - const topicData = optionData.data.find((d) => d.id == "opendiscord:channel-description") - if (topicData) topicData.id = "opendiscord:channel-topic" - if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-enabled")) optionData.data.push({id:"opendiscord:slowmode-enabled",value:false}) - if (!optionData.data.find((d) => d.id == "opendiscord:slowmode-seconds")) optionData.data.push({id:"opendiscord:slowmode-seconds",value:20}) + optionDatabase.set("opendiscord:used-option",option.key,optionData) + } - optionDatabase.set("opendiscord:used-option",option.key,optionData) - } + for (const ticket of (await ticketDatabase.getCategory("opendiscord:ticket") ?? [])){ + const ticketData = ticket.value + + if (!ticketData.data.find((d) => d.id == "opendiscord:previous-creators")) ticketData.data.push({id:"opendiscord:previous-creators",value:[]}) + if (!ticketData.data.find((d) => d.id == "opendiscord:reopened")) ticketData.data.push({id:"opendiscord:reopened",value:false}) + if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-by")) ticketData.data.push({id:"opendiscord:reopened-by",value:null}) + if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-on")) ticketData.data.push({id:"opendiscord:reopened-on",value:null}) + if (!ticketData.data.find((d) => d.id == "opendiscord:priority")) ticketData.data.push({id:"opendiscord:priority",value:-1}) + if (!ticketData.data.find((d) => d.id == "opendiscord:topic")) ticketData.data.push({id:"opendiscord:topic",value:""}) + if (!ticketData.data.find((d) => d.id == "opendiscord:message-sent")) ticketData.data.push({id:"opendiscord:message-sent",value:true}) + if (!ticketData.data.find((d) => d.id == "opendiscord:admin-message-sent")) ticketData.data.push({id:"opendiscord:admin-message-sent",value:true}) - for (const ticket of (await ticketDatabase.getCategory("opendiscord:ticket") ?? [])){ - const ticketData = ticket.value - - if (!ticketData.data.find((d) => d.id == "opendiscord:previous-creators")) ticketData.data.push({id:"opendiscord:previous-creators",value:[]}) - if (!ticketData.data.find((d) => d.id == "opendiscord:reopened")) ticketData.data.push({id:"opendiscord:reopened",value:false}) - if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-by")) ticketData.data.push({id:"opendiscord:reopened-by",value:null}) - if (!ticketData.data.find((d) => d.id == "opendiscord:reopened-on")) ticketData.data.push({id:"opendiscord:reopened-on",value:null}) - if (!ticketData.data.find((d) => d.id == "opendiscord:priority")) ticketData.data.push({id:"opendiscord:priority",value:-1}) - if (!ticketData.data.find((d) => d.id == "opendiscord:topic")) ticketData.data.push({id:"opendiscord:topic",value:""}) - if (!ticketData.data.find((d) => d.id == "opendiscord:message-sent")) ticketData.data.push({id:"opendiscord:message-sent",value:true}) - if (!ticketData.data.find((d) => d.id == "opendiscord:admin-message-sent")) ticketData.data.push({id:"opendiscord:admin-message-sent",value:true}) - - ticketDatabase.set("opendiscord:ticket",ticket.key,ticketData) + ticketDatabase.set("opendiscord:ticket",ticket.key,ticketData) + } } }), //MIGRATE TO v4.1.1 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.1"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.1"),{}), //MIGRATE TO v4.1.2 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.2"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.2"),{}), //MIGRATE TO v4.1.3 - new utilities.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.3"),async () => {},async () => {}), + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.1.3"),{}), + + //MIGRATE TO v4.2.0 + new api.ODVersionMigration(api.ODVersion.fromString("opendiscord:version","v4.2.0"),{ + beforeStartupMigrate:async () => { + const isDevconfig = (process.argv.includes("--dev-config") || process.argv.includes("-dc")) + + //transfer config files to .jsonc + const configDir = path.join(process.cwd(),(isDevconfig) ? "./devconfig/" : "./config/") + for (const file of fs.readdirSync(configDir).filter((f) => f.endsWith(".json"))){ + try{ + fs.copyFileSync(path.join(configDir,file),path.join(configDir,file.replace(".json",".jsonc"))) + fs.rmSync(path.join(configDir,file)) + }catch(err){ + process.emit("uncaughtException",err) + } + } + }, + afterStartupMigrate:async () => { + //migrate config + const generalConfig = opendiscord.configs.get("opendiscord:general") + const questionConfig = opendiscord.configs.get("opendiscord:questions") + const optionConfig = opendiscord.configs.get("opendiscord:options") + const panelConfig = opendiscord.configs.get("opendiscord:panels") + const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") + + if (!generalConfig.data.ticketSystem){ + //only migrate config when it hasn't been done manually by the user. + + if (!generalConfig.data["_INFO"]) throw new api.ODSystemError("Couldn't find general.jsonc '_INFO' category.") + delete generalConfig.data["_INFO"] + generalConfig.data._CONFIG_VERSION = "open-ticket-v4.2.0" + + if (!generalConfig.data["system"]) throw new api.ODSystemError("Couldn't find general.jsonc 'system' category.") + generalConfig.data.ticketSystem = generalConfig.data["system"] + generalConfig.data.ticketSystem.closeEmoji = "๐Ÿ”’" + generalConfig.data.ticketSystem.askPriorityOnTicketCreation = true + generalConfig.data.ticketSystem.enableCreateTicketForOtherUser = true + delete generalConfig.data["system"] + generalConfig.data.logs = generalConfig.data.ticketSystem["logs"] + delete generalConfig.data.ticketSystem["logs"] + generalConfig.data.logs.logMessages = generalConfig.data.ticketSystem["messages"] + delete generalConfig.data.ticketSystem["messages"] + generalConfig.data.permissions = generalConfig.data.ticketSystem["permissions"] + delete generalConfig.data.ticketSystem["permissions"] + generalConfig.data.permissions.transcripts = "admin" + + //closed category + const closedCategory = {enabled:false,categoryId:"DISCORD_CATEGORY_ID"} + for (const option of optionConfig.data){ + if (option.type != "ticket") continue + if (option.channel["closedCategory"] && /^\d+$/.test(option.channel["closedCategory"])){ + closedCategory.enabled = true + closedCategory.categoryId = option.channel["closedCategory"] + } + } + generalConfig.data.ticketSystem.closedCategory = closedCategory + + //backup category + const backupCategory = {enabled:false,categoryId:"DISCORD_CATEGORY_ID"} + for (const option of optionConfig.data){ + if (option.type != "ticket") continue + if (option.channel["backupCategory"] && /^\d+$/.test(option.channel["backupCategory"])){ + backupCategory.enabled = true + backupCategory.categoryId = option.channel["backupCategory"] + } + } + generalConfig.data.ticketSystem.backupCategory = backupCategory + + //claimed categories + const claimedCategories: {user:string,category:string}[] = [] + for (const option of optionConfig.data){ + if (option.type != "ticket" || !Array.isArray(option.channel["claimedCategory"])) continue + for (const {user,category} of option.channel["claimedCategory"]){ + if (typeof user == "string" && typeof category == "string" && /^\d+$/.test(user) && /^\d+$/.test(category) && !claimedCategories.find((c) => c.user == user)){ + claimedCategories.push({user,category}) + } + } + } + generalConfig.data.ticketSystem.claimedCategories = claimedCategories + + + //delete properties from options.jsonc + for (const option of optionConfig.data){ + if (option.type != "ticket") continue + delete option.channel["closedCategory"] + delete option.channel["backupCategory"] + delete option.channel["claimedCategory"] + } + + //update panels config: + for (const panel of panelConfig.data){ + panel.settings.maximumButtonsPerRow = 5 + } + + //update questions config: + for (const question of questionConfig.data){ + if (question.type !== "paragraph" && question.type !== "short") continue + question.description = "" + } + + //add new sub-panel option example (for users to try) + optionConfig.data.push({ + id:"example-sub-panel", + name:"Example Sub-Panel", + description:"This is an example of how to implement a sub-panel in Open Ticket.", + type:"sub-panel", + + button:{ + color:"gray", + label:"Sub-Panel Example", + emoji:"๐Ÿ“‹" + }, + subPanelId:panelConfig.data[0]?.id ?? "example-panel" + }) + + //add new question examples (for users to try) + questionConfig.data.push( + { + id:"example-dropdown-question", + name:"Example Dropdown Question", + description:"This is a dropdown question.", + type:"dropdown", + required:false, + + placeholder:"Choose your answer...", + choices:[ + {title:"Choice A",description:"Apple",emoji:"๐ŸŽ"}, + {title:"Choice B",description:"Banana",emoji:"๐ŸŒ"}, + {title:"Choice C",description:"Orange",emoji:"๐ŸŠ"}, + {title:"Choice D",description:"Kiwi",emoji:"๐Ÿฅ"} + ] + }, + { + id:"example-radio-question", + name:"Example Radio Question", + description:"This is a radio select question.", + type:"radio-select", + required:true, + + choices:[ + {title:"Choice A",description:"Up",selectedByDefault:false}, + {title:"Choice B",description:"Down",selectedByDefault:false}, + {title:"Choice C",description:"Left",selectedByDefault:false}, + {title:"Choice D",description:"Right",selectedByDefault:false} + ] + }, + { + id:"example-checkbox-question", + name:"Example Checkbox Question", + description:"This is a checkbox select question.", + type:"checkbox-select", + required:true, + + limits:{ + enabled:false, + min:0, + max:10 + }, + choices:[ + {title:"Choice A",description:"Happiness",selectedByDefault:false}, + {title:"Choice B",description:"Anger",selectedByDefault:false}, + {title:"Choice C",description:"Sadness",selectedByDefault:false}, + {title:"Choice D",description:"Fear",selectedByDefault:false} + ] + }, + { + id:"example-text-display-question", + type:"text-display", + textContents:"This is a text display. It isn't a question, but allows you to display a text, explaination or details." + } + ) + + await generalConfig.save() + await questionConfig.save() + await optionConfig.save() + await panelConfig.save() + await transcriptConfig.save() + } + + //migrate database + const optionDatabase = opendiscord.databases.get("opendiscord:options") + const ticketDatabase = opendiscord.databases.get("opendiscord:tickets") + + for (const option of (await optionDatabase.getCategory("opendiscord:used-option") ?? [])){ + const optionData = option.value + optionData.data = optionData.data.filter((data) => ( + data.id !== "opendiscord:channel-category-closed" && + data.id !== "pendiscord:channel-category-backup" && + data.id !== "opendiscord:channel-categories-claimed" + )) + + optionDatabase.set("opendiscord:used-option",option.key,optionData) + } + + for (const ticket of (await ticketDatabase.getCategory("opendiscord:ticket") ?? [])){ + const ticketData = ticket.value + if (!ticketData.data.find((d) => d.id == "opendiscord:channel-renamed")) ticketData.data.push({id:"opendiscord:channel-renamed",value:null}) + + ticketDatabase.set("opendiscord:ticket",ticket.key,ticketData) + } + } + }), ] \ No newline at end of file diff --git a/src/core/startup/pluginLauncher.ts b/src/core/startup/pluginLauncher.ts deleted file mode 100644 index 9cbad25..0000000 --- a/src/core/startup/pluginLauncher.ts +++ /dev/null @@ -1,260 +0,0 @@ -import {opendiscord, api, utilities} from "../../index" -import fs from "fs" - -export const loadAllPlugins = async () => { - //start launching plugins - opendiscord.log("Loading plugins...","system") - let initPluginError: boolean = false - - if (!fs.existsSync("./plugins")){ - opendiscord.log("Couldn't find ./plugins directory, canceling all plugin execution!","error") - return - } - const plugins = fs.readdirSync("./plugins") - const pluginVersionRegex = /^(OT|OM)v(\d+)\.(\d+|x)\.(\d+|x)$/ - - //check & validate - plugins.forEach((p) => { - //prechecks - if (p === ".DS_Store") return //ignore MacOS DS_Store file - if (!fs.statSync("./plugins/"+p).isDirectory()) return opendiscord.log("Plugin is not a directory, canceling plugin execution...","plugin",[ - {key:"plugin",value:"./plugins/"+p} - ]) - if (!fs.existsSync("./plugins/"+p+"/plugin.json")){ - initPluginError = true - opendiscord.log("Plugin doesn't have a plugin.json, canceling plugin execution...","plugin",[ - {key:"plugin",value:"./plugins/"+p} - ]) - return - } - - //plugin loading - try { - const rawplugindata: api.ODPluginData = JSON.parse(fs.readFileSync("./plugins/"+p+"/plugin.json").toString()) - - if (typeof rawplugindata != "object") throw new api.ODPluginError("Failed to load plugin.json") - if (typeof rawplugindata.id != "string") throw new api.ODPluginError("Failed to load plugin.json/id") - if (typeof rawplugindata.name != "string") throw new api.ODPluginError("Failed to load plugin.json/name") - if (typeof rawplugindata.version != "string") throw new api.ODPluginError("Failed to load plugin.json/version") - if (typeof rawplugindata.startFile != "string") throw new api.ODPluginError("Failed to load plugin.json/startFile") - - //only check "supportedVersions" if it exists (should be array) - if (rawplugindata.supportedVersions){ - if (!Array.isArray(rawplugindata.supportedVersions)) throw new api.ODPluginError("Failed to load plugin.json/supportedVersions (must be array)") - for (const version of rawplugindata.supportedVersions){ - if (typeof version !== "string"){ - throw new api.ODPluginError("Failed to load plugin.json/supportedVersions (all items must be strings)") - } - //only OT (Open Ticket) & OM (Open Moderation) are supported at the moment - if (!pluginVersionRegex.test(version)){ - throw new api.ODPluginError(`Failed to load plugin.json/supportedVersions (invalid format: "${version}", expected format like "OTv4.0.x" or "OMv1.0.0")`) - } - } - } - - if (typeof rawplugindata.enabled != "boolean") throw new api.ODPluginError("Failed to load plugin.json/enabled") - if (typeof rawplugindata.priority != "number") throw new api.ODPluginError("Failed to load plugin.json/priority") - if (!Array.isArray(rawplugindata.events)) throw new api.ODPluginError("Failed to load plugin.json/events") - - if (!Array.isArray(rawplugindata.npmDependencies)) throw new api.ODPluginError("Failed to load plugin.json/npmDependencies") - if (!Array.isArray(rawplugindata.requiredPlugins)) throw new api.ODPluginError("Failed to load plugin.json/requiredPlugins") - if (!Array.isArray(rawplugindata.incompatiblePlugins)) throw new api.ODPluginError("Failed to load plugin.json/incompatiblePlugins") - - if (typeof rawplugindata.details != "object") throw new api.ODPluginError("Failed to load plugin.json/details") - if (typeof rawplugindata.details.author != "string") throw new api.ODPluginError("Failed to load plugin.json/details/author") - - //only check "contributors" if it exists (should be array) - if (rawplugindata.details.contributors && !Array.isArray(rawplugindata.details.contributors)) throw new api.ODPluginError("Failed to load plugin.json/details/contributors (must be array)") - - if (typeof rawplugindata.details.shortDescription != "string") throw new api.ODPluginError("Failed to load plugin.json/details/shortDescription") - if (typeof rawplugindata.details.longDescription != "string") throw new api.ODPluginError("Failed to load plugin.json/details/longDescription") - if (typeof rawplugindata.details.imageUrl != "string") throw new api.ODPluginError("Failed to load plugin.json/details/imageUrl") - if (typeof rawplugindata.details.projectUrl != "string") throw new api.ODPluginError("Failed to load plugin.json/details/projectUrl") - if (!Array.isArray(rawplugindata.details.tags)) throw new api.ODPluginError("Failed to load plugin.json/details/tags") - - if (rawplugindata.id != p) throw new api.ODPluginError("Failed to load plugin, directory name is required to match the id") - - if (opendiscord.plugins.exists(rawplugindata.id)) throw new api.ODPluginError("Failed to load plugin, this id already exists in another plugin") - - //plugin.json is valid => load plugin - const plugin = new api.ODPlugin(p,rawplugindata) - opendiscord.plugins.add(plugin) - - }catch(e){ - //when any of the above errors happen, crash the bot when soft mode isn't enabled - initPluginError = true - opendiscord.log(e.message+", canceling plugin execution...","plugin",[ - {key:"path",value:"./plugins/"+p} - ]) - opendiscord.log("You can see more about this error in the ./otdebug.txt file!","info") - opendiscord.debugfile.writeText(e.stack) - - //try to get some crashed plugin data - try{ - const rawplugindata: api.ODPluginData = JSON.parse(fs.readFileSync("./plugins/"+p+"/plugin.json").toString()) - opendiscord.plugins.unknownCrashedPlugins.push({ - name:rawplugindata.name ?? "./plugins/"+p, - description:(rawplugindata.details && rawplugindata.details.shortDescription) ? rawplugindata.details.shortDescription : "This plugin crashed :(", - }) - }catch{} - } - }) - - //sorted plugins (sorted on priority. All plugins are loaded & enabled) - const sortedPlugins = opendiscord.plugins.getAll().sort((a,b) => { - return (b.priority - a.priority) - }) - - //check for incompatible & missing plugins/dependencies - const incompatibilities: {from:string,to:string}[] = [] - const missingDependencies: {id:string,missing:string}[] = [] - const missingPlugins: {id:string,missing:string}[] = [] - const versionIncompatibilities: {id:string}[] = [] - - //go through all plugins for errors - sortedPlugins.filter((plugin) => plugin.enabled).forEach((plugin) => { - const from = plugin.id.value - plugin.dependenciesInstalled().forEach((missing) => missingDependencies.push({id:from,missing})) - plugin.pluginsIncompatible(opendiscord.plugins).forEach((incompatible) => incompatibilities.push({from,to:incompatible})) - plugin.pluginsInstalled(opendiscord.plugins).forEach((missing) => missingPlugins.push({id:from,missing})) - - //check if plugins are compatible with version of bot - if (plugin.data.supportedVersions && plugin.data.supportedVersions.length > 0){ - const currentVersion = opendiscord.versions.get("opendiscord:version") - let isCompatible = false - - for (const versionStr of plugin.data.supportedVersions){ - const match = versionStr.match(pluginVersionRegex) - if (!match) continue - - const projectPrefix = match[1] - const primary = parseInt(match[2]) - const secondary = (match[3] === "x") ? null : parseInt(match[3]) - const tertiary = (match[4] === "x") ? null : parseInt(match[4]) - - if (projectPrefix !== "OT") continue - else if (primary !== currentVersion.primary) continue - else if (typeof secondary === "number" && secondary !== currentVersion.secondary) continue - else if (typeof tertiary === "number" && tertiary !== currentVersion.tertiary) continue - else{ - isCompatible = true - break - } - } - - if (!isCompatible) versionIncompatibilities.push({id:from}) - } - }) - - //handle all incompatibilities - const alreadyLoggedCompatPlugins: string[] = [] - incompatibilities.forEach((match) => { - if (alreadyLoggedCompatPlugins.includes(match.from) || alreadyLoggedCompatPlugins.includes(match.to)) return - else alreadyLoggedCompatPlugins.push(match.from,match.to) - - const fromPlugin = opendiscord.plugins.get(match.from) - if (fromPlugin && !fromPlugin.crashed){ - fromPlugin.crashed = true - fromPlugin.crashReason = "incompatible.plugin" - } - const toPlugin = opendiscord.plugins.get(match.to) - if (toPlugin && !toPlugin.crashed){ - toPlugin.crashed = true - toPlugin.crashReason = "incompatible.plugin" - } - - opendiscord.log(`Incompatible plugins => "${match.from}" & "${match.to}", canceling plugin execution...`,"plugin",[ - {key:"path1",value:"./plugins/"+match.from}, - {key:"path2",value:"./plugins/"+match.to} - ]) - initPluginError = true - }) - - //handle all missing dependencies - missingDependencies.forEach((match) => { - const plugin = opendiscord.plugins.get(match.id) - if (plugin && !plugin.crashed){ - plugin.crashed = true - plugin.crashReason = "missing.dependency" - } - - opendiscord.log(`Missing npm dependency "${match.missing}", canceling plugin execution...`,"plugin",[ - {key:"path",value:"./plugins/"+match.id} - ]) - initPluginError = true - }) - - //handle all missing plugins - missingPlugins.forEach((match) => { - const plugin = opendiscord.plugins.get(match.id) - if (plugin && !plugin.crashed){ - plugin.crashed = true - plugin.crashReason = "missing.plugin" - } - - opendiscord.log(`Missing required plugin "${match.missing}", canceling plugin execution...`,"plugin",[ - {key:"path",value:"./plugins/"+match.id} - ]) - initPluginError = true - }) - - //handle all bot version incompatibilities - versionIncompatibilities.forEach((match) => { - const plugin = opendiscord.plugins.get(match.id) - if (plugin && !plugin.crashed){ - plugin.crashed = true - plugin.crashReason = "incompatible.version" - } - - const versions = plugin?.data.supportedVersions?.join(", ") ?? "" - const currentVersion = opendiscord.versions.get("opendiscord:version").toString() - opendiscord.log(`Plugin version incompatibility: plugin requires "${versions}" but current bot version is "${currentVersion}", canceling plugin execution...`,"plugin",[ - {key:"path",value:"./plugins/"+match.id} - ]) - initPluginError = true - }) - - //exit on error (when soft mode disabled) - if (!opendiscord.defaults.getDefault("softPluginLoading") && initPluginError){ - console.log("") - opendiscord.log("Please fix all plugin errors above & try again!","error") - process.exit(1) - } - - //preload all events required for every plugin - for (const plugin of sortedPlugins){ - if (plugin.enabled) plugin.data.events.forEach((event) => opendiscord.events.add(new api.ODEvent(event))) - } - - //execute all working plugins - for (const plugin of sortedPlugins){ - const status = await plugin.execute(opendiscord.debug,false) - - //exit on error (when soft mode disabled) - if (!status && !opendiscord.defaults.getDefault("softPluginLoading")){ - console.log("") - opendiscord.log("Please fix all plugin errors above & try again!","error") - process.exit(1) - } - } - - for (const plugin of sortedPlugins){ - const authors = [plugin.details.author,...(plugin.details.contributors ?? [])].join(", ") - - if (plugin.enabled){ - opendiscord.debug.debug("Plugin \""+plugin.id.value+"\" loaded",[ - {key:"status",value:(plugin.crashed ? "crashed" : "success")}, - {key:"crashReason",value:(plugin.crashed ? (plugin.crashReason ?? "/") : "/")}, - {key:"authors",value:authors}, - {key:"version",value:plugin.version.toString()}, - {key:"priority",value:plugin.priority.toString()} - ]) - }else{ - opendiscord.debug.debug("Plugin \""+plugin.id.value+"\" disabled",[ - {key:"authors",value:authors}, - {key:"version",value:plugin.version.toString()}, - {key:"priority",value:plugin.priority.toString()} - ]) - } - } -} \ No newline at end of file diff --git a/src/data/framework/checkerLoader.ts b/src/data/framework/checkerLoader.ts index 38e1145..685732d 100644 --- a/src/data/framework/checkerLoader.ts +++ b/src/data/framework/checkerLoader.ts @@ -1,28 +1,8 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" const generalConfig = opendiscord.configs.get("opendiscord:general") -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW CONFIG VARIABLES? - * - Make the change to the config file in (./config/) and be aware of the following things: - * - The variable has a clear name and its function is obvious. - * - The variable is in the correct position/category of the config. - * - The variable contains a default placeholder to suggest the contents. - * - If there's a (./devconfig/), also modify this file. - * - Register the config in loadAllConfigs() in (./src/data/framework/configLoader.ts) - * - The variable should be added to the "formatters" in the correct position. - * - Add autocomplete for the variable in ODJsonConfig_Default... in (./src/core/api/defaults/config.ts) - * - Add the variable to the config checker in (./src/data/framework/checkerLoader.ts) - * - Make sure the variable is compatible with the Interactive Setup CLI. - * - The variable should be added by the migration manager (./src/core/startup/migration.ts) when missing. - * - Update the Open Ticket Documentation. - * - * IF VARIABLE IS FROM questions.json, options.json OR panels.json: - * - Check (./src/data/openticket/...) for loading/unloading of data. - * - Check (./src/actions/createTicket.ts) and related files. - * - Check (./src/builders), (./src/actions), (./src/data) & (./src/commands) in general in the areas that were changed. - */ - -export const loadAllConfigCheckers = async () => { +export async function loadAllConfigCheckers(){ opendiscord.checkers.add(new api.ODChecker("opendiscord:general",opendiscord.checkers.storage,0,opendiscord.configs.get("opendiscord:general"),defaultGeneralStructure,{cliDisplayName:"General Config",cliDisplayDescription:"Configure the bot token, status, colors, permissions & more."})) opendiscord.checkers.add(new api.ODChecker("opendiscord:questions",opendiscord.checkers.storage,2,opendiscord.configs.get("opendiscord:questions"),defaultQuestionsStructure,{cliDisplayName:"Questions Config",cliDisplayDescription:"Create, modify & delete questions which are used in options."})) opendiscord.checkers.add(new api.ODChecker("opendiscord:options",opendiscord.checkers.storage,1,opendiscord.configs.get("opendiscord:options"),defaultOptionsStructure,{cliDisplayName:"Options Config",cliDisplayDescription:"Create, modify & delete options which are used in panels."})) @@ -30,14 +10,14 @@ export const loadAllConfigCheckers = async () => { opendiscord.checkers.add(new api.ODChecker("opendiscord:transcripts",opendiscord.checkers.storage,0,opendiscord.configs.get("opendiscord:transcripts"),defaultTranscriptsStructure,{cliDisplayName:"Transcript Config",cliDisplayDescription:"Configure everything related to transcripts."})) } -export const loadAllConfigCheckerFunctions = async () => { +export async function loadAllConfigCheckerFunctions(){ opendiscord.checkers.functions.add(new api.ODCheckerFunction("opendiscord:unused-options",defaultUnusedOptionsFunction)) opendiscord.checkers.functions.add(new api.ODCheckerFunction("opendiscord:unused-questions",defaultUnusedQuestionsFunction)) opendiscord.checkers.functions.add(new api.ODCheckerFunction("opendiscord:dropdown-options",defaultDropdownOptionsFunction)) } -export const loadAllConfigCheckerTranslations = async () => { - if ((generalConfig && generalConfig.data.system && generalConfig.data.system.useTranslatedConfigChecker) ? generalConfig.data.system.useTranslatedConfigChecker : false){ +export async function loadAllConfigCheckerTranslations(){ + if ((generalConfig && generalConfig.data.ticketSystem && generalConfig.data.ticketSystem.useTranslatedConfigChecker) ? generalConfig.data.ticketSystem.useTranslatedConfigChecker : false){ registerDefaultCheckerSystemTranslations(opendiscord.checkers.translation,opendiscord.languages) //translate checker system text registerDefaultCheckerMessageTranslations(opendiscord.checkers.translation,opendiscord.languages) //translate checker messages registerDefaultCheckerCustomTranslations(opendiscord.checkers.translation,opendiscord.languages) //translate custom checker messages @@ -45,9 +25,9 @@ export const loadAllConfigCheckerTranslations = async () => { } //GLOBAL FUNCTIONS -export const registerDefaultCheckerSystemTranslations = (tm:api.ODCheckerTranslationRegister_Default,lm:api.ODLanguageManager_Default) => { +export function registerDefaultCheckerSystemTranslations(tm:api.ODMappedCheckerTranslationRegister,lm:api.ODMappedLanguageManager){ //SYSTEM - //tm.quickTranslate(lm,"checker.system.headerOpenTicket","other","opendiscord:header-openticket") //OPEN TICKET (ignore) + tm.quickTranslate(lm,"checker.system.headerOpenTicket","other","opendiscord:header-projectname") //OPEN TICKET tm.quickTranslate(lm,"checker.system.typeError","other","opendiscord:type-error") // [ERROR] (ignore) tm.quickTranslate(lm,"checker.system.typeWarning","other","opendiscord:type-warning") // [WARNING] (ignore) tm.quickTranslate(lm,"checker.system.typeInfo","other","opendiscord:type-info") // [INFO] (ignore) @@ -62,7 +42,7 @@ export const registerDefaultCheckerSystemTranslations = (tm:api.ODCheckerTransla tm.quickTranslate(lm,"checker.system.dataMessages","other","opendiscord:data-message") // message } -export const registerDefaultCheckerMessageTranslations = (tm:api.ODCheckerTranslationRegister_Default,lm:api.ODLanguageManager_Default) => { +export function registerDefaultCheckerMessageTranslations(tm:api.ODMappedCheckerTranslationRegister,lm:api.ODMappedLanguageManager){ //STRUCTURES tm.quickTranslate(lm,"checker.messages.invalidType","message","opendiscord:invalid-type") // This property needs to be the type: {0}! tm.quickTranslate(lm,"checker.messages.propertyMissing","message","opendiscord:property-missing") // The property {0} is missing from this object! @@ -137,7 +117,7 @@ export const registerDefaultCheckerMessageTranslations = (tm:api.ODCheckerTransl tm.quickTranslate(lm,"checker.messages.idNonExistent","message","opendiscord:id-non-existent") // The id {0} doesn't exist! } -export const registerDefaultCheckerCustomTranslations = (tm:api.ODCheckerTranslationRegister_Default,lm:api.ODLanguageManager_Default) => { +export function registerDefaultCheckerCustomTranslations(tm:api.ODMappedCheckerTranslationRegister,lm:api.ODMappedLanguageManager){ //CUSTOM tm.quickTranslate(lm,"checker.messages.invalidLanguage","message","opendiscord:invalid-language") // This is an invalid language! tm.quickTranslate(lm,"checker.messages.invalidButton","message","opendiscord:invalid-button") // This button needs to have at least an {0} or {1}! @@ -148,13 +128,30 @@ export const registerDefaultCheckerCustomTranslations = (tm:api.ODCheckerTransla } //UTILITY FUNCTIONS -const createMsgStructure = (id:api.ODValidId,displayName:string) => { +/**Get the panel ids from `panels.jsonc` before it has been checked by the config checker. */ +function getUnsafePanelIds(): string[] { + const panelsConfig = opendiscord.configs.get("opendiscord:panels") + if (!Array.isArray(panelsConfig.data)) return [] + + const panelIds: string[] = [] + for (const unsafePanel of panelsConfig.data){ + if (unsafePanel["id"]) panelIds.push(unsafePanel["id"]) + } + return panelIds +} + +function createMsgStructure(id:api.ODValidId,displayName:string){ return new api.ODCheckerObjectStructure(id,{children:[ {key:"dm",checker:new api.ODCheckerBooleanStructure("opendiscord:msg-dm",{cliInitDefaultValue:false,cliDisplayName:"DM Enabled",cliDisplayDescription:"Will this action be sent in DM to the creator of the ticket?"})}, {key:"logs",checker:new api.ODCheckerBooleanStructure("opendiscord:msg-logs",{cliInitDefaultValue:true,cliDisplayName:"Logs Enabled",cliDisplayDescription:"Will this action be sent in the Discord log channel?"})}, ],cliDisplayName:displayName,cliDisplayDescription:"Configure which places this action gets logged/sent to."}) } -const createTicketEmbedStructure = (id:api.ODValidId) => { + +function createPermissionStructure(id:api.ODValidId,displayName:string){ + return new api.ODCheckerCustomStructure_DiscordId(id,"role",false,["admin","everyone","none"],{cliDisplayName:displayName,cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."}) +} + +function createTicketEmbedStructure(id:api.ODValidId){ return new api.ODCheckerEnabledObjectStructure(id,{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure(id,{children:[ {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-embed-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the embed of this message."})}, {key:"title",checker:new api.ODCheckerStringStructure("opendiscord:ticket-embed-text",{maxLength:256,cliDisplayName:"Title",cliDisplayDescription:"The title of this embed."})}, @@ -171,14 +168,16 @@ const createTicketEmbedStructure = (id:api.ODValidId) => { {key:"timestamp",checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-embed-timestamp",{cliDisplayName:"Timestamp",cliDisplayDescription:"Add a timestamp to the embed."})} ],cliDisplayName:"Message Embed",cliDisplayDescription:"Configure the embed of this message."}),cliInitDefaultValue:{enabled:false,title:"",description:"",customColor:"",image:"",thumbnail:"",fields:[],timestamp:false},cliDisplayName:"Message Embed",cliDisplayDescription:"Configure the embed of this message."}) } -const createTicketPingStructure = (id:api.ODValidId) => { + +function createTicketPingStructure(id:api.ODValidId){ return new api.ODCheckerObjectStructure(id,{children:[ {key:"@here",checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-ping-here",{cliDisplayName:"@here Ping",cliDisplayDescription:"Enable/disable an '@here' ping."})}, {key:"@everyone",checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-ping-everyone",{cliDisplayName:"@everyone Ping",cliDisplayDescription:"Enable/disable an '@everyone' ping."})}, {key:"custom",checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-ping-custom","role",[],{allowDoubles:false,cliDisplayPropertyName:"custom role id",cliDisplayName:"Custom Role Ping",cliDisplayDescription:"Choose your own roles to ping in this message."},{cliDisplayName:"Custom Role",cliDisplayDescription:"The discord role ID of a custom mention/ping."})}, ],cliInitDefaultValue:{"@here":true,"@everyone":false,custom:[],cliDisplayName:"Message Pings",cliDisplayDescription:"Configure the pings/mentions of this message."}}) } -const createPanelEmbedStructure = (id:api.ODValidId) => { + +function createPanelEmbedStructure(id:api.ODValidId){ return new api.ODCheckerEnabledObjectStructure(id,{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure(id,{children:[ {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:panel-embed-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the embed of this panel."})}, {key:"title",checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-text",{maxLength:256,cliDisplayName:"Title",cliDisplayDescription:"The title of this embed."})}, @@ -208,35 +207,31 @@ function loadFromEnv(){ //STRUCTURES export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendiscord:general",{children:[ //INFO - {key:"_INFO",cliHideInEditMode:true,checker:new api.ODCheckerObjectStructure("opendiscord:info",{children:[ - {key:"support",checker:new api.ODCheckerStringStructure("opendiscord:info-support",{choices:["https://otdocs.dj-dj.be"]})}, - {key:"discord",checker:new api.ODCheckerStringStructure("opendiscord:info-discord",{choices:["https://discord.dj-dj.be"]})}, - {key:"version",checker:new api.ODCheckerStringStructure("opendiscord:info-version",{custom(checker,value,locationTrace,locationId,locationDocs) { - const lt = checker.locationTraceDeref(locationTrace) - - if (typeof value != "string") return false - else if (value != "open-ticket-"+opendiscord.versions.get("opendiscord:version").toString()){ - checker.createMessage("opendiscord:invalid-version","warning","The version specified in your config does not match! Make sure you have updated the config to the latest version!",lt,null,[],locationId,locationDocs) - return false - }else return true - },})}, - ]})}, + {key:"_CONFIG_VERSION",cliHideInEditMode:true,checker:new api.ODCheckerStringStructure("opendiscord:config-version",{custom(checker,value,locationTrace,locationId,locationDocs) { + const lt = checker.locationTraceDeref(locationTrace) + + if (typeof value != "string") return false + else if (value != "open-ticket-"+opendiscord.versions.get("opendiscord:version").toString()){ + checker.createMessage("opendiscord:invalid-version","warning","The version specified in your config does not match! Make sure you have updated the config to the latest version!",lt,null,[],locationId,locationDocs) + return false + }else return true + },})}, //BASIC {key:"token",checker:(loadFromEnv()) ? new api.ODCheckerStringStructure("opendiscord:token-disabled",{cliDisplayName:"Token",cliDisplayDescription:"The token of your discord bot."}) : new api.ODCheckerCustomStructure_DiscordToken("opendiscord:token",{cliDisplayName:"Token",cliDisplayDescription:"The token of your discord bot."})}, - {key:"tokenFromENV",checker:new api.ODCheckerBooleanStructure("opendiscord:token-env",{cliDisplayName:"Token From ENV",cliDisplayDescription:"Use the token from the .env file instead of general.json."})}, + {key:"tokenFromENV",checker:new api.ODCheckerBooleanStructure("opendiscord:token-env",{cliDisplayName:"Token From ENV",cliDisplayDescription:"Use the token from the .env file instead of general.jsonc."})}, {key:"mainColor",checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:main-color",true,false,{cliDisplayName:"Main Color",cliDisplayDescription:"The main color of your bot, used in almost all embeds."})}, {key:"language",checker:new api.ODCheckerStringStructure("opendiscord:language",{ custom:(checker,value,locationTrace,locationId,locationDocs) => { const lt = checker.locationTraceDeref(locationTrace) if (typeof value != "string") return false - else if (!opendiscord.defaults.getDefault("languageList").includes(value)){ + else if (!opendiscord.sharedFuses.getFuse("languageList").includes(value)){ checker.createMessage("opendiscord:invalid-language","error","This is an invalid language!",lt,null,[],locationId,locationDocs) return false }else return true }, - cliAutocompleteList:opendiscord.defaults.getDefault("languageList"), + cliAutocompleteList:opendiscord.sharedFuses.getFuse("languageList"), cliDisplayName:"Language", cliDisplayDescription:"The language of the bot. Visit README.md for a list of available translations." })}, @@ -250,6 +245,7 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis {key:"status",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:status",{ property:"enabled", enabledValue:true, + ignoreCheckIfDisabled:true, checker:new api.ODCheckerObjectStructure("opendiscord:status",{children:[ {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:status-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the status. When disabled, the bot will be online without any status."})}, {key:"type",checker:new api.ODCheckerStringStructure("opendiscord:status-type",{choices:["listening","watching","playing","custom"],cliDisplayName:"Type",cliDisplayDescription:"The type of status: Listening, Watching, Playing or Custom."})}, @@ -261,8 +257,33 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis cliDisplayDescription:"Manage the status of the bot." })}, - //SYSTEM - {key:"system",checker:new api.ODCheckerObjectStructure("opendiscord:system",{children:[ + //LOGS + {key:"logs",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:logs",{property:"enabled",enabledValue:true,ignoreCheckIfDisabled:true,checker:new api.ODCheckerObjectStructure("opendiscord:logs",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:logs-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable discord logs in a discord channel."})}, + {key:"channel",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:logs-channel","channel",false,[],{cliDisplayName:"Log Channel",cliDisplayDescription:"The log message channel ID."})}, + + //LOG MESSAGES + {key:"logMessages",checker:new api.ODCheckerObjectStructure("opendiscord:log-messages",{children:[ + {key:"creation",checker:createMsgStructure("opendiscord:msg-creation","Ticket Created")}, + {key:"closing",checker:createMsgStructure("opendiscord:msg-closing","Ticket Closed")}, + {key:"deleting",checker:createMsgStructure("opendiscord:msg-deleting","Ticket Deleted")}, + {key:"reopening",checker:createMsgStructure("opendiscord:msg-reopening","Ticket Reopened")}, + {key:"claiming",checker:createMsgStructure("opendiscord:msg-claiming","Ticket Claimed")}, + {key:"pinning",checker:createMsgStructure("opendiscord:msg-pinning","Ticket Pinned")}, + {key:"adding",checker:createMsgStructure("opendiscord:msg-adding","User Added")}, + {key:"removing",checker:createMsgStructure("opendiscord:msg-removing","User Removed")}, + {key:"renaming",checker:createMsgStructure("opendiscord:msg-renaming","Ticket Renamed")}, + {key:"moving",checker:createMsgStructure("opendiscord:msg-moving","Ticket Moved")}, + {key:"blacklisting",checker:createMsgStructure("opendiscord:msg-blacklisting","User Blacklisted")}, + {key:"transferring",checker:createMsgStructure("opendiscord:msg-transferring","Ticket Transferred")}, + {key:"topicChange",checker:createMsgStructure("opendiscord:msg-topic-change","Topic Changed")}, + {key:"priorityChange",checker:createMsgStructure("opendiscord:msg-priority-change","Priority Changed")}, + {key:"reactionRole",checker:createMsgStructure("opendiscord:msg-reaction-role","Reaction Role")}, + ],cliDisplayName:"Log Messages",cliDisplayDescription:"Manage all messages & DM's for each action of the bot. (Visit docs for more info)"})}, + ],cliDisplayName:"Discord Logs",cliDisplayDescription:"Manage the 'Open Ticket' logs channel."}),cliDisplayName:"Discord Logs",cliDisplayDescription:"Manage the 'Open Ticket' logs channel."})}, + + //TICKET SYSTEM + {key:"ticketSystem",checker:new api.ODCheckerObjectStructure("opendiscord:ticket-system",{children:[ {key:"preferSlashOverText",checker:new api.ODCheckerBooleanStructure("opendiscord:prefer-slash-over-text",{cliDisplayName:"Prefer Slash Over Text",cliDisplayDescription:"Prefer displaying slash commands over text commands in help menus."})}, {key:"sendErrorOnUnknownCommand",checker:new api.ODCheckerBooleanStructure("opendiscord:send-error-on-unknown-command",{cliDisplayName:"Send Error On Unknown Command",cliDisplayDescription:"Send an error when using the text-command prefix without a valid command."})}, {key:"questionFieldsInCodeBlock",checker:new api.ODCheckerBooleanStructure("opendiscord:question-fields-in-code-block",{cliDisplayName:"Questions Fields In Code Blocks",cliDisplayDescription:"Display question fields in code blocks instead of plain text."})}, @@ -272,7 +293,8 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis {key:"useRedErrorEmbeds",checker:new api.ODCheckerBooleanStructure("opendiscord:use-red-error-embeds",{cliDisplayName:"Use Red Error Embeds",cliDisplayDescription:"Display all error messages with a red border instead of the default color of the bot."})}, {key:"alwaysShowReason",checker:new api.ODCheckerBooleanStructure("opendiscord:always-show-reason",{cliDisplayName:"Always Show Reason",cliDisplayDescription:"Always show the reason field in embeds, even when there is no reason provided."})}, {key:"emojiStyle",checker:new api.ODCheckerStringStructure("opendiscord:emoji-style",{choices:["before","after","double","disabled"],cliDisplayName:"Emoji Style",cliDisplayDescription:"Choose how the bot will display emojis in message titles. (Visit docs for more info)"})}, - {key:"pinEmoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:pin-emoji",1,1,false,{cliDisplayName:"Pin Emoji",cliDisplayDescription:"The emoji used when pinning tickets. This is '๐Ÿ“Œ' by default."})}, + {key:"pinEmoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:pin-emoji",0,1,false,{cliDisplayName:"Pin Emoji",cliDisplayDescription:"The emoji used when pinning tickets. This is '๐Ÿ“Œ' by default. Leave empty to disable."})}, + {key:"closeEmoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:close-emoji",0,1,false,{cliDisplayName:"Pin Emoji",cliDisplayDescription:"The emoji used when closing tickets. This is '๐Ÿ”’' by default. Leave empty to disable."})}, {key:"replyOnTicketCreation",checker:new api.ODCheckerBooleanStructure("opendiscord:reply-on-ticket-creation",{cliDisplayName:"Reply On Ticket Creation",cliDisplayDescription:"When enabled, the bot will send an ephemeral reply in the channel of the panel when creating a ticket."})}, {key:"replyOnReactionRole",checker:new api.ODCheckerBooleanStructure("opendiscord:reply-on-reaction-role",{cliDisplayName:"Reply On Reaction Role",cliDisplayDescription:"When enabled, the bot will send an ephemeral reply in the channel of the panel when using a role button."})}, @@ -292,11 +314,7 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis {key:"enableTicketDeleteButtons",checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-delete-buttons",{cliDisplayName:"Enable Ticket Delete Buttons",cliDisplayDescription:"Enable/disable buttons for deleting a ticket. Be aware that this doesn't disable the command!"})}, {key:"enableTicketActionWithReason",checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-action-with-reason",{cliDisplayName:"Enable Ticket Action With Reason",cliDisplayDescription:"Enable/disable buttons to write an additional reason for all ticket actions."})}, {key:"enableDeleteWithoutTranscript",checker:new api.ODCheckerBooleanStructure("opendiscord:enable-delete-without-transcript",{cliDisplayName:"Enable Delete Without Transcript",cliDisplayDescription:"Enable/disable the ability to delete tickets without a transcript."})}, - - {key:"logs",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:system-logs",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:system-logs",{children:[ - {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:logs-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable discord logs in a discord channel."})}, - {key:"channel",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:logs-channel","channel",false,[],{cliDisplayName:"Log Channel",cliDisplayDescription:"The ID of the discord channel to log messages to. You can configure the messages somewhere else."})}, - ],cliDisplayName:"Discord Logs",cliDisplayDescription:"Manage everything related to logs in a discord channel."}),cliDisplayName:"Discord Logs",cliDisplayDescription:"Manage everything related to logs in a discord channel."})}, + {key:"enableCreateTicketForOtherUser",checker:new api.ODCheckerBooleanStructure("opendiscord:enable-create-for-other-user",{cliDisplayName:"Enable Create ticket for other user",cliDisplayDescription:"Enable/disable the ability for admins to create a ticket for another user."})}, {key:"limits",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:limits",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:limits",{children:[ {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:limits-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable global limits."})}, @@ -307,73 +325,72 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis {key:"channelTopic",checker:new api.ODCheckerObjectStructure("opendiscord:channel-topic",{children:[ {key:"showOptionName",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-option-name",{cliDisplayName:"Show Option Name",cliDisplayDescription:"Show the option name in the channel topic."})}, {key:"showOptionDescription",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-option-description",{cliDisplayName:"Show Option Description",cliDisplayDescription:"Show the option description in the channel topic."})}, - {key:"showOptionTopic",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-option-topic",{cliDisplayName:"Show Option Topic",cliDisplayDescription:"Show the option topic text in the channel topic (configured in the options.json config)."})}, + {key:"showOptionTopic",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-option-topic",{cliDisplayName:"Show Option Topic",cliDisplayDescription:"Show the option topic text in the channel topic (configured in the options.jsonc config)."})}, {key:"showPriority",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-priority",{cliDisplayName:"Show Priority",cliDisplayDescription:"Show the current priority in the channel topic (auto-updated)."})}, {key:"showClosed",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-closed",{cliDisplayName:"Show Closed Status",cliDisplayDescription:"Show the current close/reopen status in the channel topic (auto-updated)."})}, {key:"showClaimed",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-claimed",{cliDisplayName:"Show Claimed Status",cliDisplayDescription:"Show the current claim status in the channel topic (auto-updated)."})}, {key:"showPinned",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-pinned",{cliDisplayName:"Show Pinned Status",cliDisplayDescription:"Show the current pin status in the channel topic (auto-updated)."})}, {key:"showCreator",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-creator",{cliDisplayName:"Show Creator",cliDisplayDescription:"Show the creator of the ticket in the channel topic (auto-updated on transfer)."})}, {key:"showParticipants",checker:new api.ODCheckerBooleanStructure("opendiscord:topic-show-participants",{cliDisplayName:"Show Participants",cliDisplayDescription:"Show the first 5 participants of the ticket in the channel topic (auto-updated)."})}, - ],cliDisplayName:"Channel Topic",cliDisplayDescription:"Manage stats and text of ticket channel topics."})}, - {key:"permissions",checker:new api.ODCheckerObjectStructure("opendiscord:system-permissions",{children:[ - {key:"help",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-help","role",false,["admin","everyone","none"],{cliDisplayName:"Help",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"panel",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-panel","role",false,["admin","everyone","none"],{cliDisplayName:"Panel",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"ticket",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-ticket","role",false,["admin","everyone","none"],{cliDisplayName:"Ticket",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"close",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-close","role",false,["admin","everyone","none"],{cliDisplayName:"Close",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"delete",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-delete","role",false,["admin","everyone","none"],{cliDisplayName:"Delete",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"reopen",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-reopen","role",false,["admin","everyone","none"],{cliDisplayName:"Reopen",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"claim",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-claim","role",false,["admin","everyone","none"],{cliDisplayName:"Claim",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"unclaim",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-unclaim","role",false,["admin","everyone","none"],{cliDisplayName:"Unclaim",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"pin",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-pin","role",false,["admin","everyone","none"],{cliDisplayName:"Pin",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"unpin",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-unpin","role",false,["admin","everyone","none"],{cliDisplayName:"Unpin",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"move",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-move","role",false,["admin","everyone","none"],{cliDisplayName:"Move",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"rename",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-rename","role",false,["admin","everyone","none"],{cliDisplayName:"Rename",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"add",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-add","role",false,["admin","everyone","none"],{cliDisplayName:"Add User",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"remove",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-remove","role",false,["admin","everyone","none"],{cliDisplayName:"Remove User",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"blacklist",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-blacklist","role",false,["admin","everyone","none"],{cliDisplayName:"Blacklist",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"stats",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-stats","role",false,["admin","everyone","none"],{cliDisplayName:"Stats",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"clear",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-clear","role",false,["admin","everyone","none"],{cliDisplayName:"Clear Tickets",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"autoclose",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-autoclose","role",false,["admin","everyone","none"],{cliDisplayName:"Autoclose",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"autodelete",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-autodelete","role",false,["admin","everyone","none"],{cliDisplayName:"Autodelete",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"transfer",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-transfer","role",false,["admin","everyone","none"],{cliDisplayName:"Transfer",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"topic",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-topic","role",false,["admin","everyone","none"],{cliDisplayName:"Topic",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - {key:"priority",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-priority","role",false,["admin","everyone","none"],{cliDisplayName:"Priority",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}, - ],cliDisplayName:"Permissions",cliDisplayDescription:"Manage all button & command permissions in the bot. (Visit docs for more info)"})}, + {key:"closedCategory",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:closed-category",{property:"enabled",enabledValue:true,ignoreCheckIfDisabled:true,checker:new api.ODCheckerObjectStructure("opendiscord:closed-category",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:closed-category-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable closed category."})}, + {key:"categoryId",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:closed-category","category",true,[],{cliDisplayName:"Closed Category",cliDisplayDescription:"An additional category where tickets will be moved to when closed."})}, + ],cliDisplayName:"Closed Category",cliDisplayDescription:"An additional category where tickets will be moved to when closed."}),cliDisplayName:"Closed Category",cliDisplayDescription:"An additional category where tickets will be moved to when closed."})}, + + {key:"backupCategory",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:backup-category",{property:"enabled",enabledValue:true,ignoreCheckIfDisabled:true,checker:new api.ODCheckerObjectStructure("opendiscord:backup-category",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:backup-category-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable backup category."})}, + {key:"categoryId",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:backup-category","category",true,[],{cliDisplayName:"Backup Category",cliDisplayDescription:"An additional category where tickets will be created in when the original category is full (50 channels)."})}, + ],cliDisplayName:"Backup Category",cliDisplayDescription:"An additional category where tickets will be created in when the original category is full (50 channels)."}),cliDisplayName:"Backup Category",cliDisplayDescription:"An additional category where tickets will be created in when the original category is full (50 channels)."})}, + + {key:"claimedCategories",checker:new api.ODCheckerArrayStructure("opendiscord:claimed-categories",{allowDoubles:false,allowedTypes:["object"],cliDisplayPropertyName:"claim category",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:claimed-category",{children:[ + {key:"user",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:claimed-user","user",false,[],{cliDisplayName:"User",cliDisplayDescription:"The discord user ID of a ticket claimer."})}, + {key:"category",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:claimed-category","category",false,[],{cliDisplayName:"Category",cliDisplayDescription:"The discord category ID to move the ticket to."})} + ],cliDisplayName:"Claimed Category",cliDisplayDescription:"Move claimed tickets to the matching channel category of the user that claimed the ticket.",cliDisplayKeyInParentArray:"user",cliDisplayAdditionalKeysInParentArray:["user","category"]}),cliDisplayName:"Claimed Categories",cliDisplayDescription:"Move claimed tickets to the matching channel category of the user that claimed the ticket."})}, + + ],cliDisplayName:"Ticket System",cliDisplayDescription:"Configure the 'Open Ticket' ticket system."})}, - {key:"messages",checker:new api.ODCheckerObjectStructure("opendiscord:system-permissions",{children:[ - {key:"creation",checker:createMsgStructure("opendiscord:msg-creation","Ticket Created")}, - {key:"closing",checker:createMsgStructure("opendiscord:msg-closing","Ticket Closed")}, - {key:"deleting",checker:createMsgStructure("opendiscord:msg-deleting","Ticket Deleted")}, - {key:"reopening",checker:createMsgStructure("opendiscord:msg-reopening","Ticket Reopened")}, - {key:"claiming",checker:createMsgStructure("opendiscord:msg-claiming","Ticket Claimed")}, - {key:"pinning",checker:createMsgStructure("opendiscord:msg-pinning","Ticket Pinned")}, - {key:"adding",checker:createMsgStructure("opendiscord:msg-adding","User Added")}, - {key:"removing",checker:createMsgStructure("opendiscord:msg-removing","User Removed")}, - {key:"renaming",checker:createMsgStructure("opendiscord:msg-renaming","Ticket Renamed")}, - {key:"moving",checker:createMsgStructure("opendiscord:msg-moving","Ticket Moved")}, - {key:"blacklisting",checker:createMsgStructure("opendiscord:msg-blacklisting","User Blacklisted")}, - {key:"transferring",checker:createMsgStructure("opendiscord:msg-transferring","Ticket Transferred")}, - {key:"topicChange",checker:createMsgStructure("opendiscord:msg-topic-change","Topic Changed")}, - {key:"priorityChange",checker:createMsgStructure("opendiscord:msg-priority-change","Priority Changed")}, - {key:"reactionRole",checker:createMsgStructure("opendiscord:msg-reaction-role","Reaction Role")}, - ],cliDisplayName:"Messages",cliDisplayDescription:"Manage all messages & DM's for each action of the bot. (Visit docs for more info)"})}, - ],cliDisplayName:"System",cliDisplayDescription:"Configure everything related to the ticket system."})} + //COMMAND PERMISSIONS + {key:"permissions",checker:new api.ODCheckerObjectStructure("opendiscord:permissions",{children:[ + {key:"help",checker:createPermissionStructure("opendiscord:permissions-help","Help")}, + {key:"panel",checker:createPermissionStructure("opendiscord:permissions-panel","Panel")}, + {key:"ticket",checker:createPermissionStructure("opendiscord:permissions-ticket","Ticket")}, + {key:"close",checker:createPermissionStructure("opendiscord:permissions-close","Close")}, + {key:"delete",checker:createPermissionStructure("opendiscord:permissions-delete","Delete")}, + {key:"reopen",checker:createPermissionStructure("opendiscord:permissions-reopen","Reopen")}, + {key:"claim",checker:createPermissionStructure("opendiscord:permissions-claim","Claim")}, + {key:"unclaim",checker:createPermissionStructure("opendiscord:permissions-unclaim","Unclaim")}, + {key:"pin",checker:createPermissionStructure("opendiscord:permissions-pin","Pin")}, + {key:"unpin",checker:createPermissionStructure("opendiscord:permissions-unpin","Unpin")}, + {key:"move",checker:createPermissionStructure("opendiscord:permissions-move","Move")}, + {key:"rename",checker:createPermissionStructure("opendiscord:permissions-rename","Rename")}, + {key:"add",checker:createPermissionStructure("opendiscord:permissions-add","Add User")}, + {key:"remove",checker:createPermissionStructure("opendiscord:permissions-remove","Remove User")}, + {key:"blacklist",checker:createPermissionStructure("opendiscord:permissions-blacklist","Blacklist")}, + {key:"stats",checker:createPermissionStructure("opendiscord:permissions-stats","Stats")}, + {key:"clear",checker:createPermissionStructure("opendiscord:permissions-clear","Clear Tickets")}, + {key:"autoclose",checker:createPermissionStructure("opendiscord:permissions-autoclose","Autoclose")}, + {key:"autodelete",checker:createPermissionStructure("opendiscord:permissions-autodelete","Autodelete")}, + {key:"transfer",checker:createPermissionStructure("opendiscord:permissions-transfer","Transfer")}, + {key:"topic",checker:createPermissionStructure("opendiscord:permissions-topic","Topic")}, + {key:"priority",checker:createPermissionStructure("opendiscord:permissions-priority","Priority")}, + {key:"transcripts",checker:createPermissionStructure("opendiscord:permissions-transcripts","Transcripts History")}, + ],cliDisplayName:"Permissions",cliDisplayDescription:"Manage all button & command permissions in the bot. (Visit docs for more info)"})}, ],cliDisplayName:"General",cliDisplayDescription:"General settings for the bot."}) export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendiscord:options",{allowedTypes:["object"],cliDisplayPropertyName:"option",propertyChecker:new api.ODCheckerObjectSwitchStructure("opendiscord:options",{objects:[ //TICKET {name:"Ticket",priority:0,properties:[{key:"type",value:"ticket"}],checker:new api.ODCheckerObjectStructure("opendiscord:ticket",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],cliInitSkipKeys:["readonlyAdmins"],children:[ - {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:ticket-id","openticket","option-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this ticket option. Used in panels."})}, - {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:ticket-name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this ticket option."})}, - {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:ticket-description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this ticket option."})}, + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:id","openticket","option-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this ticket option. Used in panels."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this ticket option."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this ticket option."})}, //TICKET BUTTON - {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:ticket-button",{children:[ - {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, - {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, - {key:"color",checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})}, + {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:button",{children:[ + {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"color",checker:new api.ODCheckerStringStructure("opendiscord:button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})}, ],custom:(checker,value,locationTrace,locationId,locationDocs) => { const lt = checker.locationTraceDeref(locationTrace) //check if emoji & label exists @@ -389,7 +406,7 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc {key:"ticketAdmins",checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-ticket-admins","role",[],{allowDoubles:false,cliDisplayPropertyName:"ticket admin role",cliDisplayName:"Ticket Admin Roles",cliDisplayDescription:"A list of role IDs that are only able to interact with this ticket option."},{cliDisplayName:"Ticket Admin Role",cliDisplayDescription:"The discord role ID of a ticket admin."})}, {key:"readonlyAdmins",checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-readonly-admins","role",[],{allowDoubles:false,cliInitDefaultValue:[],cliDisplayPropertyName:"read-only ticket admin role",cliDisplayName:"Readonly Admin Roles",cliDisplayDescription:"A list of role IDs that are only able to read this ticket option."},{cliDisplayName:"Readonly Admin Role",cliDisplayDescription:"The discord role ID of a readonly admin."})}, {key:"allowCreationByBlacklistedUsers",checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-allow-blacklisted-users",{cliDisplayName:"Allow Creation By Blacklisted Users",cliDisplayDescription:"When enabled, the blacklist doesn't apply to this ticket option/type and users are still able to create a ticket."})}, - {key:"questions",checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:option-questions","openticket","question-ids","question-ids-used",{allowDoubles:false,maxLength:5,cliDisplayPropertyName:"question",cliDisplayName:"Questions",cliDisplayDescription:"A list of valid question IDs to ask before creating this ticket."},{cliDisplayName:"Question ID",cliDisplayDescription:"A valid question ID from the questions.json config.",cliAutocompleteFunc:async () => { + {key:"questions",checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:option-questions","openticket","question-ids","question-ids-used",{allowDoubles:false,maxLength:5,cliDisplayPropertyName:"question",cliDisplayName:"Questions",cliDisplayDescription:"A list of valid question IDs to ask before creating this ticket."},{cliDisplayName:"Question ID",cliDisplayDescription:"A valid question ID from the questions.jsonc config.",cliAutocompleteFunc:async () => { const uncheckedRawData = opendiscord.configs.get("opendiscord:questions").data if (!Array.isArray(uncheckedRawData)) return null const idList = uncheckedRawData.filter((option) => typeof option == "object" && typeof option["id"] == "string").map((option) => option.id) @@ -400,15 +417,8 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc {key:"channel",checker:new api.ODCheckerObjectStructure("opendiscord:ticket-channel",{cliInitSkipKeys:["backupCategory","claimedCategory"],children:[ {key:"prefix",checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-prefix",{maxLength:25,regex:/^[^\s]*$/,cliDisplayName:"Prefix",cliDisplayDescription:"The prefix of the name of the ticket channel. (e.g. 'question-')"})}, {key:"suffix",checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-suffix",{choices:["user-name","user-nickname","user-id","random-number","random-hex","counter-dynamic","counter-fixed"],cliDisplayName:"Suffix",cliDisplayDescription:"The suffix mode to use. The number/text will be appended after the prefix."})}, - {key:"category",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-category","category",true,[],{cliDisplayName:"Category",cliDisplayDescription:"The category the ticket will be created in. Leave empty for no category."})}, - {key:"closedCategory",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-closed-category","category",true,[],{cliDisplayName:"Closed Category",cliDisplayDescription:"An additional category where the ticket will be moved to when closed."})}, - {key:"backupCategory",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-backup-category","category",true,[],{cliDisplayName:"Backup Category",cliDisplayDescription:"An additional category where the ticket will be created in when the primary category is full (50 channels)."})}, - {key:"claimedCategory",checker:new api.ODCheckerArrayStructure("opendiscord:ticket-channel-claimed-category",{allowDoubles:false,allowedTypes:["object"],cliDisplayPropertyName:"claim category",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:ticket-channel-claimed-category",{children:[ - {key:"user",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-claimed-user","user",false,[],{cliDisplayName:"User",cliDisplayDescription:"A discord user ID of the ticket claimer."})}, - {key:"category",checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-claimed-category","category",false,[],{cliDisplayName:"Category",cliDisplayDescription:"A discord category ID to move the ticket to."})} - ],cliDisplayName:"Claimed Category",cliDisplayDescription:"A collection of a user ID and a category ID. The ticket will be moved to the category when this user claims the ticket."}),cliDisplayName:"Claimed Categories",cliDisplayDescription:"Add categories to move the ticket to when a user claims a ticket."})}, - {key:"topic",checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-topic",{cliDisplayName:"Channel Topic",cliDisplayDescription:"The topic text of the ticket channel. Visible in the discord client when general.json 'channelTopic'.'showOptionTopic' is enabled."})}, + {key:"topic",checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-topic",{cliDisplayName:"Channel Topic",cliDisplayDescription:"The topic text of the ticket channel. Visible in the discord client when general.jsonc 'channelTopic'.'showOptionTopic' is enabled."})}, ],cliDisplayName:"Channel",cliDisplayDescription:"Manage all settings related to the ticket channel and categories."})}, //DM MESSAGE @@ -465,14 +475,14 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc //WEBSITE {name:"Website",priority:0,properties:[{key:"type",value:"website"}],checker:new api.ODCheckerObjectStructure("opendiscord:website",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ - {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:website-id","openticket","option-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this website option. Used in panels."})}, - {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:website-name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this website option."})}, - {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:website-description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this website option."})}, + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:id","openticket","option-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this website option. Used in panels."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this website option."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this website option."})}, //WEBSITE BUTTON - {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:ticket-button",{children:[ - {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, - {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:button",{children:[ + {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, ],custom:(checker,value,locationTrace,locationId,locationDocs) => { const lt = checker.locationTraceDeref(locationTrace) //check if emoji & label exists @@ -490,15 +500,15 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc //REACTION ROLES {name:"Reaction Role",priority:0,properties:[{key:"type",value:"role"}],checker:new api.ODCheckerObjectStructure("opendiscord:role",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ - {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:role-id","openticket","option-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this role option. Used in panels."})}, - {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:role-name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this role option."})}, - {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:role-description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this role option."})}, + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:id","openticket","option-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this role option. Used in panels."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this role option."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this role option."})}, //ROLE BUTTON - {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:ticket-button",{children:[ - {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, - {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, - {key:"color",checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})}, + {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:button",{children:[ + {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"color",checker:new api.ODCheckerStringStructure("opendiscord:button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})}, ],custom:(checker,value,locationTrace,locationId,locationDocs) => { const lt = checker.locationTraceDeref(locationTrace) //check if emoji & label exists @@ -516,13 +526,52 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc {key:"removeRolesOnAdd",checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:role-remove-roles","role",[],{allowDoubles:false,cliDisplayPropertyName:"role",cliDisplayName:"Remove Roles On Add",cliDisplayDescription:"An additional list of roles to remove when the roles of this option are added. (Can be used to select between roles)"},{cliDisplayName:"Remove Role",cliDisplayDescription:"The discord role ID you want to remove when other roles are added."})}, {key:"addOnMemberJoin",checker:new api.ODCheckerBooleanStructure("opendiscord:role-add-on-join",{cliDisplayName:"Add On Member Join",cliDisplayDescription:"Automatically add these roles to a user when joining the server."})}, ],cliDisplayName:"Reaction Role Option",cliDisplayDescription:"Manage all settings of this reaction role option."})}, + + //SUB-PANEL + {name:"Reaction Role",priority:0,properties:[{key:"type",value:"sub-panel"}],checker:new api.ODCheckerObjectStructure("opendiscord:sub-panel",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:id","openticket","option-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this sub-panel option. Used in panels."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this sub-panel option."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this sub-panel option."})}, + + //SUB-PANEL BUTTON + {key:"button",checker:new api.ODCheckerObjectStructure("opendiscord:button",{children:[ + {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"label",checker:new api.ODCheckerStringStructure("opendiscord:button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})}, + {key:"color",checker:new api.ODCheckerStringStructure("opendiscord:button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})}, + ],custom:(checker,value,locationTrace,locationId,locationDocs) => { + const lt = checker.locationTraceDeref(locationTrace) + //check if emoji & label exists + if (typeof value != "object") return false + else if (value && value["emoji"].length < 1 && value["label"].length < 1){ + //label & emoji are both empty + checker.createMessage("opendiscord:invalid-button","error",`This button needs to have at least an "emoji" or "label"!`,lt,null,[`"emoji"`,`"label"`],locationId,locationDocs) + return false + }else return true + },cliDisplayName:"Button",cliDisplayDescription:"Customise the button layout of this sub-panel option."})}, + + //SUB-PANEL SETTINGS + {key:"subPanelId",checker:new api.ODCheckerStringStructure("ot-footers:panel-id",{custom:(checker,value,locationTrace,locationId,locationDocs) => { + const lt = checker.locationTraceDeref(locationTrace) + if (typeof value != "string") return false + + if (getUnsafePanelIds().includes(value)){ + //exists + return true + }else{ + //doesn't exist + checker.createMessage("opendiscord:id-non-existent","error",`The panel id "${value}" doesn't exist!`,lt,null,[`"${value}"`],locationId,locationDocs) + return false + } + }})}, + ],cliDisplayName:"Sub-Panel Option",cliDisplayDescription:"Manage all settings of this sub-panel option."})}, + ],cliDisplayName:"Option",cliDisplayDescription:"Manage an option of one of the 3 types: ticket, website, role."}),cliDisplayName:"Options",cliDisplayDescription:"A list of all options in the bot. Here you can add, modify & remove ticket types, website buttons & reaction roles!"}) export const defaultPanelsStructure = new api.ODCheckerArrayStructure("opendiscord:panels",{allowedTypes:["object"],cliDisplayPropertyName:"panel",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:panels",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","dropdown"],children:[ {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:panel-id","openticket","panel-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this panel. Used in the /panel command."})}, {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:panel-name",{minLength:3,maxLength:50,cliDisplayName:"Name",cliDisplayDescription:"The name of this panel."})}, {key:"dropdown",checker:new api.ODCheckerBooleanStructure("opendiscord:panel-dropdown",{cliDisplayName:"Dropdown",cliDisplayDescription:"Decide whether to use buttons or a dropdown in the panel. Dropdowns only support options of the 'ticket' type!"})}, - {key:"options",checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:panel-options","openticket","option-ids","option-ids-used",{allowDoubles:false,minLength:1,maxLength:25,cliDisplayPropertyName:"option",cliDisplayName:"Options",cliDisplayDescription:"A list of valid option IDs to show in this panel."},{cliDisplayName:"Option ID",cliDisplayDescription:"A valid option ID from the options.json config.",cliAutocompleteFunc:async () => { + {key:"options",checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:panel-options","openticket","option-ids","option-ids-used",{allowDoubles:false,minLength:1,maxLength:25,cliDisplayPropertyName:"option",cliDisplayName:"Options",cliDisplayDescription:"A list of valid option IDs to show in this panel."},{cliDisplayName:"Option ID",cliDisplayDescription:"A valid option ID from the options.jsonc config.",cliAutocompleteFunc:async () => { const uncheckedRawData = opendiscord.configs.get("opendiscord:options").data if (!Array.isArray(uncheckedRawData)) return null const idList = uncheckedRawData.filter((option) => typeof option == "object" && typeof option["id"] == "string").map((option) => option.id) @@ -536,6 +585,8 @@ export const defaultPanelsStructure = new api.ODCheckerArrayStructure("opendisco //SETTINGS {key:"settings",checker:new api.ODCheckerObjectStructure("opendiscord:panel-settings",{cliInitSkipKeys:["dropdownPlaceholder","describeOptionsCustomTitle"],children:[ {key:"dropdownPlaceholder",checker:new api.ODCheckerStringStructure("opendiscord:panel-settings-placeholder",{maxLength:100,cliInitDefaultValue:"Create a ticket!",cliDisplayName:"Dropdown Placeholder",cliDisplayDescription:"Configure the text displayed in the dropdown when nothing is selected."})}, + {key:"maximumButtonsPerRow",checker:new api.ODCheckerNumberStructure("opendiscord:panel-settings-row-amount",{min:1,max:5,floatAllowed:false,cliInitDefaultValue:5,cliDisplayName:"Maximum Buttons Per Row",cliDisplayDescription:"Set the maximum amount of buttons in a single row before starting a new one."})}, + {key:"enableMaxTicketsWarningInText",checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-maxtickets-text",{cliDisplayName:"Enable Max Tickets Warning (Text)",cliDisplayDescription:"Enable/disable the warning which shows how many tickets you can create in the text contents of the panel."})}, {key:"enableMaxTicketsWarningInEmbed",checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-maxtickets-embed",{cliDisplayName:"Enable Max Tickets Warning (Embed)",cliDisplayDescription:"Enable/disable the warning which shows how many tickets you can create in the embed of the panel."})}, @@ -547,20 +598,95 @@ export const defaultPanelsStructure = new api.ODCheckerArrayStructure("opendisco ],cliDisplayName:"Settings",cliDisplayDescription:"Manage additional settings & customisability for this panel."})}, ],cliDisplayName:"Panel",cliDisplayDescription:"Manage, customise and configure a panel to your preference."}),cliDisplayName:"Panels",cliDisplayDescription:"A list of all panels in the bot. Here you can add, modify & remove existing panels or customise them to your preference."}) -export const defaultQuestionsStructure = new api.ODCheckerArrayStructure("opendiscord:questions",{allowedTypes:["object"],cliDisplayPropertyName:"question",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:questions",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ - {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, - {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, - {key:"type",checker:new api.ODCheckerStringStructure("opendiscord:question-type",{choices:["short","paragraph"],cliDisplayName:"Type",cliDisplayDescription:"The type of this question (short/paragraph)."})}, - - {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, - {key:"placeholder",checker:new api.ODCheckerStringStructure("opendiscord:question-placeholder",{maxLength:100,cliDisplayName:"Placeholder",cliDisplayDescription:"The placeholder to show in the field when nothing has been written yet."})}, - - {key:"length",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:question-length",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:question-length",{children:[ - {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:question-length-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable length validation for this question."})}, - {key:"min",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-min",{min:0,max:1024,negativeAllowed:false,floatAllowed:false,cliDisplayName:"Min Length",cliDisplayDescription:"The minimum amount of characters required."})}, - {key:"max",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-max",{min:1,max:1024,negativeAllowed:false,floatAllowed:false,cliInitDefaultValue:100,cliDisplayName:"Max Length",cliDisplayDescription:"The maximum amount of characters allowed."})}, - ],cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."}),cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."})}, -],cliDisplayName:"Question",cliDisplayDescription:"Manage, customise and configure a question to your preference."}),cliDisplayName:"Questions",cliDisplayDescription:"A list of all questions in the bot. Here you can add, modify & remove existing questions or customise them to your preference."}) +export const defaultQuestionsStructure = new api.ODCheckerArrayStructure("opendiscord:questions",{allowedTypes:["object"],cliDisplayPropertyName:"question",propertyChecker:new api.ODCheckerObjectSwitchStructure("opendiscord:options",{objects:[ + //SHORT QUESTION + {name:"Short Question",priority:0,properties:[{key:"type",value:"short"}],checker:new api.ODCheckerObjectStructure("opendiscord:short-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:question-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this question."})}, + {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, + + {key:"placeholder",checker:new api.ODCheckerStringStructure("opendiscord:question-placeholder",{maxLength:100,cliDisplayName:"Placeholder",cliDisplayDescription:"The placeholder to show in the field when nothing has been written yet."})}, + + {key:"length",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:question-length",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:question-length",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:question-length-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable length validation for this question."})}, + {key:"min",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-min",{min:0,max:1024,negativeAllowed:false,floatAllowed:false,cliDisplayName:"Min Length",cliDisplayDescription:"The minimum amount of characters required."})}, + {key:"max",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-max",{min:1,max:1024,negativeAllowed:false,floatAllowed:false,cliInitDefaultValue:100,cliDisplayName:"Max Length",cliDisplayDescription:"The maximum amount of characters allowed."})}, + ],cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."}),cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."})}, + ],cliDisplayName:"Short Question",cliDisplayDescription:"Manage, customise and configure the short question to your preference."})}, + + //PARAGRAPH QUESTION + {name:"Paragraph Question",priority:0,properties:[{key:"type",value:"paragraph"}],checker:new api.ODCheckerObjectStructure("opendiscord:paragraph-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:question-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this question."})}, + {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, + + {key:"placeholder",checker:new api.ODCheckerStringStructure("opendiscord:question-placeholder",{maxLength:100,cliDisplayName:"Placeholder",cliDisplayDescription:"The placeholder to show in the field when nothing has been written yet."})}, + + {key:"length",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:question-length",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:question-length",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:question-length-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable length validation for this question."})}, + {key:"min",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-min",{min:0,max:1024,negativeAllowed:false,floatAllowed:false,cliDisplayName:"Min Length",cliDisplayDescription:"The minimum amount of characters required."})}, + {key:"max",checker:new api.ODCheckerNumberStructure("opendiscord:question-length-max",{min:1,max:1024,negativeAllowed:false,floatAllowed:false,cliInitDefaultValue:100,cliDisplayName:"Max Length",cliDisplayDescription:"The maximum amount of characters allowed."})}, + ],cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."}),cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."})}, + ],cliDisplayName:"Paragraph Question",cliDisplayDescription:"Manage, customise and configure the paragraph question to your preference."})}, + + //TEXT DISPLAY QUESTION + {name:"Text Display Question",priority:0,properties:[{key:"type",value:"text-display"}],checker:new api.ODCheckerObjectStructure("opendiscord:text-display-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this text display. Used in ticket options."})}, + {key:"textContents",checker:new api.ODCheckerStringStructure("opendiscord:text-contents",{minLength:1,maxLength:2048,cliDisplayName:"Text Contents",cliDisplayDescription:"The text contents to show in the modal."})}, + ],cliDisplayName:"Text Display Question",cliDisplayDescription:"Manage, customise and configure the text display question to your preference."})}, + + //DROPDOWN QUESTION + {name:"Dropdown Question",priority:0,properties:[{key:"type",value:"dropdown"}],checker:new api.ODCheckerObjectStructure("opendiscord:dropdown-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:question-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this question."})}, + {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, + + {key:"placeholder",checker:new api.ODCheckerStringStructure("opendiscord:question-placeholder",{maxLength:100,cliDisplayName:"Placeholder",cliDisplayDescription:"The placeholder to show in the field when nothing has been written yet."})}, + {key:"choices",checker:new api.ODCheckerArrayStructure("opendiscord:choices",{allowedTypes:["object"],minLength:1,maxLength:25,propertyChecker:new api.ODCheckerObjectStructure("opendiscord:choice",{children:[ + {key:"title",checker:new api.ODCheckerStringStructure("opendiscord:choice-title",{minLength:1,maxLength:100,cliDisplayName:"Title",cliDisplayDescription:"The title of this choice."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:choice-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this choice."})}, + {key:"emoji",checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:choice-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the choice. Leave empty to disable."})} + ],cliDisplayName:"Choice",cliDisplayDescription:"A choice for this dropdown question."}),cliDisplayName:"Choices",cliDisplayPropertyName:"choice",cliDisplayDescription:"Manage all available choices of this dropdown question."})} + ],cliDisplayName:"Dropdown Question",cliDisplayDescription:"Manage, customise and configure the dropdown question to your preference."})}, + + //RADIO SELECT QUESTION + {name:"Radio Select Question",priority:0,properties:[{key:"type",value:"radio-select"}],checker:new api.ODCheckerObjectStructure("opendiscord:radio-select-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:question-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this question."})}, + {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, + + {key:"choices",checker:new api.ODCheckerArrayStructure("opendiscord:choices",{allowedTypes:["object"],minLength:2,maxLength:10,propertyChecker:new api.ODCheckerObjectStructure("opendiscord:choice",{children:[ + {key:"title",checker:new api.ODCheckerStringStructure("opendiscord:choice-title",{minLength:1,maxLength:100,cliDisplayName:"Title",cliDisplayDescription:"The title of this choice."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:choice-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this choice."})}, + {key:"selectedByDefault",checker:new api.ODCheckerBooleanStructure("opendiscord:choice-default",{cliDisplayName:"Selected By Default",cliDisplayDescription:"Should this choice be selected by default?"})} + ],cliDisplayName:"Choice",cliDisplayDescription:"A choice for this radio select question."}),cliDisplayName:"Choices",cliDisplayPropertyName:"choice",cliDisplayDescription:"Manage all available choices of this radio select question."})} + ],cliDisplayName:"Radio Select Question",cliDisplayDescription:"Manage, customise and configure the radio select question to your preference."})}, + + //CHECKBOX SELECT QUESTION + {name:"Checkbox Select Question",priority:0,properties:[{key:"type",value:"checkbox-select"}],checker:new api.ODCheckerObjectStructure("opendiscord:checkbox-select-question",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[ + {key:"id",checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-รฉรจรงร รชรขรดรปรฎ]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})}, + {key:"name",checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:question-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this question."})}, + {key:"required",checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})}, + + {key:"limits",checker:new api.ODCheckerEnabledObjectStructure("opendiscord:amount",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:amount",{children:[ + {key:"enabled",checker:new api.ODCheckerBooleanStructure("opendiscord:amount-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable checking the min/max amount of required checkboxes."})}, + {key:"min",checker:new api.ODCheckerNumberStructure("opendiscord:amount-min",{min:0,max:10,floatAllowed:false,cliDisplayName:"Min Amount",cliDisplayDescription:"The minimum amount of checkboxes required."})}, + {key:"max",checker:new api.ODCheckerNumberStructure("opendiscord:amount-max",{min:1,max:10,floatAllowed:false,cliInitDefaultValue:10,cliDisplayName:"Max Amount",cliDisplayDescription:"The maximum amount of checkboxes allowed."})}, + ],cliDisplayName:"Checkbox Limits",cliDisplayDescription:"Verify the minimum or maximum amount of checkboxes required."}),cliDisplayName:"Checkbox Limits",cliDisplayDescription:"Verify the minimum or maximum amount of checkboxes required."})}, + + {key:"choices",checker:new api.ODCheckerArrayStructure("opendiscord:choices",{allowedTypes:["object"],minLength:1,maxLength:10,propertyChecker:new api.ODCheckerObjectStructure("opendiscord:choice",{children:[ + {key:"title",checker:new api.ODCheckerStringStructure("opendiscord:choice-title",{minLength:1,maxLength:100,cliDisplayName:"Title",cliDisplayDescription:"The title of this choice."})}, + {key:"description",checker:new api.ODCheckerStringStructure("opendiscord:choice-description",{maxLength:100,cliDisplayName:"Description",cliDisplayDescription:"The description of this choice."})}, + {key:"selectedByDefault",checker:new api.ODCheckerBooleanStructure("opendiscord:choice-default",{cliDisplayName:"Selected By Default",cliDisplayDescription:"Should this choice be selected by default?"})} + ],cliDisplayName:"Choice",cliDisplayDescription:"A choice for this checkbox select question."}),cliDisplayName:"Choices",cliDisplayPropertyName:"choice",cliDisplayDescription:"Manage all available choices of this checkbox select question."})} + ],cliDisplayName:"Checkbox Select Question",cliDisplayDescription:"Manage, customise and configure the checkbox select question to your preference."})}, + +],cliDisplayName:"Question",cliDisplayDescription:"Manage a question of one of the 6 types: short, paragraph, text-display, dropdown, radio-select, checkbox-select."}),cliDisplayName:"Questions",cliDisplayDescription:"A list of all questions in the bot. Here you can add, modify & remove existing questions or customise them to your preference."}) export const defaultTranscriptsStructure = new api.ODCheckerObjectStructure("opendiscord:transcripts",{children:[ //GENERAL diff --git a/src/data/framework/codeLoader.ts b/src/data/framework/codeLoader.ts index 08b4cd0..af57c14 100644 --- a/src/data/framework/codeLoader.ts +++ b/src/data/framework/codeLoader.ts @@ -1,4 +1,4 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") @@ -9,7 +9,7 @@ const statsDatabase = opendiscord.databases.get("opendiscord:stats") const optionDatabase = opendiscord.databases.get("opendiscord:options") const mainServer = opendiscord.client.mainServer -export const loadAllCode = async () => { +export async function loadAllCode(){ if (!generalConfig || !mainServer || !globalDatabase || !userDatabase || !ticketDatabase || !statsDatabase || !optionDatabase) return loadCommandErrorHandlingCode() @@ -20,7 +20,7 @@ export const loadAllCode = async () => { loadAutoCode() } -export const loadCommandErrorHandlingCode = async () => { +export async function loadCommandErrorHandlingCode(){ //COMMAND ERROR HANDLING opendiscord.code.add(new api.ODCode("opendiscord:command-error-handling",14,() => { //invalid/missing options @@ -30,34 +30,33 @@ export const loadCommandErrorHandlingCode = async () => { error.msg.channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-option-invalid").build("text",{guild:error.msg.guild,channel:error.msg.channel,user:error.msg.author,error})).message) }else if (error.type == "missing_option"){ error.msg.channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-option-missing").build("text",{guild:error.msg.guild,channel:error.msg.channel,user:error.msg.author,error})).message) - }else if (error.type == "unknown_command" && generalConfig.data.system.sendErrorOnUnknownCommand){ + }else if (error.type == "unknown_command" && generalConfig.data.ticketSystem.sendErrorOnUnknownCommand){ error.msg.channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-unknown-command").build("text",{guild:error.msg.guild,channel:error.msg.channel,user:error.msg.author,error})).message) } }) //responder timeout - opendiscord.responders.commands.setTimeoutErrorCallback(async (instance,source) => { - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + opendiscord.responders.commands.setTimeoutErrorCallback(async (instance,origin) => { + return await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) },null) - opendiscord.responders.buttons.setTimeoutErrorCallback(async (instance,source) => { - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + opendiscord.responders.buttons.setTimeoutErrorCallback(async (instance,origin) => { + return await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) },null) - opendiscord.responders.dropdowns.setTimeoutErrorCallback(async (instance,source) => { - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + opendiscord.responders.dropdowns.setTimeoutErrorCallback(async (instance,origin) => { + return await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) },null) - opendiscord.responders.modals.setTimeoutErrorCallback(async (instance,source) => { + opendiscord.responders.modals.setTimeoutErrorCallback(async (instance,origin) => { if (!instance.channel){ - instance.reply({id:new api.ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{ + return await instance.reply({id:new api.ODId("opendiscord:unknown-error"), ephemeral:true, message:{ content:":x: **Something went wrong while replying to this modal!**" }}) - return } - instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user})) + return await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-responder-timeout").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) },null) })) } -export const loadStartListeningInteractionsCode = async () => { +export async function loadStartListeningInteractionsCode(){ //START LISTENING TO INTERACTIONS opendiscord.code.add(new api.ODCode("opendiscord:start-listening-interactions",13,() => { opendiscord.client.slashCommands.startListeningToInteractions() @@ -67,7 +66,7 @@ export const loadStartListeningInteractionsCode = async () => { })) } -export const loadDatabaseCleanersCode = async () => { +export async function loadDatabaseCleanersCode(){ if (!mainServer) return //PANEL DATABASE CLEANER @@ -79,7 +78,7 @@ export const loadDatabaseCleanersCode = async () => { if (!validPanels.includes(panel.key)){ try{ const splittedId = panel.key.split("_") - const message = await opendiscord.client.fetchGuildChannelMessage(mainServer,splittedId[0],splittedId[1]) + const message = await opendiscord.client.fetchChannelMessage(splittedId[0],splittedId[1]) if (message) validPanels.push(panel.key) }catch{} } @@ -283,7 +282,7 @@ export const loadDatabaseCleanersCode = async () => { })) } -export const loadPanelAutoUpdateCode = async () => { +export async function loadPanelAutoUpdateCode(){ //PANEL AUTO UPDATE opendiscord.code.add(new api.ODCode("opendiscord:panel-auto-update",7,async () => { const globalDatabase = opendiscord.databases.get("opendiscord:global") @@ -303,7 +302,7 @@ export const loadPanelAutoUpdateCode = async () => { const splittedId = panelId.key.split("_") const channel = await opendiscord.client.fetchGuildTextChannel(mainServer,splittedId[0]) if (!channel) return - const message = await opendiscord.client.fetchGuildChannelMessage(mainServer,channel,splittedId[1]) + const message = await opendiscord.client.fetchChannelMessage(channel,splittedId[1]) if (!message || !message.editable) return message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild:mainServer,channel,user:opendiscord.client.client.user,panel})).message) @@ -317,7 +316,7 @@ export const loadPanelAutoUpdateCode = async () => { })) } -export const loadDatabaseSaversCode = async () => { +export async function loadDatabaseSaversCode(){ //TICKET SAVER opendiscord.code.add(new api.ODCode("opendiscord:ticket-saver",6,() => { const mainVersion = opendiscord.versions.get("opendiscord:version") @@ -382,6 +381,8 @@ export const loadDatabaseSaversCode = async () => { } const loadAutoCode = () => { + const interactiveMsgState = opendiscord.states.get("opendiscord:interactive-message") + //AUTOCLOSE TIMEOUT opendiscord.code.add(new api.ODCode("opendiscord:autoclose-timeout",3,() => { setInterval(async () => { @@ -400,17 +401,23 @@ const loadAutoCode = () => { if (enabled && (new Date().getTime() - lastMessage.createdTimestamp) >= time){ //autoclose ticket await opendiscord.actions.get("opendiscord:close-ticket").run("autoclose",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket,reason:"Autoclose",sendMessage:false}) - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("timeout",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("timeout",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) + await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"autoclose-message", + messageOrigin:"other", + messageAuthor:opendiscord.client.client.user.id, + messageReason:"Autoclose" + },false) count++ - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-autoclosed",1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-autoclosed",1,"increase") } } } opendiscord.debug.debug("Finished autoclose timeout cycle!",[ - {key:"interval",value:opendiscord.defaults.getDefault("autocloseCheckInterval").toString()}, + {key:"interval",value:opendiscord.fuses.getFuse("autocloseCheckInterval").toString()}, {key:"closed",value:count.toString()} ]) - },opendiscord.defaults.getDefault("autocloseCheckInterval")) + },opendiscord.fuses.getFuse("autocloseCheckInterval")) })) //AUTOCLOSE LEAVE @@ -427,8 +434,14 @@ const loadAutoCode = () => { if (enabled){ //autoclose ticket await opendiscord.actions.get("opendiscord:close-ticket").run("autoclose",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket,reason:"Autoclose",sendMessage:false}) - await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("leave",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-autoclosed",1,"increase") + const sentMsg = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autoclose-message").build("leave",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) + await interactiveMsgState.setMsgState({channel,message:sentMsg},{ + messageType:"autoclose-message", + messageOrigin:"other", + messageAuthor:opendiscord.client.client.user.id, + messageReason:"Autoclose" + },false) + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-autoclosed",1,"increase") } } } @@ -446,7 +459,7 @@ const loadAutoCode = () => { if (lastMessage){ //ticket has last message const disableOnClaim = ticket.option.get("opendiscord:autodelete-disable-claim").value && ticket.get("opendiscord:claimed").value - const disableWhenNotClosed = generalConfig.data.system.autodeleteRequiresClosedTicket && !ticket.get("opendiscord:closed").value + const disableWhenNotClosed = generalConfig.data.ticketSystem.autodeleteRequiresClosedTicket && !ticket.get("opendiscord:closed").value const enabled = (disableOnClaim || disableWhenNotClosed) ? false : ticket.get("opendiscord:autodelete-enabled").value const days = ticket.get("opendiscord:autodelete-days").value @@ -457,15 +470,15 @@ const loadAutoCode = () => { await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autodelete-message").build("timeout",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) await opendiscord.actions.get("opendiscord:delete-ticket").run("autodelete",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket,reason:"Autodelete",sendMessage:false,withoutTranscript:false}) count++ - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-autodeleted",1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-autodeleted",1,"increase") } } } opendiscord.debug.debug("Finished autodelete timeout cycle!",[ - {key:"interval",value:opendiscord.defaults.getDefault("autodeleteCheckInterval").toString()}, + {key:"interval",value:opendiscord.fuses.getFuse("autodeleteCheckInterval").toString()}, {key:"deleted",value:count.toString()} ]) - },opendiscord.defaults.getDefault("autodeleteCheckInterval")) + },opendiscord.fuses.getFuse("autodeleteCheckInterval")) })) //AUTODELETE LEAVE @@ -477,14 +490,14 @@ const loadAutoCode = () => { if (!channel) return //ticket has been created by this user const disableOnClaim = ticket.option.get("opendiscord:autodelete-disable-claim").value && ticket.get("opendiscord:claimed").value - const disableWhenNotClosed = generalConfig.data.system.autodeleteRequiresClosedTicket && !ticket.get("opendiscord:closed").value + const disableWhenNotClosed = generalConfig.data.ticketSystem.autodeleteRequiresClosedTicket && !ticket.get("opendiscord:closed").value const enabled = (disableOnClaim || disableWhenNotClosed || !ticket.get("opendiscord:autodelete-enabled").value) ? false : ticket.option.get("opendiscord:autodelete-enable-leave") if (enabled){ //autodelete ticket await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:autodelete-message").build("leave",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket})).message) await opendiscord.actions.get("opendiscord:delete-ticket").run("autodelete",{guild:channel.guild,channel,user:opendiscord.client.client.user,ticket,reason:"Autodelete",sendMessage:false,withoutTranscript:false}) - await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-autodeleted",1,"increase") + await opendiscord.statistics.get("opendiscord:global").setStat("opendiscord:tickets-autodeleted",1,"increase") } } } diff --git a/src/data/framework/commandLoader.ts b/src/data/framework/commandLoader.ts index b598e79..ec97ed0 100644 --- a/src/data/framework/commandLoader.ts +++ b/src/data/framework/commandLoader.ts @@ -1,19 +1,9 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" const lang = opendiscord.languages -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW COMMANDS? - * - Register the command in loadAllSlashCommands() & loadAllTextCommands() in (./src/data/framework/commandLoader.ts) - * - Add autocomplete for the command in OD(Slash/Text)CommandManagerIds_Default in (./src/core/api/defaults/client.ts) - * - Add the command to the help menu in (./src/data/framework/helpMenuLoader.ts) - * - If required, new config variables should be added (incl. logs, dm-logs & permissions). - * - Update the Open Ticket Documentation. - * - If the command contains complex logic or can be executed from a button/dropdown, it should be placed inside an `ODAction`. - * - Check all files, test the bot carefully & try a lot of different scenario's with different settings. - */ - -export const loadAllSlashCommands = async () => { +export async function loadAllSlashCommands(){ const commands = opendiscord.client.slashCommands const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return @@ -24,8 +14,8 @@ export const loadAllSlashCommands = async () => { if (!generalConfig.data.slashCommands) return const allowedCommands: string[] = [] - for (const key in generalConfig.data.system.permissions){ - if (generalConfig.data.system.permissions[key] != "none") allowedCommands.push(key) + for (const key in generalConfig.data.permissions){ + if (generalConfig.data.permissions[key] != "none") allowedCommands.push(key) } //HELP @@ -97,7 +87,7 @@ export const loadAllSlashCommands = async () => { })) //DELETE - if (allowedCommands.includes("delete") && generalConfig.data.system.enableDeleteWithoutTranscript) commands.add(new api.ODSlashCommand("opendiscord:delete",{ + if (allowedCommands.includes("delete") && generalConfig.data.ticketSystem.enableDeleteWithoutTranscript) commands.add(new api.ODSlashCommand("opendiscord:delete",{ type:act.ChatInput, name:"delete", description:lang.getTranslation("commands.delete"), @@ -642,7 +632,7 @@ export const loadAllSlashCommands = async () => { })) } -export const loadAllTextCommands = async () => { +export async function loadAllTextCommands(){ const commands = opendiscord.client.textCommands const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return @@ -664,8 +654,8 @@ export const loadAllTextCommands = async () => { }) const allowedCommands: string[] = [] - for (const key in generalConfig.data.system.permissions){ - if (generalConfig.data.system.permissions[key] != "none") allowedCommands.push(key) + for (const key in generalConfig.data.permissions){ + if (generalConfig.data.permissions[key] != "none") allowedCommands.push(key) } //HELP @@ -1216,7 +1206,7 @@ export const loadAllTextCommands = async () => { })) } -export const loadAllContextMenus = async () => { +export async function loadAllContextMenus(){ const menus = opendiscord.client.contextMenus const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return diff --git a/src/data/framework/configLoader.ts b/src/data/framework/configLoader.ts index 98945d0..d845586 100644 --- a/src/data/framework/configLoader.ts +++ b/src/data/framework/configLoader.ts @@ -1,145 +1,67 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as fjs from "formatted-json-stringify" -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW CONFIG VARIABLES? - * - Make the change to the config file in (./config/) and be aware of the following things: - * - The variable has a clear name and its function is obvious. - * - The variable is in the correct position/category of the config. - * - The variable contains a default placeholder to suggest the contents. - * - If there's a (./devconfig/), also modify this file. - * - Register the config in loadAllConfigs() in (./src/data/framework/configLoader.ts) - * - The variable should be added to the "formatters" in the correct position. - * - Add autocomplete for the variable in ODJsonConfig_Default... in (./src/core/api/defaults/config.ts) - * - Add the variable to the config checker in (./src/data/framework/checkerLoader.ts) - * - Make sure the variable is compatible with the Interactive Setup CLI. - * - The variable should be added by the migration manager (./src/core/startup/migration.ts) when missing. - * - Update the Open Ticket Documentation. - * - * IF VARIABLE IS FROM questions.json, options.json OR panels.json: - * - Check (./src/data/openticket/...) for loading/unloading of data. - * - Check (./src/actions/createTicket.ts) and related files. - * - Check (./src/builders), (./src/actions), (./src/data) & (./src/commands) in general in the areas that were changed. - */ - -export const loadAllConfigs = async () => { +export async function loadAllConfigs(){ const devconfigFlag = opendiscord.flags.get("opendiscord:dev-config") const isDevconfig = devconfigFlag ? devconfigFlag.value : false - opendiscord.configs.add(new api.ODJsonConfig("opendiscord:general","general.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultGeneralFormatter)) - opendiscord.configs.add(new api.ODJsonConfig("opendiscord:questions","questions.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultQuestionsFormatter)) - opendiscord.configs.add(new api.ODJsonConfig("opendiscord:options","options.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultOptionsFormatter)) - opendiscord.configs.add(new api.ODJsonConfig("opendiscord:panels","panels.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultPanelsFormatter)) - opendiscord.configs.add(new api.ODJsonConfig("opendiscord:transcripts","transcripts.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultTranscriptsFormatter)) + opendiscord.configs.add(new api.ODJsonCommentsConfig("opendiscord:general","general.jsonc",(isDevconfig) ? "./devconfig/" : "./config/",defaultGeneralFormatter)) + opendiscord.configs.add(new api.ODJsonCommentsConfig("opendiscord:questions","questions.jsonc",(isDevconfig) ? "./devconfig/" : "./config/",defaultQuestionsFormatter)) + opendiscord.configs.add(new api.ODJsonCommentsConfig("opendiscord:options","options.jsonc",(isDevconfig) ? "./devconfig/" : "./config/",defaultOptionsFormatter)) + opendiscord.configs.add(new api.ODJsonCommentsConfig("opendiscord:panels","panels.jsonc",(isDevconfig) ? "./devconfig/" : "./config/",defaultPanelsFormatter)) + opendiscord.configs.add(new api.ODJsonCommentsConfig("opendiscord:transcripts","transcripts.jsonc",(isDevconfig) ? "./devconfig/" : "./config/",defaultTranscriptsFormatter)) } //FORMATTERS -export const defaultGeneralFormatter = new fjs.ObjectFormatter(null,true,[ - new fjs.ObjectFormatter("_INFO",true,[ - new fjs.PropertyFormatter("support"), - new fjs.PropertyFormatter("discord"), - new fjs.PropertyFormatter("version"), - ]), +export const defaultGeneralFormatter = new fjs.TopLevelCommentFormatter(new fjs.MultiCommentFormatter([ + "Hi there! Thank you for installing Open Ticket.", + "----------------------------------------------", + "If you need any assistance with configuring the bot,", + "feel free to use the documentation or join our Discord server:", + "https://otdocs.dj-dj.be", + "https://discord.dj-dj.be", + "----------------------------------------------", + "SETUP:", + "1. Install the required dependencies using the command: \"npm install\"", + "2. Configure the bot in one of the following ways:", + " a. (easy) Using the Quick Setup CLI Tool", + " b. (difficult) Using the JSON files in `./config/`", + "", + "Start the Quick Setup CLI Tool using the command: \"npm run setup\"", + "After configuration, start the bot using the command: \"npm start\"", + "", + "Good luck! DJj123dj & contributors", + ].join("\n")),new fjs.ObjectFormatter(null,true,[ + new fjs.PropertyFormatter("_CONFIG_VERSION"), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Load the bot token from .env or the \"token\" field below. Leave \"token\" empty if using \"tokenFromENV\"."), new fjs.PropertyFormatter("token"), new fjs.PropertyFormatter("tokenFromENV"), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("mainColor"), - new fjs.PropertyFormatter("language"), - new fjs.PropertyFormatter("prefix"), + new fjs.PropertyFormatter("mainColor",new fjs.SingleCommentFormatter("Hex color used in most embeds")), + new fjs.PropertyFormatter("language",new fjs.SingleCommentFormatter("Visit README.md for list")), + new fjs.PropertyFormatter("prefix",new fjs.SingleCommentFormatter("Prefix used in text commands")), new fjs.PropertyFormatter("serverId"), - new fjs.ArrayFormatter("globalAdmins",false,new fjs.PropertyFormatter(null)), + new fjs.ArrayFormatter("globalAdmins",false,new fjs.PropertyFormatter(null),undefined,undefined,new fjs.SingleCommentFormatter("Have access to all commands")), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Enable/disable text or slash commands."), new fjs.PropertyFormatter("slashCommands"), new fjs.PropertyFormatter("textCommands"), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Configure the status of the bot."), new fjs.ObjectFormatter("status",true,[ new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("type"), - new fjs.PropertyFormatter("mode"), + new fjs.PropertyFormatter("type",new fjs.SingleCommentFormatter("Choices: listening, watching, playing, custom")), + new fjs.PropertyFormatter("mode",new fjs.SingleCommentFormatter("Choices: online, invisible, idle, dnd")), new fjs.PropertyFormatter("text"), - new fjs.PropertyFormatter("state"), + new fjs.PropertyFormatter("state",new fjs.SingleCommentFormatter("Additional text (Leave empty to disable)")), ]), new fjs.TextFormatter(""), - new fjs.ObjectFormatter("system",true,[ - new fjs.PropertyFormatter("preferSlashOverText"), - new fjs.PropertyFormatter("sendErrorOnUnknownCommand"), - new fjs.PropertyFormatter("questionFieldsInCodeBlock"), - new fjs.PropertyFormatter("displayFieldsWithQuestions"), - new fjs.PropertyFormatter("showGlobalAdminsInPanelRoles"), - new fjs.PropertyFormatter("disableVerifyBars"), - new fjs.PropertyFormatter("useRedErrorEmbeds"), - new fjs.PropertyFormatter("alwaysShowReason"), - new fjs.PropertyFormatter("emojiStyle"), - new fjs.PropertyFormatter("pinEmoji"), - new fjs.TextFormatter(""), - new fjs.PropertyFormatter("replyOnTicketCreation"), - new fjs.PropertyFormatter("replyOnReactionRole"), - new fjs.PropertyFormatter("askPriorityOnTicketCreation"), - new fjs.PropertyFormatter("removeParticipantsOnClose"), - new fjs.PropertyFormatter("disableAutocloseAfterReopen"), - new fjs.PropertyFormatter("autodeleteRequiresClosedTicket"), - new fjs.PropertyFormatter("adminOnlyDeleteWithoutTranscript"), - new fjs.PropertyFormatter("allowCloseBeforeMessage"), - new fjs.PropertyFormatter("allowCloseBeforeAdminMessage"), - new fjs.PropertyFormatter("useTranslatedConfigChecker"), - new fjs.PropertyFormatter("pinFirstTicketMessage"), - new fjs.TextFormatter(""), - new fjs.PropertyFormatter("enableTicketClaimButtons"), - new fjs.PropertyFormatter("enableTicketCloseButtons"), - new fjs.PropertyFormatter("enableTicketPinButtons"), - new fjs.PropertyFormatter("enableTicketDeleteButtons"), - new fjs.PropertyFormatter("enableTicketActionWithReason"), - new fjs.PropertyFormatter("enableDeleteWithoutTranscript"), - new fjs.TextFormatter(""), - new fjs.ObjectFormatter("logs",true,[ - new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("channel"), - ]), - new fjs.TextFormatter(""), - new fjs.ObjectFormatter("limits",true,[ - new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("globalMaximum"), - new fjs.PropertyFormatter("userMaximum"), - ]), - new fjs.TextFormatter(""), - new fjs.ObjectFormatter("channelTopic",true,[ - new fjs.PropertyFormatter("showOptionName"), - new fjs.PropertyFormatter("showOptionDescription"), - new fjs.PropertyFormatter("showOptionTopic"), - new fjs.PropertyFormatter("showPriority"), - new fjs.PropertyFormatter("showClosed"), - new fjs.PropertyFormatter("showClaimed"), - new fjs.PropertyFormatter("showPinned"), - new fjs.PropertyFormatter("showCreator"), - new fjs.PropertyFormatter("showParticipants"), - ]), - new fjs.TextFormatter(""), - new fjs.ObjectFormatter("permissions",true,[ - new fjs.PropertyFormatter("help"), - new fjs.PropertyFormatter("panel"), - new fjs.PropertyFormatter("ticket"), - new fjs.PropertyFormatter("close"), - new fjs.PropertyFormatter("delete"), - new fjs.PropertyFormatter("reopen"), - new fjs.PropertyFormatter("claim"), - new fjs.PropertyFormatter("unclaim"), - new fjs.PropertyFormatter("pin"), - new fjs.PropertyFormatter("unpin"), - new fjs.PropertyFormatter("move"), - new fjs.PropertyFormatter("rename"), - new fjs.PropertyFormatter("add"), - new fjs.PropertyFormatter("remove"), - new fjs.PropertyFormatter("blacklist"), - new fjs.PropertyFormatter("stats"), - new fjs.PropertyFormatter("clear"), - new fjs.PropertyFormatter("autoclose"), - new fjs.PropertyFormatter("autodelete"), - new fjs.PropertyFormatter("transfer"), - new fjs.PropertyFormatter("topic"), - new fjs.PropertyFormatter("priority"), - ]), - new fjs.TextFormatter(""), - new fjs.ObjectFormatter("messages",true,[ + new fjs.MultiCommentFormatter("Send ticket logs to a channel or in DM of the ticket creator."), + new fjs.ObjectFormatter("logs",true,[ + new fjs.PropertyFormatter("enabled"), + new fjs.PropertyFormatter("channel"), + new fjs.ObjectFormatter("logMessages",true,[ new fjs.DefaultFormatter("creation",false), new fjs.DefaultFormatter("closing",false), new fjs.DefaultFormatter("deleting",false), @@ -157,80 +79,267 @@ export const defaultGeneralFormatter = new fjs.ObjectFormatter(null,true,[ new fjs.DefaultFormatter("reactionRole",false) ]), ]), -]) + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("A large collection of settings for the ticket system."), + new fjs.ObjectFormatter("ticketSystem",true,[ + new fjs.PropertyFormatter("preferSlashOverText",new fjs.SingleCommentFormatter("Show slashcmds in help menu's")), + new fjs.PropertyFormatter("sendErrorOnUnknownCommand",new fjs.SingleCommentFormatter("Send error when command not found")), + new fjs.PropertyFormatter("questionFieldsInCodeBlock",new fjs.SingleCommentFormatter("Put question answers in code blocks")), + new fjs.PropertyFormatter("displayFieldsWithQuestions",new fjs.SingleCommentFormatter("Display embed fields together with question answers")), + new fjs.PropertyFormatter("showGlobalAdminsInPanelRoles",new fjs.SingleCommentFormatter("Include \"globalAdmins\" in panel admin lists")), + new fjs.PropertyFormatter("disableVerifyBars",new fjs.SingleCommentFormatter("Disable the (โŒ/โœ…) buttons")), + new fjs.PropertyFormatter("useRedErrorEmbeds",new fjs.SingleCommentFormatter("Make errors embeds always red")), + new fjs.PropertyFormatter("alwaysShowReason",new fjs.SingleCommentFormatter("Show reason even if none is provided")), + new fjs.PropertyFormatter("emojiStyle",new fjs.SingleCommentFormatter("The style of emoji's in embeds. Choices: before, after, double, disabled")), + new fjs.PropertyFormatter("pinEmoji",new fjs.SingleCommentFormatter("Channel emoji of pinned tickets (Leave empty to disable)")), + new fjs.PropertyFormatter("closeEmoji",new fjs.SingleCommentFormatter("Channel emoji of closed tickets (Leave empty to disable)")), + new fjs.TextFormatter(""), + new fjs.PropertyFormatter("replyOnTicketCreation",new fjs.SingleCommentFormatter("Reply with a msg when a ticket is created")), + new fjs.PropertyFormatter("replyOnReactionRole",new fjs.SingleCommentFormatter("Reply with a msg when a reaction role is used")), + new fjs.PropertyFormatter("askPriorityOnTicketCreation",new fjs.SingleCommentFormatter("Show a dropdown to select priority")), + new fjs.PropertyFormatter("removeParticipantsOnClose",new fjs.SingleCommentFormatter("Remove non-admins when ticket is closed")), + new fjs.PropertyFormatter("disableAutocloseAfterReopen",new fjs.SingleCommentFormatter("Disable autoclose after ticket got reopened")), + new fjs.PropertyFormatter("autodeleteRequiresClosedTicket",new fjs.SingleCommentFormatter("A ticket must be closed before autodelete works")), + new fjs.PropertyFormatter("adminOnlyDeleteWithoutTranscript",new fjs.SingleCommentFormatter("Only allow \"globalAdmins\" to delete a ticket without transcript")), + new fjs.PropertyFormatter("allowCloseBeforeMessage",new fjs.SingleCommentFormatter("Allow closing before a message is sent")), + new fjs.PropertyFormatter("allowCloseBeforeAdminMessage",new fjs.SingleCommentFormatter("Allow closing before an admin has sent a message")), + new fjs.PropertyFormatter("useTranslatedConfigChecker",new fjs.SingleCommentFormatter("Translate config errors in the console")), + new fjs.PropertyFormatter("pinFirstTicketMessage",new fjs.SingleCommentFormatter("Pin the ticket message to the channel")), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Enable/disable certain buttons & features of the bot."), + new fjs.PropertyFormatter("enableTicketClaimButtons"), + new fjs.PropertyFormatter("enableTicketCloseButtons"), + new fjs.PropertyFormatter("enableTicketPinButtons"), + new fjs.PropertyFormatter("enableTicketDeleteButtons"), + new fjs.PropertyFormatter("enableTicketActionWithReason"), + new fjs.PropertyFormatter("enableDeleteWithoutTranscript",new fjs.SingleCommentFormatter("Allow deleting tickets without transcript")), + new fjs.PropertyFormatter("enableCreateTicketForOtherUser",new fjs.SingleCommentFormatter("Allow creating tickets for other users")), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Set the maximum amount of simultaneous tickets."), + new fjs.ObjectFormatter("limits",true,[ + new fjs.PropertyFormatter("enabled"), + new fjs.PropertyFormatter("globalMaximum"), + new fjs.PropertyFormatter("userMaximum"), + ]), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Choose which data is shown in the channel topic."), + new fjs.ObjectFormatter("channelTopic",true,[ + new fjs.PropertyFormatter("showOptionName"), + new fjs.PropertyFormatter("showOptionDescription"), + new fjs.PropertyFormatter("showOptionTopic"), + new fjs.PropertyFormatter("showPriority"), + new fjs.PropertyFormatter("showClosed"), + new fjs.PropertyFormatter("showClaimed"), + new fjs.PropertyFormatter("showPinned"), + new fjs.PropertyFormatter("showCreator"), + new fjs.PropertyFormatter("showParticipants"), + ]), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Move closed tickets to a separate category."), + new fjs.ObjectFormatter("closedCategory",true,[ + new fjs.PropertyFormatter("enabled"), + new fjs.PropertyFormatter("categoryId") + ]), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Create tickets in a backup category when the original category exceeds 50 channels."), + new fjs.ObjectFormatter("backupCategory",true,[ + new fjs.PropertyFormatter("enabled"), + new fjs.PropertyFormatter("categoryId") + ]), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Move claimed tickets to a matching category of the user that claimed the ticket. Set to empty list [] to disable."), + new fjs.ArrayFormatter("claimedCategories",true,new fjs.ObjectFormatter(null,false,[ + new fjs.PropertyFormatter("user"), + new fjs.PropertyFormatter("category"), + ])), + ]), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter([ + "Set permissions for each individual command, button or action.", + "CHOICES:", + ">> \"none\" -> Command disabled", + ">> \"everyone\" -> Allowed for everyone", + ">> \"admin\" -> Global & ticket admins only", + ">> \"DISCORD_ROLE_ID\" -> Custom role only", + ].join("\n")), + new fjs.ObjectFormatter("permissions",true,[ + new fjs.PropertyFormatter("help"), + new fjs.PropertyFormatter("panel"), + new fjs.PropertyFormatter("ticket"), + new fjs.PropertyFormatter("close"), + new fjs.PropertyFormatter("delete"), + new fjs.PropertyFormatter("reopen"), + new fjs.PropertyFormatter("claim"), + new fjs.PropertyFormatter("unclaim"), + new fjs.PropertyFormatter("pin"), + new fjs.PropertyFormatter("unpin"), + new fjs.PropertyFormatter("move"), + new fjs.PropertyFormatter("rename"), + new fjs.PropertyFormatter("add"), + new fjs.PropertyFormatter("remove"), + new fjs.PropertyFormatter("blacklist"), + new fjs.PropertyFormatter("stats"), + new fjs.PropertyFormatter("clear"), + new fjs.PropertyFormatter("autoclose"), + new fjs.PropertyFormatter("autodelete"), + new fjs.PropertyFormatter("transfer"), + new fjs.PropertyFormatter("topic"), + new fjs.PropertyFormatter("priority"), + new fjs.PropertyFormatter("transcripts"), + ]), +])) -export const defaultQuestionsFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectSwitchFormatter(null,[ +export const defaultQuestionsFormatter = new fjs.TopLevelCommentFormatter(new fjs.MultiCommentFormatter([ + "OPEN TICKET MODAL QUESTIONS", + "----------------------------------------------", + "Create customizable modal questions that will be shown before creating a ticket.", + "Each ticket option (config/options.jsonc) can contain a maximum of 5 questions.", + "There are 6 types of questions available: short, paragraph, dropdown, radio-select, checkbox-select, text-display", + "", + "TIP: Create new questions by copying everything between and including the {...} brackets of a question. Paste it after the last question and make sure that they are seperated by a comma." + ].join("\n")),new fjs.ArrayFormatter(null,true,new fjs.ObjectSwitchFormatter(null,[ {key:"type",value:"short",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A short text input modal question."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.PropertyFormatter("type"), - new fjs.TextFormatter(""), new fjs.PropertyFormatter("required"), + new fjs.TextFormatter(""), new fjs.PropertyFormatter("placeholder"), new fjs.ObjectFormatter("length",true,[ + new fjs.MultiCommentFormatter("Configure length limits for the answer."), new fjs.PropertyFormatter("enabled"), new fjs.PropertyFormatter("min"), new fjs.PropertyFormatter("max"), ]), ])}, {key:"type",value:"paragraph",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A paragraph text input modal question."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.PropertyFormatter("type"), - new fjs.TextFormatter(""), new fjs.PropertyFormatter("required"), + new fjs.TextFormatter(""), new fjs.PropertyFormatter("placeholder"), new fjs.ObjectFormatter("length",true,[ + new fjs.MultiCommentFormatter("Configure length limits for the answer."), new fjs.PropertyFormatter("enabled"), new fjs.PropertyFormatter("min"), new fjs.PropertyFormatter("max"), ]), - ])} -])) - - -export const defaultOptionsFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectSwitchFormatter(null,[ - {key:"type",value:"ticket",formatter:new fjs.ObjectFormatter(null,true,[ + ])}, + {key:"type",value:"text-display",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("Show text in a modal to provide extra details or explain questions."), + new fjs.PropertyFormatter("id"), + new fjs.PropertyFormatter("type"), + new fjs.TextFormatter(""), + new fjs.PropertyFormatter("textContents"), + ])}, + {key:"type",value:"dropdown",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A dropdown menu input modal question with up to 25 choices. \"emoji\" & \"description\" fields are optional."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), - new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("type"), + new fjs.PropertyFormatter("required"), + new fjs.TextFormatter(""), + new fjs.PropertyFormatter("placeholder"), + new fjs.ArrayFormatter("choices",true,new fjs.ObjectFormatter(null,false,[ + new fjs.PropertyFormatter("title"), + new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("emoji"), + ])), + ])}, + {key:"type",value:"radio-select",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A radio select input modal question with up to 10 choices."), + new fjs.PropertyFormatter("id"), + new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("type"), + new fjs.PropertyFormatter("required"), + new fjs.TextFormatter(""), + new fjs.ArrayFormatter("choices",true,new fjs.ObjectFormatter(null,false,[ + new fjs.PropertyFormatter("title"), + new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("selectedByDefault"), + ])), + ])}, + {key:"type",value:"checkbox-select",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A checkbox select input modal question with up to 10 choices."), + new fjs.PropertyFormatter("id"), + new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("type"), + new fjs.PropertyFormatter("required"), + new fjs.TextFormatter(""), + new fjs.ObjectFormatter("limits",true,[ + new fjs.MultiCommentFormatter("Configure checkbox amount limits for the answer."), + new fjs.PropertyFormatter("enabled"), + new fjs.PropertyFormatter("min"), + new fjs.PropertyFormatter("max"), + ]), + new fjs.ArrayFormatter("choices",true,new fjs.ObjectFormatter(null,false,[ + new fjs.PropertyFormatter("title"), + new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("selectedByDefault"), + ])), + ])}, +]))) + + +export const defaultOptionsFormatter = new fjs.TopLevelCommentFormatter(new fjs.MultiCommentFormatter([ + "OPEN TICKET BUTTON OPTIONS", + "----------------------------------------------", + "Create customizable ticket, website, reaction-role or sub-panel button options.", + "Up to 25 options can be added to each panel in (config/panels.jsonc)", + "There are 4 types of options available: ticket, website, role, sub-panel", + "", + "TIP: Create new options by copying everything between and including the {...} brackets of an option. Paste it after the last option and make sure that they are seperated by a comma." + ].join("\n")),new fjs.ArrayFormatter(null,true,new fjs.ObjectSwitchFormatter(null,[ + {key:"type",value:"ticket",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A ticket option creates a button to open a ticket."), + new fjs.PropertyFormatter("id"), + new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.PropertyFormatter("type"), new fjs.TextFormatter(""), new fjs.ObjectFormatter("button",true,[ + new fjs.MultiCommentFormatter("Configure the button style of this option. At least one of \"emoji\" or \"label\" must be provided."), new fjs.PropertyFormatter("emoji"), new fjs.PropertyFormatter("label"), - new fjs.PropertyFormatter("color"), + new fjs.PropertyFormatter("color",new fjs.SingleCommentFormatter("Choices: gray, red, green, blue")), ]), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Add up to 5 modal questions IDs from (config/questions.jsonc)."), + new fjs.ArrayFormatter("questions",false,new fjs.PropertyFormatter(null)), + new fjs.TextFormatter(""), new fjs.ArrayFormatter("ticketAdmins",false,new fjs.PropertyFormatter(null)), new fjs.ArrayFormatter("readonlyAdmins",false,new fjs.PropertyFormatter(null)), new fjs.PropertyFormatter("allowCreationByBlacklistedUsers"), - new fjs.ArrayFormatter("questions",false,new fjs.PropertyFormatter(null)), new fjs.TextFormatter(""), new fjs.ObjectFormatter("channel",true,[ + new fjs.MultiCommentFormatter("Configure the name, topic and category of the ticket option."), new fjs.PropertyFormatter("prefix"), - new fjs.PropertyFormatter("suffix"), - new fjs.PropertyFormatter("category"), - new fjs.PropertyFormatter("backupCategory"), - new fjs.PropertyFormatter("closedCategory"), - new fjs.ArrayFormatter("claimedCategory",true,new fjs.ObjectFormatter(null,false,[ - new fjs.PropertyFormatter("user"), - new fjs.PropertyFormatter("category"), - ])), - new fjs.PropertyFormatter("topic"), + new fjs.PropertyFormatter("suffix",new fjs.SingleCommentFormatter("Choices: user-name, user-id, random-number, random-hex, counter-dynamic, counter-fixed")), + new fjs.PropertyFormatter("category",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("topic",new fjs.SingleCommentFormatter("Leave empty to disable")), ]), new fjs.TextFormatter(""), new fjs.ObjectFormatter("dmMessage",true,[ + new fjs.MultiCommentFormatter("Send a customisable message in DM when creating a ticket."), new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("text"), + new fjs.PropertyFormatter("text",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.ObjectFormatter("embed",true,[ new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("title"), - new fjs.PropertyFormatter("description"), - new fjs.PropertyFormatter("customColor"), + new fjs.PropertyFormatter("title",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("customColor",new fjs.SingleCommentFormatter("Leave empty to use default color")), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("image"), - new fjs.PropertyFormatter("thumbnail"), + new fjs.PropertyFormatter("image",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), + new fjs.PropertyFormatter("thumbnail",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), + new fjs.MultiCommentFormatter("Embed fields. Set to empty list [] to disable."), new fjs.ArrayFormatter("fields",true,new fjs.ObjectFormatter(null,false,[ new fjs.PropertyFormatter("name"), new fjs.PropertyFormatter("value"), @@ -240,16 +349,18 @@ export const defaultOptionsFormatter = new fjs.ArrayFormatter(null,true,new fjs. ]), ]), new fjs.ObjectFormatter("ticketMessage",true,[ + new fjs.MultiCommentFormatter("Send a customisable message in the ticket with close, claim, delete, ... buttons."), new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("text"), + new fjs.PropertyFormatter("text",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.ObjectFormatter("embed",true,[ new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("title"), - new fjs.PropertyFormatter("description"), - new fjs.PropertyFormatter("customColor"), + new fjs.PropertyFormatter("title",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("customColor",new fjs.SingleCommentFormatter("Leave empty to use default color")), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("image"), - new fjs.PropertyFormatter("thumbnail"), + new fjs.PropertyFormatter("image",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), + new fjs.PropertyFormatter("thumbnail",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), + new fjs.MultiCommentFormatter("Embed fields. Set to empty list [] to disable."), new fjs.ArrayFormatter("fields",true,new fjs.ObjectFormatter(null,false,[ new fjs.PropertyFormatter("name"), new fjs.PropertyFormatter("value"), @@ -258,44 +369,52 @@ export const defaultOptionsFormatter = new fjs.ArrayFormatter(null,true,new fjs. new fjs.PropertyFormatter("timestamp"), ]), new fjs.ObjectFormatter("ping",true,[ + new fjs.MultiCommentFormatter("Customise the user & role mentions of this ticket message."), new fjs.PropertyFormatter("@here"), new fjs.PropertyFormatter("@everyone"), - new fjs.ArrayFormatter("custom",true,new fjs.PropertyFormatter(null)), + new fjs.ArrayFormatter("custom",false,new fjs.PropertyFormatter(null)), ]), ]), new fjs.ObjectFormatter("autoclose",true,[ + new fjs.MultiCommentFormatter("Autoclose this ticket after a period of inactivity or when the creator leaves the server."), new fjs.PropertyFormatter("enableInactiveHours"), new fjs.PropertyFormatter("inactiveHours"), new fjs.PropertyFormatter("enableUserLeave"), new fjs.PropertyFormatter("disableOnClaim"), ]), new fjs.ObjectFormatter("autodelete",true,[ + new fjs.MultiCommentFormatter("Autodelete this ticket after a period of inactivity or when the creator leaves the server."), new fjs.PropertyFormatter("enableInactiveDays"), new fjs.PropertyFormatter("inactiveDays"), new fjs.PropertyFormatter("enableUserLeave"), new fjs.PropertyFormatter("disableOnClaim"), ]), new fjs.ObjectFormatter("cooldown",true,[ + new fjs.MultiCommentFormatter("Users must wait a certain period before being able to create another ticket of this type."), new fjs.PropertyFormatter("enabled"), new fjs.PropertyFormatter("cooldownMinutes"), ]), new fjs.ObjectFormatter("limits",true,[ + new fjs.MultiCommentFormatter("Set the maximum amount of simultaneous tickets of this option."), new fjs.PropertyFormatter("enabled"), new fjs.PropertyFormatter("globalMaximum"), new fjs.PropertyFormatter("userMaximum"), ]), new fjs.ObjectFormatter("slowMode",true,[ + new fjs.MultiCommentFormatter("Enable slow-mode in the ticket channel."), new fjs.PropertyFormatter("enabled"), new fjs.PropertyFormatter("slowModeSeconds"), ]), ])}, {key:"type",value:"website",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A website option creates a button with a URL to an external website."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), - new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.PropertyFormatter("type"), new fjs.TextFormatter(""), new fjs.ObjectFormatter("button",true,[ + new fjs.MultiCommentFormatter("Configure the button style of this option. At least one of \"emoji\" or \"label\" must be provided."), new fjs.PropertyFormatter("emoji"), new fjs.PropertyFormatter("label"), ]), @@ -303,43 +422,75 @@ export const defaultOptionsFormatter = new fjs.ArrayFormatter(null,true,new fjs. new fjs.PropertyFormatter("url"), ])}, {key:"type",value:"role",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A reaction-role option creates a button for members to choose roles."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), - new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.PropertyFormatter("type"), new fjs.TextFormatter(""), new fjs.ObjectFormatter("button",true,[ + new fjs.MultiCommentFormatter("Configure the button style of this option. At least one of \"emoji\" or \"label\" must be provided."), new fjs.PropertyFormatter("emoji"), new fjs.PropertyFormatter("label"), - new fjs.PropertyFormatter("color"), + new fjs.PropertyFormatter("color",new fjs.SingleCommentFormatter("Choices: gray, red, green, blue")), ]), new fjs.TextFormatter(""), new fjs.ArrayFormatter("roles",false,new fjs.PropertyFormatter(null)), - new fjs.PropertyFormatter("mode"), - new fjs.ArrayFormatter("removeRolesOnAdd",false,new fjs.PropertyFormatter(null)), - new fjs.PropertyFormatter("addOnMemberJoin"), + new fjs.PropertyFormatter("mode",new fjs.SingleCommentFormatter("What to do with the roles. Choices: add&remove, add, remove")), + new fjs.ArrayFormatter("removeRolesOnAdd",false,new fjs.PropertyFormatter(null),undefined,undefined,new fjs.SingleCommentFormatter("Remove these old roles when new roles are added.")), + new fjs.PropertyFormatter("addOnMemberJoin",new fjs.SingleCommentFormatter("Add these roles automatically when joining the server.")), + ])}, + {key:"type",value:"sub-panel",formatter:new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A sub-panel option creates a button which sends another panel for additional options."), + new fjs.PropertyFormatter("id"), + new fjs.PropertyFormatter("name"), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("type"), + new fjs.TextFormatter(""), + new fjs.ObjectFormatter("button",true,[ + new fjs.MultiCommentFormatter("Configure the button style of this option. At least one of \"emoji\" or \"label\" must be provided."), + new fjs.PropertyFormatter("emoji"), + new fjs.PropertyFormatter("label"), + new fjs.PropertyFormatter("color",new fjs.SingleCommentFormatter("Choices: gray, red, green, blue")), + ]), + new fjs.TextFormatter(""), + new fjs.PropertyFormatter("subPanelId",new fjs.SingleCommentFormatter("Choose a panel ID from (config/panels.jsonc)")), ])} -])) +]))) -export const defaultPanelsFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectFormatter(null,true,[ +export const defaultPanelsFormatter = new fjs.TopLevelCommentFormatter(new fjs.MultiCommentFormatter([ + "OPEN TICKET PANELS", + "----------------------------------------------", + "Create customizable panel messages with buttons or a dropdown.", + "Add up to 25 options to this panel from (config/options.jsonc)", + "Panels can be customised with text, images, colors and more.", + "", + "Spawn the panel in Discord using: /panel ", + "", + "TIP: Create new panels by copying everything between and including the {...} brackets of an panel. Paste it after the last panel and make sure that they are seperated by a comma." + ].join("\n")),new fjs.ArrayFormatter(null,true,new fjs.ObjectFormatter(null,true,[ + new fjs.MultiCommentFormatter("A panel is creates a message with up to 25 options as buttons or dropdown."), new fjs.PropertyFormatter("id"), new fjs.PropertyFormatter("name"), new fjs.PropertyFormatter("dropdown"), + new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Add up to 5 option IDs from (config/options.jsonc)."), new fjs.ArrayFormatter("options",false,new fjs.PropertyFormatter(null)), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("text"), + new fjs.PropertyFormatter("text",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.ObjectFormatter("embed",true,[ new fjs.PropertyFormatter("enabled"), - new fjs.PropertyFormatter("title"), - new fjs.PropertyFormatter("description"), + new fjs.PropertyFormatter("title",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.PropertyFormatter("description",new fjs.SingleCommentFormatter("Leave empty to disable")), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("customColor"), - new fjs.PropertyFormatter("url"), + new fjs.PropertyFormatter("customColor",new fjs.SingleCommentFormatter("Leave empty to use default color")), + new fjs.PropertyFormatter("url",new fjs.SingleCommentFormatter("URL. Leave empty to disable")), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("image"), - new fjs.PropertyFormatter("thumbnail"), + new fjs.PropertyFormatter("image",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), + new fjs.PropertyFormatter("thumbnail",new fjs.SingleCommentFormatter("Image URL. Leave empty to disable")), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("footer"), + new fjs.PropertyFormatter("footer",new fjs.SingleCommentFormatter("Leave empty to disable")), + new fjs.MultiCommentFormatter("Embed fields. Set to empty list [] to disable."), new fjs.ArrayFormatter("fields",true,new fjs.ObjectFormatter(null,false,[ new fjs.PropertyFormatter("name"), new fjs.PropertyFormatter("value"), @@ -348,53 +499,70 @@ export const defaultPanelsFormatter = new fjs.ArrayFormatter(null,true,new fjs.O new fjs.PropertyFormatter("timestamp"), ]), new fjs.ObjectFormatter("settings",true,[ - new fjs.PropertyFormatter("dropdownPlaceholder"), + new fjs.PropertyFormatter("dropdownPlaceholder",new fjs.SingleCommentFormatter("Leave empty to use default.")), + new fjs.PropertyFormatter("maximumButtonsPerRow"), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Display the maximum amount of tickets per user."), new fjs.PropertyFormatter("enableMaxTicketsWarningInText"), new fjs.PropertyFormatter("enableMaxTicketsWarningInEmbed"), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("describeOptionsLayout"), + new fjs.MultiCommentFormatter("Automatically generate option descriptions from (config/options.jsonc)."), + new fjs.PropertyFormatter("describeOptionsLayout",new fjs.SingleCommentFormatter("Choices: simple, normal, detailed")), new fjs.PropertyFormatter("describeOptionsCustomTitle"), new fjs.PropertyFormatter("describeOptionsInText"), new fjs.PropertyFormatter("describeOptionsInEmbedFields"), new fjs.PropertyFormatter("describeOptionsInEmbedDescription"), ]), -])) +]))) -export const defaultTranscriptsFormatter = new fjs.ObjectFormatter(null,true,[ +export const defaultTranscriptsFormatter = new fjs.TopLevelCommentFormatter(new fjs.MultiCommentFormatter([ + "OPEN TICKET TRANSCRIPTS", + "----------------------------------------------", + "Enable transcript creation when tickets are deleted. There are 2 available transcript types: HTML & Text", + "", + "HTML Transcripts (recommended):", + "Generate transcripts as HTML files to view in the browser. No server or domain required. HTML Transcripts use an external service to process and host the transcripts.", + "", + "Text Transcripts:", + "Generate transcripts as simple .txt files with limited details. Processing happens fully local.", + ].join("\n")),new fjs.ObjectFormatter(null,true,[ new fjs.ObjectFormatter("general",true,[ new fjs.PropertyFormatter("enabled"), new fjs.TextFormatter(""), + new fjs.MultiCommentFormatter("Choose which users and channel get the generated transcript."), new fjs.PropertyFormatter("enableChannel"), new fjs.PropertyFormatter("enableCreatorDM"), new fjs.PropertyFormatter("enableParticipantDM"), new fjs.PropertyFormatter("enableActiveAdminDM"), new fjs.PropertyFormatter("enableEveryAdminDM"), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("channel"), - new fjs.PropertyFormatter("mode"), + new fjs.PropertyFormatter("channel",new fjs.SingleCommentFormatter("Where to send transcripts. Leave empty when disabled.")), + new fjs.PropertyFormatter("mode",new fjs.SingleCommentFormatter("The type of transcript to use. Choices: html, text")), ]), new fjs.ObjectFormatter("embedSettings",true,[ - new fjs.PropertyFormatter("customColor"), + new fjs.MultiCommentFormatter("Customise the embed which contains the generated transcript file or URL."), + new fjs.PropertyFormatter("customColor",new fjs.SingleCommentFormatter("Leave empty to use default color")), new fjs.PropertyFormatter("listAllParticipants"), new fjs.PropertyFormatter("includeTicketStats"), ]), new fjs.ObjectFormatter("textTranscriptStyle",true,[ - new fjs.PropertyFormatter("layout"), + new fjs.MultiCommentFormatter("Customise layout of the text transcripts."), + new fjs.PropertyFormatter("layout",new fjs.SingleCommentFormatter("Choices: simple, normal, detailed")), new fjs.PropertyFormatter("includeStats"), new fjs.PropertyFormatter("includeIds"), new fjs.PropertyFormatter("includeEmbeds"), new fjs.PropertyFormatter("includeFiles"), new fjs.PropertyFormatter("includeBotMessages"), new fjs.TextFormatter(""), - new fjs.PropertyFormatter("fileMode"), - new fjs.PropertyFormatter("customFileName"), + new fjs.PropertyFormatter("fileMode",new fjs.SingleCommentFormatter("How to name the transcript file? Choices: custom, channel-name, channel-id, user-name, user-id")), + new fjs.PropertyFormatter("customFileName",new fjs.SingleCommentFormatter("Custom filename without extension")), ]), new fjs.ObjectFormatter("htmlTranscriptStyle",true,[ + new fjs.MultiCommentFormatter("Customise layout of the HTML transcripts."), new fjs.ObjectFormatter("background",true,[ new fjs.PropertyFormatter("enableCustomBackground"), - new fjs.PropertyFormatter("backgroundColor"), - new fjs.PropertyFormatter("backgroundImage"), + new fjs.PropertyFormatter("backgroundColor",new fjs.SingleCommentFormatter("Leave empty to use Open Ticket color (#f8ba00)")), + new fjs.PropertyFormatter("backgroundImage",new fjs.SingleCommentFormatter("Image URL to fill entire background. Leave empty to disable")), ]), new fjs.ObjectFormatter("header",true,[ new fjs.PropertyFormatter("enableCustomHeader"), @@ -415,4 +583,4 @@ export const defaultTranscriptsFormatter = new fjs.ObjectFormatter(null,true,[ new fjs.PropertyFormatter("imageUrl"), ]), ]), -]) \ No newline at end of file +])) \ No newline at end of file diff --git a/src/data/framework/cooldownLoader.ts b/src/data/framework/cooldownLoader.ts index 2a431ac..e043b9f 100644 --- a/src/data/framework/cooldownLoader.ts +++ b/src/data/framework/cooldownLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllCooldowns = async () => { +export async function loadAllCooldowns(){ await opendiscord.options.loopAll((option) => { if (!(option instanceof api.ODTicketOption)) return loadTicketOptionCooldown(option) diff --git a/src/data/framework/databaseLoader.ts b/src/data/framework/databaseLoader.ts index 65baeef..1f01732 100644 --- a/src/data/framework/databaseLoader.ts +++ b/src/data/framework/databaseLoader.ts @@ -1,15 +1,16 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as fjs from "formatted-json-stringify" const devdatabaseFlag = opendiscord.flags.get("opendiscord:dev-database") const isDevdatabase = devdatabaseFlag ? devdatabaseFlag.value : false -export const loadAllDatabases = async () => { +export async function loadAllDatabases(){ opendiscord.databases.add(defaultGlobalDatabase) opendiscord.databases.add(defaultStatsDatabase) opendiscord.databases.add(defaultTicketsDatabase) opendiscord.databases.add(defaultUsersDatabase) opendiscord.databases.add(defaultOptionsDatabase) + opendiscord.databases.add(defaultMessageStatesDatabase) } const defaultInlineFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectFormatter(null,false,[ @@ -46,8 +47,9 @@ const defaultOptionFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectFo ]) ])) -export const defaultGlobalDatabase = new api.ODFormattedJsonDatabase("opendiscord:global","global.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") -export const defaultStatsDatabase = new api.ODFormattedJsonDatabase("opendiscord:stats","stats.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") -export const defaultTicketsDatabase = new api.ODFormattedJsonDatabase("opendiscord:tickets","tickets.json",defaultTicketFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") -export const defaultUsersDatabase = new api.ODFormattedJsonDatabase("opendiscord:users","users.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") -export const defaultOptionsDatabase = new api.ODFormattedJsonDatabase("opendiscord:options","options.json",defaultOptionFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") \ No newline at end of file +export const defaultGlobalDatabase = new api.ODGlobalDatabase("opendiscord:global","global.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultStatsDatabase = new api.ODStatsDatabase("opendiscord:stats","stats.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultTicketsDatabase = new api.ODTicketsDatabase("opendiscord:tickets","tickets.json",defaultTicketFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultUsersDatabase = new api.ODUsersDatabase("opendiscord:users","users.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultOptionsDatabase = new api.ODOptionsDatabase("opendiscord:options","options.json",defaultOptionFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultMessageStatesDatabase = new api.ODMessageStatesDatabase("opendiscord:message-states","states.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") \ No newline at end of file diff --git a/src/data/framework/eventLoader.ts b/src/data/framework/eventLoader.ts index cd195c5..fbe627c 100644 --- a/src/data/framework/eventLoader.ts +++ b/src/data/framework/eventLoader.ts @@ -1,7 +1,7 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" export const loadAllEvents = () => { - const eventList: (keyof api.ODEventIds_Default)[] = [ + const eventList: (keyof api.ODNoGeneric)[] = [ //error handling "onErrorHandling", "afterErrorHandling", @@ -96,6 +96,12 @@ export const loadAllEvents = () => { "onTextCommandLoad", "afterTextCommandsLoaded", + //states + "onStateLoad", + "afterStatesLoaded", + "onStateInit", + "afterStatesInitiated", + //plugin loading before managers "onPluginBeforeManagerLoad", "afterPluginBeforeManagerLoaded", @@ -206,6 +212,16 @@ export const loadAllEvents = () => { "onModalBuilderLoad", "afterModalBuildersLoaded", + //components + "onSharedComponentLoad", + "afterSharedComponentsLoaded", + "onMessageComponentLoad", + "afterMessageComponentsLoaded", + "onModalComponentLoad", + "afterModalComponentsLoaded", + "onComponentModifierLoad", + "afterComponentModifiersLoaded", + //plugin loading before responders "onPluginBeforeResponderLoad", "afterPluginBeforeResponderLoaded", @@ -258,13 +274,13 @@ export const loadAllEvents = () => { "onHelpMenuComponentLoad", "afterHelpMenuComponentsLoaded", - //stats - "onStatScopeLoad", - "afterStatScopesLoaded", - "onStatLoad", - "afterStatsLoaded", - "onStatInit", - "afterStatsInitiated", + //statistics + "onStatisticScopeLoad", + "afterStatisticScopesLoaded", + "onStatisticLoad", + "afterStatisticsLoaded", + "onStatisticInit", + "afterStatisticsInitiated", //plugin loading before code "onPluginBeforeCodeLoad", diff --git a/src/data/framework/flagLoader.ts b/src/data/framework/flagLoader.ts index 9c80a99..31e7054 100644 --- a/src/data/framework/flagLoader.ts +++ b/src/data/framework/flagLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllFlags = async () => { +export async function loadAllFlags(){ opendiscord.flags.add(new api.ODFlag("opendiscord:no-migration","No Migration","Disable Open Ticket data migration on update!","--no-migration",["-nm"])) opendiscord.flags.add(new api.ODFlag("opendiscord:dev-config","Developer Config","Use the configs in /devconfig/ instead of /config/!","--dev-config",["-dc"])) opendiscord.flags.add(new api.ODFlag("opendiscord:dev-database","Developer Database","Use the databases in /devdatabase/ instead of /database/!","--dev-database",["-dd"])) diff --git a/src/data/framework/helpMenuLoader.ts b/src/data/framework/helpMenuLoader.ts index 92c96bb..ab29a72 100644 --- a/src/data/framework/helpMenuLoader.ts +++ b/src/data/framework/helpMenuLoader.ts @@ -1,18 +1,8 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" const lang = opendiscord.languages -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW COMMANDS? - * - Register the command in loadAllSlashCommands() & loadAllTextCommands() in (./src/data/framework/commandLoader.ts) - * - Add autocomplete for the command in OD(Slash/Text)CommandManagerIds_Default in (./src/core/api/defaults/client.ts) - * - Add the command to the help menu in (./src/data/framework/helpMenuLoader.ts) - * - If required, new config variables should be added (incl. logs, dm-logs & permissions). - * - Update the Open Ticket Documentation. - * - If the command contains complex logic or can be executed from a button/dropdown, it should be placed inside an `ODAction`. - * - Check all files, test the bot carefully & try a lot of different scenario's with different settings. - */ - -export const loadAllHelpMenuCategories = async () => { +export async function loadAllHelpMenuCategories(){ const helpmenu = opendiscord.helpmenu helpmenu.add(new api.ODHelpMenuCategory("opendiscord:general",5,utilities.emojiTitle("๐Ÿ“Ž",lang.getTranslation("helpMenu.categories.general")))) @@ -24,17 +14,17 @@ export const loadAllHelpMenuCategories = async () => { helpmenu.add(new api.ODHelpMenuCategory("opendiscord:extra",0,utilities.emojiTitle("โœจ",lang.getTranslation("helpMenu.categories.extra")))) } -export const loadAllHelpMenuComponents = async () => { +export async function loadAllHelpMenuComponents(){ const helpmenu = opendiscord.helpmenu const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return const prefix = generalConfig.data.prefix - const enableDeleteWithoutTranscript = generalConfig.data.system.enableDeleteWithoutTranscript + const enableDeleteWithoutTranscript = generalConfig.data.ticketSystem.enableDeleteWithoutTranscript const allowedCommands: string[] = [] - for (const key in generalConfig.data.system.permissions){ - if (generalConfig.data.system.permissions[key] != "none") allowedCommands.push(key) + for (const key in generalConfig.data.permissions){ + if (generalConfig.data.permissions[key] != "none") allowedCommands.push(key) } const general = helpmenu.get("opendiscord:general") diff --git a/src/data/framework/languageLoader.ts b/src/data/framework/languageLoader.ts index b09a10d..3a91ac8 100644 --- a/src/data/framework/languageLoader.ts +++ b/src/data/framework/languageLoader.ts @@ -1,131 +1,44 @@ -import { opendiscord, api, utilities } from "../../index"; +import {opendiscord, api, utilities} from "../../index.js" -/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW LANGUAGES? - * - Add the file to (./languages/) and make sure the metadata is valid. - * - Register the language in loadAllLanguages() in (./src/data/framework/languageLoader.ts). - * - Add autocomplete for the language in ODLanguageManagerIds_Default in (./src/core/api/defaults/language.ts). - * - Update the language list in the README.md translator list. - * - Update the 2 language counters in the README.md features list. - * - Update the Open Ticket Documentation. - */ - -export const loadAllLanguages = async () => { - //register languages - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:custom", "custom.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:english", "english.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:dutch", "dutch.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:portuguese", "portuguese.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:czech", "czech.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:german", "german.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:catalan", "catalan.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:hungarian", "hungarian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:spanish", "spanish.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:romanian", "romanian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:ukrainian", "ukrainian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:indonesian", "indonesian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:italian", "italian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:estonian", "estonian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:finnish", "finnish.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:danish", "danish.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:thai", "thai.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:turkish", "turkish.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:french", "french.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:arabic", "arabic.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:hindi", "hindi.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:lithuanian", "lithuanian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:polish", "polish.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:latvian", "latvian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:norwegian", "norwegian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:russian", "russian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:swedish", "swedish.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:vietnamese", "vietnamese.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:persian", "persian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:bengali", "bengali.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:greek", "greek.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:japanese", "japanese.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:khmer", "khmer.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:korean", "korean.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:kurdish", "kurdish.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage( - "opendiscord:simplified-chinese", - "simplified-chinese.json", - ), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:slovenian", "slovenian.json"), - ); - opendiscord.languages.add( - new api.ODJsonLanguage("opendiscord:tamil", "tamil.json"), - ); -}; +export async function loadAllLanguages(){ + //register languages + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:custom","custom.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:english","english.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:dutch","dutch.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:portuguese","portuguese.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:czech","czech.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:german","german.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:catalan","catalan.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:hungarian","hungarian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:spanish","spanish.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:romanian","romanian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:ukrainian","ukrainian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:indonesian","indonesian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:italian","italian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:estonian","estonian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:finnish","finnish.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:danish","danish.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:thai","thai.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:turkish","turkish.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:french","french.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:arabic","arabic.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:hindi","hindi.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:lithuanian","lithuanian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:polish","polish.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:latvian","latvian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:norwegian","norwegian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:russian","russian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:swedish","swedish.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:vietnamese","vietnamese.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:persian","persian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:bengali","bengali.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:greek","greek.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:japanese","japanese.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:korean","korean.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:kurdish","kurdish.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:simplified-chinese","simplified-chinese.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:traditional-chinese","traditional-chinese.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:slovenian","slovenian.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:tamil","tamil.json")) + opendiscord.languages.add(new api.ODJsonLanguage("opendiscord:khmer","khmer.json")) +} diff --git a/src/data/framework/liveStatusLoader.ts b/src/data/framework/liveStatusLoader.ts index 647f63e..26f95ee 100644 --- a/src/data/framework/liveStatusLoader.ts +++ b/src/data/framework/liveStatusLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllLiveStatusSources = async () => { +export async function loadAllLiveStatusSources(){ //DEFAULT DJDJ DEV - opendiscord.livestatus.add(new api.ODLiveStatusUrlSource("opendiscord:default-djdj-dev","https://raw.githubusercontent.com/open-discord-bots/open-ticket/refs/heads/dev/src/livestatus.json")) + opendiscord.livestatus.add(new api.ODLiveStatusUrlSource(opendiscord,"opendiscord:default-djdj-dev","https://raw.githubusercontent.com/open-discord-bots/open-ticket/refs/heads/dev/src/livestatus.json")) } \ No newline at end of file diff --git a/src/data/framework/permissionLoader.ts b/src/data/framework/permissionLoader.ts index 7601113..b091ca1 100644 --- a/src/data/framework/permissionLoader.ts +++ b/src/data/framework/permissionLoader.ts @@ -1,7 +1,7 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" -export const loadAllPermissions = async () => { +export async function loadAllPermissions(){ const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return const mainServer = opendiscord.client.mainServer diff --git a/src/data/framework/postLoader.ts b/src/data/framework/postLoader.ts index 4098ee1..06cfce4 100644 --- a/src/data/framework/postLoader.ts +++ b/src/data/framework/postLoader.ts @@ -1,13 +1,13 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllPosts = async () => { +export async function loadAllPosts(){ const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") if (!transcriptConfig) return //LOGS CHANNEL - if (generalConfig.data.system.logs.enabled) opendiscord.posts.add(new api.ODPost("opendiscord:logs",generalConfig.data.system.logs.channel)) + if (generalConfig.data.logs.enabled) opendiscord.posts.add(new api.ODPost("opendiscord:logs",generalConfig.data.logs.channel)) //TRANSCRIPTS CHANNEL if (transcriptConfig.data.general.enabled && transcriptConfig.data.general.enableChannel) opendiscord.posts.add(new api.ODPost("opendiscord:transcripts",transcriptConfig.data.general.channel)) diff --git a/src/data/framework/progressBarLoader.ts b/src/data/framework/progressBarLoader.ts index 63eacb1..538ee42 100644 --- a/src/data/framework/progressBarLoader.ts +++ b/src/data/framework/progressBarLoader.ts @@ -1,7 +1,7 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllProgressBarRenderers = async () => { - const defaultSettings: api.ODProgressBarRenderer_DefaultSettings = { +export async function loadAllProgressBarRenderers(){ + const defaultSettings: api.ODDefaultProgressBarRendererSettings = { borderColor:"gray", filledBarColor:"openticket", emptyBarColor:"gray", @@ -23,37 +23,37 @@ export const loadAllProgressBarRenderers = async () => { } //VALUE RENDERER - const valueRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const valueRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} valueRendererSettings.labelType = "value" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:value-renderer",valueRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:value-renderer",valueRendererSettings)) //FRACTION RENDERER - const fractionRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const fractionRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} fractionRendererSettings.labelType = "fraction" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:fraction-renderer",fractionRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:fraction-renderer",fractionRendererSettings)) //PERCENTAGE RENDERER - const percentageRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const percentageRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} percentageRendererSettings.labelType = "percentage" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:percentage-renderer",percentageRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:percentage-renderer",percentageRendererSettings)) //TIME MS RENDERER - const timeMsRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const timeMsRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} timeMsRendererSettings.labelType = "time-ms" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:time-ms-renderer",timeMsRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:time-ms-renderer",timeMsRendererSettings)) //TIME SEC RENDERER - const timeSecRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const timeSecRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} timeSecRendererSettings.labelType = "time-sec" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:time-sec-renderer",timeSecRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:time-sec-renderer",timeSecRendererSettings)) //TIME MIN RENDERER - const timeMinRendererSettings: api.ODProgressBarRenderer_DefaultSettings = {...defaultSettings} + const timeMinRendererSettings: api.ODDefaultProgressBarRendererSettings = {...defaultSettings} timeMinRendererSettings.labelType = "time-min" - opendiscord.progressbars.renderers.add(new api.ODProgressBarRenderer_Default("opendiscord:time-min-renderer",timeMinRendererSettings)) + opendiscord.progressbars.renderers.add(new api.ODDefaultProgressBarRenderer("opendiscord:time-min-renderer",timeMinRendererSettings)) } -export const loadAllProgressBars = async () => { +export async function loadAllProgressBars(){ const fractRenderer = opendiscord.progressbars.renderers.get("opendiscord:fraction-renderer") //SLASH COMMAND REMOVE (doesn't have correct amount yet) diff --git a/src/data/framework/startScreenLoader.ts b/src/data/framework/startScreenLoader.ts index 328e01b..5220d17 100644 --- a/src/data/framework/startScreenLoader.ts +++ b/src/data/framework/startScreenLoader.ts @@ -1,7 +1,7 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import ansis from "ansis" -export const loadAllStartScreenComponents = async () => { +export async function loadAllStartScreenComponents(){ //LOGO opendiscord.startscreen.add(new api.ODStartScreenLogoComponent("opendiscord:logo",1000,[ " โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— ", diff --git a/src/data/framework/stateLoader.ts b/src/data/framework/stateLoader.ts new file mode 100644 index 0000000..7654feb --- /dev/null +++ b/src/data/framework/stateLoader.ts @@ -0,0 +1,8 @@ +import {opendiscord, api, utilities} from "../../index.js" + +export async function loadAllStates(){ + const stateDatabase = opendiscord.databases.get("opendiscord:message-states") + + opendiscord.states.add(new api.ODInteractiveMessageState("opendiscord:interactive-message",opendiscord.client,stateDatabase)) + opendiscord.states.add(new api.ODClearMessageState("opendiscord:clear-message",opendiscord.client,stateDatabase)) +} \ No newline at end of file diff --git a/src/data/framework/statLoader.ts b/src/data/framework/statisticLoader.ts similarity index 56% rename from src/data/framework/statLoader.ts rename to src/data/framework/statisticLoader.ts index 9392c5f..97d785a 100644 --- a/src/data/framework/statLoader.ts +++ b/src/data/framework/statisticLoader.ts @@ -1,41 +1,41 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" -const stats = opendiscord.stats +const stats = opendiscord.statistics const lang = opendiscord.languages -export const loadAllStatScopes = async () => { - stats.add(new api.ODStatGlobalScope("opendiscord:global",utilities.emojiTitle("๐Ÿ“Š",lang.getTranslation("stats.scopes.global")))) - stats.add(new api.ODStatGlobalScope("opendiscord:system",utilities.emojiTitle("โš™๏ธ",lang.getTranslation("stats.scopes.system")))) - stats.add(new api.ODStatScope("opendiscord:user",utilities.emojiTitle("๐Ÿ“Š",lang.getTranslation("stats.scopes.user")))) - stats.add(new api.ODStatScope("opendiscord:ticket",utilities.emojiTitle("๐Ÿ“Š",lang.getTranslation("stats.scopes.ticket")))) - stats.add(new api.ODStatScope("opendiscord:participants",utilities.emojiTitle("๐Ÿ‘ฅ",lang.getTranslation("stats.scopes.participants")))) - stats.add(new api.ODStatScope("opendiscord:messages",utilities.emojiTitle("๐Ÿ’ฌ",lang.getTranslation("stats.scopes.messages")))) +export async function loadAllStatisticScopes(){ + stats.add(new api.ODStatisticGlobalScope("opendiscord:global",utilities.emojiTitle("๐Ÿ“Š",lang.getTranslation("stats.scopes.global")))) + stats.add(new api.ODStatisticGlobalScope("opendiscord:system",utilities.emojiTitle("โš™๏ธ",lang.getTranslation("stats.scopes.system")))) + stats.add(new api.ODStatisticScope("opendiscord:user",utilities.emojiTitle("๐Ÿ“Š",lang.getTranslation("stats.scopes.user")))) + stats.add(new api.ODStatisticScope("opendiscord:ticket",utilities.emojiTitle("๐Ÿ“Š",lang.getTranslation("stats.scopes.ticket")))) + stats.add(new api.ODStatisticScope("opendiscord:participants",utilities.emojiTitle("๐Ÿ‘ฅ",lang.getTranslation("stats.scopes.participants")))) + stats.add(new api.ODStatisticScope("opendiscord:messages",utilities.emojiTitle("๐Ÿ’ฌ",lang.getTranslation("stats.scopes.messages")))) } -export const loadAllStats = async () => { +export async function loadAllStatistics(){ const generalConfig = opendiscord.configs.get("opendiscord:general") if (!generalConfig) return const global = stats.get("opendiscord:global") if (global){ - global.add(new api.ODBasicStat("opendiscord:tickets-created",13,lang.getTranslation("stats.properties.ticketsCreated"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-closed",12,lang.getTranslation("stats.properties.ticketsClosed"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-deleted",11,lang.getTranslation("stats.properties.ticketsDeleted"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-reopened",10,lang.getTranslation("stats.properties.ticketsReopened"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-autoclosed",9,lang.getTranslation("stats.properties.ticketsAutoclosed"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-autodeleted",8,lang.getTranslation("stats.properties.ticketsAutodeleted"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-claimed",7,lang.getTranslation("stats.properties.ticketsClaimed"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-pinned",6,lang.getTranslation("stats.properties.ticketsPinned"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-moved",5,lang.getTranslation("stats.properties.ticketsMoved"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-transferred",4,lang.getTranslation("stats.properties.ticketsTransferred"),0)) - global.add(new api.ODBasicStat("opendiscord:users-blacklisted",3,lang.getTranslation("stats.properties.usersBlacklisted"),0)) - global.add(new api.ODBasicStat("opendiscord:transcripts-created",2,lang.getTranslation("stats.properties.transcriptsCreated"),0)) - global.add(new api.ODDynamicStat("opendiscord:ticket-volume",1,() => { + global.add(new api.ODBaseStatistic("opendiscord:tickets-created",13,lang.getTranslation("stats.properties.ticketsCreated"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-closed",12,lang.getTranslation("stats.properties.ticketsClosed"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-deleted",11,lang.getTranslation("stats.properties.ticketsDeleted"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-reopened",10,lang.getTranslation("stats.properties.ticketsReopened"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-autoclosed",9,lang.getTranslation("stats.properties.ticketsAutoclosed"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-autodeleted",8,lang.getTranslation("stats.properties.ticketsAutodeleted"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-claimed",7,lang.getTranslation("stats.properties.ticketsClaimed"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-pinned",6,lang.getTranslation("stats.properties.ticketsPinned"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-moved",5,lang.getTranslation("stats.properties.ticketsMoved"),0)) + global.add(new api.ODBaseStatistic("opendiscord:tickets-transferred",4,lang.getTranslation("stats.properties.ticketsTransferred"),0)) + global.add(new api.ODBaseStatistic("opendiscord:users-blacklisted",3,lang.getTranslation("stats.properties.usersBlacklisted"),0)) + global.add(new api.ODBaseStatistic("opendiscord:transcripts-created",2,lang.getTranslation("stats.properties.transcriptsCreated"),0)) + global.add(new api.ODDynamicStatistic("opendiscord:ticket-volume",1,() => { return lang.getTranslation("stats.properties.ticketVolume")+": `"+opendiscord.tickets.getLength()+"`" })) - global.add(new api.ODDynamicStat("opendiscord:average-tickets",0,async () => { - const userTicketsCreated = await opendiscord.stats.get("opendiscord:user").getAllStats("opendiscord:tickets-created") + global.add(new api.ODDynamicStatistic("opendiscord:average-tickets",0,async () => { + const userTicketsCreated = await opendiscord.statistics.get("opendiscord:user").getAllStats("opendiscord:tickets-created") const average = userTicketsCreated.map((s) => s.value as number).filter((t) => t > 0).reduce((prev,curr) => prev+curr,0)/userTicketsCreated.length const roundedAverage = Math.round(average*1000)/1000 return lang.getTranslation("stats.properties.averageTickets")+": `"+roundedAverage+"`" @@ -44,23 +44,23 @@ export const loadAllStats = async () => { const system = stats.get("opendiscord:system") if (system){ - system.add(new api.ODDynamicStat("opendiscord:startup-date",2,() => { + system.add(new api.ODDynamicStatistic("opendiscord:startup-date",2,() => { return lang.getTranslation("params.uppercase.startupDate")+": "+discord.time(opendiscord.processStartupDate,"f") })) - system.add(new api.ODDynamicStat("opendiscord:system-uptime",1,() => { + system.add(new api.ODDynamicStatistic("opendiscord:system-uptime",1,() => { return lang.getTranslation("params.uppercase.uptime")+": "+discord.time(opendiscord.processStartupDate,"R") })) - system.add(new api.ODDynamicStat("opendiscord:version",0,() => { + system.add(new api.ODDynamicStatistic("opendiscord:version",0,() => { return lang.getTranslation("params.uppercase.version")+": `"+opendiscord.versions.get("opendiscord:version").toString()+"`" })) } const user = stats.get("opendiscord:user") if (user){ - user.add(new api.ODDynamicStat("opendiscord:name",11,async (scopeId,guild,channel,user) => { + user.add(new api.ODDynamicStatistic("opendiscord:name",11,async (scopeId,guild,channel,user) => { return lang.getTranslation("params.uppercase.name")+": "+discord.userMention(scopeId) })) - user.add(new api.ODDynamicStat("opendiscord:role",10,async (scopeId,guild,channel,user) => { + user.add(new api.ODDynamicStatistic("opendiscord:role",10,async (scopeId,guild,channel,user) => { const scopeMember = await opendiscord.client.fetchGuildMember(guild,scopeId) if (!scopeMember) return "" @@ -72,27 +72,27 @@ export const loadAllStats = async () => { if (permissions.type == "support") return lang.getTranslation("params.uppercase.role")+": ๐Ÿ’ฌ `"+lang.getTranslation("stats.roles.support")+"`" else return lang.getTranslation("params.uppercase.role")+": ๐Ÿ‘ค `"+lang.getTranslation("stats.roles.member")+"`" })) - user.add(new api.ODBasicStat("opendiscord:tickets-created",10,lang.getTranslation("stats.properties.ticketsCreated"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-closed",9,lang.getTranslation("stats.properties.ticketsClosed"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-deleted",8,lang.getTranslation("stats.properties.ticketsDeleted"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-reopened",7,lang.getTranslation("stats.properties.ticketsReopened"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-claimed",6,lang.getTranslation("stats.properties.ticketsClaimed"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-pinned",5,lang.getTranslation("stats.properties.ticketsPinned"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-moved",4,lang.getTranslation("stats.properties.ticketsMoved"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-transferred",3,lang.getTranslation("stats.properties.ticketsTransferred"),0)) - user.add(new api.ODBasicStat("opendiscord:users-blacklisted",2,lang.getTranslation("stats.properties.usersBlacklisted"),0)) - user.add(new api.ODBasicStat("opendiscord:transcripts-created",1,lang.getTranslation("stats.properties.transcriptsCreated"),0)) - user.add(new api.ODDynamicStat("opendiscord:current-tickets",0,async (scopeId,guild,channel,user) => { + user.add(new api.ODBaseStatistic("opendiscord:tickets-created",10,lang.getTranslation("stats.properties.ticketsCreated"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-closed",9,lang.getTranslation("stats.properties.ticketsClosed"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-deleted",8,lang.getTranslation("stats.properties.ticketsDeleted"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-reopened",7,lang.getTranslation("stats.properties.ticketsReopened"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-claimed",6,lang.getTranslation("stats.properties.ticketsClaimed"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-pinned",5,lang.getTranslation("stats.properties.ticketsPinned"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-moved",4,lang.getTranslation("stats.properties.ticketsMoved"),0)) + user.add(new api.ODBaseStatistic("opendiscord:tickets-transferred",3,lang.getTranslation("stats.properties.ticketsTransferred"),0)) + user.add(new api.ODBaseStatistic("opendiscord:users-blacklisted",2,lang.getTranslation("stats.properties.usersBlacklisted"),0)) + user.add(new api.ODBaseStatistic("opendiscord:transcripts-created",1,lang.getTranslation("stats.properties.transcriptsCreated"),0)) + user.add(new api.ODDynamicStatistic("opendiscord:current-tickets",0,async (scopeId,guild,channel,user) => { return lang.getTranslation("stats.properties.currentTickets")+": `"+opendiscord.tickets.getFiltered((t) => t.get("opendiscord:opened-by").value === scopeId).length+"`" })) } const ticket = stats.get("opendiscord:ticket") if (ticket){ - ticket.add(new api.ODDynamicStat("opendiscord:name",5,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:name",5,async (scopeId,guild,channel,user) => { return lang.getTranslation("params.uppercase.ticket")+": "+discord.channelMention(scopeId) })) - ticket.add(new api.ODDynamicStat("opendiscord:status",4,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:status",4,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" @@ -100,35 +100,35 @@ export const loadAllStats = async () => { return closed ? lang.getTranslation("params.uppercase.status")+": ๐Ÿ”’ `"+lang.getTranslation("params.uppercase.closed")+"`" : lang.getTranslation("params.uppercase.status")+": ๐Ÿ”“ `"+lang.getTranslation("params.uppercase.open")+"`" })) - ticket.add(new api.ODDynamicStat("opendiscord:claimed",3,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:claimed",3,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" const claimed = ticket.exists("opendiscord:claimed") ? ticket.get("opendiscord:claimed").value : false return claimed ? lang.getTranslation("params.uppercase.claimed")+": ๐ŸŸข `"+lang.getTranslation("params.uppercase.yes")+"`" : lang.getTranslation("params.uppercase.claimed")+": ๐Ÿ”ด `"+lang.getTranslation("params.uppercase.no")+"`" })) - ticket.add(new api.ODDynamicStat("opendiscord:pinned",2,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:pinned",2,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" const pinned = ticket.exists("opendiscord:pinned") ? ticket.get("opendiscord:pinned").value : false return pinned ? lang.getTranslation("params.uppercase.pinned")+": ๐ŸŸข `"+lang.getTranslation("params.uppercase.yes")+"`" : lang.getTranslation("params.uppercase.pinned")+": ๐Ÿ”ด `"+lang.getTranslation("params.uppercase.no")+"`" })) - ticket.add(new api.ODDynamicStat("opendiscord:creation-date",1,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:creation-date",1,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" const rawDate = ticket.get("opendiscord:opened-on").value ?? new Date().getTime() return lang.getTranslation("params.uppercase.creationDate")+": "+discord.time(new Date(rawDate),"f") })) - ticket.add(new api.ODDynamicStat("opendiscord:creator",0,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:creator",0,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" const creator = ticket.get("opendiscord:opened-by").value return lang.getTranslation("params.uppercase.creator")+": "+ (creator ? discord.userMention(creator) : "`unknown`") })) - ticket.add(new api.ODDynamicStat("opendiscord:ticket-age",-1,async (scopeId,guild,channel,user) => { + ticket.add(new api.ODDynamicStatistic("opendiscord:ticket-age",-1,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" @@ -142,7 +142,7 @@ export const loadAllStats = async () => { const participants = stats.get("opendiscord:participants") if (participants){ - participants.add(new api.ODDynamicStat("opendiscord:participants",0,async (scopeId,guild,channel,user) => { + participants.add(new api.ODDynamicStatistic("opendiscord:participants",0,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" @@ -157,7 +157,7 @@ export const loadAllStats = async () => { const messages = stats.get("opendiscord:messages") if (messages){ - messages.add(new api.ODDynamicStat("opendiscord:count",0,async (scopeId,guild,channel,user) => { + messages.add(new api.ODDynamicStatistic("opendiscord:count",0,async (scopeId,guild,channel,user) => { const ticket = opendiscord.tickets.get(scopeId) if (!ticket) return "" diff --git a/src/data/openticket/blacklistLoader.ts b/src/data/openticket/blacklistLoader.ts index 6e8ed6a..dcd95d2 100644 --- a/src/data/openticket/blacklistLoader.ts +++ b/src/data/openticket/blacklistLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllBlacklistedUsers = async () => { +export async function loadAllBlacklistedUsers(){ const userDatabase = opendiscord.databases.get("opendiscord:users") if (!userDatabase) return diff --git a/src/data/openticket/optionLoader.ts b/src/data/openticket/optionLoader.ts index 38dd5de..de8639d 100644 --- a/src/data/openticket/optionLoader.ts +++ b/src/data/openticket/optionLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllOptions = async () => { +export async function loadAllOptions(){ const optionConfig = opendiscord.configs.get("opendiscord:options") if (!optionConfig) return @@ -54,7 +54,7 @@ export const loadAllOptions = async () => { }) } -export const loadTicketOption = (option:api.ODJsonConfig_DefaultOptionTicketType): api.ODTicketOption => { +export const loadTicketOption = (option:api.ODOptionsJsonConfig_TicketOption): api.ODTicketOption => { return new api.ODTicketOption(option.id,[ new api.ODOptionData("opendiscord:name",option.name), new api.ODOptionData("opendiscord:description",option.description), @@ -71,9 +71,6 @@ export const loadTicketOption = (option:api.ODJsonConfig_DefaultOptionTicketType new api.ODOptionData("opendiscord:channel-prefix",option.channel.prefix), new api.ODOptionData("opendiscord:channel-suffix",option.channel.suffix), new api.ODOptionData("opendiscord:channel-category",option.channel.category), - new api.ODOptionData("opendiscord:channel-category-closed",option.channel.closedCategory), - new api.ODOptionData("opendiscord:channel-category-backup",option.channel.backupCategory), - new api.ODOptionData("opendiscord:channel-categories-claimed",option.channel.claimedCategory), new api.ODOptionData("opendiscord:channel-topic",option.channel.topic), new api.ODOptionData("opendiscord:dm-message-enabled",option.dmMessage.enabled), @@ -107,7 +104,7 @@ export const loadTicketOption = (option:api.ODJsonConfig_DefaultOptionTicketType ]) } -export const loadWebsiteOption = (opt:api.ODJsonConfig_DefaultOptionWebsiteType): api.ODWebsiteOption => { +export const loadWebsiteOption = (opt:api.ODOptionsJsonConfig_WebsiteOption): api.ODWebsiteOption => { return new api.ODWebsiteOption(opt.id,[ new api.ODOptionData("opendiscord:name",opt.name), new api.ODOptionData("opendiscord:description",opt.description), @@ -119,7 +116,7 @@ export const loadWebsiteOption = (opt:api.ODJsonConfig_DefaultOptionWebsiteType) ]) } -export const loadRoleOption = (opt:api.ODJsonConfig_DefaultOptionRoleType): api.ODRoleOption => { +export const loadRoleOption = (opt:api.ODOptionsJsonConfig_RoleOption): api.ODRoleOption => { return new api.ODRoleOption(opt.id,[ new api.ODOptionData("opendiscord:name",opt.name), new api.ODOptionData("opendiscord:description",opt.description), diff --git a/src/data/openticket/panelLoader.ts b/src/data/openticket/panelLoader.ts index 7d902cf..df9b150 100644 --- a/src/data/openticket/panelLoader.ts +++ b/src/data/openticket/panelLoader.ts @@ -1,9 +1,9 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" const lang = opendiscord.languages -export const loadAllPanels = async () => { +export async function loadAllPanels(){ const panelConfig = opendiscord.configs.get("opendiscord:panels") if (!panelConfig) return @@ -23,7 +23,7 @@ export const loadAllPanels = async () => { }) } -export const loadPanel = (panel:api.ODJsonConfig_DefaultPanelType) => { +export const loadPanel = (panel:api.ODPanelsJsonConfig_Panel) => { return new api.ODPanel(panel.id,[ new api.ODPanelData("opendiscord:name",panel.name), new api.ODPanelData("opendiscord:options",panel.options), @@ -93,7 +93,7 @@ export function describePanelOptions(mode:"fields"|"text", panel:api.ODPanel): { } if (layout == "detailed"){ const optionAdmins = [...opt.get("opendiscord:admins").value] - if (generalConfig.data.system.showGlobalAdminsInPanelRoles){ + if (generalConfig.data.ticketSystem.showGlobalAdminsInPanelRoles){ for (const admin of generalConfig.data.globalAdmins){ if (!optionAdmins.includes(admin)) optionAdmins.push(admin) } @@ -147,7 +147,7 @@ export function describePanelOptions(mode:"fields"|"text", panel:api.ODPanel): { } if (layout == "detailed"){ const optionAdmins = [...opt.get("opendiscord:admins").value] - if (generalConfig.data.system.showGlobalAdminsInPanelRoles){ + if (generalConfig.data.ticketSystem.showGlobalAdminsInPanelRoles){ for (const admin of generalConfig.data.globalAdmins){ if (!optionAdmins.includes(admin)) optionAdmins.push(admin) } diff --git a/src/data/openticket/priorityLoader.ts b/src/data/openticket/priorityLoader.ts index c568fca..a61f04b 100644 --- a/src/data/openticket/priorityLoader.ts +++ b/src/data/openticket/priorityLoader.ts @@ -1,8 +1,8 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" const lang = opendiscord.languages -export const loadAllPriorityLevels = async () => { +export async function loadAllPriorityLevels(){ opendiscord.priorities.add(new api.ODPriorityLevel("opendiscord:urgent",5,"urgent",lang.getTranslation("priorities.urgent"),"๐Ÿ”ด","๐Ÿ”ด")) opendiscord.priorities.add(new api.ODPriorityLevel("opendiscord:very-high",4,"very-high",lang.getTranslation("priorities.veryHigh"),"๐ŸŸ ","๐ŸŸ ")) opendiscord.priorities.add(new api.ODPriorityLevel("opendiscord:high",3,"high",lang.getTranslation("priorities.high"),"๐ŸŸก","๐ŸŸก")) diff --git a/src/data/openticket/questionLoader.ts b/src/data/openticket/questionLoader.ts index 5a2f0be..0d5d17a 100644 --- a/src/data/openticket/questionLoader.ts +++ b/src/data/openticket/questionLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllQuestions = async () => { +export async function loadAllQuestions(){ const questionConfig = opendiscord.configs.get("opendiscord:questions") if (!questionConfig) return @@ -28,7 +28,7 @@ export const loadAllQuestions = async () => { }) } -export const loadShortQuestion = (option:api.ODJsonConfig_DefaultShortQuestionType) => { +export const loadShortQuestion = (option:api.ODQuestionsJsonConfig_ShortQuestion) => { return new api.ODShortQuestion(option.id,[ new api.ODQuestionData("opendiscord:name",option.name), new api.ODQuestionData("opendiscord:required",option.required), @@ -40,7 +40,7 @@ export const loadShortQuestion = (option:api.ODJsonConfig_DefaultShortQuestionTy ]) } -export const loadParagraphQuestion = (option:api.ODJsonConfig_DefaultParagraphQuestionType) => { +export const loadParagraphQuestion = (option:api.ODQuestionsJsonConfig_ParagraphQuestion) => { return new api.ODParagraphQuestion(option.id,[ new api.ODQuestionData("opendiscord:name",option.name), new api.ODQuestionData("opendiscord:required",option.required), diff --git a/src/data/openticket/roleLoader.ts b/src/data/openticket/roleLoader.ts index 1db2c7a..5205e32 100644 --- a/src/data/openticket/roleLoader.ts +++ b/src/data/openticket/roleLoader.ts @@ -1,6 +1,6 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" -export const loadAllRoles = async () => { +export async function loadAllRoles(){ await opendiscord.options.loopAll((opt) => { if (opt instanceof api.ODRoleOption){ opendiscord.roles.add(loadRole(opt)) diff --git a/src/data/openticket/ticketLoader.ts b/src/data/openticket/ticketLoader.ts index 86b88ca..0d1dfa6 100644 --- a/src/data/openticket/ticketLoader.ts +++ b/src/data/openticket/ticketLoader.ts @@ -1,8 +1,8 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" const optionDatabase = opendiscord.databases.get("opendiscord:options") -export const loadAllTickets = async () => { +export async function loadAllTickets(){ const ticketDatabase = opendiscord.databases.get("opendiscord:tickets") if (!ticketDatabase) return @@ -11,7 +11,7 @@ export const loadAllTickets = async () => { for (const ticket of tickets){ try { opendiscord.tickets.add(await loadTicket(ticket.value)) - }catch (err){ + }catch (err:any){ process.emit("uncaughtException",err) process.emit("uncaughtException",new api.ODSystemError("Failed to load ticket from database! => id: "+ticket.key+"\n ===> "+err)) } diff --git a/src/data/openticket/transcriptLoader.ts b/src/data/openticket/transcriptLoader.ts index 6d6afd2..adc3e22 100644 --- a/src/data/openticket/transcriptLoader.ts +++ b/src/data/openticket/transcriptLoader.ts @@ -1,4 +1,4 @@ -import {opendiscord, api, utilities} from "../../index" +import {opendiscord, api, utilities} from "../../index.js" import * as discord from "discord.js" const collector = opendiscord.transcripts.collector @@ -46,10 +46,10 @@ function transcriptAuth(_A:{salt:number,secret:string}){ const _B = Buffer.from(_A.secret,"hex");const _C = Math["floor"](new Date().getTime()/(30*1000)).toString();const _D = Buffer.from(_C);const _E = Buffer.alloc(_D["length"]);_D.forEach((v,i) => {_E[i] = v ^ _B[i % _B["length"]];});return btoa(JSON.stringify({salt:_A.salt,secret:_A.secret,token:_E.toString("hex")})) } -export const loadAllTranscriptCompilers = async () => { +export async function loadAllTranscriptCompilers(){ class ODHTTPHtmlPostRequest extends api.ODHTTPPostRequest { constructor(transcriptAuth:string,htmlFinal:api.ODTranscriptHtmlV2Data){ - super("https://"+htmlDomain+"/api/v2/upload?auth="+htmlVersion+"&token="+transcriptAuth,true,{ + super(opendiscord,"https://"+htmlDomain+"/api/v2/upload?auth="+htmlVersion+"&token="+transcriptAuth,true,{ body:JSON.stringify(htmlFinal), headers:{ "Content-Type":"application/json" @@ -178,7 +178,7 @@ export const loadAllTranscriptCompilers = async () => { //HTML COMPILER opendiscord.transcripts.add(new api.ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}|null>("opendiscord:html-compiler",async (ticket,channel,user) => { //INIT - const req = new api.ODHTTPGetRequest(atob("aHR0cHM6Ly90LmRqLWRqLmJlL2FwaS92Mi9pbml0"),false) + const req = new api.ODHTTPGetRequest(opendiscord,atob("aHR0cHM6Ly90LmRqLWRqLmJlL2FwaS92Mi9pbml0"),false) const res = await req.run() //PENDING MESSAGE (not required anymore) => await messages.getSafe("opendiscord:transcript-html-progress").build("channel",{guild:channel.guild,channel,user,ticket,compiler:opendiscord.transcripts.get("opendiscord:html-compiler"),remaining:16000}) if (res.status == 200 && res.body){ @@ -552,6 +552,6 @@ export const loadAllTranscriptCompilers = async () => { })) } -export const loadTranscriptHistory = async () => { +export async function loadTranscriptHistory(){ //UNIMPLEMENTED (made for html transcripts v3 update) } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 5a067b3..2c61a46 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,7 @@ INFORMATION: ============ - Open Ticket v4.1.3 - ยฉ DJdj Development + Open Ticket v4.2.0 - ยฉ DJdj Development support us: https://github.com/sponsors/DJj123dj discord: https://discord.dj-dj.be @@ -35,59 +35,49 @@ */ //initialize API & check npm libraries -import { api, opendiscord, utilities } from "./core/startup/init" -export { api, opendiscord, utilities } from "./core/startup/init" +import { loadDumpCommand, loadAllPlugins, loadErrorHandling } from "@open-discord-bots/framework" +import * as utilities from "@open-discord-bots/framework/utilities" +import * as api from "./core/api.js" import ansis from "ansis" +export * as utilities from "@open-discord-bots/framework/utilities" +export * as api from "./core/api.js" + +utilities.checkNodeVersion("openticket") + +utilities.moduleInstalled("@open-discord-bots/framework",true) +utilities.moduleInstalled("@discordjs/rest",true) +utilities.moduleInstalled("discord.js",true) +utilities.moduleInstalled("ansis",true) +utilities.moduleInstalled("formatted-json-stringify",true) +utilities.moduleInstalled("typescript",true) +utilities.moduleInstalled("terminal-kit",true) + +export const opendiscord: api.ODOpenTicketMain = new api.ODOpenTicketMain() +export * as openticketUtils from "./actions/utilities.js" + +utilities.initialStartupLogs(opendiscord,"openticket") + /**The main sequence of Open Ticket. Runs `async` */ const main = async () => { //load all events (await import("./data/framework/eventLoader.js")).loadAllEvents() //error handling system - process.on("uncaughtException",async (error,origin) => { - try{ - await opendiscord.events.get("onErrorHandling").emit([error,origin]) - if (opendiscord.defaults.getDefault("errorHandling")){ - //custom error messages for known errors - if (error.message.toLowerCase().includes("used disallowed intents")){ - //invalid intents - opendiscord.log("Open Ticket doesn't work without Privileged Gateway Intents enabled!","error") - opendiscord.log("Enable them in the discord developer portal!","info") - console.log("\n") - process.exit(1) - }else if (error.message.toLowerCase().includes("invalid discord bot token provided")){ - //invalid token - opendiscord.log("An invalid discord auth token was provided!","error") - opendiscord.log("Check the config if you have inserted the bot token correctly!","info") - console.log("\n") - process.exit(1) - }else{ - //unknown error - const errmsg = new api.ODError(error,origin) - opendiscord.log(errmsg) - if (opendiscord.defaults.getDefault("crashOnError")) process.exit(1) - await opendiscord.events.get("afterErrorHandling").emit([error,origin,errmsg]) - } - } - - }catch(err){ - console.log("[ERROR HANDLER ERROR]:",err) - } - }) + loadErrorHandling(opendiscord,"openticket") //handle data migration (PART 1) const lastVersion = await (await import("./core/startup/manageMigration.js")).loadVersionMigrationSystem() //load plugins - if (opendiscord.defaults.getDefault("pluginLoading")){ - await (await import("./core/startup/pluginLauncher.js")).loadAllPlugins() + if (opendiscord.sharedFuses.getFuse("pluginLoading")){ + await loadAllPlugins(opendiscord) } await opendiscord.events.get("afterPluginsLoaded").emit([opendiscord.plugins]) //load plugin classes opendiscord.log("Loading plugin classes...","system") - if (opendiscord.defaults.getDefault("pluginClassLoading")){ + if (opendiscord.sharedFuses.getFuse("pluginClassLoading")){ } await opendiscord.events.get("onPluginClassLoad").emit([opendiscord.plugins.classes,opendiscord.plugins]) @@ -95,7 +85,7 @@ const main = async () => { //load flags opendiscord.log("Loading flags...","system") - if (opendiscord.defaults.getDefault("flagLoading")){ + if (opendiscord.sharedFuses.getFuse("flagLoading")){ await (await import("./data/framework/flagLoader.js")).loadAllFlags() } await opendiscord.events.get("onFlagLoad").emit([opendiscord.flags]) @@ -103,20 +93,20 @@ const main = async () => { //initiate flags await opendiscord.events.get("onFlagInit").emit([opendiscord.flags]) - if (opendiscord.defaults.getDefault("flagInitiating")){ + if (opendiscord.sharedFuses.getFuse("flagInitiating")){ await opendiscord.flags.init() opendiscord.debugfile.writeText("\n[ENABLED FLAGS]:\n"+opendiscord.flags.getFiltered((flag) => (flag.value == true)).map((flag) => flag.id.value).join("\n")+"\n") await opendiscord.events.get("afterFlagsInitiated").emit([opendiscord.flags]) } //load debug - if (opendiscord.defaults.getDefault("debugLoading")){ + if (opendiscord.sharedFuses.getFuse("debugLoading")){ const debugFlag = opendiscord.flags.get("opendiscord:debug") opendiscord.debug.visible = (debugFlag) ? debugFlag.value : false } //load silent mode - if (opendiscord.defaults.getDefault("silentLoading")){ + if (opendiscord.sharedFuses.getFuse("silentLoading")){ const silentFlag = opendiscord.flags.get("opendiscord:silent") opendiscord.console.silent = (silentFlag) ? silentFlag.value : false if (opendiscord.console.silent){ @@ -128,14 +118,14 @@ const main = async () => { //load progress bar renderers opendiscord.log("Loading progress bars...","system") - if (opendiscord.defaults.getDefault("progressBarRendererLoading")){ + if (opendiscord.sharedFuses.getFuse("progressBarRendererLoading")){ await (await import("./data/framework/progressBarLoader.js")).loadAllProgressBarRenderers() } await opendiscord.events.get("onProgressBarRendererLoad").emit([opendiscord.progressbars.renderers]) await opendiscord.events.get("afterProgressBarRenderersLoaded").emit([opendiscord.progressbars.renderers]) //load progress bars - if (opendiscord.defaults.getDefault("progressBarLoading")){ + if (opendiscord.sharedFuses.getFuse("progressBarLoading")){ await (await import("./data/framework/progressBarLoader.js")).loadAllProgressBars() } await opendiscord.events.get("onProgressBarLoad").emit([opendiscord.progressbars]) @@ -143,7 +133,7 @@ const main = async () => { //load config opendiscord.log("Loading configs...","system") - if (opendiscord.defaults.getDefault("configLoading")){ + if (opendiscord.sharedFuses.getFuse("configLoading")){ await (await import("./data/framework/configLoader.js")).loadAllConfigs() } await opendiscord.events.get("onConfigLoad").emit([opendiscord.configs]) @@ -151,7 +141,7 @@ const main = async () => { //initiate config await opendiscord.events.get("onConfigInit").emit([opendiscord.configs]) - if (opendiscord.defaults.getDefault("configInitiating")){ + if (opendiscord.sharedFuses.getFuse("configInitiating")){ await opendiscord.configs.init() await opendiscord.events.get("afterConfigsInitiated").emit([opendiscord.configs]) } @@ -159,14 +149,15 @@ const main = async () => { //UTILITY CONFIG const generalConfig = opendiscord.configs.get("opendiscord:general") - if (opendiscord.defaults.getDefault("emojiTitleStyleLoading")){ + if (opendiscord.sharedFuses.getFuse("emojiTitleStyleLoading")){ //set emoji style based on config - opendiscord.defaults.setDefault("emojiTitleStyle",generalConfig.data.system.emojiStyle) + const emojiStyle = (generalConfig.data && generalConfig.data.ticketSystem && generalConfig.data.ticketSystem.emojiStyle) ? generalConfig.data.ticketSystem.emojiStyle : "before" + opendiscord.sharedFuses.setFuse("emojiTitleStyle",emojiStyle) } //load database opendiscord.log("Loading databases...","system") - if (opendiscord.defaults.getDefault("databaseLoading")){ + if (opendiscord.sharedFuses.getFuse("databaseLoading")){ await (await import("./data/framework/databaseLoader.js")).loadAllDatabases() } await opendiscord.events.get("onDatabaseLoad").emit([opendiscord.databases]) @@ -174,14 +165,14 @@ const main = async () => { //initiate database await opendiscord.events.get("onDatabaseInit").emit([opendiscord.databases]) - if (opendiscord.defaults.getDefault("databaseInitiating")){ + if (opendiscord.sharedFuses.getFuse("databaseInitiating")){ await opendiscord.databases.init() await opendiscord.events.get("afterDatabasesInitiated").emit([opendiscord.databases]) } //load sessions opendiscord.log("Loading sessions...","system") - if (opendiscord.defaults.getDefault("sessionLoading")){ + if (opendiscord.sharedFuses.getFuse("sessionLoading")){ } await opendiscord.events.get("onSessionLoad").emit([opendiscord.sessions]) @@ -189,7 +180,7 @@ const main = async () => { //load language opendiscord.log("Loading languages...","system") - if (opendiscord.defaults.getDefault("languageLoading")){ + if (opendiscord.sharedFuses.getFuse("languageLoading")){ await (await import("./data/framework/languageLoader.js")).loadAllLanguages() } await opendiscord.events.get("onLanguageLoad").emit([opendiscord.languages]) @@ -197,12 +188,12 @@ const main = async () => { //initiate language await opendiscord.events.get("onLanguageInit").emit([opendiscord.languages]) - if (opendiscord.defaults.getDefault("languageInitiating")){ + if (opendiscord.sharedFuses.getFuse("languageInitiating")){ await opendiscord.languages.init() await opendiscord.events.get("afterLanguagesInitiated").emit([opendiscord.languages]) //add available languages to list for config checker - const languageList = opendiscord.defaults.getDefault("languageList") + const languageList = opendiscord.sharedFuses.getFuse("languageList") const languageIds = opendiscord.languages.getIds().map((id) => { if (id.value.startsWith("opendiscord:")){ //is open ticket language => return without prefix @@ -210,12 +201,12 @@ const main = async () => { }else return id.value }) languageList.push(...languageIds) - opendiscord.defaults.setDefault("languageList",languageList) + opendiscord.sharedFuses.setFuse("languageList",languageList) } //select language await opendiscord.events.get("onLanguageSelect").emit([opendiscord.languages]) - if (opendiscord.defaults.getDefault("languageSelection")){ + if (opendiscord.sharedFuses.getFuse("languageSelection")){ //set current language const languageId = (generalConfig?.data?.language) ? generalConfig.data.language : "english" if (languageId.includes(":")){ @@ -225,7 +216,7 @@ const main = async () => { } //set backup language - const backupLanguageId = opendiscord.defaults.getDefault("backupLanguage") + const backupLanguageId = opendiscord.sharedFuses.getFuse("backupLanguage") if (opendiscord.languages.exists(backupLanguageId)){ opendiscord.languages.setBackupLanguage(backupLanguageId) @@ -235,18 +226,18 @@ const main = async () => { } //handle data migration (PART 2) - if (lastVersion) await (await import("./core/startup/manageMigration.js")).loadAllAfterInitVersionMigrations(lastVersion) + if (lastVersion) await (await import("./core/startup/manageMigration.js")).loadAfterStartupMigrations(lastVersion) //load config checker opendiscord.log("Loading config checker...","system") - if (opendiscord.defaults.getDefault("checkerLoading")){ + if (opendiscord.sharedFuses.getFuse("checkerLoading")){ await (await import("./data/framework/checkerLoader.js")).loadAllConfigCheckers() } await opendiscord.events.get("onCheckerLoad").emit([opendiscord.checkers]) await opendiscord.events.get("afterCheckersLoaded").emit([opendiscord.checkers]) //load config checker functions - if (opendiscord.defaults.getDefault("checkerFunctionLoading")){ + if (opendiscord.sharedFuses.getFuse("checkerFunctionLoading")){ await (await import("./data/framework/checkerLoader.js")).loadAllConfigCheckerFunctions() } await opendiscord.events.get("onCheckerFunctionLoad").emit([opendiscord.checkers.functions,opendiscord.checkers]) @@ -254,16 +245,16 @@ const main = async () => { //execute config checker await opendiscord.events.get("onCheckerExecute").emit([opendiscord.checkers]) - if (opendiscord.defaults.getDefault("checkerExecution")){ + if (opendiscord.sharedFuses.getFuse("checkerExecution")){ const result = opendiscord.checkers.checkAll(true) await opendiscord.events.get("afterCheckersExecuted").emit([result,opendiscord.checkers]) } //load config checker translations - if (opendiscord.defaults.getDefault("checkerTranslationLoading")){ + if (opendiscord.sharedFuses.getFuse("checkerTranslationLoading")){ await (await import("./data/framework/checkerLoader.js")).loadAllConfigCheckerTranslations() } - await opendiscord.events.get("onCheckerTranslationLoad").emit([opendiscord.checkers.translation,((generalConfig && generalConfig.data.system && generalConfig.data.system.useTranslatedConfigChecker) ? generalConfig.data.system.useTranslatedConfigChecker : false),opendiscord.checkers]) + await opendiscord.events.get("onCheckerTranslationLoad").emit([opendiscord.checkers.translation,((generalConfig && generalConfig.data.ticketSystem && generalConfig.data.ticketSystem.useTranslatedConfigChecker) ? generalConfig.data.ticketSystem.useTranslatedConfigChecker : false),opendiscord.checkers]) await opendiscord.events.get("afterCheckerTranslationsLoaded").emit([opendiscord.checkers.translation,opendiscord.checkers]) //render config checker @@ -272,13 +263,13 @@ const main = async () => { const useCliFlag = opendiscord.flags.get("opendiscord:cli") await opendiscord.events.get("onCheckerRender").emit([opendiscord.checkers.renderer,opendiscord.checkers]) - if (opendiscord.defaults.getDefault("checkerRendering") && !(disableCheckerFlag ? disableCheckerFlag.value : false) && !(useCliFlag ? useCliFlag.value : false)){ + if (opendiscord.sharedFuses.getFuse("checkerRendering") && !(disableCheckerFlag ? disableCheckerFlag.value : false) && !(useCliFlag ? useCliFlag.value : false)){ //check if there is a result (otherwise throw minor error) const result = opendiscord.checkers.lastResult if (!result) return opendiscord.log("Failed to render Config Checker! (couldn't fetch result)","error") //get components & check if full mode enabled - const components = opendiscord.checkers.renderer.getComponents(!(advancedCheckerFlag ? advancedCheckerFlag.value : false),opendiscord.defaults.getDefault("checkerRenderEmpty"),opendiscord.checkers.translation,result) + const components = opendiscord.checkers.renderer.getComponents(!(advancedCheckerFlag ? advancedCheckerFlag.value : false),opendiscord.sharedFuses.getFuse("checkerRenderEmpty"),opendiscord.checkers.translation,result) //render opendiscord.debugfile.writeText("\n[CONFIG CHECKER RESULT]:\n"+ansis.strip(components.join("\n"))+"\n") @@ -293,7 +284,7 @@ const main = async () => { //quit config checker (when required) if (opendiscord.checkers.lastResult && !opendiscord.checkers.lastResult.valid && !(disableCheckerFlag ? disableCheckerFlag.value : false) && !(useCliFlag ? useCliFlag.value : false)){ await opendiscord.events.get("onCheckerQuit").emit([opendiscord.checkers]) - if (opendiscord.defaults.getDefault("checkerQuit")){ + if (opendiscord.sharedFuses.getFuse("checkerQuit")){ process.exit(1) //there is no afterCheckerQuitted event :) } @@ -313,7 +304,7 @@ const main = async () => { //client configuration opendiscord.log("Loading client...","system") - if (opendiscord.defaults.getDefault("clientLoading")){ + if (opendiscord.sharedFuses.getFuse("clientLoading")){ //add intents (for basic permissions) opendiscord.client.intents.push( "Guilds", @@ -369,7 +360,7 @@ const main = async () => { opendiscord.client.readyListener = async () => { opendiscord.log("Loading client setup...","system") await opendiscord.events.get("onClientReady").emit([opendiscord.client]) - if (opendiscord.defaults.getDefault("clientReady")){ + if (opendiscord.sharedFuses.getFuse("clientReady")){ const client = opendiscord.client //check if all servers are valid @@ -391,26 +382,27 @@ const main = async () => { //throw if bot doesn't have permissions in main server if (!client.checkGuildPerms(mainServer)){ console.log("\n") - opendiscord.log("The bot doesn't have the correct permissions in the server provided in the config!","error") + opendiscord.log("The bot doesnโ€™t have the required permissions for the server specified in the configuration.","error") opendiscord.log("Please give the bot \"Administrator\" permissions or visit the documentation!","info") console.log("\n") process.exit(1) } - if (opendiscord.defaults.getDefault("clientMultiGuildWarning")){ + if (opendiscord.sharedFuses.getFuse("clientMultiGuildWarning")){ //warn if bot is in multiple servers if (botServers.length > 1){ - opendiscord.log("This bot is part of multiple servers, but Open Ticket doesn't provide support for this!","warning") - opendiscord.log("As a result, the bot may crash & glitch when used in the additional servers!","info") + opendiscord.log("This bot is part of multiple servers, but Open Ticket does not support this.","warning") + opendiscord.log("The bot may have weird behaviour when used in external servers!","info") + await utilities.timer(2000) } botServers.forEach((server) => { //warn if bot doesn't have permissions in multiple servers - if (!client.checkGuildPerms(server)) opendiscord.log(`The bot doesn't have the correct permissions in the server "${server.name}"!`,"warning") + if (!client.checkGuildPerms(server)) opendiscord.log(`The bot doesnโ€™t have the required permissions for the server "${server.name}".`,"warning") }) } //load client activity opendiscord.log("Loading client activity...","system") - if (opendiscord.defaults.getDefault("clientActivityLoading")){ + if (opendiscord.sharedFuses.getFuse("clientActivityLoading")){ //load config status if (generalConfig.data.status && generalConfig.data.status.enabled) opendiscord.client.activity.setStatus(generalConfig.data.status.type,generalConfig.data.status.text,generalConfig.data.status.mode,generalConfig.data.status.state) } @@ -419,14 +411,14 @@ const main = async () => { //initiate client activity await opendiscord.events.get("onClientActivityInit").emit([opendiscord.client.activity,opendiscord.client]) - if (opendiscord.defaults.getDefault("clientActivityInitiating")){ + if (opendiscord.sharedFuses.getFuse("clientActivityInitiating")){ opendiscord.client.activity.initStatus() await opendiscord.events.get("afterClientActivityInitiated").emit([opendiscord.client.activity,opendiscord.client]) } //load priority levels opendiscord.log("Loading prioritiy levels...","system") - if (opendiscord.defaults.getDefault("priorityLoading")){ + if (opendiscord.fuses.getFuse("priorityLoading")){ await (await import("./data/openticket/priorityLoader.js")).loadAllPriorityLevels() } await opendiscord.events.get("onPriorityLoad").emit([opendiscord.priorities]) @@ -434,22 +426,22 @@ const main = async () => { //load slash commands opendiscord.log("Loading slash commands...","system") - if (opendiscord.defaults.getDefault("slashCommandLoading")){ + if (opendiscord.sharedFuses.getFuse("slashCommandLoading")){ await (await import("./data/framework/commandLoader.js")).loadAllSlashCommands() } await opendiscord.events.get("onSlashCommandLoad").emit([opendiscord.client.slashCommands,opendiscord.client]) await opendiscord.events.get("afterSlashCommandsLoaded").emit([opendiscord.client.slashCommands,opendiscord.client]) //register slash commands (create, update & remove) - if (opendiscord.defaults.getDefault("forceSlashCommandRegistration")) opendiscord.log("Forcing all slash commands to be re-registered...","system") + if (opendiscord.sharedFuses.getFuse("forceSlashCommandRegistration")) opendiscord.log("Forcing all slash commands to be re-registered...","system") opendiscord.log("Registering slash commands... (this can take up to 2 minutes)","system") await opendiscord.events.get("onSlashCommandRegister").emit([opendiscord.client.slashCommands,opendiscord.client]) - if (opendiscord.defaults.getDefault("slashCommandRegistering")){ + if (opendiscord.sharedFuses.getFuse("slashCommandRegistering")){ //get all commands that are already registered in the bot const cmds = await opendiscord.client.slashCommands.getAllRegisteredCommands() const removableCmds = cmds.unused.map((cmd) => cmd.cmd) const newCmds = cmds.unregistered.map((cmd) => cmd.instance) - const updatableCmds = cmds.registered.filter((cmd) => cmd.requiresUpdate || opendiscord.defaults.getDefault("forceSlashCommandRegistration")).map((cmd) => cmd.instance) + const updatableCmds = cmds.registered.filter((cmd) => cmd.requiresUpdate || opendiscord.sharedFuses.getFuse("forceSlashCommandRegistration")).map((cmd) => cmd.instance) //init progress bars const removeProgress = opendiscord.progressbars.get("opendiscord:slash-command-remove") @@ -457,7 +449,7 @@ const main = async () => { const updateProgress = opendiscord.progressbars.get("opendiscord:slash-command-update") //remove unused cmds, create new cmds & update existing cmds - if (opendiscord.defaults.getDefault("allowSlashCommandRemoval")) await opendiscord.client.slashCommands.removeUnusedCommands(removableCmds,undefined,removeProgress) + if (opendiscord.sharedFuses.getFuse("allowSlashCommandRemoval")) await opendiscord.client.slashCommands.removeUnusedCommands(removableCmds,undefined,removeProgress) await opendiscord.client.slashCommands.createNewCommands(newCmds,createProgress) await opendiscord.client.slashCommands.updateExistingCommands(updatableCmds,updateProgress) @@ -466,22 +458,22 @@ const main = async () => { //load context menus opendiscord.log("Loading context menus...","system") - if (opendiscord.defaults.getDefault("contextMenuLoading")){ + if (opendiscord.sharedFuses.getFuse("contextMenuLoading")){ await (await import("./data/framework/commandLoader.js")).loadAllContextMenus() } await opendiscord.events.get("onContextMenuLoad").emit([opendiscord.client.contextMenus,opendiscord.client]) await opendiscord.events.get("afterContextMenusLoaded").emit([opendiscord.client.contextMenus,opendiscord.client]) //register context menus (create, update & remove) - if (opendiscord.defaults.getDefault("forceContextMenuRegistration")) opendiscord.log("Forcing all context menus to be re-registered...","system") + if (opendiscord.sharedFuses.getFuse("forceContextMenuRegistration")) opendiscord.log("Forcing all context menus to be re-registered...","system") opendiscord.log("Registering context menus... (this can take up to a minute)","system") await opendiscord.events.get("onContextMenuRegister").emit([opendiscord.client.contextMenus,opendiscord.client]) - if (opendiscord.defaults.getDefault("contextMenuRegistering")){ + if (opendiscord.sharedFuses.getFuse("contextMenuRegistering")){ //get all context menus that are already registered in the bot const menus = await opendiscord.client.contextMenus.getAllRegisteredMenus() const removableMenus = menus.unused.map((menu) => menu.menu) const newMenus = menus.unregistered.map((menu) => menu.instance) - const updatableMenus = menus.registered.filter((menu) => menu.requiresUpdate || opendiscord.defaults.getDefault("forceContextMenuRegistration")).map((menu) => menu.instance) + const updatableMenus = menus.registered.filter((menu) => menu.requiresUpdate || opendiscord.sharedFuses.getFuse("forceContextMenuRegistration")).map((menu) => menu.instance) //init progress bars const removeProgress = opendiscord.progressbars.get("opendiscord:context-menu-remove") @@ -489,7 +481,7 @@ const main = async () => { const updateProgress = opendiscord.progressbars.get("opendiscord:context-menu-update") //remove unused menus, create new menus & update existing menus - if (opendiscord.defaults.getDefault("allowContextMenuRemoval")) await opendiscord.client.contextMenus.removeUnusedMenus(removableMenus,undefined,removeProgress) + if (opendiscord.sharedFuses.getFuse("allowContextMenuRemoval")) await opendiscord.client.contextMenus.removeUnusedMenus(removableMenus,undefined,removeProgress) await opendiscord.client.contextMenus.createNewMenus(newMenus,createProgress) await opendiscord.client.contextMenus.updateExistingMenus(updatableMenus,updateProgress) @@ -498,10 +490,10 @@ const main = async () => { //load text commands opendiscord.log("Loading text commands...","system") - if (opendiscord.defaults.getDefault("allowDumpCommand")){ - (await import("./core/startup/dump.js")).loadDumpCommand() + if (opendiscord.sharedFuses.getFuse("allowDumpCommand")){ + loadDumpCommand(opendiscord) } - if (opendiscord.defaults.getDefault("textCommandLoading")){ + if (opendiscord.sharedFuses.getFuse("textCommandLoading")){ await (await import("./data/framework/commandLoader.js")).loadAllTextCommands() } await opendiscord.events.get("onTextCommandLoad").emit([opendiscord.client.textCommands,opendiscord.client]) @@ -515,7 +507,7 @@ const main = async () => { //client init (login) opendiscord.log("Logging in...","system") await opendiscord.events.get("onClientInit").emit([opendiscord.client]) - if (opendiscord.defaults.getDefault("clientInitiating")){ + if (opendiscord.sharedFuses.getFuse("clientInitiating")){ //init client opendiscord.client.initClient() await opendiscord.events.get("afterClientInitiated").emit([opendiscord.client]) @@ -525,13 +517,28 @@ const main = async () => { opendiscord.log("discord.js client ready!","info") } + //load states + opendiscord.log("Loading states...","system") + if (opendiscord.sharedFuses.getFuse("stateLoading")){ + await (await import("./data/framework/stateLoader.js")).loadAllStates() + } + await opendiscord.events.get("onStateLoad").emit([opendiscord.states]) + await opendiscord.events.get("afterStatesLoaded").emit([opendiscord.states]) + + //init states + await opendiscord.events.get("onStateInit").emit([opendiscord.states]) + if (opendiscord.sharedFuses.getFuse("stateInitiating")){ + await opendiscord.states.init() + await opendiscord.events.get("afterStatesInitiated").emit([opendiscord.states]) + } + //plugin loading before managers await opendiscord.events.get("onPluginBeforeManagerLoad").emit([]) await opendiscord.events.get("afterPluginBeforeManagerLoaded").emit([]) //load questions opendiscord.log("Loading questions...","system") - if (opendiscord.defaults.getDefault("questionLoading")){ + if (opendiscord.fuses.getFuse("questionLoading")){ await (await import("./data/openticket/questionLoader.js")).loadAllQuestions() } await opendiscord.events.get("onQuestionLoad").emit([opendiscord.questions]) @@ -539,7 +546,7 @@ const main = async () => { //load options opendiscord.log("Loading options...","system") - if (opendiscord.defaults.getDefault("optionLoading")){ + if (opendiscord.fuses.getFuse("optionLoading")){ await (await import("./data/openticket/optionLoader.js")).loadAllOptions() } await opendiscord.events.get("onOptionLoad").emit([opendiscord.options]) @@ -547,7 +554,7 @@ const main = async () => { //load panels opendiscord.log("Loading panels...","system") - if (opendiscord.defaults.getDefault("panelLoading")){ + if (opendiscord.fuses.getFuse("panelLoading")){ await (await import("./data/openticket/panelLoader.js")).loadAllPanels() } await opendiscord.events.get("onPanelLoad").emit([opendiscord.panels]) @@ -555,7 +562,7 @@ const main = async () => { //load tickets opendiscord.log("Loading tickets...","system") - if (opendiscord.defaults.getDefault("ticketLoading")){ + if (opendiscord.fuses.getFuse("ticketLoading")){ opendiscord.tickets.useGuild(opendiscord.client.mainServer) await (await import("./data/openticket/ticketLoader.js")).loadAllTickets() } @@ -564,7 +571,7 @@ const main = async () => { //load roles opendiscord.log("Loading roles...","system") - if (opendiscord.defaults.getDefault("roleLoading")){ + if (opendiscord.fuses.getFuse("roleLoading")){ await (await import("./data/openticket/roleLoader.js")).loadAllRoles() } await opendiscord.events.get("onRoleLoad").emit([opendiscord.roles]) @@ -572,7 +579,7 @@ const main = async () => { //load blacklist opendiscord.log("Loading blacklist...","system") - if (opendiscord.defaults.getDefault("blacklistLoading")){ + if (opendiscord.fuses.getFuse("blacklistLoading")){ await (await import("./data/openticket/blacklistLoader.js")).loadAllBlacklistedUsers() } await opendiscord.events.get("onBlacklistLoad").emit([opendiscord.blacklist]) @@ -580,14 +587,14 @@ const main = async () => { //load transcript compilers opendiscord.log("Loading transcripts...","system") - if (opendiscord.defaults.getDefault("transcriptCompilerLoading")){ + if (opendiscord.fuses.getFuse("transcriptCompilerLoading")){ await (await import("./data/openticket/transcriptLoader.js")).loadAllTranscriptCompilers() } await opendiscord.events.get("onTranscriptCompilerLoad").emit([opendiscord.transcripts]) await opendiscord.events.get("afterTranscriptCompilersLoaded").emit([opendiscord.transcripts]) //load transcript history - if (opendiscord.defaults.getDefault("transcriptHistoryLoading")){ + if (opendiscord.fuses.getFuse("transcriptHistoryLoading")){ await (await import("./data/openticket/transcriptLoader.js")).loadTranscriptHistory() } await opendiscord.events.get("onTranscriptHistoryLoad").emit([opendiscord.transcripts]) @@ -599,7 +606,7 @@ const main = async () => { //load button builders opendiscord.log("Loading buttons...","system") - if (opendiscord.defaults.getDefault("buttonBuildersLoading")){ + if (opendiscord.sharedFuses.getFuse("buttonBuildersLoading")){ await (await import("./builders/buttons.js")).registerAllButtons() } await opendiscord.events.get("onButtonBuilderLoad").emit([opendiscord.builders.buttons,opendiscord.builders,opendiscord.actions]) @@ -607,7 +614,7 @@ const main = async () => { //load dropdown builders opendiscord.log("Loading dropdowns...","system") - if (opendiscord.defaults.getDefault("dropdownBuildersLoading")){ + if (opendiscord.sharedFuses.getFuse("dropdownBuildersLoading")){ await (await import("./builders/dropdowns.js")).registerAllDropdowns() } await opendiscord.events.get("onDropdownBuilderLoad").emit([opendiscord.builders.dropdowns,opendiscord.builders,opendiscord.actions]) @@ -615,7 +622,7 @@ const main = async () => { //load file builders opendiscord.log("Loading files...","system") - if (opendiscord.defaults.getDefault("fileBuildersLoading")){ + if (opendiscord.sharedFuses.getFuse("fileBuildersLoading")){ await (await import("./builders/files.js")).registerAllFiles() } await opendiscord.events.get("onFileBuilderLoad").emit([opendiscord.builders.files,opendiscord.builders,opendiscord.actions]) @@ -623,7 +630,7 @@ const main = async () => { //load embed builders opendiscord.log("Loading embeds...","system") - if (opendiscord.defaults.getDefault("embedBuildersLoading")){ + if (opendiscord.sharedFuses.getFuse("embedBuildersLoading")){ await (await import("./builders/embeds.js")).registerAllEmbeds() } await opendiscord.events.get("onEmbedBuilderLoad").emit([opendiscord.builders.embeds,opendiscord.builders,opendiscord.actions]) @@ -631,7 +638,7 @@ const main = async () => { //load message builders opendiscord.log("Loading messages...","system") - if (opendiscord.defaults.getDefault("messageBuildersLoading")){ + if (opendiscord.sharedFuses.getFuse("messageBuildersLoading")){ await (await import("./builders/messages.js")).registerAllMessages() } await opendiscord.events.get("onMessageBuilderLoad").emit([opendiscord.builders.messages,opendiscord.builders,opendiscord.actions]) @@ -639,19 +646,51 @@ const main = async () => { //load modal builders opendiscord.log("Loading modals...","system") - if (opendiscord.defaults.getDefault("modalBuildersLoading")){ + if (opendiscord.sharedFuses.getFuse("modalBuildersLoading")){ await (await import("./builders/modals.js")).registerAllModals() } await opendiscord.events.get("onModalBuilderLoad").emit([opendiscord.builders.modals,opendiscord.builders,opendiscord.actions]) await opendiscord.events.get("afterModalBuildersLoaded").emit([opendiscord.builders.modals,opendiscord.builders,opendiscord.actions]) + //load shared components + opendiscord.log("Loading shared components...","system") + if (opendiscord.sharedFuses.getFuse("sharedComponentsLoading")){ + //TODO!! + } + await opendiscord.events.get("onSharedComponentLoad").emit([opendiscord.components.shared,opendiscord.components,opendiscord.actions]) + await opendiscord.events.get("afterSharedComponentsLoaded").emit([opendiscord.components.shared,opendiscord.components,opendiscord.actions]) + + //load message components + opendiscord.log("Loading message components...","system") + if (opendiscord.sharedFuses.getFuse("messageComponentsLoading")){ + //TODO!! + } + await opendiscord.events.get("onMessageComponentLoad").emit([opendiscord.components.messages,opendiscord.components,opendiscord.actions]) + await opendiscord.events.get("afterMessageComponentsLoaded").emit([opendiscord.components.messages,opendiscord.components,opendiscord.actions]) + + //load modal components + opendiscord.log("Loading modal components...","system") + if (opendiscord.sharedFuses.getFuse("modalComponentsLoading")){ + //TODO!! + } + await opendiscord.events.get("onModalComponentLoad").emit([opendiscord.components.modals,opendiscord.components,opendiscord.actions]) + await opendiscord.events.get("afterModalComponentsLoaded").emit([opendiscord.components.modals,opendiscord.components,opendiscord.actions]) + + //load component modifiers + opendiscord.log("Loading component modifiers...","system") + if (opendiscord.sharedFuses.getFuse("componentModifiersLoading")){ + await (await import("./components/verifybarModifiers.js")).registerAllVerifyBarModifiers() + } + await opendiscord.events.get("onComponentModifierLoad").emit([opendiscord.components.modifiers,opendiscord.components.messages,opendiscord.builders.messages]) + await opendiscord.events.get("afterComponentModifiersLoaded").emit([opendiscord.components.modifiers,opendiscord.components.messages,opendiscord.builders.messages]) + //plugin loading before responders await opendiscord.events.get("onPluginBeforeResponderLoad").emit([]) await opendiscord.events.get("afterPluginBeforeResponderLoaded").emit([]) //load command responders opendiscord.log("Loading command responders...","system") - if (opendiscord.defaults.getDefault("commandRespondersLoading")){ + if (opendiscord.sharedFuses.getFuse("commandRespondersLoading")){ await (await import("./commands/help.js")).registerCommandResponders() await (await import("./commands/stats.js")).registerCommandResponders() await (await import("./commands/panel.js")).registerCommandResponders() @@ -680,7 +719,7 @@ const main = async () => { //load button responders opendiscord.log("Loading button responders...","system") - if (opendiscord.defaults.getDefault("buttonRespondersLoading")){ + if (opendiscord.sharedFuses.getFuse("buttonRespondersLoading")){ await (await import("./actions/handleVerifyBar.js")).registerButtonResponders() await (await import("./actions/handleTranscriptErrors.js")).registerButtonResponders() await (await import("./commands/help.js")).registerButtonResponders() @@ -700,7 +739,7 @@ const main = async () => { //load dropdown responders opendiscord.log("Loading dropdown responders...","system") - if (opendiscord.defaults.getDefault("dropdownRespondersLoading")){ + if (opendiscord.sharedFuses.getFuse("dropdownRespondersLoading")){ await (await import("./commands/ticket.js")).registerDropdownResponders() } await opendiscord.events.get("onDropdownResponderLoad").emit([opendiscord.responders.dropdowns,opendiscord.responders,opendiscord.actions]) @@ -708,7 +747,7 @@ const main = async () => { //load modal responders opendiscord.log("Loading modal responders...","system") - if (opendiscord.defaults.getDefault("modalRespondersLoading")){ + if (opendiscord.sharedFuses.getFuse("modalRespondersLoading")){ await (await import("./commands/ticket.js")).registerModalResponders() await (await import("./commands/close.js")).registerModalResponders() await (await import("./commands/reopen.js")).registerModalResponders() @@ -723,7 +762,7 @@ const main = async () => { //load context menu responders opendiscord.log("Loading context menu responders...","system") - if (opendiscord.defaults.getDefault("contextMenuRespondersLoading")){ + if (opendiscord.sharedFuses.getFuse("contextMenuRespondersLoading")){ //TODO!! } await opendiscord.events.get("onContextMenuResponderLoad").emit([opendiscord.responders.contextMenus,opendiscord.responders,opendiscord.actions]) @@ -731,7 +770,7 @@ const main = async () => { //load autocomplete responders opendiscord.log("Loading autocomplete responders...","system") - if (opendiscord.defaults.getDefault("autocompleteRespondersLoading")){ + if (opendiscord.sharedFuses.getFuse("autocompleteRespondersLoading")){ await (await import("./commands/autocomplete.js")).registerAutocompleteResponders() } await opendiscord.events.get("onAutocompleteResponderLoad").emit([opendiscord.responders.autocomplete,opendiscord.responders,opendiscord.actions]) @@ -743,8 +782,10 @@ const main = async () => { //load actions opendiscord.log("Loading actions...","system") - if (opendiscord.defaults.getDefault("actionsLoading")){ + if (opendiscord.sharedFuses.getFuse("actionsLoading")){ await (await import("./actions/createTicketPermissions.js")).registerActions() + await (await import("./actions/calculateTicketCategory.js")).registerActions() + await (await import("./actions/calculateTicketName.js")).registerActions() await (await import("./actions/createTranscript.js")).registerActions() await (await import("./actions/createTicket.js")).registerActions() await (await import("./actions/closeTicket.js")).registerActions() @@ -769,21 +810,21 @@ const main = async () => { //load verifybars opendiscord.log("Loading verifybars...","system") - if (opendiscord.defaults.getDefault("verifyBarsLoading")){ - await (await import("./actions/closeTicket.js")).registerVerifyBars() - await (await import("./actions/deleteTicket.js")).registerVerifyBars() - await (await import("./actions/reopenTicket.js")).registerVerifyBars() - await (await import("./actions/claimTicket.js")).registerVerifyBars() - await (await import("./actions/unclaimTicket.js")).registerVerifyBars() - await (await import("./actions/pinTicket.js")).registerVerifyBars() - await (await import("./actions/unpinTicket.js")).registerVerifyBars() + if (opendiscord.sharedFuses.getFuse("verifyBarsLoading")){ + await (await import("./commands/close.js")).registerVerifyBars() + await (await import("./commands/delete.js")).registerVerifyBars() + await (await import("./commands/reopen.js")).registerVerifyBars() + await (await import("./commands/claim.js")).registerVerifyBars() + await (await import("./commands/unclaim.js")).registerVerifyBars() + await (await import("./commands/pin.js")).registerVerifyBars() + await (await import("./commands/unpin.js")).registerVerifyBars() } await opendiscord.events.get("onVerifyBarLoad").emit([opendiscord.verifybars]) await opendiscord.events.get("afterVerifyBarsLoaded").emit([opendiscord.verifybars]) //load permissions opendiscord.log("Loading permissions...","system") - if (opendiscord.defaults.getDefault("permissionsLoading")){ + if (opendiscord.sharedFuses.getFuse("permissionsLoading")){ await (await import("./data/framework/permissionLoader.js")).loadAllPermissions() } await opendiscord.events.get("onPermissionLoad").emit([opendiscord.permissions]) @@ -791,7 +832,7 @@ const main = async () => { //load posts opendiscord.log("Loading posts...","system") - if (opendiscord.defaults.getDefault("postsLoading")){ + if (opendiscord.sharedFuses.getFuse("postsLoading")){ await (await import("./data/framework/postLoader.js")).loadAllPosts() } await opendiscord.events.get("onPostLoad").emit([opendiscord.posts]) @@ -799,14 +840,14 @@ const main = async () => { //init posts await opendiscord.events.get("onPostInit").emit([opendiscord.posts]) - if (opendiscord.defaults.getDefault("postsInitiating")){ - if (opendiscord.client.mainServer) opendiscord.posts.init(opendiscord.client.mainServer) + if (opendiscord.sharedFuses.getFuse("postsInitiating")){ + if (opendiscord.client.mainServer) await opendiscord.posts.init(opendiscord.client.mainServer) await opendiscord.events.get("afterPostsInitiated").emit([opendiscord.posts]) } //load cooldowns opendiscord.log("Loading cooldowns...","system") - if (opendiscord.defaults.getDefault("cooldownsLoading")){ + if (opendiscord.sharedFuses.getFuse("cooldownsLoading")){ await (await import("./data/framework/cooldownLoader.js")).loadAllCooldowns() } await opendiscord.events.get("onCooldownLoad").emit([opendiscord.cooldowns]) @@ -814,47 +855,47 @@ const main = async () => { //init cooldowns await opendiscord.events.get("onCooldownInit").emit([opendiscord.cooldowns]) - if (opendiscord.defaults.getDefault("cooldownsInitiating")){ + if (opendiscord.sharedFuses.getFuse("cooldownsInitiating")){ await opendiscord.cooldowns.init() await opendiscord.events.get("afterCooldownsInitiated").emit([opendiscord.cooldowns]) } //load help menu categories opendiscord.log("Loading help menu...","system") - if (opendiscord.defaults.getDefault("helpMenuCategoryLoading")){ + if (opendiscord.sharedFuses.getFuse("helpMenuCategoryLoading")){ await (await import("./data/framework/helpMenuLoader.js")).loadAllHelpMenuCategories() } await opendiscord.events.get("onHelpMenuCategoryLoad").emit([opendiscord.helpmenu]) await opendiscord.events.get("afterHelpMenuCategoriesLoaded").emit([opendiscord.helpmenu]) //load help menu components - if (opendiscord.defaults.getDefault("helpMenuComponentLoading")){ + if (opendiscord.sharedFuses.getFuse("helpMenuComponentLoading")){ await (await import("./data/framework/helpMenuLoader.js")).loadAllHelpMenuComponents() } await opendiscord.events.get("onHelpMenuComponentLoad").emit([opendiscord.helpmenu]) await opendiscord.events.get("afterHelpMenuComponentsLoaded").emit([opendiscord.helpmenu]) - //load stat scopes - opendiscord.log("Loading stats...","system") - if (opendiscord.defaults.getDefault("statScopesLoading")){ - opendiscord.stats.useDatabase(opendiscord.databases.get("opendiscord:stats")) - await (await import("./data/framework/statLoader.js")).loadAllStatScopes() + //load statistic scopes + opendiscord.log("Loading statistics...","system") + if (opendiscord.sharedFuses.getFuse("statisticScopesLoading")){ + opendiscord.statistics.useDatabase(opendiscord.databases.get("opendiscord:stats")) + await (await import("./data/framework/statisticLoader.js")).loadAllStatisticScopes() } - await opendiscord.events.get("onStatScopeLoad").emit([opendiscord.stats]) - await opendiscord.events.get("afterStatScopesLoaded").emit([opendiscord.stats]) + await opendiscord.events.get("onStatisticScopeLoad").emit([opendiscord.statistics]) + await opendiscord.events.get("afterStatisticScopesLoaded").emit([opendiscord.statistics]) - //load stats - if (opendiscord.defaults.getDefault("statLoading")){ - await (await import("./data/framework/statLoader.js")).loadAllStats() + //load statistics + if (opendiscord.sharedFuses.getFuse("statisticLoading")){ + await (await import("./data/framework/statisticLoader.js")).loadAllStatistics() } - await opendiscord.events.get("onStatLoad").emit([opendiscord.stats]) - await opendiscord.events.get("afterStatsLoaded").emit([opendiscord.stats]) + await opendiscord.events.get("onStatisticLoad").emit([opendiscord.statistics]) + await opendiscord.events.get("afterStatisticsLoaded").emit([opendiscord.statistics]) - //init stats - await opendiscord.events.get("onStatInit").emit([opendiscord.stats]) - if (opendiscord.defaults.getDefault("statInitiating")){ - await opendiscord.stats.init() - await opendiscord.events.get("afterStatsInitiated").emit([opendiscord.stats]) + //init statistics + await opendiscord.events.get("onStatisticInit").emit([opendiscord.statistics]) + if (opendiscord.sharedFuses.getFuse("statisticInitiating")){ + await opendiscord.statistics.init() + await opendiscord.events.get("afterStatisticsInitiated").emit([opendiscord.statistics]) } //plugin loading before code @@ -863,7 +904,7 @@ const main = async () => { //load code opendiscord.log("Loading code...","system") - if (opendiscord.defaults.getDefault("codeLoading")){ + if (opendiscord.sharedFuses.getFuse("codeLoading")){ await (await import("./data/framework/codeLoader.js")).loadAllCode() } await opendiscord.events.get("onCodeLoad").emit([opendiscord.code]) @@ -871,7 +912,7 @@ const main = async () => { //execute code await opendiscord.events.get("onCodeExecute").emit([opendiscord.code]) - if (opendiscord.defaults.getDefault("codeExecution")){ + if (opendiscord.sharedFuses.getFuse("codeExecution")){ await opendiscord.code.execute() await opendiscord.events.get("afterCodeExecuted").emit([opendiscord.code]) } @@ -881,7 +922,7 @@ const main = async () => { //load livestatus sources opendiscord.log("Loading livestatus...","system") - if (opendiscord.defaults.getDefault("liveStatusLoading")){ + if (opendiscord.sharedFuses.getFuse("liveStatusLoading")){ await (await import("./data/framework/liveStatusLoader.js")).loadAllLiveStatusSources() } await opendiscord.events.get("onLiveStatusSourceLoad").emit([opendiscord.livestatus]) @@ -889,7 +930,7 @@ const main = async () => { //load startscreen opendiscord.log("Loading startscreen...","system") - if (opendiscord.defaults.getDefault("startScreenLoading")){ + if (opendiscord.sharedFuses.getFuse("startScreenLoading")){ await (await import("./data/framework/startScreenLoader.js")).loadAllStartScreenComponents() } await opendiscord.events.get("onStartScreenLoad").emit([opendiscord.startscreen]) @@ -897,7 +938,7 @@ const main = async () => { //render startscreen await opendiscord.events.get("onStartScreenRender").emit([opendiscord.startscreen]) - if (opendiscord.defaults.getDefault("startScreenRendering")){ + if (opendiscord.sharedFuses.getFuse("startScreenRendering")){ await opendiscord.startscreen.renderAllComponents() if (opendiscord.languages.getLanguageMetadata(false)?.automated){ console.log("===================") diff --git a/tsconfig.json b/tsconfig.json index 95a1996..c833325 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES2022", + "target": "es2024", "rootDir":"./", "outDir": "./dist/", "module": "NodeNext", @@ -13,6 +13,7 @@ "allowJs": false, "checkJs": false, "declaration": true, + "strict": false, "strictNullChecks": true, "strictPropertyInitialization": true, "skipLibCheck": true,