From 83e7ff6019b9e7c679d49e337fd2b722f614ac31 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 11 Mar 2022 21:09:17 +0000 Subject: [PATCH 001/409] Reset for new version --- Dockerfile | 9 - README.md | 142 - example.env | 9 - package.json | 69 - pnpm-lock.yaml | 3577 ------------------ pterodactyl.egg.json | 50 - src/banner.js | 30 - src/commands/add.js | 118 - src/commands/blacklist.js | 156 - src/commands/close.js | 322 -- src/commands/extra/survey.template.html | 67 - src/commands/help.js | 49 - src/commands/new.js | 190 - src/commands/panel.js | 210 - src/commands/remove.js | 111 - src/commands/settings.js | 358 -- src/commands/stats.js | 95 - src/commands/survey.js | 113 - src/commands/tag.js | 71 - src/commands/topic.js | 87 - src/database/dialects.js | 42 - src/database/index.js | 73 - src/database/models/category.model.js | 93 - src/database/models/channel_entity.model.js | 21 - src/database/models/guild.model.js | 66 - src/database/models/message.model.js | 35 - src/database/models/panel.model.js | 22 - src/database/models/role_entity.model.js | 25 - src/database/models/survey.model.js | 32 - src/database/models/survey_response.model.js | 36 - src/database/models/ticket.model.js | 81 - src/database/models/user_entity.model.js | 34 - src/index.js | 217 -- src/listeners/debug.js | 13 - src/listeners/error.js | 12 - src/listeners/guildCreate.js | 12 - src/listeners/guildDelete.js | 11 - src/listeners/guildMemberRemove.js | 22 - src/listeners/interactionCreate.js | 323 -- src/listeners/messageCreate.js | 190 - src/listeners/messageDelete.js | 15 - src/listeners/messageUpdate.js | 23 - src/listeners/rateLimit.js | 11 - src/listeners/ready.js | 35 - src/listeners/warn.js | 11 - src/locales/ar.json | 7 - src/locales/cs-CZ.json | 340 -- src/locales/da-DK.json | 610 --- src/locales/de-DE.json | 610 --- src/locales/en-GB.json | 610 --- src/locales/en-US.json | 324 -- src/locales/es-ES.json | 612 --- src/locales/fr-FR.json | 566 --- src/locales/he-IL.json | 141 - src/locales/hi-IN.json | 324 -- src/locales/hu.json | 610 --- src/locales/id-ID.json | 324 -- src/locales/it-IT.json | 324 -- src/locales/ko-KR.json | 344 -- src/locales/nl-NL.json | 402 -- src/locales/no-NO.json | 324 -- src/locales/pl-PL.json | 348 -- src/locales/pt-BR.json | 324 -- src/locales/pt.json | 598 --- src/locales/ru-RU.json | 324 -- src/locales/th-TH.json | 373 -- src/locales/vi.json | 287 -- src/locales/zh-Hans.json | 610 --- src/locales/zh-Hant.json | 610 --- src/logger.js | 39 - src/modules/commands/command.js | 125 - src/modules/commands/manager.js | 213 -- src/modules/listeners/listener.js | 15 - src/modules/listeners/loader.js | 36 - src/modules/plugins/manager.js | 94 - src/modules/plugins/plugin.js | 141 - src/modules/tickets/archives.js | 202 - src/modules/tickets/manager.js | 440 --- src/update/notifier.js | 38 - src/utils/discord.js | 96 - src/utils/emoji.js | 43 - src/utils/fs.js | 10 - src/utils/index.js | 10 - user/example.config.js | 56 - user/plugins/.gitkeep | 0 85 files changed, 18792 deletions(-) delete mode 100644 Dockerfile delete mode 100644 README.md delete mode 100644 example.env delete mode 100644 package.json delete mode 100644 pnpm-lock.yaml delete mode 100644 pterodactyl.egg.json delete mode 100644 src/banner.js delete mode 100644 src/commands/add.js delete mode 100644 src/commands/blacklist.js delete mode 100644 src/commands/close.js delete mode 100644 src/commands/extra/survey.template.html delete mode 100644 src/commands/help.js delete mode 100644 src/commands/new.js delete mode 100644 src/commands/panel.js delete mode 100644 src/commands/remove.js delete mode 100644 src/commands/settings.js delete mode 100644 src/commands/stats.js delete mode 100644 src/commands/survey.js delete mode 100644 src/commands/tag.js delete mode 100644 src/commands/topic.js delete mode 100644 src/database/dialects.js delete mode 100644 src/database/index.js delete mode 100644 src/database/models/category.model.js delete mode 100644 src/database/models/channel_entity.model.js delete mode 100644 src/database/models/guild.model.js delete mode 100644 src/database/models/message.model.js delete mode 100644 src/database/models/panel.model.js delete mode 100644 src/database/models/role_entity.model.js delete mode 100644 src/database/models/survey.model.js delete mode 100644 src/database/models/survey_response.model.js delete mode 100644 src/database/models/ticket.model.js delete mode 100644 src/database/models/user_entity.model.js delete mode 100644 src/index.js delete mode 100644 src/listeners/debug.js delete mode 100644 src/listeners/error.js delete mode 100644 src/listeners/guildCreate.js delete mode 100644 src/listeners/guildDelete.js delete mode 100644 src/listeners/guildMemberRemove.js delete mode 100644 src/listeners/interactionCreate.js delete mode 100644 src/listeners/messageCreate.js delete mode 100644 src/listeners/messageDelete.js delete mode 100644 src/listeners/messageUpdate.js delete mode 100644 src/listeners/rateLimit.js delete mode 100644 src/listeners/ready.js delete mode 100644 src/listeners/warn.js delete mode 100644 src/locales/ar.json delete mode 100644 src/locales/cs-CZ.json delete mode 100644 src/locales/da-DK.json delete mode 100644 src/locales/de-DE.json delete mode 100644 src/locales/en-GB.json delete mode 100644 src/locales/en-US.json delete mode 100644 src/locales/es-ES.json delete mode 100644 src/locales/fr-FR.json delete mode 100644 src/locales/he-IL.json delete mode 100644 src/locales/hi-IN.json delete mode 100644 src/locales/hu.json delete mode 100644 src/locales/id-ID.json delete mode 100644 src/locales/it-IT.json delete mode 100644 src/locales/ko-KR.json delete mode 100644 src/locales/nl-NL.json delete mode 100644 src/locales/no-NO.json delete mode 100644 src/locales/pl-PL.json delete mode 100644 src/locales/pt-BR.json delete mode 100644 src/locales/pt.json delete mode 100644 src/locales/ru-RU.json delete mode 100644 src/locales/th-TH.json delete mode 100644 src/locales/vi.json delete mode 100644 src/locales/zh-Hans.json delete mode 100644 src/locales/zh-Hant.json delete mode 100644 src/logger.js delete mode 100644 src/modules/commands/command.js delete mode 100644 src/modules/commands/manager.js delete mode 100644 src/modules/listeners/listener.js delete mode 100644 src/modules/listeners/loader.js delete mode 100644 src/modules/plugins/manager.js delete mode 100644 src/modules/plugins/plugin.js delete mode 100644 src/modules/tickets/archives.js delete mode 100644 src/modules/tickets/manager.js delete mode 100644 src/update/notifier.js delete mode 100644 src/utils/discord.js delete mode 100644 src/utils/emoji.js delete mode 100644 src/utils/fs.js delete mode 100644 src/utils/index.js delete mode 100644 user/example.config.js delete mode 100644 user/plugins/.gitkeep diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 57cccb6..0000000 --- a/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM node:16 - -WORKDIR /usr/src/app -COPY package*.json ./ - -RUN npm i --production - -COPY . . -CMD ["npm", "start"] \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 62e53f6..0000000 --- a/README.md +++ /dev/null @@ -1,142 +0,0 @@ -[![GitHub stars](https://img.shields.io/github/stars/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/stargazers) -[![License](https://img.shields.io/github/license/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/blob/main/LICENSE) -![](https://img.shields.io/badge/dynamic/json?color=5865F2&label=bots&query=clients&url=https%3A%2F%2Fstats.discordtickets.app&logo=discord&logoColor=white&style=flat-square) -![](https://img.shields.io/badge/dynamic/json?color=5865F2&label=tickets&query=tickets&url=https%3A%2F%2Fstats.discordtickets.app&logo=discord&logoColor=white&style=flat-square) -[![Codacy](https://img.shields.io/codacy/grade/b974eb5f984c40868e07d82c968bd02d?logo=codacy&style=flat-square)](https://www.codacy.com/gh/discord-tickets/bot/dashboard?utm_source=github.com&utm_medium=referral&utm_content=discord-tickets/bot&utm_campaign=Badge_Grade) -[![Discord](https://img.shields.io/discord/451745464480432129?label=discord&color=7289DA&style=flat-square)](https://go.eartharoid.me/discord) -[![Weblate](http://i18n.capestar.net/widgets/discord-tickets/-/bot/svg-badge.svg)](https://i18n.capestar.net/engage/discord-tickets/) - -
- ---- - -
- - - -
-
- Partnered with PebbleHost -
- - for cheap bot hosting - -
- ---- - -
- -![Discord Tickets](https://img.eartharoid.me/insecure/plain/https://static.eartharoid.me/discord-tickets/logo/wordmark/gradient-by-eartharoid.png@png) - -An open-source ticket management bot for Discord - a free alternative to the premium and white-label plans of other popular ticketing bots. - -[![ProductHunt](https://api.producthunt.com/widgets/embed-image/v1/review.svg?post_id=321112&theme=light)](https://www.producthunt.com/posts/discord-tickets?utm_source=badge-review&utm_medium=badge&utm_souce=badge-discord-tickets#discussion-body) - -## What is this? - -Discord Tickets is a Discord bot for creating and managing ticket channels. It is a free and open-source alternative to the paid "premium" and "white-label" plans of popular ticketing bots, such as [Ticket Tool](https://tickettool.xyz/), [TicketsBot](https://ticketsbot.net/), [Tickety](https://tickety.net/), [Helper.gg](https://helper.gg/), [Helper](https://helper.wtf), and others. - -Discord Tickets is feature-rich and much more customisable than many of the bots mentioned above. As it is intended for self-hosting, the bot can have your community/company's logo, for free. - -Although intended for use in a single Discord server, the bot can also function in multiple servers at once if you run more than one community. - -### Features - -Discord Tickets is packed full of features, many of which were suggested by its users. If it's missing a feature you want, you can: - -- Create a plugin for it, if you can code JavaScript -- Request someone else to make a plugin -- [Submit a feature request](https://github.com/discord-tickets/.github/blob/main//CONTRIBUTING.md#submitting-a-feature-request) if you think many other users would benefit from it - -Here's some of the things that makes Discord Tickets awesome: - -1. **Highly customisable** -Some messages can be configured for each server and for each ticket category. Every other message is set in the locale files, making it relatively easy to override the default messages. -You can also configure the functionality of the bot to your liking and add commands with plugins. - -2. **Localisable** -If the bot hasn't already been translated to your (community's) language, you can [translate](https://github.com/discord-tickets/.github/blob/main//CONTRIBUTING.md#translating) it yourself. -Plugin authors are encouraged to support multiple languages as well. - - -
- Translation status - - Weblate - -
- -3. **Multiple ticket categories** -Each ticket category has its own settings for messages and the support team roles. There's also multiple methods of creating a ticket. - -4. **A beautiful ticket archives portal** -**\[COMING SOON\] :** Add the official [Discord Tickets Portal](https://github.com/discord-tickets/portal) plugin for an instant ticket archives website. - - You can use [text transcripts](https://discordtickets.app/plugins/official/text-transcripts/) whilst you wait for the portal. - -5. **Open-source and self-hosted** -It's yours, you have full control. - - -## Getting started - -| [**Host it yourself**](https://discordtickets.app/getting-started) | [**Managed bot**](https://discordtickets.app/getting-started#managed-hosting) | [**Public test bot**](https://discord.com/oauth2/authorize?permissions=8&scope=applications.commands%20bot&client_id=475371285531066368) | -|:-:|:-:|:-:| -| Recommended if you have some experience. | Recommended if you want a bot without hosting it. | Try out the bot for a few days. | -| [Go to the docs »](https://discordtickets.app/getting-started) | [Learn more »](https://discordtickets.app/getting-started#managed-hosting) | [Add to Discord »](https://discord.com/oauth2/authorize?permissions=8&scope=applications.commands%20bot&client_id=475371285531066368) | - -Discord Tickets is partnered with [PebbleHost](https://discordtickets.app/getting-started#pebblehost). Click on the logo below if you want to self host but you don't have a server. - -[![PebbleHost](https://img.eartharoid.me/insecure/rs:auto:180/plain/s3://eartharoid/sharex/21/10/pebblehost.webp)](https://discordtickets.app/getting-started#pebblehost) - -## Documentation - -You will find most of information you need at [discordtickets.app](https://discordtickets.app). - -## Support - -If the [documentation](https://discordtickets.app) leaves you with questions, you can ask for help in the [discussions](https://github.com/discord-tickets/bot/discussions/categories/support-q-a) or join the support server on Discord. - -[![Join the Discord server](https://img.eartharoid.me/insecure/rs:auto:440:200/plain/s3://eartharoid/images/join-discord.png@png)](https://go.eartharoid.me/discord) - -## Contributing - -For contributing instructions, or to find out all of the ways you can contribute, read [CONTRIBUTING.md](https://github.com/discord-tickets/.github/blob/main//CONTRIBUTING.md). All contributions are welcome and encouraged, but please [read the information](https://github.com/discord-tickets/.github/blob/main//CONTRIBUTING.md) given before doing so. - -## Contributors - -Thank you to everyone to has contributed to Discord Tickets, including everyone who has: - -- Contributed code -- Translated -- Improved documentation -- Reported bugs -- Requested a feature -- Given help or support to other users -- Created a public plugin -- Created resources such as tutorials - -**A full list of contributors can be found in [CONTRIBUTORS.md](https://github.com/discord-tickets/bot/blob/main/CONTRIBUTORS.md).** - -## Sponsors - -*Does your community or company use Discord Tickets? [Sponsor the project](https://github.com/discord-tickets/bot/?sponsor=1) to get your logo shown here.* - -**These awesome people and communities sponsor Discord Tickets:** - -- [reSkybounds](https://reskybounds.com/) ([Discord](https://discord.reskybounds.com/)) -- [thephilluk#0204](https://discord.com/users/166187860968472577) - -### Donate - -[![Donate at ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/eartharoid) - -## License - -Discord Tickets is licensed under the [GPLv3 license](https://github.com/discord-tickets/bot/blob/main/LICENSE). - -This is not an official Discord product. It is not affiliated with nor endorsed by Discord Inc. - -© 2021 Isaac Saunders diff --git a/example.env b/example.env deleted file mode 100644 index db544bb..0000000 --- a/example.env +++ /dev/null @@ -1,9 +0,0 @@ -DISCORD_TOKEN= -DB_ENCRYPTION_KEY= -DB_TYPE=sqlite -DB_HOST= -DB_PORT= -DB_NAME= -DB_USER= -DB_PASS= -DB_TABLE_PREFIX=dsctickets_ \ No newline at end of file diff --git a/package.json b/package.json deleted file mode 100644 index 6b1f7f3..0000000 --- a/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@eartharoid/discord-tickets", - "version": "3.1.3", - "private": true, - "description": "An open-source Discord bot for ticket management", - "main": "src/index.js", - "scripts": { - "contributors:add": "all-contributors add", - "contributors:generate": "all-contributors generate", - "lint": "eslint src --fix", - "start": "node src/", - "test": "echo \"Nothing to test! Run with 'npm start'\" && exit 1" - }, - "engines": { - "node": ">=16.6" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/discord-tickets/bot.git" - }, - "keywords": [ - "discord", - "tickets", - "bot" - ], - "author": "eartharoid", - "license": "GPL-3.0", - "bugs": { - "url": "https://github.com/discord-tickets/bot/issues" - }, - "homepage": "https://discordtickets.app", - "funding": "https://github.com/discord-tickets/bot/?sponsor=1", - "dependencies": { - "@eartharoid/i18n": "^1.0.1", - "boxen": "^5.1.2", - "cryptr": "^6.0.2", - "discord.js": "^13.2.0", - "dotenv": "^8.6.0", - "keyv": "^4.0.3", - "leeks.js": "^0.2.4", - "leekslazylogger": "^3.0.2", - "ms": "^2.1.3", - "mustache": "^4.2.0", - "node-fetch": "^2.6.5", - "semver": "^7.3.5", - "sequelize": "^6.6.5", - "terminal-link": "^2.1.1" - }, - "devDependencies": { - "all-contributors-cli": "^6.20.0", - "eslint": "^7.32.0", - "mariadb": "^2.5.4", - "mysql2": "^2.3.0", - "nodemon": "^2.0.13", - "pg": "^8.7.1", - "pg-hstore": "^2.3.4", - "tedious": "^11.8.0" - }, - "optionalDependencies": { - "sqlite3": "^5.0.2" - }, - "peerDependencies": { - "mariadb": "^2.5.4", - "mysql2": "^2.3.0", - "pg": "^8.7.1", - "pg-hstore": "^2.3.4", - "tedious": "^11.4.0" - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 9426162..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,3577 +0,0 @@ -lockfileVersion: 5.3 - -specifiers: - '@eartharoid/i18n': ^1.0.1 - all-contributors-cli: ^6.20.0 - boxen: ^5.1.2 - cryptr: ^6.0.2 - discord.js: ^13.2.0 - dotenv: ^8.6.0 - eslint: ^7.32.0 - keyv: ^4.0.3 - leeks.js: ^0.2.4 - leekslazylogger: ^3.0.2 - mariadb: ^2.5.4 - ms: ^2.1.3 - mustache: ^4.2.0 - mysql2: ^2.3.0 - node-fetch: ^2.6.5 - nodemon: ^2.0.13 - pg: ^8.7.1 - pg-hstore: ^2.3.4 - semver: ^7.3.5 - sequelize: ^6.6.5 - sqlite3: ^5.0.2 - tedious: ^11.8.0 - terminal-link: ^2.1.1 - -dependencies: - '@eartharoid/i18n': 1.0.1 - boxen: 5.1.2 - cryptr: 6.0.2 - discord.js: 13.2.0 - dotenv: 8.6.0 - keyv: 4.0.3 - leeks.js: 0.2.4 - leekslazylogger: 3.0.2 - ms: 2.1.3 - mustache: 4.2.0 - node-fetch: 2.6.5 - semver: 7.3.5 - sequelize: 6.6.5_348d7a9ed83738128221dabb96d70298 - terminal-link: 2.1.1 - -optionalDependencies: - sqlite3: 5.0.2 - -devDependencies: - all-contributors-cli: 6.20.0 - eslint: 7.32.0 - mariadb: 2.5.4 - mysql2: 2.3.0 - nodemon: 2.0.13 - pg: 8.7.1 - pg-hstore: 2.3.4 - tedious: 11.8.0 - -packages: - - /@azure/abort-controller/1.0.4: - resolution: {integrity: sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw==} - engines: {node: '>=8.0.0'} - dependencies: - tslib: 2.3.1 - dev: true - - /@azure/core-asynciterator-polyfill/1.0.0: - resolution: {integrity: sha512-kmv8CGrPfN9SwMwrkiBK9VTQYxdFQEGe0BmQk+M8io56P9KNzpAxcWE/1fxJj7uouwN4kXF0BHW8DNlgx+wtCg==} - dev: true - - /@azure/core-auth/1.3.2: - resolution: {integrity: sha512-7CU6DmCHIZp5ZPiZ9r3J17lTKMmYsm/zGvNkjArQwPkrLlZ1TZ+EUYfGgh2X31OLMVAQCTJZW4cXHJi02EbJnA==} - engines: {node: '>=12.0.0'} - dependencies: - '@azure/abort-controller': 1.0.4 - tslib: 2.3.1 - dev: true - - /@azure/core-client/1.3.1: - resolution: {integrity: sha512-7IHm2DGg2u7dJYtCW84Ik7uENHfE8VsM/sWloZezPKYDoWZrg7JzwjvdGAfsaELKi2p0GE+JBaAbDYnNpr5V1w==} - engines: {node: '>=12.0.0'} - dependencies: - '@azure/abort-controller': 1.0.4 - '@azure/core-asynciterator-polyfill': 1.0.0 - '@azure/core-auth': 1.3.2 - '@azure/core-rest-pipeline': 1.3.1 - '@azure/core-tracing': 1.0.0-preview.13 - tslib: 2.3.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@azure/core-http/2.2.1: - resolution: {integrity: sha512-7ATnV3OGzCO2K9kMrh3NKUM8b4v+xasmlUhkNZz6uMbm+8XH/AexLkhRGsoo0GyKNlEGvyGEfytqTk0nUY2I4A==} - engines: {node: '>=12.0.0'} - dependencies: - '@azure/abort-controller': 1.0.4 - '@azure/core-asynciterator-polyfill': 1.0.0 - '@azure/core-auth': 1.3.2 - '@azure/core-tracing': 1.0.0-preview.13 - '@azure/logger': 1.0.3 - '@types/node-fetch': 2.5.12 - '@types/tunnel': 0.0.3 - form-data: 4.0.0 - node-fetch: 2.6.5 - process: 0.11.10 - tough-cookie: 4.0.0 - tslib: 2.3.1 - tunnel: 0.0.6 - uuid: 8.3.2 - xml2js: 0.4.23 - dev: true - - /@azure/core-lro/2.2.1: - resolution: {integrity: sha512-HE6PBl+mlKa0eBsLwusHqAqjLc5n9ByxeDo3Hz4kF3B1hqHvRkBr4oMgoT6tX7Hc3q97KfDctDUon7EhvoeHPA==} - engines: {node: '>=12.0.0'} - dependencies: - '@azure/abort-controller': 1.0.4 - '@azure/core-tracing': 1.0.0-preview.13 - '@azure/logger': 1.0.3 - tslib: 2.3.1 - dev: true - - /@azure/core-paging/1.2.0: - resolution: {integrity: sha512-ZX1bCjm/MjKPCN6kQD/9GJErYSoKA8YWp6YWoo5EIzcTWlSBLXu3gNaBTUl8usGl+UShiKo7b4Gdy1NSTIlpZg==} - engines: {node: '>=12.0.0'} - dependencies: - '@azure/core-asynciterator-polyfill': 1.0.0 - tslib: 2.3.1 - dev: true - - /@azure/core-rest-pipeline/1.3.1: - resolution: {integrity: sha512-xTQiv47O5cWzJFkwiDrUTT4K4IYbUIts0gaou5TZxAAuhQi9kAKWHEmFTjHVMOeAmyDhlMM5cb21M2n4WDto1A==} - engines: {node: '>=12.0.0'} - dependencies: - '@azure/abort-controller': 1.0.4 - '@azure/core-auth': 1.3.2 - '@azure/core-tracing': 1.0.0-preview.13 - '@azure/logger': 1.0.3 - form-data: 4.0.0 - http-proxy-agent: 4.0.1 - https-proxy-agent: 5.0.0 - tslib: 2.3.1 - uuid: 8.3.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@azure/core-tracing/1.0.0-preview.12: - resolution: {integrity: sha512-nvo2Wc4EKZGN6eFu9n3U7OXmASmL8VxoPIH7xaD6OlQqi44bouF0YIi9ID5rEsKLiAU59IYx6M297nqWVMWPDg==} - engines: {node: '>=12.0.0'} - dependencies: - '@opentelemetry/api': 1.0.3 - tslib: 2.3.1 - dev: true - - /@azure/core-tracing/1.0.0-preview.13: - resolution: {integrity: sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==} - engines: {node: '>=12.0.0'} - dependencies: - '@opentelemetry/api': 1.0.3 - tslib: 2.3.1 - dev: true - - /@azure/identity/1.5.2: - resolution: {integrity: sha512-vqyeRbd2i0h9F4mqW5JbkP1xfabqKQ21l/81osKhpOQ2LtwaJW6nw4+0PsVYnxcbPHFCIZt6EWAk74a3OGYZJA==} - engines: {node: '>=12.0.0'} - dependencies: - '@azure/core-auth': 1.3.2 - '@azure/core-client': 1.3.1 - '@azure/core-rest-pipeline': 1.3.1 - '@azure/core-tracing': 1.0.0-preview.12 - '@azure/logger': 1.0.3 - '@azure/msal-node': 1.0.0-beta.6 - '@types/stoppable': 1.1.1 - axios: 0.21.4 - events: 3.3.0 - jws: 4.0.0 - msal: 1.4.14 - open: 7.4.2 - qs: 6.10.1 - stoppable: 1.1.0 - tslib: 2.3.1 - uuid: 8.3.2 - optionalDependencies: - keytar: 7.7.0 - transitivePeerDependencies: - - debug - - supports-color - dev: true - - /@azure/keyvault-keys/4.3.0: - resolution: {integrity: sha512-OEosl0/rE/mKD5Ji9KaQN7UH+yQnV5MS0MRhGqQIiJrG+qAvAla0MYudJzv3XvBlplpGk0+MVgyL9H3KX/UAwQ==} - engines: {node: '>=8.0.0'} - dependencies: - '@azure/abort-controller': 1.0.4 - '@azure/core-http': 2.2.1 - '@azure/core-lro': 2.2.1 - '@azure/core-paging': 1.2.0 - '@azure/core-tracing': 1.0.0-preview.13 - '@azure/logger': 1.0.3 - tslib: 2.3.1 - dev: true - - /@azure/logger/1.0.3: - resolution: {integrity: sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==} - engines: {node: '>=12.0.0'} - dependencies: - tslib: 2.3.1 - dev: true - - /@azure/ms-rest-azure-env/2.0.0: - resolution: {integrity: sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw==} - dev: true - - /@azure/ms-rest-js/2.6.0: - resolution: {integrity: sha512-4C5FCtvEzWudblB+h92/TYYPiq7tuElX8icVYToxOdggnYqeec4Se14mjse5miInKtZahiFHdl8lZA/jziEc5g==} - dependencies: - '@azure/core-auth': 1.3.2 - abort-controller: 3.0.0 - form-data: 2.5.1 - node-fetch: 2.6.5 - tough-cookie: 3.0.1 - tslib: 1.14.1 - tunnel: 0.0.6 - uuid: 8.3.2 - xml2js: 0.4.23 - dev: true - - /@azure/ms-rest-nodeauth/3.1.0: - resolution: {integrity: sha512-F4NKrbkZg0qD3+rUM8fvJHOFRkXFoEiptYTZtLBruN3VwBFIqbTFW0fmgRyBW9seZl+mX2OexQA5GzWenSA3Kw==} - dependencies: - '@azure/ms-rest-azure-env': 2.0.0 - '@azure/ms-rest-js': 2.6.0 - adal-node: 0.2.3 - transitivePeerDependencies: - - debug - dev: true - - /@azure/msal-common/4.5.1: - resolution: {integrity: sha512-/i5dXM+QAtO+6atYd5oHGBAx48EGSISkXNXViheliOQe+SIFMDo3gSq3lL54W0suOSAsVPws3XnTaIHlla0PIQ==} - engines: {node: '>=0.8.0'} - dependencies: - debug: 4.3.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@azure/msal-node/1.0.0-beta.6: - resolution: {integrity: sha512-ZQI11Uz1j0HJohb9JZLRD8z0moVcPks1AFW4Q/Gcl67+QvH4aKEJti7fjCcipEEZYb/qzLSO8U6IZgPYytsiJQ==} - dependencies: - '@azure/msal-common': 4.5.1 - axios: 0.21.4 - jsonwebtoken: 8.5.1 - uuid: 8.3.2 - transitivePeerDependencies: - - debug - - supports-color - dev: true - - /@babel/code-frame/7.12.11: - resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==} - dependencies: - '@babel/highlight': 7.14.5 - dev: true - - /@babel/helper-validator-identifier/7.15.7: - resolution: {integrity: sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/highlight/7.14.5: - resolution: {integrity: sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.15.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - dev: true - - /@babel/runtime/7.15.4: - resolution: {integrity: sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.13.9 - dev: true - - /@discordjs/builders/0.6.0: - resolution: {integrity: sha512-mH3Gx61LKk2CD05laCI9K5wp+a3NyASHDUGx83DGJFkqJlRlSV5WMJNY6RS37A5SjqDtGMF4wVR9jzFaqShe6Q==} - engines: {node: '>=14.0.0', npm: '>=7.0.0'} - dependencies: - '@sindresorhus/is': 4.2.0 - discord-api-types: 0.22.0 - ow: 0.27.0 - ts-mixer: 6.0.0 - tslib: 2.3.1 - dev: false - - /@discordjs/collection/0.2.1: - resolution: {integrity: sha512-vhxqzzM8gkomw0TYRF3tgx7SwElzUlXT/Aa41O7mOcyN6wIJfj5JmDWaO5XGKsGSsNx7F3i5oIlrucCCWV1Nog==} - engines: {node: '>=14.0.0'} - dev: false - - /@discordjs/form-data/3.0.1: - resolution: {integrity: sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.33 - dev: false - - /@eartharoid/deep-merge/0.0.1: - resolution: {integrity: sha512-nxv2DRXgyqjNcbgDMOB2SoGlzPvge+yjZEI6+Ez+Ei5j00kAT4tzMG/mxseFhTRgZIlPgcaJ03THInVQnNmwqA==} - dev: false - - /@eartharoid/dtf/1.0.8: - resolution: {integrity: sha512-e3mR8JY6Uuy1Zj89iYEZfvK6s81GlWEPP4gO8NzfItzF4xFGFTVTwRjZ6sCVBmbhj3ouX2pyvx8O9snx4r5Xrg==} - dev: false - - /@eartharoid/i18n/1.0.1: - resolution: {integrity: sha512-5Vl7RrRpSXEmza8bO2+4l4f5jwxfkUoiCzc7TEXyIPaG0lrjLLHwZE1aGuOpV67/HvsvAA2LQSKZcbt9I4LszA==} - dev: false - - /@eslint/eslintrc/0.4.3: - resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.2 - espree: 7.3.1 - globals: 13.11.0 - ignore: 4.0.6 - import-fresh: 3.3.0 - js-yaml: 3.14.1 - minimatch: 3.0.4 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@humanwhocodes/config-array/0.5.0: - resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 1.2.0 - debug: 4.3.2 - minimatch: 3.0.4 - transitivePeerDependencies: - - supports-color - dev: true - - /@humanwhocodes/object-schema/1.2.0: - resolution: {integrity: sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==} - dev: true - - /@js-joda/core/3.2.0: - resolution: {integrity: sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg==} - dev: true - - /@opentelemetry/api/1.0.3: - resolution: {integrity: sha512-puWxACExDe9nxbBB3lOymQFrLYml2dVOrd7USiVRnSbgXE+KwBu+HxFvxrzfqsiSda9IWsXJG1ef7C1O2/GmKQ==} - engines: {node: '>=8.0.0'} - dev: true - - /@sapphire/async-queue/1.1.5: - resolution: {integrity: sha512-NQ8GeTBeOkeAylVYTnO9zfEZO74iMNGCRrR3uIRnCrhkyPC+nsewyQtTamjSDWxXFTf+xGSJ9khiY2p56k/bMA==} - engines: {node: '>=v14.18.0', npm: '>=7.24.1'} - dev: false - - /@sindresorhus/is/0.14.0: - resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} - engines: {node: '>=6'} - dev: true - - /@sindresorhus/is/4.2.0: - resolution: {integrity: sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==} - engines: {node: '>=10'} - dev: false - - /@szmarczak/http-timer/1.1.2: - resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} - engines: {node: '>=6'} - dependencies: - defer-to-connect: 1.1.3 - dev: true - - /@tootallnate/once/1.1.2: - resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} - engines: {node: '>= 6'} - dev: true - - /@types/geojson/7946.0.8: - resolution: {integrity: sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==} - dev: true - - /@types/node-fetch/2.5.12: - resolution: {integrity: sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==} - dependencies: - '@types/node': 16.10.3 - form-data: 3.0.1 - dev: true - - /@types/node/14.17.21: - resolution: {integrity: sha512-zv8ukKci1mrILYiQOwGSV4FpkZhyxQtuFWGya2GujWg+zVAeRQ4qbaMmWp9vb9889CFA8JECH7lkwCL6Ygg8kA==} - dev: true - - /@types/node/16.10.3: - resolution: {integrity: sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==} - - /@types/stoppable/1.1.1: - resolution: {integrity: sha512-b8N+fCADRIYYrGZOcmOR8ZNBOqhktWTB/bMUl5LvGtT201QKJZOOH5UsFyI3qtteM6ZAJbJqZoBcLqqxKIwjhw==} - dependencies: - '@types/node': 16.10.3 - dev: true - - /@types/tunnel/0.0.3: - resolution: {integrity: sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==} - dependencies: - '@types/node': 16.10.3 - dev: true - - /@types/ws/8.2.0: - resolution: {integrity: sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg==} - dependencies: - '@types/node': 16.10.3 - dev: false - - /@xmldom/xmldom/0.7.5: - resolution: {integrity: sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==} - engines: {node: '>=10.0.0'} - dev: true - - /abbrev/1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - - /abort-controller/3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - dependencies: - event-target-shim: 5.0.1 - dev: true - - /acorn-jsx/5.3.2_acorn@7.4.1: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 7.4.1 - dev: true - - /acorn/7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /adal-node/0.2.3: - resolution: {integrity: sha512-gMKr8RuYEYvsj7jyfCv/4BfKToQThz20SP71N3AtFn3ia3yAR8Qt2T3aVQhuJzunWs2b38ZsQV0qsZPdwZr7VQ==} - engines: {node: '>= 0.6.15'} - dependencies: - '@xmldom/xmldom': 0.7.5 - async: 2.6.3 - axios: 0.21.4 - date-utils: 1.2.21 - jws: 3.2.2 - underscore: 1.13.1 - uuid: 3.4.0 - xpath.js: 1.1.0 - transitivePeerDependencies: - - debug - dev: true - - /agent-base/6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.2 - transitivePeerDependencies: - - supports-color - dev: true - - /ajv/6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - /ajv/8.6.3: - resolution: {integrity: sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==} - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - dev: true - - /all-contributors-cli/6.20.0: - resolution: {integrity: sha512-trEQlL1s1u8FSWSwY2w9uL4GCG7Fo9HIW5rm5LtlE0SQHSolfXQBzJib07Qes5j52/t72wjuE6sEKkuRrwiuuQ==} - engines: {node: '>=4'} - hasBin: true - dependencies: - '@babel/runtime': 7.15.4 - async: 3.2.1 - chalk: 4.1.2 - didyoumean: 1.2.2 - inquirer: 7.3.3 - json-fixer: 1.6.12 - lodash: 4.17.21 - node-fetch: 2.6.5 - pify: 5.0.0 - yargs: 15.4.1 - dev: true - - /ansi-align/3.0.1: - resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - dependencies: - string-width: 4.2.3 - - /ansi-colors/4.1.1: - resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} - engines: {node: '>=6'} - dev: true - - /ansi-escapes/4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.21.3 - - /ansi-regex/2.1.1: - resolution: {integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8=} - engines: {node: '>=0.10.0'} - optional: true - - /ansi-regex/5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - /ansi-styles/3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - dev: true - - /ansi-styles/4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - - /any-promise/1.3.0: - resolution: {integrity: sha1-q8av7tzqUugJzcA3au0845Y10X8=} - dev: false - - /anymatch/3.1.2: - resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} - engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.0 - dev: true - - /aproba/1.2.0: - resolution: {integrity: sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==} - optional: true - - /are-we-there-yet/1.1.7: - resolution: {integrity: sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==} - dependencies: - delegates: 1.0.0 - readable-stream: 2.3.7 - optional: true - - /argparse/1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - dependencies: - sprintf-js: 1.0.3 - dev: true - - /asn1/0.2.4: - resolution: {integrity: sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==} - dependencies: - safer-buffer: 2.1.2 - dev: false - optional: true - - /assert-plus/1.0.0: - resolution: {integrity: sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=} - engines: {node: '>=0.8'} - dev: false - optional: true - - /astral-regex/2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - dev: true - - /async/2.6.3: - resolution: {integrity: sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==} - dependencies: - lodash: 4.17.21 - dev: true - - /async/3.2.1: - resolution: {integrity: sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==} - dev: true - - /asynckit/0.4.0: - resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} - - /aws-sign2/0.7.0: - resolution: {integrity: sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=} - dev: false - optional: true - - /aws4/1.11.0: - resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==} - dev: false - optional: true - - /axios/0.21.4: - resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} - dependencies: - follow-redirects: 1.14.4 - transitivePeerDependencies: - - debug - dev: true - - /balanced-match/1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - /base64-js/1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true - - /bcrypt-pbkdf/1.0.2: - resolution: {integrity: sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=} - dependencies: - tweetnacl: 0.14.5 - dev: false - optional: true - - /binary-extensions/2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} - dev: true - - /bl/4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.0 - dev: true - optional: true - - /bl/5.0.0: - resolution: {integrity: sha512-8vxFNZ0pflFfi0WXA3WQXlj6CaMEwsmh63I1CNp0q+wWv8sD0ARx1KovSQd0l2GkwrMIOyedq0EF1FxI+RCZLQ==} - dependencies: - buffer: 6.0.3 - inherits: 2.0.4 - readable-stream: 3.6.0 - dev: true - - /block-stream/0.0.9: - resolution: {integrity: sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=} - engines: {node: 0.4 || >=0.5.8} - dependencies: - inherits: 2.0.4 - dev: false - optional: true - - /boxen/5.1.2: - resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} - engines: {node: '>=10'} - dependencies: - ansi-align: 3.0.1 - camelcase: 6.2.0 - chalk: 4.1.2 - cli-boxes: 2.2.1 - string-width: 4.2.3 - type-fest: 0.20.2 - widest-line: 3.1.0 - wrap-ansi: 7.0.0 - - /brace-expansion/1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - /braces/3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - dev: true - - /buffer-equal-constant-time/1.0.1: - resolution: {integrity: sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=} - dev: true - - /buffer-writer/2.0.0: - resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} - engines: {node: '>=4'} - dev: true - - /buffer/5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: true - optional: true - - /buffer/6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: true - - /cacheable-request/6.1.0: - resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} - engines: {node: '>=8'} - dependencies: - clone-response: 1.0.2 - get-stream: 5.2.0 - http-cache-semantics: 4.1.0 - keyv: 3.1.0 - lowercase-keys: 2.0.0 - normalize-url: 4.5.1 - responselike: 1.0.2 - dev: true - - /call-bind/1.0.2: - resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} - dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.1.1 - dev: true - - /callsites/3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - /camelcase/5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - dev: true - - /camelcase/6.2.0: - resolution: {integrity: sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==} - engines: {node: '>=10'} - - /caseless/0.12.0: - resolution: {integrity: sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=} - dev: false - optional: true - - /chalk/2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - dev: true - - /chalk/4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - /chardet/0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - dev: true - - /chokidar/3.5.2: - resolution: {integrity: sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.2 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /chownr/1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - optional: true - - /ci-info/2.0.0: - resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} - dev: true - - /cli-boxes/2.2.1: - resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} - engines: {node: '>=6'} - - /cli-cursor/3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - dependencies: - restore-cursor: 3.1.0 - dev: true - - /cli-width/3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} - dev: true - - /cliui/6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - dev: true - - /clone-response/1.0.2: - resolution: {integrity: sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=} - dependencies: - mimic-response: 1.0.1 - dev: true - - /code-point-at/1.1.0: - resolution: {integrity: sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=} - engines: {node: '>=0.10.0'} - optional: true - - /color-convert/1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - dev: true - - /color-convert/2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - - /color-name/1.1.3: - resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} - dev: true - - /color-name/1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - /combined-stream/1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - dependencies: - delayed-stream: 1.0.0 - - /concat-map/0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} - - /configstore/5.0.1: - resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==} - engines: {node: '>=8'} - dependencies: - dot-prop: 5.3.0 - graceful-fs: 4.2.8 - make-dir: 3.1.0 - unique-string: 2.0.0 - write-file-atomic: 3.0.3 - xdg-basedir: 4.0.0 - dev: true - - /console-control-strings/1.1.0: - resolution: {integrity: sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=} - optional: true - - /core-util-is/1.0.2: - resolution: {integrity: sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=} - dev: false - optional: true - - /core-util-is/1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - optional: true - - /cross-spawn/7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - - /crypto-random-string/2.0.0: - resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} - engines: {node: '>=8'} - dev: true - - /cryptr/6.0.2: - resolution: {integrity: sha512-1TRHI4bmuLIB8WgkH9eeYXzhEg1T4tonO4vVaMBKKde8Dre51J68nAgTVXTwMYXAf7+mV2gBCkm/9wksjSb2sA==} - dev: false - - /dashdash/1.14.1: - resolution: {integrity: sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=} - engines: {node: '>=0.10'} - dependencies: - assert-plus: 1.0.0 - dev: false - optional: true - - /date-utils/1.2.21: - resolution: {integrity: sha1-YfsWzcEnSzyayq/+n8ad+HIKK2Q=} - engines: {node: '>0.4.0'} - dev: true - - /debug/2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - dependencies: - ms: 2.0.0 - dev: true - - /debug/3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - dependencies: - ms: 2.1.3 - - /debug/4.3.2: - resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - - /decamelize/1.2.0: - resolution: {integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=} - engines: {node: '>=0.10.0'} - dev: true - - /decompress-response/3.3.0: - resolution: {integrity: sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=} - engines: {node: '>=4'} - dependencies: - mimic-response: 1.0.1 - dev: true - - /decompress-response/4.2.1: - resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} - engines: {node: '>=8'} - dependencies: - mimic-response: 2.1.0 - dev: true - optional: true - - /deep-extend/0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - - /deep-is/0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true - - /defer-to-connect/1.1.3: - resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==} - dev: true - - /delayed-stream/1.0.0: - resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} - engines: {node: '>=0.4.0'} - - /delegates/1.0.0: - resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=} - optional: true - - /denque/1.5.1: - resolution: {integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==} - engines: {node: '>=0.10'} - dev: true - - /depd/2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - dev: true - - /detect-libc/1.0.3: - resolution: {integrity: sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=} - engines: {node: '>=0.10'} - hasBin: true - optional: true - - /didyoumean/1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dev: true - - /discord-api-types/0.22.0: - resolution: {integrity: sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg==} - engines: {node: '>=12'} - dev: false - - /discord-api-types/0.23.1: - resolution: {integrity: sha512-igWmn+45mzXRWNEPU25I/pr8MwxHb767wAr51oy3VRLRcTlp5ADBbrBR0lq3SA1Rfw3MtM4TQu1xo3kxscfVdQ==} - engines: {node: '>=12'} - dev: false - - /discord.js/13.2.0: - resolution: {integrity: sha512-nyxUvL8wuQG38zx13wUMkpcA8koFszyiXdkSLwwM9opKW2LC2H5gD0cTZxImeJ6GtEnKPWT8xBiE8lLBmbNIhw==} - engines: {node: '>=16.6.0', npm: '>=7.0.0'} - dependencies: - '@discordjs/builders': 0.6.0 - '@discordjs/collection': 0.2.1 - '@discordjs/form-data': 3.0.1 - '@sapphire/async-queue': 1.1.5 - '@types/ws': 8.2.0 - discord-api-types: 0.23.1 - node-fetch: 2.6.5 - ws: 8.2.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - - /doctrine/3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /dot-prop/5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} - dependencies: - is-obj: 2.0.0 - dev: true - - /dot-prop/6.0.1: - resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} - engines: {node: '>=10'} - dependencies: - is-obj: 2.0.0 - dev: false - - /dotenv/8.6.0: - resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} - engines: {node: '>=10'} - dev: false - - /dottie/2.0.2: - resolution: {integrity: sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==} - dev: false - - /duplexer3/0.1.4: - resolution: {integrity: sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=} - dev: true - - /ecc-jsbn/0.1.2: - resolution: {integrity: sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=} - dependencies: - jsbn: 0.1.1 - safer-buffer: 2.1.2 - dev: false - optional: true - - /ecdsa-sig-formatter/1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - dependencies: - safe-buffer: 5.2.1 - dev: true - - /emoji-regex/8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - /end-of-stream/1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - dependencies: - once: 1.4.0 - dev: true - - /enquirer/2.3.6: - resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} - engines: {node: '>=8.6'} - dependencies: - ansi-colors: 4.1.1 - dev: true - - /escape-goat/2.1.1: - resolution: {integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==} - engines: {node: '>=8'} - dev: true - - /escape-string-regexp/1.0.5: - resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} - engines: {node: '>=0.8.0'} - dev: true - - /escape-string-regexp/4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true - - /eslint-scope/5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - dev: true - - /eslint-utils/2.1.0: - resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} - engines: {node: '>=6'} - dependencies: - eslint-visitor-keys: 1.3.0 - dev: true - - /eslint-visitor-keys/1.3.0: - resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} - engines: {node: '>=4'} - dev: true - - /eslint-visitor-keys/2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - dev: true - - /eslint/7.32.0: - resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==} - engines: {node: ^10.12.0 || >=12.0.0} - hasBin: true - dependencies: - '@babel/code-frame': 7.12.11 - '@eslint/eslintrc': 0.4.3 - '@humanwhocodes/config-array': 0.5.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.2 - doctrine: 3.0.0 - enquirer: 2.3.6 - escape-string-regexp: 4.0.0 - eslint-scope: 5.1.1 - eslint-utils: 2.1.0 - eslint-visitor-keys: 2.1.0 - espree: 7.3.1 - esquery: 1.4.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - functional-red-black-tree: 1.0.1 - glob-parent: 5.1.2 - globals: 13.11.0 - ignore: 4.0.6 - import-fresh: 3.3.0 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - js-yaml: 3.14.1 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.0.4 - natural-compare: 1.4.0 - optionator: 0.9.1 - progress: 2.0.3 - regexpp: 3.2.0 - semver: 7.3.5 - strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 - table: 6.7.2 - text-table: 0.2.0 - v8-compile-cache: 2.3.0 - transitivePeerDependencies: - - supports-color - dev: true - - /espree/7.3.1: - resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - acorn: 7.4.1 - acorn-jsx: 5.3.2_acorn@7.4.1 - eslint-visitor-keys: 1.3.0 - dev: true - - /esprima/4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - dev: true - - /esquery/1.4.0: - resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.2.0 - dev: true - - /esrecurse/4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - dependencies: - estraverse: 5.2.0 - dev: true - - /estraverse/4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - dev: true - - /estraverse/5.2.0: - resolution: {integrity: sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==} - engines: {node: '>=4.0'} - dev: true - - /esutils/2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true - - /event-target-shim/5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - dev: true - - /events/3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - dev: true - - /expand-template/2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} - dev: true - optional: true - - /extend/3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - dev: false - optional: true - - /external-editor/3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 - dev: true - - /extsprintf/1.3.0: - resolution: {integrity: sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=} - engines: {'0': node >=0.6.0} - dev: false - optional: true - - /fast-deep-equal/3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - /fast-json-stable-stringify/2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - /fast-levenshtein/2.0.6: - resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=} - dev: true - - /figures/3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - dependencies: - escape-string-regexp: 1.0.5 - dev: true - - /file-entry-cache/6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.0.4 - dev: true - - /fill-range/7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - dev: true - - /find-up/4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - dev: true - - /flat-cache/3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flatted: 3.2.2 - rimraf: 3.0.2 - dev: true - - /flatted/3.2.2: - resolution: {integrity: sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==} - dev: true - - /follow-redirects/1.14.4: - resolution: {integrity: sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dev: true - - /forever-agent/0.6.1: - resolution: {integrity: sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=} - dev: false - optional: true - - /form-data/2.3.3: - resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} - engines: {node: '>= 0.12'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.33 - dev: false - optional: true - - /form-data/2.5.1: - resolution: {integrity: sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==} - engines: {node: '>= 0.12'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.33 - dev: true - - /form-data/3.0.1: - resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.33 - dev: true - - /form-data/4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.33 - dev: true - - /fs-constants/1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - dev: true - optional: true - - /fs-minipass/1.2.7: - resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==} - dependencies: - minipass: 2.9.0 - dev: false - optional: true - - /fs.realpath/1.0.0: - resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} - - /fsevents/2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - dev: true - optional: true - - /fstream/1.0.12: - resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} - engines: {node: '>=0.6'} - dependencies: - graceful-fs: 4.2.8 - inherits: 2.0.4 - mkdirp: 0.5.5 - rimraf: 2.7.1 - dev: false - optional: true - - /function-bind/1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: true - - /functional-red-black-tree/1.0.1: - resolution: {integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=} - dev: true - - /gauge/2.7.4: - resolution: {integrity: sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=} - dependencies: - aproba: 1.2.0 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - object-assign: 4.1.1 - signal-exit: 3.0.5 - string-width: 1.0.2 - strip-ansi: 3.0.1 - wide-align: 1.1.3 - optional: true - - /generate-function/2.3.1: - resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} - dependencies: - is-property: 1.0.2 - dev: true - - /get-caller-file/2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - dev: true - - /get-intrinsic/1.1.1: - resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==} - dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-symbols: 1.0.2 - dev: true - - /get-stream/4.1.0: - resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} - engines: {node: '>=6'} - dependencies: - pump: 3.0.0 - dev: true - - /get-stream/5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - dependencies: - pump: 3.0.0 - dev: true - - /getpass/0.1.7: - resolution: {integrity: sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=} - dependencies: - assert-plus: 1.0.0 - dev: false - optional: true - - /github-from-package/0.0.0: - resolution: {integrity: sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=} - dev: true - optional: true - - /glob-parent/5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob/7.2.0: - resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.0.4 - once: 1.4.0 - path-is-absolute: 1.0.1 - - /global-dirs/3.0.0: - resolution: {integrity: sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==} - engines: {node: '>=10'} - dependencies: - ini: 2.0.0 - dev: true - - /globals/13.11.0: - resolution: {integrity: sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: true - - /got/9.6.0: - resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==} - engines: {node: '>=8.6'} - dependencies: - '@sindresorhus/is': 0.14.0 - '@szmarczak/http-timer': 1.1.2 - cacheable-request: 6.1.0 - decompress-response: 3.3.0 - duplexer3: 0.1.4 - get-stream: 4.1.0 - lowercase-keys: 1.0.1 - mimic-response: 1.0.1 - p-cancelable: 1.1.0 - to-readable-stream: 1.0.0 - url-parse-lax: 3.0.0 - dev: true - - /graceful-fs/4.2.8: - resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==} - - /har-schema/2.0.0: - resolution: {integrity: sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=} - engines: {node: '>=4'} - dev: false - optional: true - - /har-validator/5.1.5: - resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} - engines: {node: '>=6'} - deprecated: this library is no longer supported - dependencies: - ajv: 6.12.6 - har-schema: 2.0.0 - dev: false - optional: true - - /has-flag/3.0.0: - resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} - engines: {node: '>=4'} - dev: true - - /has-flag/4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - /has-symbols/1.0.2: - resolution: {integrity: sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==} - engines: {node: '>= 0.4'} - dev: true - - /has-unicode/2.0.1: - resolution: {integrity: sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=} - optional: true - - /has-yarn/2.1.0: - resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} - engines: {node: '>=8'} - dev: true - - /has/1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - dev: true - - /http-cache-semantics/4.1.0: - resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} - dev: true - - /http-proxy-agent/4.0.1: - resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} - engines: {node: '>= 6'} - dependencies: - '@tootallnate/once': 1.1.2 - agent-base: 6.0.2 - debug: 4.3.2 - transitivePeerDependencies: - - supports-color - dev: true - - /http-signature/1.2.0: - resolution: {integrity: sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=} - engines: {node: '>=0.8', npm: '>=1.3.7'} - dependencies: - assert-plus: 1.0.0 - jsprim: 1.4.1 - sshpk: 1.16.1 - dev: false - optional: true - - /https-proxy-agent/5.0.0: - resolution: {integrity: sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==} - engines: {node: '>= 6'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.2 - transitivePeerDependencies: - - supports-color - dev: true - - /iconv-lite/0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - - /iconv-lite/0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - dev: true - - /ieee754/1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: true - - /ignore-by-default/1.0.1: - resolution: {integrity: sha1-SMptcvbGo68Aqa1K5odr44ieKwk=} - dev: true - - /ignore-walk/3.0.4: - resolution: {integrity: sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==} - dependencies: - minimatch: 3.0.4 - dev: false - optional: true - - /ignore/4.0.6: - resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} - engines: {node: '>= 4'} - dev: true - - /import-fresh/3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: true - - /import-lazy/2.1.0: - resolution: {integrity: sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=} - engines: {node: '>=4'} - dev: true - - /imurmurhash/0.1.4: - resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=} - engines: {node: '>=0.8.19'} - dev: true - - /inflection/1.13.1: - resolution: {integrity: sha512-dldYtl2WlN0QDkIDtg8+xFwOS2Tbmp12t1cHa5/YClU6ZQjTFm7B66UcVbh9NQB+HvT5BAd2t5+yKsBkw5pcqA==} - engines: {'0': node >= 0.4.0} - dev: false - - /inflight/1.0.6: - resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - /inherits/2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - /ini/1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - - /ini/2.0.0: - resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} - engines: {node: '>=10'} - dev: true - - /inquirer/7.3.3: - resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} - engines: {node: '>=8.0.0'} - dependencies: - ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-width: 3.0.0 - external-editor: 3.1.0 - figures: 3.2.0 - lodash: 4.17.21 - mute-stream: 0.0.8 - run-async: 2.4.1 - rxjs: 6.6.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - through: 2.3.8 - dev: true - - /ip-regex/2.1.0: - resolution: {integrity: sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=} - engines: {node: '>=4'} - dev: true - - /is-binary-path/2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - dependencies: - binary-extensions: 2.2.0 - dev: true - - /is-ci/2.0.0: - resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} - hasBin: true - dependencies: - ci-info: 2.0.0 - dev: true - - /is-docker/2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - dev: true - - /is-extglob/2.1.1: - resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} - engines: {node: '>=0.10.0'} - dev: true - - /is-fullwidth-code-point/1.0.0: - resolution: {integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs=} - engines: {node: '>=0.10.0'} - dependencies: - number-is-nan: 1.0.1 - optional: true - - /is-fullwidth-code-point/3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - /is-glob/4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - - /is-installed-globally/0.4.0: - resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} - engines: {node: '>=10'} - dependencies: - global-dirs: 3.0.0 - is-path-inside: 3.0.3 - dev: true - - /is-npm/5.0.0: - resolution: {integrity: sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==} - engines: {node: '>=10'} - dev: true - - /is-number/7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - dev: true - - /is-obj/2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - - /is-path-inside/3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true - - /is-property/1.0.2: - resolution: {integrity: sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=} - dev: true - - /is-typedarray/1.0.0: - resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=} - - /is-wsl/2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - dependencies: - is-docker: 2.2.1 - dev: true - - /is-yarn-global/0.3.0: - resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} - dev: true - - /isarray/1.0.0: - resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=} - optional: true - - /isexe/2.0.0: - resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} - - /isstream/0.1.2: - resolution: {integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=} - dev: false - optional: true - - /js-tokens/4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true - - /js-yaml/3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - dev: true - - /jsbi/3.2.5: - resolution: {integrity: sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ==} - dev: true - - /jsbn/0.1.1: - resolution: {integrity: sha1-peZUwuWi3rXyAdls77yoDA7y9RM=} - dev: false - optional: true - - /json-buffer/3.0.0: - resolution: {integrity: sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=} - dev: true - - /json-buffer/3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: false - - /json-fixer/1.6.12: - resolution: {integrity: sha512-BGO9HExf0ZUVYvuWsps71Re513Ss0il1Wp7wYWkir2NthzincvNJEUu82KagEfAkGdjOMsypj3t2JB7drBKWnA==} - engines: {node: '>=10'} - dependencies: - '@babel/runtime': 7.15.4 - chalk: 4.1.2 - pegjs: 0.10.0 - dev: true - - /json-schema-traverse/0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - /json-schema-traverse/1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: true - - /json-schema/0.2.3: - resolution: {integrity: sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=} - dev: false - optional: true - - /json-stable-stringify-without-jsonify/1.0.1: - resolution: {integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=} - dev: true - - /json-stringify-safe/5.0.1: - resolution: {integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=} - dev: false - optional: true - - /jsonwebtoken/8.5.1: - resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==} - engines: {node: '>=4', npm: '>=1.4.28'} - dependencies: - jws: 3.2.2 - lodash.includes: 4.3.0 - lodash.isboolean: 3.0.3 - lodash.isinteger: 4.0.4 - lodash.isnumber: 3.0.3 - lodash.isplainobject: 4.0.6 - lodash.isstring: 4.0.1 - lodash.once: 4.1.1 - ms: 2.1.3 - semver: 5.7.1 - dev: true - - /jsprim/1.4.1: - resolution: {integrity: sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=} - engines: {'0': node >=0.6.0} - dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.2.3 - verror: 1.10.0 - dev: false - optional: true - - /jwa/1.4.1: - resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 - dev: true - - /jwa/2.0.0: - resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 - dev: true - - /jws/3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} - dependencies: - jwa: 1.4.1 - safe-buffer: 5.2.1 - dev: true - - /jws/4.0.0: - resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} - dependencies: - jwa: 2.0.0 - safe-buffer: 5.2.1 - dev: true - - /keytar/7.7.0: - resolution: {integrity: sha512-YEY9HWqThQc5q5xbXbRwsZTh2PJ36OSYRjSv3NN2xf5s5dpLTjEZnC2YikR29OaVybf9nQ0dJ/80i40RS97t/A==} - requiresBuild: true - dependencies: - node-addon-api: 3.2.1 - prebuild-install: 6.1.4 - dev: true - optional: true - - /keyv/3.1.0: - resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} - dependencies: - json-buffer: 3.0.0 - dev: true - - /keyv/4.0.3: - resolution: {integrity: sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==} - dependencies: - json-buffer: 3.0.1 - dev: false - - /latest-version/5.1.0: - resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==} - engines: {node: '>=8'} - dependencies: - package-json: 6.5.0 - dev: true - - /leeks.js/0.2.4: - resolution: {integrity: sha512-yFR6BtcTA/5s2FJAVkPn2VEzqO76DqBIdkC+1vNyersz1Hw0DxhunRu7bI7/XKxwrNy8x0K4e1p4YQKIGRCBww==} - dev: false - - /leekslazylogger/3.0.2: - resolution: {integrity: sha512-eXgQuEgoSIbtwJRQXy/DHFp1255C4br+x6CtOnrBb5j3GwmeARPMutPur1cMB5YonKBZxFXnvZLqQhr69qKg5A==} - dependencies: - '@eartharoid/deep-merge': 0.0.1 - '@eartharoid/dtf': 1.0.8 - leeks.js: 0.2.4 - dev: false - - /levn/0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /locate-path/5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - dependencies: - p-locate: 4.1.0 - dev: true - - /lodash.clonedeep/4.5.0: - resolution: {integrity: sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=} - dev: true - - /lodash.includes/4.3.0: - resolution: {integrity: sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=} - dev: true - - /lodash.isboolean/3.0.3: - resolution: {integrity: sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=} - dev: true - - /lodash.isequal/4.5.0: - resolution: {integrity: sha1-QVxEePK8wwEgwizhDtMib30+GOA=} - dev: false - - /lodash.isinteger/4.0.4: - resolution: {integrity: sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=} - dev: true - - /lodash.isnumber/3.0.3: - resolution: {integrity: sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=} - dev: true - - /lodash.isplainobject/4.0.6: - resolution: {integrity: sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=} - dev: true - - /lodash.isstring/4.0.1: - resolution: {integrity: sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=} - dev: true - - /lodash.merge/4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true - - /lodash.once/4.1.1: - resolution: {integrity: sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=} - dev: true - - /lodash.truncate/4.4.2: - resolution: {integrity: sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=} - dev: true - - /lodash/4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - /long/4.0.0: - resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} - dev: true - - /lowercase-keys/1.0.1: - resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} - engines: {node: '>=0.10.0'} - dev: true - - /lowercase-keys/2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} - dev: true - - /lru-cache/4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} - dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 - dev: true - - /lru-cache/6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - - /make-dir/3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - dependencies: - semver: 6.3.0 - dev: true - - /mariadb/2.5.4: - resolution: {integrity: sha512-4vQgMRyBIN9EwSQG0vzjR9D8bscPH0dGPJt67qVlOkHSiSm0xUatg1Pft4o1LzORgeOW4PheiY/HBE9bYYmNCA==} - engines: {node: '>= 10.13'} - dependencies: - '@types/geojson': 7946.0.8 - '@types/node': 14.17.21 - denque: 1.5.1 - iconv-lite: 0.6.3 - long: 4.0.0 - moment-timezone: 0.5.33 - please-upgrade-node: 3.2.0 - dev: true - - /mime-db/1.50.0: - resolution: {integrity: sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==} - engines: {node: '>= 0.6'} - - /mime-types/2.1.33: - resolution: {integrity: sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.50.0 - - /mimic-fn/2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - dev: true - - /mimic-response/1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - dev: true - - /mimic-response/2.1.0: - resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} - engines: {node: '>=8'} - dev: true - optional: true - - /minimatch/3.0.4: - resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} - dependencies: - brace-expansion: 1.1.11 - - /minimist/1.2.5: - resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==} - - /minipass/2.9.0: - resolution: {integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==} - dependencies: - safe-buffer: 5.2.1 - yallist: 3.1.1 - dev: false - optional: true - - /minizlib/1.3.3: - resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==} - dependencies: - minipass: 2.9.0 - dev: false - optional: true - - /mkdirp-classic/0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - dev: true - optional: true - - /mkdirp/0.5.5: - resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==} - hasBin: true - dependencies: - minimist: 1.2.5 - dev: false - optional: true - - /moment-timezone/0.5.33: - resolution: {integrity: sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==} - dependencies: - moment: 2.29.1 - - /moment/2.29.1: - resolution: {integrity: sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==} - - /ms/2.0.0: - resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} - dev: true - - /ms/2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - - /ms/2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - /msal/1.4.14: - resolution: {integrity: sha512-k8M5+/jbfSQoCf7CyQzBP5HE5mY8TkBujykLGTEp2x0MvOK/FQsfUTNis28zlvvPVzhgrhb5GQiGM8rRpXyHdA==} - engines: {node: '>=0.8.0'} - dependencies: - tslib: 1.14.1 - dev: true - - /mustache/4.2.0: - resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true - dev: false - - /mute-stream/0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - dev: true - - /mysql2/2.3.0: - resolution: {integrity: sha512-0t5Ivps5Tdy5YHk5NdKwQhe/4Qyn2pload+S+UooDBvsqngtzujG1BaTWBihQLfeKO3t3122/GtusBtmHEHqww==} - engines: {node: '>= 8.0'} - dependencies: - denque: 1.5.1 - generate-function: 2.3.1 - iconv-lite: 0.6.3 - long: 4.0.0 - lru-cache: 6.0.0 - named-placeholders: 1.1.2 - seq-queue: 0.0.5 - sqlstring: 2.3.2 - dev: true - - /named-placeholders/1.1.2: - resolution: {integrity: sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==} - engines: {node: '>=6.0.0'} - dependencies: - lru-cache: 4.1.5 - dev: true - - /napi-build-utils/1.0.2: - resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - dev: true - optional: true - - /native-duplexpair/1.0.0: - resolution: {integrity: sha1-eJkHjmS/PIo9cyYBs9QP8F21j6A=} - dev: true - - /natural-compare/1.4.0: - resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} - dev: true - - /needle/2.9.1: - resolution: {integrity: sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==} - engines: {node: '>= 4.4.x'} - hasBin: true - dependencies: - debug: 3.2.7 - iconv-lite: 0.4.24 - sax: 1.2.4 - dev: false - optional: true - - /node-abi/2.30.1: - resolution: {integrity: sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==} - dependencies: - semver: 5.7.1 - dev: true - optional: true - - /node-abort-controller/2.0.0: - resolution: {integrity: sha512-L8RfEgjBTHAISTuagw51PprVAqNZoG6KSB6LQ6H1bskMVkFs5E71IyjauLBv3XbuomJlguWF/VnRHdJ1gqiAqA==} - dev: true - - /node-addon-api/3.2.1: - resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} - optional: true - - /node-fetch/2.6.5: - resolution: {integrity: sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==} - engines: {node: 4.x || >=6.0.0} - dependencies: - whatwg-url: 5.0.0 - - /node-gyp/3.8.0: - resolution: {integrity: sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==} - engines: {node: '>= 0.8.0'} - hasBin: true - dependencies: - fstream: 1.0.12 - glob: 7.2.0 - graceful-fs: 4.2.8 - mkdirp: 0.5.5 - nopt: 3.0.6 - npmlog: 4.1.2 - osenv: 0.1.5 - request: 2.88.2 - rimraf: 2.7.1 - semver: 5.3.0 - tar: 2.2.2 - which: 1.3.1 - dev: false - optional: true - - /node-pre-gyp/0.11.0: - resolution: {integrity: sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==} - deprecated: 'Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future' - hasBin: true - dependencies: - detect-libc: 1.0.3 - mkdirp: 0.5.5 - needle: 2.9.1 - nopt: 4.0.3 - npm-packlist: 1.4.8 - npmlog: 4.1.2 - rc: 1.2.8 - rimraf: 2.7.1 - semver: 5.7.1 - tar: 4.4.19 - dev: false - optional: true - - /nodemon/2.0.13: - resolution: {integrity: sha512-UMXMpsZsv1UXUttCn6gv8eQPhn6DR4BW+txnL3IN5IHqrCwcrT/yWHfL35UsClGXknTH79r5xbu+6J1zNHuSyA==} - engines: {node: '>=8.10.0'} - hasBin: true - requiresBuild: true - dependencies: - chokidar: 3.5.2 - debug: 3.2.7 - ignore-by-default: 1.0.1 - minimatch: 3.0.4 - pstree.remy: 1.1.8 - semver: 5.7.1 - supports-color: 5.5.0 - touch: 3.1.0 - undefsafe: 2.0.3 - update-notifier: 5.1.0 - dev: true - - /nopt/1.0.10: - resolution: {integrity: sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=} - hasBin: true - dependencies: - abbrev: 1.1.1 - dev: true - - /nopt/3.0.6: - resolution: {integrity: sha1-xkZdvwirzU2zWTF/eaxopkayj/k=} - hasBin: true - dependencies: - abbrev: 1.1.1 - dev: false - optional: true - - /nopt/4.0.3: - resolution: {integrity: sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==} - hasBin: true - dependencies: - abbrev: 1.1.1 - osenv: 0.1.5 - dev: false - optional: true - - /normalize-path/3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - dev: true - - /normalize-url/4.5.1: - resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} - engines: {node: '>=8'} - dev: true - - /npm-bundled/1.1.2: - resolution: {integrity: sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==} - dependencies: - npm-normalize-package-bin: 1.0.1 - dev: false - optional: true - - /npm-normalize-package-bin/1.0.1: - resolution: {integrity: sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==} - dev: false - optional: true - - /npm-packlist/1.4.8: - resolution: {integrity: sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==} - dependencies: - ignore-walk: 3.0.4 - npm-bundled: 1.1.2 - npm-normalize-package-bin: 1.0.1 - dev: false - optional: true - - /npmlog/4.1.2: - resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==} - dependencies: - are-we-there-yet: 1.1.7 - console-control-strings: 1.1.0 - gauge: 2.7.4 - set-blocking: 2.0.0 - optional: true - - /number-is-nan/1.0.1: - resolution: {integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=} - engines: {node: '>=0.10.0'} - optional: true - - /oauth-sign/0.9.0: - resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} - dev: false - optional: true - - /object-assign/4.1.1: - resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} - engines: {node: '>=0.10.0'} - optional: true - - /object-inspect/1.11.0: - resolution: {integrity: sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==} - dev: true - - /once/1.4.0: - resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} - dependencies: - wrappy: 1.0.2 - - /onetime/5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - dependencies: - mimic-fn: 2.1.0 - dev: true - - /open/7.4.2: - resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} - engines: {node: '>=8'} - dependencies: - is-docker: 2.2.1 - is-wsl: 2.2.0 - dev: true - - /optionator/0.9.1: - resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} - engines: {node: '>= 0.8.0'} - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.3 - dev: true - - /os-homedir/1.0.2: - resolution: {integrity: sha1-/7xJiDNuDoM94MFox+8VISGqf7M=} - engines: {node: '>=0.10.0'} - dev: false - optional: true - - /os-tmpdir/1.0.2: - resolution: {integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=} - engines: {node: '>=0.10.0'} - - /osenv/0.1.5: - resolution: {integrity: sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==} - dependencies: - os-homedir: 1.0.2 - os-tmpdir: 1.0.2 - dev: false - optional: true - - /ow/0.27.0: - resolution: {integrity: sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==} - engines: {node: '>=12'} - dependencies: - '@sindresorhus/is': 4.2.0 - callsites: 3.1.0 - dot-prop: 6.0.1 - lodash.isequal: 4.5.0 - type-fest: 1.4.0 - vali-date: 1.0.0 - dev: false - - /p-cancelable/1.1.0: - resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} - engines: {node: '>=6'} - dev: true - - /p-limit/2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - dependencies: - p-try: 2.2.0 - dev: true - - /p-locate/4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - dependencies: - p-limit: 2.3.0 - dev: true - - /p-try/2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - dev: true - - /package-json/6.5.0: - resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} - engines: {node: '>=8'} - dependencies: - got: 9.6.0 - registry-auth-token: 4.2.1 - registry-url: 5.1.0 - semver: 6.3.0 - dev: true - - /packet-reader/1.0.0: - resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} - dev: true - - /parent-module/1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: true - - /path-exists/4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true - - /path-is-absolute/1.0.1: - resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} - engines: {node: '>=0.10.0'} - - /path-key/3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true - - /pegjs/0.10.0: - resolution: {integrity: sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=} - engines: {node: '>=0.10'} - hasBin: true - dev: true - - /performance-now/2.1.0: - resolution: {integrity: sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=} - dev: false - optional: true - - /pg-connection-string/2.5.0: - resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==} - dev: true - - /pg-hstore/2.3.4: - resolution: {integrity: sha512-N3SGs/Rf+xA1M2/n0JBiXFDVMzdekwLZLAO0g7mpDY9ouX+fDI7jS6kTq3JujmYbtNSJ53TJ0q4G98KVZSM4EA==} - engines: {node: '>= 0.8.x'} - dependencies: - underscore: 1.13.1 - dev: true - - /pg-int8/1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - dev: true - - /pg-pool/3.4.1_pg@8.7.1: - resolution: {integrity: sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==} - peerDependencies: - pg: '>=8.0' - dependencies: - pg: 8.7.1 - dev: true - - /pg-protocol/1.5.0: - resolution: {integrity: sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==} - dev: true - - /pg-types/2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} - dependencies: - pg-int8: 1.0.1 - postgres-array: 2.0.0 - postgres-bytea: 1.0.0 - postgres-date: 1.0.7 - postgres-interval: 1.2.0 - dev: true - - /pg/8.7.1: - resolution: {integrity: sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==} - engines: {node: '>= 8.0.0'} - peerDependencies: - pg-native: '>=2.0.0' - peerDependenciesMeta: - pg-native: - optional: true - dependencies: - buffer-writer: 2.0.0 - packet-reader: 1.0.0 - pg-connection-string: 2.5.0 - pg-pool: 3.4.1_pg@8.7.1 - pg-protocol: 1.5.0 - pg-types: 2.2.0 - pgpass: 1.0.4 - dev: true - - /pgpass/1.0.4: - resolution: {integrity: sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==} - dependencies: - split2: 3.2.2 - dev: true - - /picomatch/2.3.0: - resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==} - engines: {node: '>=8.6'} - dev: true - - /pify/5.0.0: - resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} - engines: {node: '>=10'} - dev: true - - /please-upgrade-node/3.2.0: - resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} - dependencies: - semver-compare: 1.0.0 - dev: true - - /postgres-array/2.0.0: - resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} - engines: {node: '>=4'} - dev: true - - /postgres-bytea/1.0.0: - resolution: {integrity: sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=} - engines: {node: '>=0.10.0'} - dev: true - - /postgres-date/1.0.7: - resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} - engines: {node: '>=0.10.0'} - dev: true - - /postgres-interval/1.2.0: - resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} - engines: {node: '>=0.10.0'} - dependencies: - xtend: 4.0.2 - dev: true - - /prebuild-install/6.1.4: - resolution: {integrity: sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==} - engines: {node: '>=6'} - hasBin: true - dependencies: - detect-libc: 1.0.3 - expand-template: 2.0.3 - github-from-package: 0.0.0 - minimist: 1.2.5 - mkdirp-classic: 0.5.3 - napi-build-utils: 1.0.2 - node-abi: 2.30.1 - npmlog: 4.1.2 - pump: 3.0.0 - rc: 1.2.8 - simple-get: 3.1.0 - tar-fs: 2.1.1 - tunnel-agent: 0.6.0 - dev: true - optional: true - - /prelude-ls/1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true - - /prepend-http/2.0.0: - resolution: {integrity: sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=} - engines: {node: '>=4'} - dev: true - - /process-nextick-args/2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - optional: true - - /process/0.11.10: - resolution: {integrity: sha1-czIwDoQBYb2j5podHZGn1LwW8YI=} - engines: {node: '>= 0.6.0'} - dev: true - - /progress/2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - dev: true - - /pseudomap/1.0.2: - resolution: {integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM=} - dev: true - - /psl/1.8.0: - resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==} - - /pstree.remy/1.1.8: - resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} - dev: true - - /pump/3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - dev: true - - /punycode/2.1.1: - resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} - engines: {node: '>=6'} - - /pupa/2.1.1: - resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} - engines: {node: '>=8'} - dependencies: - escape-goat: 2.1.1 - dev: true - - /qs/6.10.1: - resolution: {integrity: sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==} - engines: {node: '>=0.6'} - dependencies: - side-channel: 1.0.4 - dev: true - - /qs/6.5.2: - resolution: {integrity: sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==} - engines: {node: '>=0.6'} - dev: false - optional: true - - /rc/1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.5 - strip-json-comments: 2.0.1 - - /readable-stream/2.3.7: - resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - optional: true - - /readable-stream/3.6.0: - resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} - engines: {node: '>= 6'} - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - dev: true - - /readdirp/3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.0 - dev: true - - /regenerator-runtime/0.13.9: - resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} - dev: true - - /regexpp/3.2.0: - resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} - engines: {node: '>=8'} - dev: true - - /registry-auth-token/4.2.1: - resolution: {integrity: sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==} - engines: {node: '>=6.0.0'} - dependencies: - rc: 1.2.8 - dev: true - - /registry-url/5.1.0: - resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} - engines: {node: '>=8'} - dependencies: - rc: 1.2.8 - dev: true - - /request/2.88.2: - resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} - engines: {node: '>= 6'} - deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 - dependencies: - aws-sign2: 0.7.0 - aws4: 1.11.0 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 2.3.3 - har-validator: 5.1.5 - http-signature: 1.2.0 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.33 - oauth-sign: 0.9.0 - performance-now: 2.1.0 - qs: 6.5.2 - safe-buffer: 5.2.1 - tough-cookie: 2.5.0 - tunnel-agent: 0.6.0 - uuid: 3.4.0 - dev: false - optional: true - - /require-directory/2.1.1: - resolution: {integrity: sha1-jGStX9MNqxyXbiNE/+f3kqam30I=} - engines: {node: '>=0.10.0'} - dev: true - - /require-from-string/2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - dev: true - - /require-main-filename/2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - dev: true - - /resolve-from/4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true - - /responselike/1.0.2: - resolution: {integrity: sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=} - dependencies: - lowercase-keys: 1.0.1 - dev: true - - /restore-cursor/3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.5 - dev: true - - /retry-as-promised/3.2.0: - resolution: {integrity: sha512-CybGs60B7oYU/qSQ6kuaFmRd9sTZ6oXSc0toqePvV74Ac6/IFZSI1ReFQmtCN+uvW1Mtqdwpvt/LGOiCBAY2Mg==} - dependencies: - any-promise: 1.3.0 - dev: false - - /rimraf/2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - hasBin: true - dependencies: - glob: 7.2.0 - dev: false - optional: true - - /rimraf/3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - dependencies: - glob: 7.2.0 - dev: true - - /run-async/2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} - dev: true - - /rxjs/6.6.7: - resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} - engines: {npm: '>=2.0.0'} - dependencies: - tslib: 1.14.1 - dev: true - - /safe-buffer/5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - optional: true - - /safe-buffer/5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - /safer-buffer/2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - /sax/1.2.4: - resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} - - /semver-compare/1.0.0: - resolution: {integrity: sha1-De4hahyUGrN+nvsXiPavxf9VN/w=} - dev: true - - /semver-diff/3.1.1: - resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==} - engines: {node: '>=8'} - dependencies: - semver: 6.3.0 - dev: true - - /semver/5.3.0: - resolution: {integrity: sha1-myzl094C0XxgEq0yaqa00M9U+U8=} - hasBin: true - dev: false - optional: true - - /semver/5.7.1: - resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} - hasBin: true - - /semver/6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true - dev: true - - /semver/7.3.5: - resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - - /seq-queue/0.0.5: - resolution: {integrity: sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=} - dev: true - - /sequelize-pool/6.1.0: - resolution: {integrity: sha512-4YwEw3ZgK/tY/so+GfnSgXkdwIJJ1I32uZJztIEgZeAO6HMgj64OzySbWLgxj+tXhZCJnzRfkY9gINw8Ft8ZMg==} - engines: {node: '>= 10.0.0'} - dev: false - - /sequelize/6.6.5_348d7a9ed83738128221dabb96d70298: - resolution: {integrity: sha512-QyRrJrDRiwuiILqTMHUA1yWOPIL12KlfmgZ3hnzQwbMvp2vJ6fzu9bYJQB+qPMosck4mBUggY4Cjoc6Et8FBIQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - mariadb: '*' - mysql2: '*' - pg: '*' - pg-hstore: '*' - sqlite3: '*' - tedious: '*' - peerDependenciesMeta: - mariadb: - optional: true - mysql2: - optional: true - pg: - optional: true - pg-hstore: - optional: true - sqlite3: - optional: true - tedious: - optional: true - dependencies: - debug: 4.3.2 - dottie: 2.0.2 - inflection: 1.13.1 - lodash: 4.17.21 - mariadb: 2.5.4 - moment: 2.29.1 - moment-timezone: 0.5.33 - mysql2: 2.3.0 - pg: 8.7.1 - pg-hstore: 2.3.4 - retry-as-promised: 3.2.0 - semver: 7.3.5 - sequelize-pool: 6.1.0 - sqlite3: 5.0.2 - tedious: 11.8.0 - toposort-class: 1.0.1 - uuid: 8.3.2 - validator: 13.6.0 - wkx: 0.5.0 - transitivePeerDependencies: - - supports-color - dev: false - - /set-blocking/2.0.0: - resolution: {integrity: sha1-BF+XgtARrppoA93TgrJDkrPYkPc=} - - /shebang-command/2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: true - - /shebang-regex/3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true - - /side-channel/1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.1.1 - object-inspect: 1.11.0 - dev: true - - /signal-exit/3.0.5: - resolution: {integrity: sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==} - - /simple-concat/1.0.1: - resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - dev: true - optional: true - - /simple-get/3.1.0: - resolution: {integrity: sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==} - dependencies: - decompress-response: 4.2.1 - once: 1.4.0 - simple-concat: 1.0.1 - dev: true - optional: true - - /slice-ansi/4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - - /split2/3.2.2: - resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} - dependencies: - readable-stream: 3.6.0 - dev: true - - /sprintf-js/1.0.3: - resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} - dev: true - - /sprintf-js/1.1.2: - resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} - dev: true - - /sqlite3/5.0.2: - resolution: {integrity: sha512-1SdTNo+BVU211Xj1csWa8lV6KM0CtucDwRyA0VHl91wEH1Mgh7RxUpI4rVvG7OhHrzCSGaVyW5g8vKvlrk9DJA==} - requiresBuild: true - peerDependenciesMeta: - node-gyp: - optional: true - dependencies: - node-addon-api: 3.2.1 - node-pre-gyp: 0.11.0 - optionalDependencies: - node-gyp: 3.8.0 - dev: false - optional: true - - /sqlstring/2.3.2: - resolution: {integrity: sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==} - engines: {node: '>= 0.6'} - dev: true - - /sshpk/1.16.1: - resolution: {integrity: sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==} - engines: {node: '>=0.10.0'} - hasBin: true - dependencies: - asn1: 0.2.4 - assert-plus: 1.0.0 - bcrypt-pbkdf: 1.0.2 - dashdash: 1.14.1 - ecc-jsbn: 0.1.2 - getpass: 0.1.7 - jsbn: 0.1.1 - safer-buffer: 2.1.2 - tweetnacl: 0.14.5 - dev: false - optional: true - - /stoppable/1.1.0: - resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} - engines: {node: '>=4', npm: '>=6'} - dev: true - - /string-width/1.0.2: - resolution: {integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=} - engines: {node: '>=0.10.0'} - dependencies: - code-point-at: 1.1.0 - is-fullwidth-code-point: 1.0.0 - strip-ansi: 3.0.1 - optional: true - - /string-width/4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - /string_decoder/1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - dependencies: - safe-buffer: 5.1.2 - optional: true - - /string_decoder/1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - dependencies: - safe-buffer: 5.2.1 - dev: true - - /strip-ansi/3.0.1: - resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=} - engines: {node: '>=0.10.0'} - dependencies: - ansi-regex: 2.1.1 - optional: true - - /strip-ansi/6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - - /strip-json-comments/2.0.1: - resolution: {integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo=} - engines: {node: '>=0.10.0'} - - /strip-json-comments/3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true - - /supports-color/5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - dev: true - - /supports-color/7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - - /supports-hyperlinks/2.2.0: - resolution: {integrity: sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - supports-color: 7.2.0 - dev: false - - /table/6.7.2: - resolution: {integrity: sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==} - engines: {node: '>=10.0.0'} - dependencies: - ajv: 8.6.3 - lodash.clonedeep: 4.5.0 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - - /tar-fs/2.1.1: - resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.0 - tar-stream: 2.2.0 - dev: true - optional: true - - /tar-stream/2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.4 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.0 - dev: true - optional: true - - /tar/2.2.2: - resolution: {integrity: sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==} - deprecated: This version of tar is no longer supported, and will not receive security updates. Please upgrade asap. - dependencies: - block-stream: 0.0.9 - fstream: 1.0.12 - inherits: 2.0.4 - dev: false - optional: true - - /tar/4.4.19: - resolution: {integrity: sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==} - engines: {node: '>=4.5'} - dependencies: - chownr: 1.1.4 - fs-minipass: 1.2.7 - minipass: 2.9.0 - minizlib: 1.3.3 - mkdirp: 0.5.5 - safe-buffer: 5.2.1 - yallist: 3.1.1 - dev: false - optional: true - - /tedious/11.8.0: - resolution: {integrity: sha512-GtFrO694x/7CRiUBt0AI4jrMtrkXV+ywifiOrDy4K0ufJLeKB4rgmPjy5Ws366fCaBaKlqQ9RnJ+sCJ1Jbd1lw==} - engines: {node: '>= 10'} - dependencies: - '@azure/identity': 1.5.2 - '@azure/keyvault-keys': 4.3.0 - '@azure/ms-rest-nodeauth': 3.1.0 - '@js-joda/core': 3.2.0 - adal-node: 0.2.3 - bl: 5.0.0 - depd: 2.0.0 - iconv-lite: 0.6.3 - jsbi: 3.2.5 - native-duplexpair: 1.0.0 - node-abort-controller: 2.0.0 - punycode: 2.1.1 - sprintf-js: 1.1.2 - transitivePeerDependencies: - - debug - - supports-color - dev: true - - /terminal-link/2.1.1: - resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} - engines: {node: '>=8'} - dependencies: - ansi-escapes: 4.3.2 - supports-hyperlinks: 2.2.0 - dev: false - - /text-table/0.2.0: - resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=} - dev: true - - /through/2.3.8: - resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=} - dev: true - - /tmp/0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - dependencies: - os-tmpdir: 1.0.2 - dev: true - - /to-readable-stream/1.0.0: - resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} - engines: {node: '>=6'} - dev: true - - /to-regex-range/5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - dev: true - - /toposort-class/1.0.1: - resolution: {integrity: sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=} - dev: false - - /touch/3.1.0: - resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} - hasBin: true - dependencies: - nopt: 1.0.10 - dev: true - - /tough-cookie/2.5.0: - resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} - engines: {node: '>=0.8'} - dependencies: - psl: 1.8.0 - punycode: 2.1.1 - dev: false - optional: true - - /tough-cookie/3.0.1: - resolution: {integrity: sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==} - engines: {node: '>=6'} - dependencies: - ip-regex: 2.1.0 - psl: 1.8.0 - punycode: 2.1.1 - dev: true - - /tough-cookie/4.0.0: - resolution: {integrity: sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==} - engines: {node: '>=6'} - dependencies: - psl: 1.8.0 - punycode: 2.1.1 - universalify: 0.1.2 - dev: true - - /tr46/0.0.3: - resolution: {integrity: sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=} - - /ts-mixer/6.0.0: - resolution: {integrity: sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ==} - dev: false - - /tslib/1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true - - /tslib/2.3.1: - resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==} - - /tunnel-agent/0.6.0: - resolution: {integrity: sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=} - dependencies: - safe-buffer: 5.2.1 - optional: true - - /tunnel/0.0.6: - resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} - engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} - dev: true - - /tweetnacl/0.14.5: - resolution: {integrity: sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=} - dev: false - optional: true - - /type-check/0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - dev: true - - /type-fest/0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - /type-fest/0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - /type-fest/1.4.0: - resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} - engines: {node: '>=10'} - dev: false - - /typedarray-to-buffer/3.1.5: - resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - dependencies: - is-typedarray: 1.0.0 - dev: true - - /undefsafe/2.0.3: - resolution: {integrity: sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==} - dependencies: - debug: 2.6.9 - dev: true - - /underscore/1.13.1: - resolution: {integrity: sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==} - dev: true - - /unique-string/2.0.0: - resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} - engines: {node: '>=8'} - dependencies: - crypto-random-string: 2.0.0 - dev: true - - /universalify/0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - dev: true - - /update-notifier/5.1.0: - resolution: {integrity: sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==} - engines: {node: '>=10'} - dependencies: - boxen: 5.1.2 - chalk: 4.1.2 - configstore: 5.0.1 - has-yarn: 2.1.0 - import-lazy: 2.1.0 - is-ci: 2.0.0 - is-installed-globally: 0.4.0 - is-npm: 5.0.0 - is-yarn-global: 0.3.0 - latest-version: 5.1.0 - pupa: 2.1.1 - semver: 7.3.5 - semver-diff: 3.1.1 - xdg-basedir: 4.0.0 - dev: true - - /uri-js/4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.1.1 - - /url-parse-lax/3.0.0: - resolution: {integrity: sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=} - engines: {node: '>=4'} - dependencies: - prepend-http: 2.0.0 - dev: true - - /util-deprecate/1.0.2: - resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} - - /uuid/3.4.0: - resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. - hasBin: true - - /uuid/8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - - /v8-compile-cache/2.3.0: - resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} - dev: true - - /vali-date/1.0.0: - resolution: {integrity: sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=} - engines: {node: '>=0.10.0'} - dev: false - - /validator/13.6.0: - resolution: {integrity: sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==} - engines: {node: '>= 0.10'} - dev: false - - /verror/1.10.0: - resolution: {integrity: sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=} - engines: {'0': node >=0.6.0} - dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.3.0 - dev: false - optional: true - - /webidl-conversions/3.0.1: - resolution: {integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=} - - /whatwg-url/5.0.0: - resolution: {integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0=} - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - - /which-module/2.0.0: - resolution: {integrity: sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=} - dev: true - - /which/1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: false - optional: true - - /which/2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - - /wide-align/1.1.3: - resolution: {integrity: sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==} - dependencies: - string-width: 1.0.2 - optional: true - - /widest-line/3.1.0: - resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} - engines: {node: '>=8'} - dependencies: - string-width: 4.2.3 - - /wkx/0.5.0: - resolution: {integrity: sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==} - dependencies: - '@types/node': 16.10.3 - dev: false - - /word-wrap/1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} - engines: {node: '>=0.10.0'} - dev: true - - /wrap-ansi/6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - - /wrap-ansi/7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - /wrappy/1.0.2: - resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} - - /write-file-atomic/3.0.3: - resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} - dependencies: - imurmurhash: 0.1.4 - is-typedarray: 1.0.0 - signal-exit: 3.0.5 - typedarray-to-buffer: 3.1.5 - dev: true - - /ws/8.2.3: - resolution: {integrity: sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: false - - /xdg-basedir/4.0.0: - resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} - engines: {node: '>=8'} - dev: true - - /xml2js/0.4.23: - resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} - engines: {node: '>=4.0.0'} - dependencies: - sax: 1.2.4 - xmlbuilder: 11.0.1 - dev: true - - /xmlbuilder/11.0.1: - resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} - engines: {node: '>=4.0'} - dev: true - - /xpath.js/1.1.0: - resolution: {integrity: sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==} - engines: {node: '>=0.4.0'} - dev: true - - /xtend/4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - dev: true - - /y18n/4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - dev: true - - /yallist/2.1.2: - resolution: {integrity: sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=} - dev: true - - /yallist/3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - dev: false - optional: true - - /yallist/4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - /yargs-parser/18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - dev: true - - /yargs/15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} - dependencies: - cliui: 6.0.0 - decamelize: 1.2.0 - find-up: 4.1.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 4.2.3 - which-module: 2.0.0 - y18n: 4.0.3 - yargs-parser: 18.1.3 - dev: true diff --git a/pterodactyl.egg.json b/pterodactyl.egg.json deleted file mode 100644 index 9a08b16..0000000 --- a/pterodactyl.egg.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "_comment": "DO NOT EDIT: FILE GENERATED AUTOMATICALLY BY PTERODACTYL PANEL - PTERODACTYL.IO", - "meta": { - "version": "PTDL_v1", - "update_url": null - }, - "exported_at": "2021-05-26T22:41:44+02:00", - "name": "Discord Tickets", - "author": "contact@discordtickets.app", - "description": "https:\/\/discordtickets.app", - "features": null, - "images": [ - "ghcr.io\/parkervcp\/yolks:nodejs_16" - ], - "file_denylist": [], - "startup": "if [[ ! -z ${VERSION} ]]; then\r\n echo -e \\\"Using version ${VERSION}\\\";\r\nelse\r\n echo -e \\\"Please set the VERSION variable \\(e.g. v3.1.1\\)\\\";\r\n exit 0;\r\nfi;\r\n\r\nif [[ -d .git ]]; then\r\n git fetch --all --tags;\r\n git checkout tags\/${VERSION};\r\nfi;\r\n\r\nif [ -f \/home\/container\/package.json ]; then\r\n \/usr\/local\/bin\/npm install --production;\r\nfi;\r\n\r\nif [[ ! -z ${PLUGINS} ]]; then\r\n \/usr\/local\/bin\/npm install ${PLUGINS} --no-save;\r\nfi;\r\n\r\n\/usr\/local\/bin\/npm start", - "config": { - "files": "{}", - "startup": "{\r\n \"done\": \"Connected to Discord as \",\r\n \"userInteraction\": [\r\n \"Please set your bot's \\\"DISCORD_TOKEN\\\" in \\\".\/.env\\\".\"\r\n ]\r\n}", - "logs": "{}", - "stop": "^C^C" - }, - "scripts": { - "installation": { - "script": "#!\/bin\/bash\r\n\r\napt update\r\napt install -y git curl jq file unzip make gcc g++ python python-dev libtool\r\n\r\nmkdir -p \/mnt\/server\r\ncd \/mnt\/server\r\n\r\nif [[ ! -z ${VERSION} ]]; then\r\n echo -e \\\"Using version ${VERSION}\\\"\r\nelse\r\n echo -e \\\"Please set the VERSION variable \\(e.g. v3.0.0\\)\\\"\r\n exit 1\r\nfi\r\n\r\nif [ \\\"$(ls -A \/mnt\/server)\\\" ] && [[ -d .git ]]; then\r\n echo -e \\\".git directory exists\\\"\r\n if [ -f .git\\\/config ]; then\r\n echo \\\"Upating...\\\"\r\n git fetch --all --tags\r\n git checkout tags\/${VERSION}\r\n else\r\n echo -e \\\"files found with no git config\\\"\r\n echo -e \\\"closing out without touching things to not break anything\\\"\r\n exit 1\r\n fi\r\nelse\r\n echo -e \\\"Cloning...\\\"\r\n git clone https:\/\/github.com\/discord-tickets\/bot.git .\r\n\tgit checkout tags\/${VERSION}\r\nfi\r\n\r\necho \\\"Installing dependencies\\\"\r\n\r\nif [[ ! -z ${PUGINS} ]]; then\r\n \/usr\/local\/bin\/npm install ${PUGINS}\r\nfi\r\n\r\nif [ -f \/mnt\/server\/package.json ]; then\r\n \/usr\/local\/bin\/npm install --production\r\nfi\r\n\r\necho -e \\\"Installed\\\"\r\nexit 0", - "container": "node:14-buster-slim", - "entrypoint": "bash" - } - }, - "variables": [ - { - "name": "Version", - "description": "The version of the bot to use.", - "env_variable": "VERSION", - "default_value": "v3.1.1", - "user_viewable": true, - "user_editable": true, - "rules": "required|string|max:20" - }, - { - "name": "Plugins", - "description": "A list of extra NPM package names to install as plugins, separated by a space: \"example1 exmaple2 example3\"", - "env_variable": "PLUGINS", - "default_value": "", - "user_viewable": true, - "user_editable": true, - "rules": "nullable|string" - } - ] -} diff --git a/src/banner.js b/src/banner.js deleted file mode 100644 index daec75d..0000000 --- a/src/banner.js +++ /dev/null @@ -1,30 +0,0 @@ -/* eslint-disable no-console */ -const link = require('terminal-link'); -const leeks = require('leeks.js'); - -const { - version, homepage -} = require('../package.json'); - -module.exports = () => { - console.log(leeks.colours.cyan(` -######## #### ###### ###### ####### ######## ######## -## ## ## ## ## ## ## ## ## ## ## ## ## -## ## ## ## ## ## ## ## ## ## ## -## ## ## ###### ## ## ## ######## ## ## -## ## ## ## ## ## ## ## ## ## ## -## ## ## ## ## ## ## ## ## ## ## ## ## -######## #### ###### ###### ####### ## ## ######## - -######## #### ###### ## ## ######## ######## ###### - ## ## ## ## ## ## ## ## ## ## - ## ## ## ## ## ## ## ## - ## ## ## ##### ###### ## ###### - ## ## ## ## ## ## ## ## - ## ## ## ## ## ## ## ## ## ## - ## #### ###### ## ## ######## ## ###### -`)); - console.log(leeks.colours.cyanBright(`Discord Tickets bot v${version} by eartharoid`)); - console.log(leeks.colours.cyanBright(homepage + '\n')); - console.log(leeks.colours.cyanBright(link('Sponsor this project', 'https://discordtickets.app/sponsor') + '\n')); -}; diff --git a/src/commands/add.js b/src/commands/add.js deleted file mode 100644 index b6afbd0..0000000 --- a/src/commands/add.js +++ /dev/null @@ -1,118 +0,0 @@ -const Command = require('../modules/commands/command'); -const { - Interaction, // eslint-disable-line no-unused-vars - MessageEmbed -} = require('discord.js'); - -module.exports = class AddCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.add.description'), - internal: true, - name: i18n('commands.add.name'), - options: [ - { - description: i18n('commands.add.options.member.description'), - name: i18n('commands.add.options.member.name'), - required: true, - type: Command.option_types.USER - }, - { - description: i18n('commands.add.options.ticket.description'), - name: i18n('commands.add.options.ticket.name'), - required: false, - type: Command.option_types.CHANNEL - } - ] - }); - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale - const i18n = this.client.i18n.getLocale(settings.locale); - - const channel = interaction.options.getChannel(default_i18n('commands.add.options.ticket.name')) ?? interaction.channel; - const t_row = await this.client.tickets.resolve(channel.id, interaction.guild.id); - - if (!t_row) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.add.response.not_a_ticket.title')) - .setDescription(i18n('commands.add.response.not_a_ticket.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - const member = interaction.options.getMember(default_i18n('commands.add.options.member.name')); - - if (!member) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.add.response.no_member.title')) - .setDescription(i18n('commands.add.response.no_member.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - if (t_row.creator !== interaction.member.id && !await this.client.utils.isStaff(interaction.member)) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.add.response.no_permission.title')) - .setDescription(i18n('commands.add.response.no_permission.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setAuthor(member.user.username, member.user.displayAvatarURL()) - .setTitle(i18n('commands.add.response.added.title')) - .setDescription(i18n('commands.add.response.added.description', member.toString(), channel.toString())) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - - await channel.send({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setAuthor(member.user.username, member.user.displayAvatarURL()) - .setTitle(i18n('ticket.member_added.title')) - .setDescription(i18n('ticket.member_added.description', member.toString(), interaction.user.toString())) - .setFooter(settings.footer, interaction.guild.iconURL()) - ] - }); - - await channel.permissionOverwrites.edit(member, { - ATTACH_FILES: true, - READ_MESSAGE_HISTORY: true, - SEND_MESSAGES: true, - VIEW_CHANNEL: true - }, `${interaction.user.tag} added ${member.user.tag} to the ticket`); - - await this.client.tickets.archives.updateMember(channel.id, member); - - this.client.log.info(`${interaction.user.tag} added ${member.user.tag} to ${channel.id}`); - } -}; diff --git a/src/commands/blacklist.js b/src/commands/blacklist.js deleted file mode 100644 index 29c7997..0000000 --- a/src/commands/blacklist.js +++ /dev/null @@ -1,156 +0,0 @@ -const Command = require('../modules/commands/command'); -const { - Interaction, // eslint-disable-line no-unused-vars - MessageEmbed, - Role -} = require('discord.js'); - -module.exports = class BlacklistCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.blacklist.description'), - internal: true, - name: i18n('commands.blacklist.name'), - options: [ - { - description: i18n('commands.blacklist.options.add.description'), - name: i18n('commands.blacklist.options.add.name'), - options: [ - { - description: i18n('commands.blacklist.options.add.options.member_or_role.description'), - name: i18n('commands.blacklist.options.add.options.member_or_role.name'), - required: true, - type: Command.option_types.MENTIONABLE - } - ], - type: Command.option_types.SUB_COMMAND - }, - { - description: i18n('commands.blacklist.options.remove.description'), - name: i18n('commands.blacklist.options.remove.name'), - options: [ - { - description: i18n('commands.blacklist.options.remove.options.member_or_role.description'), - name: i18n('commands.blacklist.options.remove.options.member_or_role.name'), - required: true, - type: Command.option_types.MENTIONABLE - } - ], - type: Command.option_types.SUB_COMMAND - }, - { - description: i18n('commands.blacklist.options.show.description'), - name: i18n('commands.blacklist.options.show.name'), - type: Command.option_types.SUB_COMMAND - } - ], - staff_only: true - }); - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale - const i18n = this.client.i18n.getLocale(settings.locale); - const blacklist = JSON.parse(JSON.stringify(settings.blacklist)); // not the same as `const blacklist = { ...settings.blacklist };` ..? - - switch (interaction.options.getSubcommand()) { - case default_i18n('commands.blacklist.options.add.name'): { - const member_or_role = interaction.options.getMentionable(default_i18n('commands.blacklist.options.add.options.member_or_role.name')); - const type = member_or_role instanceof Role ? 'role' : 'member'; - - if (type === 'member' && await this.client.utils.isStaff(member_or_role)) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.blacklist.response.illegal_action.title')) - .setDescription(i18n('commands.blacklist.response.illegal_action.description', member_or_role.toString())) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - blacklist[type + 's'].push(member_or_role.id); - await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setTitle(i18n(`commands.blacklist.response.${type}_added.title`)) - .setDescription(i18n(`commands.blacklist.response.${type}_added.description`, member_or_role.id)) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - await settings.update({ blacklist }); - break; - } - case default_i18n('commands.blacklist.options.remove.name'): { - const member_or_role = interaction.options.getMentionable(default_i18n('commands.blacklist.options.remove.options.member_or_role.name')); - const type = member_or_role instanceof Role ? 'role' : 'member'; - const index = blacklist[type + 's'].findIndex(element => element === member_or_role.id); - - if (index === -1) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.blacklist.response.invalid.title')) - .setDescription(i18n('commands.blacklist.response.invalid.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - blacklist[type + 's'].splice(index, 1); - await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setTitle(i18n(`commands.blacklist.response.${type}_removed.title`)) - .setDescription(i18n(`commands.blacklist.response.${type}_removed.description`, member_or_role.id)) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - await settings.update({ blacklist }); - break; - } - case default_i18n('commands.blacklist.options.show.name'): { - if (blacklist.members.length === 0 && blacklist.roles.length === 0) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.blacklist.response.empty_list.title')) - .setDescription(i18n('commands.blacklist.response.empty_list.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } else { - const members = blacklist.members.map(id => `**·** <@${id}>`); - const roles = blacklist.roles.map(id => `**·** <@&${id}>`); - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.blacklist.response.list.title')) - .addField(i18n('commands.blacklist.response.list.fields.members'), members.join('\n') || 'none') - .addField(i18n('commands.blacklist.response.list.fields.roles'), roles.join('\n') || 'none') - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - } - } - } -}; diff --git a/src/commands/close.js b/src/commands/close.js deleted file mode 100644 index a026cce..0000000 --- a/src/commands/close.js +++ /dev/null @@ -1,322 +0,0 @@ -const Command = require('../modules/commands/command'); -const { - Interaction, // eslint-disable-line no-unused-vars - MessageActionRow, - MessageButton, - MessageEmbed -} = require('discord.js'); -const { Op } = require('sequelize'); -const ms = require('ms'); - -module.exports = class CloseCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.close.description'), - internal: true, - name: i18n('commands.close.name'), - options: [ - { - description: i18n('commands.close.options.reason.description'), - name: i18n('commands.close.options.reason.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.close.options.ticket.description'), - name: i18n('commands.close.options.ticket.name'), - required: false, - type: Command.option_types.INTEGER - }, - { - description: i18n('commands.close.options.time.description'), - name: i18n('commands.close.options.time.name'), - required: false, - type: Command.option_types.STRING - } - ] - }); - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale - const i18n = this.client.i18n.getLocale(settings.locale); - - const reason = interaction.options.getString(default_i18n('commands.close.options.reason.name')); - const ticket = interaction.options.getInteger(default_i18n('commands.close.options.ticket.name')); - const time = interaction.options.getString(default_i18n('commands.close.options.time.name')); - - if (time) { - if (!await this.client.utils.isStaff(interaction.member)) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.close.response.no_permission.title')) - .setDescription(i18n('commands.close.response.no_permission.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - let period; - try { - period = ms(time); - } catch { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.close.response.invalid_time.title')) - .setDescription(i18n('commands.close.response.invalid_time.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - const tickets = await this.client.db.models.Ticket.findAndCountAll({ - where: { - guild: interaction.guild.id, - last_message: { [Op.lte]: new Date(Date.now() - period) }, - open: true - } - }); - - if (tickets.count === 0) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.close.response.no_tickets.title')) - .setDescription(i18n('commands.close.response.no_tickets.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } else { - await interaction.reply({ - components: [ - new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId(`confirm_close_multiple:${interaction.id}`) - .setLabel(i18n('commands.close.response.confirm_multiple.buttons.confirm', tickets.count, tickets.count)) - .setEmoji('✅') - .setStyle('SUCCESS') - ) - .addComponents( - new MessageButton() - .setCustomId(`cancel_close_multiple:${interaction.id}`) - .setLabel(i18n('commands.close.response.confirm_multiple.buttons.cancel')) - .setEmoji('❌') - .setStyle('SECONDARY') - ) - ], - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.close.response.confirm_multiple.title')) - .setDescription(i18n('commands.close.response.confirm_multiple.description', tickets.count, tickets.count)) - .setFooter(this.client.utils.footer(settings.footer, i18n('collector_expires_in', 30)), interaction.guild.iconURL()) - ], - ephemeral: true - }); - - - const filter = i => i.user.id === interaction.user.id && i.customId.includes(interaction.id); - const collector = interaction.channel.createMessageComponentCollector({ - filter, - time: 30000 - }); - - collector.on('collect', async i => { - await i.deferUpdate(); - - if (i.customId === `confirm_close_multiple:${interaction.id}`) { - for (const ticket of tickets.rows) { - await this.client.tickets.close(ticket.id, interaction.user.id, interaction.guild.id, reason); - } - - await i.editReply({ - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setTitle(i18n('commands.close.response.closed_multiple.title', tickets.count, tickets.count)) - .setDescription(i18n('commands.close.response.closed_multiple.description', tickets.count, tickets.count)) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } else { - await i.editReply({ - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.close.response.canceled.title')) - .setDescription(i18n('commands.close.response.canceled.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - collector.stop(); - }); - - collector.on('end', async collected => { - if (collected.size === 0) { - await interaction.editReply({ - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.close.response.confirmation_timeout.title')) - .setDescription(i18n('commands.close.response.confirmation_timeout.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - }); - } - } else { - let t_row; - if (ticket) { - t_row = await this.client.tickets.resolve(ticket, interaction.guild.id); - if (!t_row) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.close.response.unresolvable.title')) - .setDescription(i18n('commands.close.response.unresolvable.description', ticket)) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - } else { - t_row = await this.client.db.models.Ticket.findOne({ where: { id: interaction.channel.id } }); - if (!t_row) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.close.response.not_a_ticket.title')) - .setDescription(i18n('commands.close.response.not_a_ticket.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - } - - if (t_row.creator !== interaction.member.id && !await this.client.utils.isStaff(interaction.member)) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.close.response.no_permission.title')) - .setDescription(i18n('commands.close.response.no_permission.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - await interaction.reply({ - components: [ - new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId(`confirm_close:${interaction.id}`) - .setLabel(i18n('commands.close.response.confirm.buttons.confirm')) - .setEmoji('✅') - .setStyle('SUCCESS') - ) - .addComponents( - new MessageButton() - .setCustomId(`cancel_close:${interaction.id}`) - .setLabel(i18n('commands.close.response.confirm.buttons.cancel')) - .setEmoji('❌') - .setStyle('SECONDARY') - ) - ], - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.close.response.confirm.title')) - .setDescription(settings.log_messages ? i18n('commands.close.response.confirm.description_with_archive') : i18n('commands.close.response.confirm.description')) - .setFooter(this.client.utils.footer(settings.footer, i18n('collector_expires_in', 30)), interaction.guild.iconURL()) - ], - ephemeral: true - }); - - - const filter = i => i.user.id === interaction.user.id && i.customId.includes(interaction.id); - const collector = interaction.channel.createMessageComponentCollector({ - filter, - time: 30000 - }); - - collector.on('collect', async i => { - await i.deferUpdate(); - - if (i.customId === `confirm_close:${interaction.id}`) { - await this.client.tickets.close(t_row.id, interaction.user.id, interaction.guild.id, reason); - await i.editReply({ - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setTitle(i18n('commands.close.response.closed.title', t_row.number)) - .setDescription(i18n('commands.close.response.closed.description', t_row.number)) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } else { - await i.editReply({ - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.close.response.canceled.title')) - .setDescription(i18n('commands.close.response.canceled.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - collector.stop(); - }); - - collector.on('end', async collected => { - if (collected.size === 0) { - await interaction.editReply({ - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.close.response.confirmation_timeout.title')) - .setDescription(i18n('commands.close.response.confirmation_timeout.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - }); - } - } -}; diff --git a/src/commands/extra/survey.template.html b/src/commands/extra/survey.template.html deleted file mode 100644 index a20ed5a..0000000 --- a/src/commands/extra/survey.template.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - {{survey}} Survey Responses | Discord Tickets - - - - - - - - - -
- -
-

{{survey}} survey responses

-
- -
-
-
-

{{count.responses}}

-

Responses

-
-
-
-
-

{{count.users}}

-

Users

-
-
-
- -
- - - - {{#columns}} - - {{/columns}} - - - - {{#responses}} - - {{#.}} - - {{/.}} - - {{/responses}} - - - - {{#columns}} - - {{/columns}} - - -
{{.}}
{{.}}
{{.}}
-
-
-
- - - \ No newline at end of file diff --git a/src/commands/help.js b/src/commands/help.js deleted file mode 100644 index eed7c8a..0000000 --- a/src/commands/help.js +++ /dev/null @@ -1,49 +0,0 @@ -const Command = require('../modules/commands/command'); -const { - Interaction, // eslint-disable-line no-unused-vars - MessageEmbed -} = require('discord.js'); - -module.exports = class HelpCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.help.description'), - internal: true, - name: i18n('commands.help.name') - }); - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const i18n = this.client.i18n.getLocale(settings.locale); - - const is_staff = await this.client.utils.isStaff(interaction.member); - const commands = this.manager.commands.filter(command => { - if (command.permissions.length >= 1) return interaction.member.permissions.has(command.permissions); - else if (command.staff_only) return is_staff; - else return true; - }); - const list = commands.map(command => { - const description = command.description.length > 50 - ? command.description.substring(0, 50) + '...' - : command.description; - return `**\`/${command.name}\` ·** ${description}`; - }); - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.help.response.list.title')) - .setDescription(i18n('commands.help.response.list.description')) - .addField(i18n('commands.help.response.list.fields.commands'), list.join('\n')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } -}; diff --git a/src/commands/new.js b/src/commands/new.js deleted file mode 100644 index 8bec13e..0000000 --- a/src/commands/new.js +++ /dev/null @@ -1,190 +0,0 @@ -const Command = require('../modules/commands/command'); -const { - Interaction, // eslint-disable-line no-unused-vars, - MessageActionRow, - MessageEmbed, - MessageSelectMenu -} = require('discord.js'); - -module.exports = class NewCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.new.description'), - internal: true, - name: i18n('commands.new.name'), - options: [ - { - description: i18n('commands.new.options.topic.description'), - name: i18n('commands.new.options.topic.name'), - required: false, - type: Command.option_types.STRING - } - ] - }); - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale - const i18n = this.client.i18n.getLocale(settings.locale); - - const topic = interaction.options.getString(default_i18n('commands.new.options.topic.name')); - - const create = async (cat_row, i) => { - const tickets = await this.client.db.models.Ticket.findAndCountAll({ - where: { - category: cat_row.id, - creator: interaction.user.id, - open: true - } - }); - - if (tickets.count >= cat_row.max_per_member) { - if (cat_row.max_per_member === 1) { - const response = { - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.new.response.has_a_ticket.title')) - .setDescription(i18n('commands.new.response.has_a_ticket.description', tickets.rows[0].id)) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }; - await i ? i.editReply(response) : interaction.reply(response); - } else { - const list = tickets.rows.map(row => { - if (row.topic) { - const description = row.topic.substring(0, 30); - const ellipses = row.topic.length > 30 ? '...' : ''; - return `<#${row.id}>: \`${description}${ellipses}\``; - } else { - return `<#${row.id}>`; - } - }); - const response = { - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.new.response.max_tickets.title', tickets.count)) - .setDescription(i18n('commands.new.response.max_tickets.description', list.join('\n'))) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }; - await i ? i.editReply(response) : interaction.reply(response); - } - } else { - try { - const t_row = await this.client.tickets.create(interaction.guild.id, interaction.user.id, cat_row.id, topic); - const response = { - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.new.response.created.title')) - .setDescription(i18n('commands.new.response.created.description', `<#${t_row.id}>`)) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }; - await i ? i.editReply(response) : interaction.reply(response); - } catch (error) { - const response = { - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.new.response.error.title')) - .setDescription(error.message) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }; - await i ? i.editReply(response) : interaction.reply(response); - } - } - }; - - const categories = await this.client.db.models.Category.findAndCountAll({ where: { guild: interaction.guild.id } }); - - if (categories.count === 0) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.new.response.no_categories.title')) - .setDescription(i18n('commands.new.response.no_categories.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ] - }); - } else if (categories.count === 1) { - create(categories.rows[0]); // skip the category selection - } else { - await interaction.reply({ - components: [ - new MessageActionRow() - .addComponents( - new MessageSelectMenu() - .setCustomId(`select_category:${interaction.id}`) - .setPlaceholder('Select a category') - .addOptions(categories.rows.map(row => ({ - label: row.name, - value: row.id - }))) - ) - ], - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.new.response.select_category.title')) - .setDescription(i18n('commands.new.response.select_category.description')) - .setFooter(this.client.utils.footer(settings.footer, i18n('collector_expires_in', 30)), interaction.guild.iconURL()) - ], - ephemeral: true - }); - - const filter = i => i.user.id === interaction.user.id && i.customId.includes(interaction.id); - const collector = interaction.channel.createMessageComponentCollector({ - filter, - time: 30000 - }); - - collector.on('collect', async i => { - await i.deferUpdate(); - create(categories.rows.find(row => row.id === i.values[0]), i); - collector.stop(); - }); - - collector.on('end', async collected => { - if (collected.size === 0) { - await interaction.editReply({ - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.new.response.select_category_timeout.title')) - .setDescription(i18n('commands.new.response.select_category_timeout.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - }); - } - } -}; diff --git a/src/commands/panel.js b/src/commands/panel.js deleted file mode 100644 index 03ef9ae..0000000 --- a/src/commands/panel.js +++ /dev/null @@ -1,210 +0,0 @@ -const Command = require('../modules/commands/command'); -const { - Interaction, // eslint-disable-line no-unused-vars - MessageActionRow, - MessageButton, - MessageEmbed, - MessageSelectMenu -} = require('discord.js'); -const { some } = require('../utils'); - -module.exports = class PanelCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.panel.description'), - internal: true, - name: i18n('commands.panel.name'), - options: [ - { - description: i18n('commands.panel.options.categories.description'), - multiple: true, - name: i18n('commands.panel.options.categories.name'), - required: true, - type: Command.option_types.STRING - }, - { - description: i18n('commands.panel.options.description.description'), - name: i18n('commands.panel.options.description.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.panel.options.image.description'), - name: i18n('commands.panel.options.image.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.panel.options.just_type.description') + ' (false)', - name: i18n('commands.panel.options.just_type.name'), - required: false, - type: Command.option_types.BOOLEAN - }, - { - description: i18n('commands.panel.options.title.description'), - name: i18n('commands.panel.options.title.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.panel.options.thumbnail.description'), - name: i18n('commands.panel.options.thumbnail.name'), - required: false, - type: Command.option_types.STRING - } - ], - staff_only: true - }); - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale - const i18n = this.client.i18n.getLocale(settings.locale); - - const categories = interaction.options.getString(default_i18n('commands.panel.options.categories.name')).match(/\d{17,19}/g) ?? []; - const description = interaction.options.getString(default_i18n('commands.panel.options.description.name'))?.replace(/\\n/g, '\n'); - const image = interaction.options.getString(default_i18n('commands.panel.options.image.name')); - const just_type = interaction.options.getBoolean(default_i18n('commands.panel.options.just_type.name')); - const title = interaction.options.getString(default_i18n('commands.panel.options.title.name')); - const thumbnail = interaction.options.getString(default_i18n('commands.panel.options.thumbnail.name')); - - if (just_type && categories.length > 1) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.panel.response.too_many_categories.title')) - .setDescription(i18n('commands.panel.response.too_many_categories.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - const invalid_category = await some(categories, async id => { - const cat_row = await this.client.db.models.Category.findOne({ - where: { - guild: interaction.guild.id, - id - } - }); - return !cat_row; - }); - - if (invalid_category) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.panel.response.invalid_category.title')) - .setDescription(i18n('commands.panel.response.invalid_category.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - let panel_channel; - - const embed = new MessageEmbed() - .setColor(settings.colour) - .setFooter(settings.footer, interaction.guild.iconURL()); - - if (description) embed.setDescription(description); - if (image) embed.setImage(image); - if (title) embed.setTitle(title); - if (thumbnail) embed.setThumbnail(thumbnail); - - if (just_type) { - panel_channel = await interaction.guild.channels.create('create-a-ticket', { - permissionOverwrites: [ - { - allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'READ_MESSAGE_HISTORY'], - deny: ['ATTACH_FILES', 'EMBED_LINKS', 'ADD_REACTIONS'], - id: interaction.guild.roles.everyone - }, - { - allow: ['SEND_MESSAGES', 'EMBED_LINKS', 'ADD_REACTIONS'], - id: this.client.user.id - } - ], - position: 1, - rateLimitPerUser: 30, - reason: `${interaction.user.tag} created a new message panel`, - type: 'GUILD_TEXT' - }); - await panel_channel.send({ embeds: [embed] }); - this.client.log.info(`${interaction.user.tag} has created a new message panel`); - } else { - panel_channel = await interaction.guild.channels.create('create-a-ticket', { - permissionOverwrites: [ - { - allow: ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY'], - deny: ['SEND_MESSAGES', 'ADD_REACTIONS'], - id: interaction.guild.roles.everyone - }, - { - allow: ['SEND_MESSAGES', 'EMBED_LINKS', 'ADD_REACTIONS'], - id: this.client.user.id - } - ], - position: 1, - reason: `${interaction.user.tag} created a new panel`, - type: 'GUILD_TEXT' - }); - - if (categories.length === 1) { - // single category - await panel_channel.send({ - components: [ - new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId(`panel.single:${categories[0]}`) - .setLabel(i18n('panel.create_ticket')) - .setStyle('PRIMARY') - ) - ], - embeds: [embed] - }); - this.client.log.info(`${interaction.user.tag} has created a new button panel`); - } else { - // multi category - const rows = await this.client.db.models.Category.findAll({ where: { guild: interaction.guild.id } }); - await panel_channel.send({ - components: [ - new MessageActionRow() - .addComponents( - new MessageSelectMenu() - .setCustomId(`panel.multiple:${panel_channel.id}`) - .setPlaceholder('Select a category') - .addOptions(rows.map(row => ({ - label: row.name, - value: row.id - }))) - ) - ], - embeds: [embed] - }); - this.client.log.info(`${interaction.user.tag} has created a new select panel`); - } - } - - interaction.reply({ - content: `✅ ${panel_channel}`, - ephemeral: true - }); - - await this.client.db.models.Panel.create({ - category: categories.length === 1 ? categories[0] : null, - channel: panel_channel.id, - guild: interaction.guild.id - }); - } -}; diff --git a/src/commands/remove.js b/src/commands/remove.js deleted file mode 100644 index 85de1da..0000000 --- a/src/commands/remove.js +++ /dev/null @@ -1,111 +0,0 @@ -const Command = require('../modules/commands/command'); -const { - Interaction, // eslint-disable-line no-unused-vars - MessageEmbed -} = require('discord.js'); - -module.exports = class RemoveCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.remove.description'), - internal: true, - name: i18n('commands.remove.name'), - options: [ - { - description: i18n('commands.remove.options.member.description'), - name: i18n('commands.remove.options.member.name'), - required: true, - type: Command.option_types.USER - }, - { - description: i18n('commands.remove.options.ticket.description'), - name: i18n('commands.remove.options.ticket.name'), - required: false, - type: Command.option_types.CHANNEL - } - ] - }); - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale - const i18n = this.client.i18n.getLocale(settings.locale); - - const channel = interaction.options.getChannel(default_i18n('commands.remove.options.channel.name')) ?? interaction.channel; - const t_row = await this.client.tickets.resolve(channel.id, interaction.guild.id); - - if (!t_row) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.remove.response.not_a_channel.title')) - .setDescription(i18n('commands.remove.response.not_a_channel.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - const member = interaction.options.getMember(default_i18n('commands.remove.options.member.name')); - - if (!member) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.remove.response.no_member.title')) - .setDescription(i18n('commands.remove.response.no_member.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - if (t_row.creator !== interaction.user.id && !await this.client.utils.isStaff(interaction.member)) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.remove.response.no_permission.title')) - .setDescription(i18n('commands.remove.response.no_permission.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setAuthor(member.user.username, member.user.displayAvatarURL()) - .setTitle(i18n('commands.remove.response.removed.title')) - .setDescription(i18n('commands.remove.response.removed.description', member.toString(), channel.toString())) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - - await channel.send({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setAuthor(member.user.username, member.user.displayAvatarURL()) - .setTitle(i18n('ticket.member_removed.title')) - .setDescription(i18n('ticket.member_removed.description', member.toString(), interaction.user.toString())) - .setFooter(settings.footer, interaction.guild.iconURL()) - ] - }); - - await channel.permissionOverwrites.delete(member.user.id, `${interaction.user.tag} removed ${member.user.tag} from the ticket`); - - this.client.log.info(`${interaction.user.tag} removed ${member.user.tag} from ${channel.id}`); - } -}; diff --git a/src/commands/settings.js b/src/commands/settings.js deleted file mode 100644 index bdb6f81..0000000 --- a/src/commands/settings.js +++ /dev/null @@ -1,358 +0,0 @@ -/* eslint-disable max-lines */ -const Command = require('../modules/commands/command'); -const { - Interaction, // eslint-disable-line no-unused-vars - MessageEmbed -} = require('discord.js'); - -module.exports = class SettingsCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.settings.description'), - internal: true, - name: i18n('commands.settings.name'), - options: [ - { - description: i18n('commands.settings.options.categories.description'), - name: i18n('commands.settings.options.categories.name'), - options: [ - { - description: i18n('commands.settings.options.categories.options.create.description'), - name: i18n('commands.settings.options.categories.options.create.name'), - options: [ - { - description: i18n('commands.settings.options.categories.options.create.options.name.description'), - name: i18n('commands.settings.options.categories.options.create.options.name.name'), - required: true, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.categories.options.create.options.roles.description'), - name: i18n('commands.settings.options.categories.options.create.options.roles.name'), - required: true, - type: Command.option_types.STRING - } - ], - type: Command.option_types.SUB_COMMAND - }, - { - description: i18n('commands.settings.options.categories.options.delete.description'), - name: i18n('commands.settings.options.categories.options.delete.name'), - options: [ - { - description: i18n('commands.settings.options.categories.options.delete.options.id.description'), - name: i18n('commands.settings.options.categories.options.delete.options.id.name'), - required: true, - type: Command.option_types.STRING - } - ], - type: Command.option_types.SUB_COMMAND - }, - { - description: i18n('commands.settings.options.categories.options.edit.description'), - name: i18n('commands.settings.options.categories.options.edit.name'), - options: [ - { - description: i18n('commands.settings.options.categories.options.edit.options.id.description'), - name: i18n('commands.settings.options.categories.options.edit.options.id.name'), - required: true, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.categories.options.edit.options.claiming.description'), - name: i18n('commands.settings.options.categories.options.edit.options.claiming.name'), - required: false, - type: Command.option_types.BOOLEAN - }, - { - description: i18n('commands.settings.options.categories.options.edit.options.image.description'), - name: i18n('commands.settings.options.categories.options.edit.options.image.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.categories.options.edit.options.max_per_member.description'), - name: i18n('commands.settings.options.categories.options.edit.options.max_per_member.name'), - required: false, - type: Command.option_types.INTEGER - }, - { - description: i18n('commands.settings.options.categories.options.edit.options.name.description'), - name: i18n('commands.settings.options.categories.options.edit.options.name.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.categories.options.edit.options.name_format.description'), - name: i18n('commands.settings.options.categories.options.edit.options.name_format.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.categories.options.edit.options.opening_message.description'), - name: i18n('commands.settings.options.categories.options.edit.options.opening_message.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.categories.options.edit.options.opening_questions.description'), - name: i18n('commands.settings.options.categories.options.edit.options.opening_questions.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.categories.options.edit.options.ping.description'), - name: i18n('commands.settings.options.categories.options.edit.options.ping.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.categories.options.edit.options.require_topic.description'), - name: i18n('commands.settings.options.categories.options.edit.options.require_topic.name'), - required: false, - type: Command.option_types.BOOLEAN - }, - { - description: i18n('commands.settings.options.categories.options.edit.options.roles.description'), - name: i18n('commands.settings.options.categories.options.edit.options.roles.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.categories.options.edit.options.survey.description'), - name: i18n('commands.settings.options.categories.options.edit.options.survey.name'), - required: false, - type: Command.option_types.STRING - } - ], - type: Command.option_types.SUB_COMMAND - }, - { - description: i18n('commands.settings.options.categories.options.list.description'), - name: i18n('commands.settings.options.categories.options.list.name'), - type: Command.option_types.SUB_COMMAND - } - ], - type: Command.option_types.SUB_COMMAND_GROUP - }, - { - description: i18n('commands.settings.options.set.description'), - name: i18n('commands.settings.options.set.name'), - options: [ - { - description: i18n('commands.settings.options.set.options.close_button.description'), - name: i18n('commands.settings.options.set.options.close_button.name'), - required: false, - type: Command.option_types.BOOLEAN - }, - { - description: i18n('commands.settings.options.set.options.colour.description'), - name: i18n('commands.settings.options.set.options.colour.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.set.options.error_colour.description'), - name: i18n('commands.settings.options.set.options.error_colour.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.set.options.footer.description'), - name: i18n('commands.settings.options.set.options.footer.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.set.options.locale.description'), - name: i18n('commands.settings.options.set.options.locale.name'), - required: false, - type: Command.option_types.STRING - }, - { - description: i18n('commands.settings.options.set.options.log_messages.description'), - name: i18n('commands.settings.options.set.options.log_messages.name'), - required: false, - type: Command.option_types.BOOLEAN - }, - { - description: i18n('commands.settings.options.set.options.success_colour.description'), - name: i18n('commands.settings.options.set.options.success_colour.name'), - required: false, - type: Command.option_types.STRING - } - ], - type: Command.option_types.SUB_COMMAND - } - ], - permissions: ['MANAGE_GUILD'] - }); - - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale - const i18n = this.client.i18n.getLocale(settings.locale); - - switch (interaction.options.getSubcommand()) { - case default_i18n('commands.settings.options.categories.options.create.name'): { - const name = interaction.options.getString(default_i18n('commands.settings.options.categories.options.create.options.name.name')); - const roles = interaction.options.getString(default_i18n('commands.settings.options.categories.options.create.options.roles.name'))?.match(/\d{17,19}/g) ?? []; - const allowed_permissions = ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES']; - const cat_channel = await interaction.guild.channels.create(name, { - permissionOverwrites: [ - ...[ - { - deny: ['VIEW_CHANNEL'], - id: interaction.guild.roles.everyone - }, - { - allow: allowed_permissions, - id: this.client.user.id - } - ], - ...roles.map(r => ({ - allow: allowed_permissions, - id: r - })) - ], - position: 1, - reason: `Tickets category created by ${interaction.user.tag}`, - type: 'GUILD_CATEGORY' - }); - await this.client.db.models.Category.create({ - guild: interaction.guild.id, - id: cat_channel.id, - name, - roles - }); - await this.client.commands.updatePermissions(interaction.guild); - interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setTitle(i18n('commands.settings.response.category_created', name)) - ], - ephemeral: true - }); - break; - } - case default_i18n('commands.settings.options.categories.options.delete.name'): { - const category = await this.client.db.models.Category.findOne({ where: { id: interaction.options.getString(default_i18n('commands.settings.options.categories.options.delete.options.id.name')) } }); - if (category) { - const channel = this.client.channels.cache.get(interaction.options.getString(default_i18n('commands.settings.options.categories.options.delete.options.id.name'))); - if (channel) channel.delete(); - await category.destroy(); - interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setTitle(i18n('commands.settings.response.category_deleted', category.name)) - ], - ephemeral: true - }); - } else { - interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.settings.response.category_does_not_exist')) - ], - ephemeral: true - }); - } - break; - } - case default_i18n('commands.settings.options.categories.options.edit.name'): { - const category = await this.client.db.models.Category.findOne({ where: { id: interaction.options.getString(default_i18n('commands.settings.options.categories.options.delete.options.id.name')) } }); - if (!category) { - return interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.settings.response.category_does_not_exist')) - ], - ephemeral: true - }); - } - const claiming = interaction.options.getBoolean(default_i18n('commands.settings.options.categories.options.edit.options.claiming.name')); - const image = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.image.name')); - const max_per_member = interaction.options.getInteger(default_i18n('commands.settings.options.categories.options.edit.options.max_per_member.name')); - const name = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.name.name')); - const name_format = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.name_format.name')); - const opening_message = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.opening_message.name')); - const opening_questions = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.opening_questions.name')); - const ping = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.ping.name')); - const require_topic = interaction.options.getBoolean(default_i18n('commands.settings.options.categories.options.edit.options.require_topic.name')); - const roles = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.roles.name')); - const survey = interaction.options.getString(default_i18n('commands.settings.options.categories.options.edit.options.survey.name')); - if (claiming !== null) category.set('claiming', claiming); - if (max_per_member !== null) category.set('max_per_member', max_per_member); - if (image !== null) category.set('image', image); - if (name !== null) category.set('name', name); - if (name_format !== null) category.set('name_format', name_format); - if (opening_message !== null) category.set('opening_message', opening_message.replace(/\\n/g, '\n')); - if (opening_questions !== null) category.set('opening_questions', JSON.parse(opening_questions)); - if (ping !== null) category.set('ping', ping.match(/\d{17,19}/g) ?? []); - if (require_topic !== null) category.set('require_topic', require_topic); - if (roles !== null) category.set('roles', roles.match(/\d{17,19}/g) ?? []); - if (survey !== null) category.set('survey', survey); - await category.save(); - interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setTitle(i18n('commands.settings.response.category_updated', category.name)) - ], - ephemeral: true - }); - break; - } - case default_i18n('commands.settings.options.categories.options.list.name'): { - const categories = await this.client.db.models.Category.findAll({ where: { guild: interaction.guild.id } }); - await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.settings.response.category_list')) - .setDescription(categories.map(c => `- ${c.name} (\`${c.id}\`)`).join('\n')) - ], - ephemeral: true - }); - break; - } - case default_i18n('commands.settings.options.set.name'): { - const close_button = interaction.options.getBoolean(default_i18n('commands.settings.options.set.options.close_button.name')); - const colour = interaction.options.getString(default_i18n('commands.settings.options.set.options.colour.name')); - const error_colour = interaction.options.getString(default_i18n('commands.settings.options.set.options.error_colour.name')); - const footer = interaction.options.getString(default_i18n('commands.settings.options.set.options.footer.name')); - const locale = interaction.options.getString(default_i18n('commands.settings.options.set.options.locale.name')); - const log_messages = interaction.options.getBoolean(default_i18n('commands.settings.options.set.options.log_messages.name')); - const success_colour = interaction.options.getString(default_i18n('commands.settings.options.set.options.success_colour.name')); - if (close_button !== null) settings.set('close_button', close_button); - if (colour !== null) settings.set('colour', colour.toUpperCase()); - if (error_colour !== null) settings.set('error_colour', error_colour.toUpperCase()); - if (footer !== null) settings.set('footer', footer); - if (locale !== null) settings.set('locale', locale); - if (log_messages !== null) settings.set('log_messages', log_messages); - if (success_colour !== null) settings.set('success_colour', success_colour.toUpperCase()); - await settings.save(); - interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setTitle(i18n('commands.settings.response.settings_updated')) - ], - ephemeral: true - }); - break; - } - } - } -}; \ No newline at end of file diff --git a/src/commands/stats.js b/src/commands/stats.js deleted file mode 100644 index 79a832d..0000000 --- a/src/commands/stats.js +++ /dev/null @@ -1,95 +0,0 @@ -const Command = require('../modules/commands/command'); -const Keyv = require('keyv'); -const { - Interaction, // eslint-disable-line no-unused-vars - MessageEmbed -} = require('discord.js'); - -module.exports = class StatsCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.stats.description'), - internal: true, - name: i18n('commands.stats.name'), - staff_only: true - }); - - this.cache = new Keyv({ namespace: 'cache.commands.stats' }); - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const i18n = this.client.i18n.getLocale(settings.locale); - - const messages = await this.client.db.models.Message.findAndCountAll(); - - let stats = await this.cache.get(interaction.guild.id); - - if (!stats) { - const tickets = await this.client.db.models.Ticket.findAndCountAll({ where: { guild: interaction.guild.id } }); - stats = { // maths - messages: settings.log_messages - ? await messages.rows - .reduce(async (acc, row) => (await this.client.db.models.Ticket.findOne({ where: { id: row.ticket } })) - .guild === interaction.guild.id - ? await acc + 1 - : await acc, 0) - : null, - response_time: Math.floor(tickets.rows.reduce((acc, row) => row.first_response - ? acc + ((Math.abs(new Date(row.createdAt) - new Date(row.first_response)) / 1000) / 60) - : acc, 0) / tickets.count), - tickets: tickets.count - }; - await this.cache.set(interaction.guild.id, stats, 60 * 60 * 1000); // cache for an hour - } - - const guild_embed = new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.stats.response.guild.title')) - .setDescription(i18n('commands.stats.response.guild.description')) - .addField(i18n('commands.stats.fields.tickets'), String(stats.tickets), true) - .addField(i18n('commands.stats.fields.response_time.title'), i18n('commands.stats.fields.response_time.minutes', stats.response_time), true) - .setFooter(settings.footer, interaction.guild.iconURL()); - - if (stats.messages) guild_embed.addField(i18n('commands.stats.fields.messages'), String(stats.messages), true); - - const embeds = [guild_embed]; - - if (this.client.guilds.cache.size > 1) { - let global = await this.cache.get('global'); - - if (!global) { - const tickets = await this.client.db.models.Ticket.findAndCountAll(); - global = { // maths - messages: settings.log_messages - ? await messages.count - : null, - response_time: Math.floor(tickets.rows.reduce((acc, row) => row.first_response - ? acc + ((Math.abs(new Date(row.createdAt) - new Date(row.first_response)) / 1000) / 60) - : acc, 0) / tickets.count), - tickets: tickets.count - }; - await this.cache.set('global', global, 60 * 60 * 1000); // cache for an hour - } - - const global_embed = new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.stats.response.global.title')) - .setDescription(i18n('commands.stats.response.global.description')) - .addField(i18n('commands.stats.fields.tickets'), String(global.tickets), true) - .addField(i18n('commands.stats.fields.response_time.title'), i18n('commands.stats.fields.response_time.minutes', global.response_time), true) - .setFooter(settings.footer, interaction.guild.iconURL()); - - if (stats.messages) global_embed.addField(i18n('commands.stats.fields.messages'), String(global.messages), true); - - embeds.push(global_embed); - } - - await interaction.reply({ embeds }); - } -}; diff --git a/src/commands/survey.js b/src/commands/survey.js deleted file mode 100644 index 86be0a7..0000000 --- a/src/commands/survey.js +++ /dev/null @@ -1,113 +0,0 @@ -const Command = require('../modules/commands/command'); -// eslint-disable-next-line no-unused-vars -const { - Message, // eslint-disable-line no-unused-vars - MessageAttachment, - MessageEmbed -} = require('discord.js'); -const fsp = require('fs').promises; -const { path } = require('../utils/fs'); -const mustache = require('mustache'); - -module.exports = class SurveyCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.survey.description'), - internal: true, - name: i18n('commands.survey.name'), - options: async guild => { - const surveys = await this.client.db.models.Survey.findAll({ where: { guild: guild.id } }); - return [ - { - choices: surveys.map(survey => ({ - name: survey.name, - value: survey.name - })), - description: i18n('commands.survey.options.survey.description'), - name: i18n('commands.survey.options.survey.name'), - required: true, - type: Command.option_types.STRING - } - ]; - }, - staff_only: true - - }); - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale - const i18n = this.client.i18n.getLocale(settings.locale); - - const name = interaction.options.getString(default_i18n('commands.survey.options.survey.name')); - - const survey = await this.client.db.models.Survey.findOne({ - where: { - guild: interaction.guild.id, - name - } - }); - - if (survey) { - const { - rows: responses, count - } = await this.client.db.models.SurveyResponse.findAndCountAll({ where: { survey: survey.id } }); - - const users = new Set(); - - for (const i in responses) { - const ticket = await this.client.db.models.Ticket.findOne({ where: { id: responses[i].ticket } }); - users.add(ticket.creator); - const answers = responses[i].answers.map(a => this.client.cryptr.decrypt(a)); - answers.unshift(ticket.number); - responses[i] = answers; - } - - let template = await fsp.readFile(path('./src/commands/extra/survey.template.html'), { encoding: 'utf8' }); - - template = template.replace(/[\r\n\t]/g, ''); - - survey.questions.unshift('Ticket #'); - - const html = mustache.render(template, { - columns: survey.questions, - count: { - responses: count, - users: users.size - }, - responses, - survey: survey.name.charAt(0).toUpperCase() + survey.name.slice(1) - }); - - const attachment = new MessageAttachment( - Buffer.from(html), - `${survey.name}.html` - ); - - return await interaction.reply({ - ephemeral: true, - files: [attachment] - }); - } else { - const surveys = await this.client.db.models.Survey.findAll({ where: { guild: interaction.guild.id } }); - - const list = surveys.map(s => `❯ **\`${s.name}\`**`); - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.survey.response.list.title')) - .setDescription(list.join('\n')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - } -}; diff --git a/src/commands/tag.js b/src/commands/tag.js deleted file mode 100644 index 7bbf00b..0000000 --- a/src/commands/tag.js +++ /dev/null @@ -1,71 +0,0 @@ -const Command = require('../modules/commands/command'); -const { - Interaction, // eslint-disable-line no-unused-vars - MessageEmbed -} = require('discord.js'); - -module.exports = class TagCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.tag.description'), - internal: true, - name: i18n('commands.tag.name'), - options: async guild => { - const settings = await client.utils.getSettings(guild.id); - return Object.keys(settings.tags).map(tag => ({ - description: settings.tags[tag].substring(0, 100), - name: tag, - options: [...settings.tags[tag].matchAll(/(? ({ - description: match[1], - name: match[1], - required: true, - type: Command.option_types.STRING - })), - type: Command.option_types.SUB_COMMAND - })); - }, - staff_only: true - }); - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const i18n = this.client.i18n.getLocale(settings.locale); - - try { - const tag_name = interaction.options.getSubcommand(); - const tag = settings.tags[tag_name]; - const args = interaction.options.data[0]?.options; - const text = tag.replace(/(? { - const arg = args.find(arg => arg.name === $1); - return arg ? arg.value : $; - }); - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setDescription(text) - ], - ephemeral: false - }); - } catch { - const list = Object.keys(settings.tags).map(t => `❯ **\`${t}\`**`); - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.tag.response.list.title')) - .setDescription(list.join('\n')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - } -}; diff --git a/src/commands/topic.js b/src/commands/topic.js deleted file mode 100644 index 7fa6139..0000000 --- a/src/commands/topic.js +++ /dev/null @@ -1,87 +0,0 @@ -const Command = require('../modules/commands/command'); -const { - Interaction, // eslint-disable-line no-unused-vars - MessageEmbed -} = require('discord.js'); - -module.exports = class TopicCommand extends Command { - constructor(client) { - const i18n = client.i18n.getLocale(client.config.locale); - super(client, { - description: i18n('commands.topic.description'), - internal: true, - name: i18n('commands.topic.name'), - options: [ - { - description: i18n('commands.topic.options.new_topic.description'), - name: i18n('commands.topic.options.new_topic.name'), - required: true, - type: Command.option_types.STRING - } - ] - }); - } - - /** - * @param {Interaction} interaction - * @returns {Promise} - */ - async execute(interaction) { - const settings = await this.client.utils.getSettings(interaction.guild.id); - const default_i18n = this.client.i18n.getLocale(this.client.config.defaults.locale); // command properties could be in a different locale - const i18n = this.client.i18n.getLocale(settings.locale); - - const topic = interaction.options.getString(default_i18n('commands.topic.options.new_topic.name')); - - const t_row = await this.client.db.models.Ticket.findOne({ where: { id: interaction.channel.id } }); - - if (!t_row) { - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.topic.response.not_a_ticket.title')) - .setDescription(i18n('commands.topic.response.not_a_ticket.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - await t_row.update({ topic: this.client.cryptr.encrypt(topic) }); - - const member = await interaction.guild.members.fetch(t_row.creator); - interaction.channel.setTopic(`${member} | ${topic}`, { reason: 'User updated ticket topic' }); - - const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } }); - const description = cat_row.opening_message - .replace(/{+\s?(user)?name\s?}+/gi, member.displayName) - .replace(/{+\s?(tag|ping|mention)?\s?}+/gi, member.user.toString()); - const opening_message = await interaction.channel.messages.fetch(t_row.opening_message); - - await opening_message.edit({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setAuthor(member.user.username, member.user.displayAvatarURL()) - .setDescription(description) - .addField(i18n('ticket.opening_message.fields.topic'), topic) - .setFooter(settings.footer, interaction.guild.iconURL()) - ] - }); - - await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.topic.response.changed.title')) - .setDescription(i18n('commands.topic.response.changed.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: false - }); - - this.client.log.info(`${interaction.user.tag} changed the topic of #${interaction.channel.name}`); - } -}; diff --git a/src/database/dialects.js b/src/database/dialects.js deleted file mode 100644 index d7ecbf6..0000000 --- a/src/database/dialects.js +++ /dev/null @@ -1,42 +0,0 @@ -module.exports = { - maria: { - dialect: 'mariadb', - name: 'MariaDB', - packages: ['mariadb'] - }, - mariadb: { - dialect: 'mariadb', - name: 'MariaDB', - packages: ['mariadb'] - }, - microsoft: { - dialect: 'mssql', - name: 'Microsoft SQL', - packages: ['tedious'] - }, - mysql: { - dialect: 'mysql', - name: 'MySQL', - packages: ['mysql2'] - }, - postgre: { // this is wrong - dialect: 'postgres', - name: 'PostgreSQL', - packages: ['pg', 'pg-hstore'] - }, - postgres: { - dialect: 'postgres', - name: 'PostgreSQL', - packages: ['pg', 'pg-hstore'] - }, - postgresql: { - dialect: 'postgres', - name: 'PostgreSQL', - packages: ['pg', 'pg-hstore'] - }, - sqlite: { - dialect: 'sqlite', - name: 'SQLite', - packages: ['sqlite3'] - } -}; \ No newline at end of file diff --git a/src/database/index.js b/src/database/index.js deleted file mode 100644 index 9b391f9..0000000 --- a/src/database/index.js +++ /dev/null @@ -1,73 +0,0 @@ -const { Sequelize } = require('sequelize'); -const fs = require('fs'); -const { path } = require('../utils/fs'); -const types = require('./dialects'); - -module.exports = async client => { - - const { - DB_TYPE, - DB_HOST, - DB_PORT, - DB_USER, - DB_PASS, - DB_NAME - } = process.env; - - const type = (DB_TYPE || 'sqlite').toLowerCase(); - - const supported = Object.keys(types); - if (!supported.includes(type)) { - client.log.error(new Error(`DB_TYPE (${type}) is not a valid type`)); - return process.exit(); - } - - try { - types[type].packages.forEach(pkg => require(pkg)); - } catch { - const required = types[type].packages.map(i => `"${i}"`).join(' and '); - client.log.error(new Error(`Please install the package(s) for your selected database type: ${required}`)); - return process.exit(); - } - - let sequelize; - - if (type === 'sqlite') { - client.log.info('Using SQLite storage'); - sequelize = new Sequelize({ - dialect: types[type].dialect, - logging: text => client.log.debug(text), - storage: path('./user/database.sqlite') - }); - client.config.defaults.log_messages = false; - client.log.warn('Message logging is disabled due to insufficient database'); - } else { - client.log.info(`Connecting to ${types[type].name} database...`); - sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASS, { - dialect: types[type].dialect, - host: DB_HOST, - logging: text => client.log.debug(text), - port: DB_PORT - }); - } - - try { - await sequelize.authenticate(); - client.log.success('Connected to database successfully'); - } catch (error) { - client.log.warn('Failed to connect to database'); - client.log.error(error); - return process.exit(); - } - - const models = fs.readdirSync(path('./src/database/models')) - .filter(filename => filename.endsWith('.model.js')); - - for (const model of models) { - require(`./models/${model}`)(client, sequelize); - } - - await sequelize.sync({ alter: false }); - - return sequelize; -}; \ No newline at end of file diff --git a/src/database/models/category.model.js b/src/database/models/category.model.js deleted file mode 100644 index d9887e8..0000000 --- a/src/database/models/category.model.js +++ /dev/null @@ -1,93 +0,0 @@ -const { DataTypes } = require('sequelize'); -module.exports = ({ config }, sequelize) => { - const { DB_TABLE_PREFIX } = process.env; - sequelize.define('Category', { - claiming: { - defaultValue: false, - type: DataTypes.BOOLEAN - }, - guild: { - allowNull: false, - references: { - key: 'id', - model: DB_TABLE_PREFIX + 'guilds' - }, - type: DataTypes.CHAR(19), - unique: 'name-guild' - }, - id: { - allowNull: false, - primaryKey: true, - type: DataTypes.CHAR(19) - }, - image: { - allowNull: true, - type: DataTypes.STRING - }, - max_per_member: { - defaultValue: 1, - type: DataTypes.INTEGER - }, - name: { - allowNull: false, - type: DataTypes.STRING, - unique: 'name-guild' - }, - name_format: { - allowNull: false, - defaultValue: config.defaults.name_format, - type: DataTypes.STRING - }, - opening_message: { - defaultValue: config.defaults.opening_message, - type: DataTypes.TEXT - }, - opening_questions: { - allowNull: true, - get() { - const raw_value = this.getDataValue('opening_questions'); - return raw_value - ? typeof raw_value === 'string' - ? JSON.parse(raw_value) - : raw_value - : null; - }, - type: DataTypes.JSON - }, - ping: { - defaultValue: [], - get() { - const raw_value = this.getDataValue('ping'); - return raw_value - ? typeof raw_value === 'string' - ? JSON.parse(raw_value) - : raw_value - : null; - }, - type: DataTypes.JSON - }, - require_topic: { - defaultValue: false, - type: DataTypes.BOOLEAN - }, - roles: { - allowNull: false, - get() { - const raw_value = this.getDataValue('roles'); - return raw_value - ? typeof raw_value === 'string' - ? JSON.parse(raw_value) - : raw_value - : null; - }, - type: DataTypes.JSON - }, - survey: { - allowNull: true, - type: DataTypes.STRING - } - }, { - paranoid: true, - tableName: DB_TABLE_PREFIX + 'categories' - }); -}; \ No newline at end of file diff --git a/src/database/models/channel_entity.model.js b/src/database/models/channel_entity.model.js deleted file mode 100644 index d0a403d..0000000 --- a/src/database/models/channel_entity.model.js +++ /dev/null @@ -1,21 +0,0 @@ -const { DataTypes } = require('sequelize'); -module.exports = (_client, sequelize) => { - const { DB_TABLE_PREFIX } = process.env; - sequelize.define('ChannelEntity', { - channel: { - allowNull: false, - type: DataTypes.CHAR(19), - unique: 'channel-ticket' - }, - name: DataTypes.TEXT, - ticket: { - allowNull: false, - references: { - key: 'id', - model: DB_TABLE_PREFIX + 'tickets' - }, - type: DataTypes.CHAR(19), - unique: 'channel-ticket' - } - }, { tableName: DB_TABLE_PREFIX + 'channel_entities' }); -}; \ No newline at end of file diff --git a/src/database/models/guild.model.js b/src/database/models/guild.model.js deleted file mode 100644 index bf8bfbf..0000000 --- a/src/database/models/guild.model.js +++ /dev/null @@ -1,66 +0,0 @@ -const { DataTypes } = require('sequelize'); -module.exports = ({ config }, sequelize) => { - const { DB_TABLE_PREFIX } = process.env; - sequelize.define('Guild', { - blacklist: { - defaultValue: { - members: [], - roles: [] - }, - get() { - const raw_value = this.getDataValue('blacklist'); - return raw_value - ? typeof raw_value === 'string' - ? JSON.parse(raw_value) - : raw_value - : null; - }, - type: DataTypes.JSON - }, - close_button: { - defaultValue: false, - type: DataTypes.BOOLEAN - }, - colour: { - defaultValue: config.defaults.colour, - type: DataTypes.STRING - }, - error_colour: { - defaultValue: 'RED', - type: DataTypes.STRING - }, - footer: { - defaultValue: 'Discord Tickets by eartharoid', - type: DataTypes.STRING - }, - id: { - allowNull: false, - primaryKey: true, - type: DataTypes.CHAR(19) - }, - locale: { - defaultValue: config.locale, - type: DataTypes.STRING - }, - log_messages: { - defaultValue: config.defaults.log_messages, - type: DataTypes.BOOLEAN - }, - success_colour: { - defaultValue: 'GREEN', - type: DataTypes.STRING - }, - tags: { - defaultValue: {}, - get() { - const raw_value = this.getDataValue('tags'); - return raw_value - ? typeof raw_value === 'string' - ? JSON.parse(raw_value) - : raw_value - : null; - }, - type: DataTypes.JSON - } - }, { tableName: DB_TABLE_PREFIX + 'guilds' }); -}; diff --git a/src/database/models/message.model.js b/src/database/models/message.model.js deleted file mode 100644 index 85b9ee6..0000000 --- a/src/database/models/message.model.js +++ /dev/null @@ -1,35 +0,0 @@ -const { DataTypes } = require('sequelize'); -module.exports = (_client, sequelize) => { - const { DB_TABLE_PREFIX } = process.env; - sequelize.define('Message', { - author: { - allowNull: false, - type: DataTypes.CHAR(19) - }, - data: { - allowNull: false, - type: DataTypes.TEXT - }, - deleted: { - defaultValue: false, - type: DataTypes.BOOLEAN - }, - edited: { - defaultValue: false, - type: DataTypes.BOOLEAN - }, - id: { - allowNull: false, - primaryKey: true, - type: DataTypes.CHAR(19) - }, - ticket: { - allowNull: false, - references: { - key: 'id', - model: DB_TABLE_PREFIX + 'tickets' - }, - type: DataTypes.CHAR(19) - } - }, { tableName: DB_TABLE_PREFIX + 'messages' }); -}; \ No newline at end of file diff --git a/src/database/models/panel.model.js b/src/database/models/panel.model.js deleted file mode 100644 index ea36aef..0000000 --- a/src/database/models/panel.model.js +++ /dev/null @@ -1,22 +0,0 @@ -const { DataTypes } = require('sequelize'); -module.exports = (client, sequelize) => { - const { DB_TABLE_PREFIX } = process.env; - sequelize.define('Panel', { - category: { - allowNull: true, - type: DataTypes.CHAR(19) - }, - channel: { - allowNull: false, - type: DataTypes.CHAR(19) - }, - guild: { - allowNull: false, - references: { - key: 'id', - model: DB_TABLE_PREFIX + 'guilds' - }, - type: DataTypes.CHAR(19) - } - }, { tableName: DB_TABLE_PREFIX + 'panels' }); -}; \ No newline at end of file diff --git a/src/database/models/role_entity.model.js b/src/database/models/role_entity.model.js deleted file mode 100644 index c6ea0e9..0000000 --- a/src/database/models/role_entity.model.js +++ /dev/null @@ -1,25 +0,0 @@ -const { DataTypes } = require('sequelize'); -module.exports = (client, sequelize) => { - const { DB_TABLE_PREFIX } = process.env; - sequelize.define('RoleEntity', { - colour: { - defaultValue: '7289DA', - type: DataTypes.CHAR(6) - }, - name: DataTypes.TEXT, - role: { - allowNull: false, - type: DataTypes.CHAR(19), - unique: 'role-ticket' - }, - ticket: { - allowNull: false, - references: { - key: 'id', - model: DB_TABLE_PREFIX + 'tickets' - }, - type: DataTypes.CHAR(19), - unique: 'role-ticket' - } - }, { tableName: DB_TABLE_PREFIX + 'role_entities' }); -}; \ No newline at end of file diff --git a/src/database/models/survey.model.js b/src/database/models/survey.model.js deleted file mode 100644 index 3a2d6d0..0000000 --- a/src/database/models/survey.model.js +++ /dev/null @@ -1,32 +0,0 @@ -const { DataTypes } = require('sequelize'); -module.exports = (client, sequelize) => { - const { DB_TABLE_PREFIX } = process.env; - sequelize.define('Survey', { - guild: { - allowNull: false, - references: { - key: 'id', - model: DB_TABLE_PREFIX + 'guilds' - }, - type: DataTypes.CHAR(19), - unique: 'name-guild' - }, - name: { - allowNull: false, - type: DataTypes.STRING, - unique: 'name-guild' - }, - questions: { - allowNull: true, - get() { - const raw_value = this.getDataValue('questions'); - return raw_value - ? typeof raw_value === 'string' - ? JSON.parse(raw_value) - : raw_value - : null; - }, - type: DataTypes.JSON - } - }, { tableName: DB_TABLE_PREFIX + 'surveys' }); -}; \ No newline at end of file diff --git a/src/database/models/survey_response.model.js b/src/database/models/survey_response.model.js deleted file mode 100644 index 379fe79..0000000 --- a/src/database/models/survey_response.model.js +++ /dev/null @@ -1,36 +0,0 @@ -const { DataTypes } = require('sequelize'); -module.exports = (client, sequelize) => { - const { DB_TABLE_PREFIX } = process.env; - sequelize.define('SurveyResponse', { - answers: { - allowNull: true, - get() { - const raw_value = this.getDataValue('answers'); - return raw_value - ? typeof raw_value === 'string' - ? JSON.parse(raw_value) - : raw_value - : null; - }, - type: DataTypes.JSON - }, - survey: { - allowNull: false, - references: { - key: 'id', - model: DB_TABLE_PREFIX + 'surveys' - }, - type: DataTypes.INTEGER, - unique: 'survey-ticket' - }, - ticket: { - allowNull: false, - references: { - key: 'id', - model: DB_TABLE_PREFIX + 'tickets' - }, - type: DataTypes.CHAR(19), - unique: 'survey-ticket' - } - }, { tableName: DB_TABLE_PREFIX + 'survey_responses' }); -}; \ No newline at end of file diff --git a/src/database/models/ticket.model.js b/src/database/models/ticket.model.js deleted file mode 100644 index 370e1be..0000000 --- a/src/database/models/ticket.model.js +++ /dev/null @@ -1,81 +0,0 @@ -const { DataTypes } = require('sequelize'); -module.exports = (_client, sequelize) => { - const { DB_TABLE_PREFIX } = process.env; - sequelize.define('Ticket', { - category: { - allowNull: false, - references: { - key: 'id', - model: DB_TABLE_PREFIX + 'categories' - }, - type: DataTypes.CHAR(19) - }, - claimed_by: { - allowNull: true, - type: DataTypes.CHAR(19) - }, - closed_by: { - allowNull: true, - type: DataTypes.CHAR(19) - }, - closed_reason: { - allowNull: true, - type: DataTypes.STRING - }, - creator: { - allowNull: false, - type: DataTypes.CHAR(19) - }, - first_response: { - allowNull: true, - type: DataTypes.DATE - }, - guild: { - allowNull: false, - references: { - key: 'id', - model: DB_TABLE_PREFIX + 'guilds' - }, - type: DataTypes.CHAR(19), - unique: 'number-guild' - }, - id: { - allowNull: false, - primaryKey: true, - type: DataTypes.CHAR(19) - }, - last_message: { - allowNull: true, - type: DataTypes.DATE - }, - number: { - allowNull: false, - type: DataTypes.INTEGER, - unique: 'number-guild' - }, - open: { - defaultValue: true, - type: DataTypes.BOOLEAN - }, - opening_message: { - allowNull: true, - type: DataTypes.CHAR(19) - }, - pinned_messages: { - defaultValue: [], - get() { - const raw_value = this.getDataValue('pinned_messages'); - return raw_value - ? typeof raw_value === 'string' - ? JSON.parse(raw_value) - : raw_value - : null; - }, - type: DataTypes.JSON - }, - topic: { - allowNull: true, - type: DataTypes.TEXT - } - }, { tableName: DB_TABLE_PREFIX + 'tickets' }); -}; \ No newline at end of file diff --git a/src/database/models/user_entity.model.js b/src/database/models/user_entity.model.js deleted file mode 100644 index 0117ae6..0000000 --- a/src/database/models/user_entity.model.js +++ /dev/null @@ -1,34 +0,0 @@ -const { DataTypes } = require('sequelize'); -module.exports = (client, sequelize) => { - const { DB_TABLE_PREFIX } = process.env; - sequelize.define('UserEntity', { - avatar: DataTypes.STRING, - bot: DataTypes.BOOLEAN, - discriminator: DataTypes.STRING, - display_name: DataTypes.TEXT, - role: { - allowNull: false, - references: { - key: 'role', - model: DB_TABLE_PREFIX + 'role_entities' - }, - type: DataTypes.CHAR(19) - }, - ticket: { - allowNull: false, - references: { - key: 'id', - model: DB_TABLE_PREFIX + 'tickets' - }, - type: DataTypes.CHAR(19), - unique: 'user-ticket' - }, - user: { - allowNull: false, - type: DataTypes.CHAR(19), - unique: 'user-ticket' - }, - username: DataTypes.TEXT - - }, { tableName: DB_TABLE_PREFIX + 'user_entities' }); -}; \ No newline at end of file diff --git a/src/index.js b/src/index.js deleted file mode 100644 index a3b866b..0000000 --- a/src/index.js +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Discord Tickets - * Copyright (C) 2021 Isaac Saunders - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * @name @discord-tickets/bot - * @description An open-source Discord bot for ticket management - * @copyright 2021 Isaac Saunders - * @license GNU-GPLv3 - */ - -/* eslint-disable no-console */ - -process.title = 'Discord Tickets'; - -const min_node_version = '16.6.0'; -const semver = require('semver'); -if (semver.lt(process.versions.node, min_node_version)) return console.log(`\x07Error: Discord Tickets does not work on Node v${process.versions.node}; please upgrade to v${min_node_version} or above.`); - -const leeks = require('leeks.js'); -const fs = require('fs'); -const { path } = require('./utils/fs'); - -const checkFile = (file, example) => { - if (fs.existsSync(path(file))) return true; - if (!fs.existsSync(path(example))) { - console.log(`\x07Error: "${file}" not found, and unable to create it due to "${example}" being missing.`); - return process.exit(); - } - console.log(`Copying "${example}" to "${file}"...`); - fs.copyFileSync(path(example), path(file)); - return false; -}; - -checkFile('./user/config.js', './user/example.config.js'); - -if (!checkFile('./.env', './example.env')) { - console.log('Generating database encryption key...'); - - const file = path('./.env'); - const crypto = require('crypto'); - - const key = 'DB_ENCRYPTION_KEY='; - const value = crypto - .randomBytes(24) - .toString('hex'); - - let data = fs.readFileSync(file, { encoding: 'utf-8' }); - data = data.replace(key, key + value); - - fs.writeFileSync(file, data); - - console.log('Saved.'); - console.log(leeks.colours.yellow('Warning: do not lose your ENV file or encryption key; you will lose access to data in the database.')); - console.log('\x07Please set your bot\'s "DISCORD_TOKEN" in "./.env".'); - - process.exit(); -} - -require('dotenv').config({ path: path('./.env') }); - -require('./banner')(); - -const log = require('./logger'); - -const { version } = require('../package.json'); -process.on('unhandledRejection', error => { - log.notice('PLEASE INCLUDE THIS INFORMATION IF YOU ASK FOR HELP ABOUT THE FOLLOWING ERROR:'); - log.notice(`Discord Tickets v${version}, Node v${process.versions.node} on ${process.platform}`); - log.warn('An error was not caught'); - if (error instanceof Error) log.warn(`Uncaught ${error.name}`); - log.error(error); -}); - -const DiscordUtils = require('./utils/discord'); -const Cryptr = require('cryptr'); -const I18n = require('@eartharoid/i18n'); -const ListenerLoader = require('./modules/listeners/loader'); -const CommandManager = require('./modules/commands/manager'); -const PluginManager = require('./modules/plugins/manager'); -const TicketManager = require('./modules/tickets/manager'); - -const fetch = require('node-fetch'); - -const { - Client, - Intents -} = require('discord.js'); -// eslint-disable-next-line no-unused-vars -const Logger = require('leekslazylogger'); - -/** - * The Discord client - * @typedef {Bot} Bot - * @extends {Client} - */ -class Bot extends Client { - constructor() { - super({ - intents: [ - Intents.FLAGS.GUILDS, - Intents.FLAGS.GUILD_MEMBERS, - Intents.FLAGS.GUILD_MESSAGES, - Intents.FLAGS.GUILD_MESSAGE_REACTIONS - ], - partials: [ - 'CHANNEL', - 'MESSAGE', - 'REACTION' - ], - presence: DiscordUtils.selectPresence() - }); - - (async () => { - this.version = version; - - /** The global bot configuration */ - this.config = require('../user/config'); - - /** - * A [leekslazylogger](https://logger.eartharoid.me) instance - * @type {Logger} - */ - this.log = log; - - /** - * A [Cryptr](https://www.npmjs.com/package/cryptr) instance - * @type {Cryptr} - */ - this.cryptr = new Cryptr(process.env.DB_ENCRYPTION_KEY); - - const locales = {}; - fs.readdirSync(path('./src/locales')) - .filter(file => file.endsWith('.json')) - .forEach(file => { - const data = fs.readFileSync(path(`./src/locales/${file}`), { encoding: 'utf8' }); - const name = file.slice(0, file.length - 5); - locales[name] = JSON.parse(data); - }); - - /** - * An [@eartharoid/i18n](https://github.com/eartharoid/i18n) instance - * @type {I18n} - */ - this.i18n = new I18n('en-GB', locales); - - /** A sequelize instance */ - this.db = await require('./database')(this), // this.db.models.Ticket... - - this.setMaxListeners(this.config.max_listeners); // set the max listeners for each event - - require('./update/notifier')(this); // check for updates - - const listeners = new ListenerLoader(this); - listeners.load(); // load listeners - - /** The ticket manager */ - this.tickets = new TicketManager(this); - - /** The command manager, used by internal and plugin commands */ - this.commands = new CommandManager(this); - - /** The plugin manager */ - this.plugins = new PluginManager(this); - this.plugins.load(); // load plugins - - /** Some utility methods */ - this.utils = new DiscordUtils(this); - - this.log.info('Connecting to Discord API...'); - - this.login(); - })(); - } - - async postStats() { - /** - * OH NO, TELEMETRY!? - * Relax, it just counts how many people are using Discord Tickets. - * You can see the source here: https://github.com/discord-tickets/stats - */ - if (this.config.super_secret_setting) { // you can disable it if you really want - const data = { - client: this.user.id, - guilds: this.guilds.cache.size, - members: await this.guilds.cache.reduce(async (acc, guild) => await acc + (await guild.fetch()).approximateMemberCount, 0), - tickets: await this.db.models.Ticket.count(), - version: this.version - }; - this.log.debug('Sending statistics', data); - await fetch('https://stats.discordtickets.app/v2', { - body: JSON.stringify(data), - headers: { 'Content-Type': 'application/json' }, - method: 'POST' - }) - .catch(error => { - this.log.warn('Failed to send statistics'); - this.log.debug(error); - }); - } - } - -} - -new Bot(); \ No newline at end of file diff --git a/src/listeners/debug.js b/src/listeners/debug.js deleted file mode 100644 index ad62c68..0000000 --- a/src/listeners/debug.js +++ /dev/null @@ -1,13 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); - -module.exports = class DebugEventListener extends EventListener { - constructor(client) { - super(client, { event: 'debug' }); - } - - async execute(data) { - if (this.client.config.developer.debug) { - this.client.log.debug(data); - } - } -}; \ No newline at end of file diff --git a/src/listeners/error.js b/src/listeners/error.js deleted file mode 100644 index ddfe695..0000000 --- a/src/listeners/error.js +++ /dev/null @@ -1,12 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); - -module.exports = class ErrorEventListener extends EventListener { - constructor(client) { - super(client, { event: 'error' }); - } - - async execute(error) { - this.client.log.warn('The client encountered an error'); - this.client.log.error(error); - } -}; \ No newline at end of file diff --git a/src/listeners/guildCreate.js b/src/listeners/guildCreate.js deleted file mode 100644 index 0d01ae5..0000000 --- a/src/listeners/guildCreate.js +++ /dev/null @@ -1,12 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); - -module.exports = class GuildCreateEventListener extends EventListener { - constructor(client) { - super(client, { event: 'guildCreate' }); - } - - async execute(guild) { - this.client.log.info(`Added to "${guild.name}"`); - this.client.commands.publish(guild); - } -}; \ No newline at end of file diff --git a/src/listeners/guildDelete.js b/src/listeners/guildDelete.js deleted file mode 100644 index a4b55cd..0000000 --- a/src/listeners/guildDelete.js +++ /dev/null @@ -1,11 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); - -module.exports = class GuildDeleteEventListener extends EventListener { - constructor(client) { - super(client, { event: 'guildDelete' }); - } - - async execute(guild) { - this.client.log.info(`Removed from "${guild.name}"`); - } -}; \ No newline at end of file diff --git a/src/listeners/guildMemberRemove.js b/src/listeners/guildMemberRemove.js deleted file mode 100644 index e698d3f..0000000 --- a/src/listeners/guildMemberRemove.js +++ /dev/null @@ -1,22 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); - -module.exports = class GuildMemberRemoveEventListener extends EventListener { - constructor(client) { - super(client, { event: 'guildMemberRemove' }); - } - - async execute(member) { - const tickets = await this.client.db.models.Ticket.findAndCountAll({ - where: { - creator: member.id, - guild: member.guild.id - } - }); - - for (const ticket of tickets.rows) { - await this.client.tickets.close(ticket.id, null, member.guild.id, 'Member left the guild'); - } - - this.client.log.info(`Closed ${tickets.count} ticket(s) belonging to ${member.user.tag} who left "${member.guild.name}"`); - } -}; \ No newline at end of file diff --git a/src/listeners/interactionCreate.js b/src/listeners/interactionCreate.js deleted file mode 100644 index c1449c3..0000000 --- a/src/listeners/interactionCreate.js +++ /dev/null @@ -1,323 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); -const { - Interaction, // eslint-disable-line no-unused-vars - MessageActionRow, - MessageButton, - MessageEmbed -} = require('discord.js'); - -module.exports = class InteractionCreateEventListener extends EventListener { - constructor(client) { - super(client, { event: 'interactionCreate' }); - } - - /** - * @param {Interaction} interaction - */ - async execute(interaction) { - this.client.log.debug(interaction); - - const settings = await this.client.utils.getSettings(interaction.guild.id); - const i18n = this.client.i18n.getLocale(settings.locale); - - const blacklisted = settings.blacklist.members.includes[interaction.user.id] || - interaction.member?.roles.cache?.some(role => settings.blacklist.roles.includes(role)); - if (blacklisted) { - return interaction.reply({ - content: i18n('blacklisted'), - ephemeral: true - }); - } - - const handlePanel = async id => { - const cat_row = await this.client.db.models.Category.findOne({ where: { id } }); - - if (!cat_row) { - this.client.log.warn('Could not find a category with the ID given by a panel interaction'); - return interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('command_execution_error.title')) - .setDescription(i18n('command_execution_error.description')) - ], - ephemeral: true - }); - } - - const tickets = await this.client.db.models.Ticket.findAndCountAll({ - where: { - category: cat_row.id, - creator: interaction.user.id, - open: true - } - }); - - if (tickets.count >= cat_row.max_per_member) { - if (cat_row.max_per_member === 1) { - return interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.new.response.has_a_ticket.title')) - .setDescription(i18n('commands.new.response.has_a_ticket.description', tickets.rows[0].id)) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } else { - const list = tickets.rows.map(row => { - if (row.topic) { - const description = row.topic.substring(0, 30); - const ellipses = row.topic.length > 30 ? '...' : ''; - return `<#${row.id}>: \`${description}${ellipses}\``; - } else { - return `<#${row.id}>`; - } - }); - return interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.new.response.max_tickets.title', tickets.count)) - .setDescription(i18n('commands.new.response.max_tickets.description', list.join('\n'))) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - } else { - try { - const t_row = await this.client.tickets.create(interaction.guild.id, interaction.user.id, id); - return interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.new.response.created.title')) - .setDescription(i18n('commands.new.response.created.description', `<#${t_row.id}>`)) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } catch (error) { - this.client.log.error(error); - return interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.new.response.error.title')) - .setDescription(error.message) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - } - }; - - if (interaction.isCommand()) { - // handle slash commands - this.client.commands.handle(interaction); - } else if (interaction.isButton()) { - if (interaction.customId.startsWith('panel.single')) { - // handle single-category panels - handlePanel(interaction.customId.split(':')[1]); - } else if (interaction.customId.startsWith('ticket.claim')) { - // handle ticket claiming - if (!(await this.client.utils.isStaff(interaction.member))) return; - const t_row = await this.client.db.models.Ticket.findOne({ where: { id: interaction.channel.id } }); - await t_row.update({ claimed_by: interaction.user.id }); - await interaction.channel.permissionOverwrites.edit(interaction.user.id, { VIEW_CHANNEL: true }, `Ticket claimed by ${interaction.user.tag}`); - - const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } }); - - for (const role of cat_row.roles) { - await interaction.channel.permissionOverwrites.edit(role, { VIEW_CHANNEL: false }, `Ticket claimed by ${interaction.user.tag}`); - } - - this.client.log.info(`${interaction.user.tag} has claimed "${interaction.channel.name}" in "${interaction.guild.name}"`); - - await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('ticket.claimed.title')) - .setDescription(i18n('ticket.claimed.description', interaction.member.toString())) - .setFooter(settings.footer, interaction.guild.iconURL()) - ] - }); - - const components = new MessageActionRow(); - - if (cat_row.claiming) { - components.addComponents( - new MessageButton() - .setCustomId('ticket.unclaim') - .setLabel(i18n('ticket.unclaim')) - .setEmoji('♻️') - .setStyle('SECONDARY') - ); - } - - if (settings.close_button) { - components.addComponents( - new MessageButton() - .setCustomId('ticket.close') - .setLabel(i18n('ticket.close')) - .setEmoji('✖️') - .setStyle('DANGER') - ); - } - - await interaction.message.edit({ components: [components] }); - } else if (interaction.customId.startsWith('ticket.unclaim')) { - // handle ticket unclaiming - if (!(await this.client.utils.isStaff(interaction.member))) return; - const t_row = await this.client.db.models.Ticket.findOne({ where: { id: interaction.channel.id } }); - await t_row.update({ claimed_by: null }); - - await interaction.channel.permissionOverwrites.delete(interaction.user.id, `Ticket released by ${interaction.user.tag}`); - - const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } }); - - for (const role of cat_row.roles) { - await interaction.channel.permissionOverwrites.edit(role, { VIEW_CHANNEL: true }, `Ticket released by ${interaction.user.tag}`); - } - - this.client.log.info(`${interaction.user.tag} has released "${interaction.channel.name}" in "${interaction.guild.name}"`); - - await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('ticket.released.title')) - .setDescription(i18n('ticket.released.description', interaction.member.toString())) - .setFooter(settings.footer, interaction.guild.iconURL()) - ] - }); - - const components = new MessageActionRow(); - - if (cat_row.claiming) { - components.addComponents( - new MessageButton() - .setCustomId('ticket.claim') - .setLabel(i18n('ticket.claim')) - .setEmoji('🙌') - .setStyle('SECONDARY') - ); - } - - if (settings.close_button) { - components.addComponents( - new MessageButton() - .setCustomId('ticket.close') - .setLabel(i18n('ticket.close')) - .setEmoji('✖️') - .setStyle('DANGER') - ); - } - - await interaction.message.edit({ components: [components] }); - } else if (interaction.customId.startsWith('ticket.close')) { - // handle ticket close button - const t_row = await this.client.db.models.Ticket.findOne({ where: { id: interaction.channel.id } }); - await interaction.reply({ - components: [ - new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId(`confirm_close:${interaction.id}`) - .setLabel(i18n('commands.close.response.confirm.buttons.confirm')) - .setEmoji('✅') - .setStyle('SUCCESS') - ) - .addComponents( - new MessageButton() - .setCustomId(`cancel_close:${interaction.id}`) - .setLabel(i18n('commands.close.response.confirm.buttons.cancel')) - .setEmoji('❌') - .setStyle('SECONDARY') - ) - ], - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.close.response.confirm.title')) - .setDescription(settings.log_messages ? i18n('commands.close.response.confirm.description_with_archive') : i18n('commands.close.response.confirm.description')) - .setFooter(this.client.utils.footer(settings.footer, i18n('collector_expires_in', 30)), interaction.guild.iconURL()) - ], - ephemeral: true - }); - - - const filter = i => i.user.id === interaction.user.id && i.customId.includes(interaction.id); - const collector = interaction.channel.createMessageComponentCollector({ - filter, - time: 30000 - }); - - collector.on('collect', async i => { - await i.deferUpdate(); - - if (i.customId === `confirm_close:${interaction.id}`) { - await this.client.tickets.close(t_row.id, interaction.user.id, interaction.guild.id); - await i.editReply({ - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setTitle(i18n('commands.close.response.closed.title', t_row.number)) - .setDescription(i18n('commands.close.response.closed.description', t_row.number)) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } else { - await i.editReply({ - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('commands.close.response.canceled.title')) - .setDescription(i18n('commands.close.response.canceled.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - - collector.stop(); - }); - - collector.on('end', async collected => { - if (collected.size === 0) { - await interaction.editReply({ - components: [], - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(interaction.user.username, interaction.user.displayAvatarURL()) - .setTitle(i18n('commands.close.response.confirmation_timeout.title')) - .setDescription(i18n('commands.close.response.confirmation_timeout.description')) - .setFooter(settings.footer, interaction.guild.iconURL()) - ], - ephemeral: true - }); - } - }); - } - } else if (interaction.isSelectMenu()) { - if (interaction.customId.startsWith('panel.multiple')) { - // handle multi-category panels and new command - handlePanel(interaction.values[0]); - } - } - } -}; \ No newline at end of file diff --git a/src/listeners/messageCreate.js b/src/listeners/messageCreate.js deleted file mode 100644 index fbab7a4..0000000 --- a/src/listeners/messageCreate.js +++ /dev/null @@ -1,190 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); -const fetch = require('node-fetch'); -const { - MessageAttachment, - MessageEmbed -} = require('discord.js'); - -module.exports = class MessageCreateEventListener extends EventListener { - constructor(client) { - super(client, { event: 'messageCreate' }); - } - - async execute(message) { - if (!message.guild) return; - - const settings = await this.client.utils.getSettings(message.guild.id); - const i18n = this.client.i18n.getLocale(settings.locale); - - const t_row = await this.client.db.models.Ticket.findOne({ where: { id: message.channel.id } }); - - if (t_row) { - const should_log_message = process.env.DB_TYPE.toLowerCase() !== 'sqlite' && settings.log_messages && !message.system; - if (should_log_message) this.client.tickets.archives.addMessage(message); // add the message to the archives (if it is in a ticket channel) - - const ignore = [this.client.user.id, t_row.creator]; - if (!t_row.first_response && !ignore.includes(message.author.id)) t_row.first_response = new Date(); - - t_row.last_message = new Date(); - await t_row.save(); - } else if (message.content.startsWith('tickets/')) { - if (!message.member.permissions.has('MANAGE_GUILD')) return; - - const match = message.content.toLowerCase().match(/tickets\/(\w+)/i); - - if (!match) return; - - switch (match[1]) { - case 'surveys': { - const attachments = [...message.attachments.values()]; - if (attachments.length >= 1) { - this.client.log.info(`Downloading surveys for "${message.guild.name}"`); - const data = await (await fetch(attachments[0].url)).json(); - for (const survey in data) { - const survey_data = { - guild: message.guild.id, - name: survey - }; - const [s_row] = await this.client.db.models.Survey.findOrCreate({ - defaults: survey_data, - where: survey_data - }); - s_row.questions = data[survey]; - await s_row.save(); - } - this.client.log.success(`Updated surveys for "${message.guild.name}"`); - message.channel.send({ content: i18n('commands.settings.response.settings_updated') }); - } else { - const surveys = await this.client.db.models.Survey.findAll({ where: { guild: message.guild.id } }); - const data = {}; - - for (const survey in surveys) { - const { - name, questions - } = surveys[survey]; - data[name] = questions; - } - - const attachment = new MessageAttachment( - Buffer.from(JSON.stringify(data, null, 2)), - 'surveys.json' - ); - message.channel.send({ files: [attachment] }); - } - break; - } - case 'tags': { - const attachments = [...message.attachments.values()]; - if (attachments.length >= 1) { - this.client.log.info(`Downloading tags for "${message.guild.name}"`); - const data = await (await fetch(attachments[0].url)).json(); - settings.tags = data; - await settings.save(); - this.client.log.success(`Updated tags for "${message.guild.name}"`); - this.client.commands.publish(message.guild); - message.channel.send({ content: i18n('commands.settings.response.settings_updated') }); - } else { - const list = Object.keys(settings.tags).map(t => `❯ **\`${t}\`**`); - const attachment = new MessageAttachment( - Buffer.from(JSON.stringify(settings.tags, null, 2)), - 'tags.json' - ); - return await message.channel.send({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.tag.response.list.title')) - .setDescription(list.join('\n')) - .setFooter(settings.footer, message.guild.iconURL()) - ], - files: [attachment] - }); - } - break; - } - } - } else { - if (message.author.bot) return; - - const p_row = await this.client.db.models.Panel.findOne({ where: { channel: message.channel.id } }); - - if (p_row) { - // handle message panels - - await message.delete(); - - const cat_row = await this.client.db.models.Category.findOne({ where: { id: p_row.category } }); - - const tickets = await this.client.db.models.Ticket.findAndCountAll({ - where: { - category: cat_row.id, - creator: message.author.id, - open: true - } - }); - - let response; - - if (tickets.count >= cat_row.max_per_member) { - if (cat_row.max_per_member === 1) { - const embed = new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(message.author.username, message.author.displayAvatarURL()) - .setTitle(i18n('commands.new.response.has_a_ticket.title')) - .setDescription(i18n('commands.new.response.has_a_ticket.description', tickets.rows[0].id)) - .setFooter(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL()); - try { - response = await message.author.send({ embeds: [embed] }); - } catch { - response = await message.channel.send({ embeds: [embed] }); - } - } else { - const list = tickets.rows.map(row => { - if (row.topic) { - const description = row.topic.substring(0, 30); - const ellipses = row.topic.length > 30 ? '...' : ''; - return `<#${row.id}>: \`${description}${ellipses}\``; - } else { - return `<#${row.id}>`; - } - }); - const embed = new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(message.author.username, message.author.displayAvatarURL()) - .setTitle(i18n('commands.new.response.max_tickets.title', tickets.count)) - .setDescription(i18n('commands.new.response.max_tickets.description', list.join('\n'))) - .setFooter(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.author.iconURL()); - try { - response = await message.author.send({ embeds: [embed] }); - } catch { - response = await message.channel.send({ embeds: [embed] }); - } - } - } else { - try { - await this.client.tickets.create(message.guild.id, message.author.id, cat_row.id, message.cleanContent); - } catch (error) { - const embed = new MessageEmbed() - .setColor(settings.error_colour) - .setAuthor(message.author.username, message.author.displayAvatarURL()) - .setTitle(i18n('commands.new.response.error.title')) - .setDescription(error.message) - .setFooter(this.client.utils.footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL()); - try { - response = await message.author.send({ embeds: [embed] }); - } catch { - response = await message.channel.send({ embeds: [embed] }); - } - } - } - - if (response) { - setTimeout(async () => { - await response.delete(); - }, 15000); - } - } - } - } -}; - diff --git a/src/listeners/messageDelete.js b/src/listeners/messageDelete.js deleted file mode 100644 index 540a2e9..0000000 --- a/src/listeners/messageDelete.js +++ /dev/null @@ -1,15 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); - -module.exports = class MessageDeleteEventListener extends EventListener { - constructor(client) { - super(client, { event: 'messageDelete' }); - } - - async execute(message) { - if (!message.guild) return; - - const settings = await this.client.utils.getSettings(message.guild.id); - - if (settings.log_messages && !message.system) this.client.tickets.archives.deleteMessage(message); // mark the message as deleted in the database (if it exists) - } -}; \ No newline at end of file diff --git a/src/listeners/messageUpdate.js b/src/listeners/messageUpdate.js deleted file mode 100644 index 9f06053..0000000 --- a/src/listeners/messageUpdate.js +++ /dev/null @@ -1,23 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); - -module.exports = class MessageUpdateEventListener extends EventListener { - constructor(client) { - super(client, { event: 'messageUpdate' }); - } - - async execute(oldm, newm) { - if (newm.partial) { - try { - await newm.fetch(); - } catch (error) { - return this.client.log.error(error); - } - } - - if (!newm.guild) return; - - const settings = await this.client.utils.getSettings(newm.guild.id); - - if (settings.log_messages && !newm.system) this.client.tickets.archives.updateMessage(newm); // update the message in the database - } -}; \ No newline at end of file diff --git a/src/listeners/rateLimit.js b/src/listeners/rateLimit.js deleted file mode 100644 index 29053e0..0000000 --- a/src/listeners/rateLimit.js +++ /dev/null @@ -1,11 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); - -module.exports = class RateLimitEventListener extends EventListener { - constructor(client) { - super(client, { event: 'rateLimit' }); - } - - async execute(limit) { - this.client.log.warn('Rate-limited!', limit); - } -}; \ No newline at end of file diff --git a/src/listeners/ready.js b/src/listeners/ready.js deleted file mode 100644 index dc01439..0000000 --- a/src/listeners/ready.js +++ /dev/null @@ -1,35 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); - -module.exports = class ReadyEventListener extends EventListener { - constructor(client) { - super(client, { - event: 'ready', - once: true - }); - } - - async execute() { - this.client.log.success(`Connected to Discord as "${this.client.user.tag}"`); - - this.client.log.info('Loading commands'); - this.client.commands.load(); // load internal commands - this.client.plugins.plugins.forEach(p => p.load()); // call load function for each plugin - this.client.commands.publish(); // send commands to discord - - if (this.client.config.presence.presences.length > 1) { - const { selectPresence } = require('../utils/discord'); - setInterval(() => { - const presence = selectPresence(); - this.client.user.setPresence(presence); - this.client.log.debug(`Updated presence: ${presence.activities[0].type} ${presence.activities[0].name}`); - }, this.client.config.presence.duration * 1000); - } - - if (this.client.config.super_secret_setting) { - setInterval(async () => { - await this.client.postStats(); - }, 3600000); - await this.client.postStats(); - } - } -}; \ No newline at end of file diff --git a/src/listeners/warn.js b/src/listeners/warn.js deleted file mode 100644 index f1020e9..0000000 --- a/src/listeners/warn.js +++ /dev/null @@ -1,11 +0,0 @@ -const EventListener = require('../modules/listeners/listener'); - -module.exports = class WarnEventListener extends EventListener { - constructor(client) { - super(client, { event: 'warn' }); - } - - async execute(warning) { - this.client.log.warn(warning); - } -}; \ No newline at end of file diff --git a/src/locales/ar.json b/src/locales/ar.json deleted file mode 100644 index 7cfdb0a..0000000 --- a/src/locales/ar.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "bot": { - "missing_permissions": { - "description": "احتاج الى الاذونات التالية : \n%s" - } - } -} diff --git a/src/locales/cs-CZ.json b/src/locales/cs-CZ.json deleted file mode 100644 index 3f5feae..0000000 --- a/src/locales/cs-CZ.json +++ /dev/null @@ -1,340 +0,0 @@ -{ - "blacklisted": "❌ Jsi na černé listině", - "bot": { - "missing_permissions": { - "description": "Discord Tickets potřebuje následující oprávnění\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s od [eartharoid](%s)" - }, - "collector_expires_in": "Vyprší za %d sekund", - "command_execution_error": { - "description": "Během provádění příkazu došlo k neočekávané chybě.\nPožádej správce serveru, aby zkontroloval výstup konzole / protokoly pro více detailů.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Přidat člena do tiketu", - "name": "pridat", - "options": { - "member": { - "description": "Člen k přidání do tiketu", - "name": "člen" - }, - "ticket": { - "description": "Tiket, do kterého chcete přidat člena", - "name": "ticket" - } - }, - "response": { - "added": { - "description": "%s byl přidán do %s.", - "title": "✅ Člen přidán" - }, - "no_member": { - "description": "Označte prosím člena, kterého chcete přidat.", - "title": "❌ Neznámý člen" - }, - "no_permission": { - "description": "Nejste tvůrcem tohoto tiketu a nejste ani člen týmu, nemůžete do tohoto tiketu přidávat členy.", - "title": "❌ Nedostatečná oprávnění" - }, - "not_a_ticket": { - "description": "Použijte tento příkaz c kanálu tiketu nebo označte kanál.", - "title": "❌ Toto není kanál tiketu" - } - } - }, - "blacklist": { - "description": "Zablokovat/povolit uživateli interakci s botem", - "name": "blacklist", - "response": { - "empty_list": { - "description": "Na černé listině nejsou žádní uživatelé nebo role. Použij `%sblacklist <členNeboRole>` pro přidání uživatele nebo role na černou listinu.", - "title": "📃 Zablokovaní uživatelé a role" - }, - "illegal_action": { - "description": "%s je člen týmu a nemůže být na černé listině.", - "title": "❌ Tohoto člena nemůžeš přidat na černou listinu" - }, - "list": { - "title": "📃 Zablokovaní uživatelé a role" - }, - "member_added": { - "description": "Uživatel <@%s> byl přidán na černou listinu. Už nebude moci využívat bota.", - "title": "✅ Uživatel přidán na černou listinu" - }, - "member_removed": { - "description": "Uživatel <@%s> byl odebrán z černé listiny. Nyní bude moci využívat bota.", - "title": "✅ Uživatel odebrán z z černé listiny" - }, - "role_added": { - "description": "Role <@&%s> byla přidána na černou listinu. Uživatelé s touto rolí nyní nebudou moci využívat bota.", - "title": "✅ Role přidána na černou listinu" - }, - "role_removed": { - "description": "Role <@&%s> byla odebrána z černé listiny. Nyní bude moci využívat bota.", - "title": "✅ Role odebrána z černé listiny" - } - } - }, - "close": { - "description": "Uzavře kanál s ticketem", - "name": "zavrit", - "response": { - "closed": { - "description": "Tiket #%s byl uzavřen.", - "title": "✅ Ticket uzavřen" - }, - "closed_multiple": { - "description": [ - "%d tiket byl uzavřen.", - "%d tiketů bylo uzavřeno." - ], - "title": [ - "✅ Ticket uzavřen", - "✅ Tikety uzavřeny" - ] - }, - "confirm": { - "buttons": { - "confirm": "zavrit" - }, - "description": "Zareagujte pomocí ✅ k uzavření tohoto tiketu.", - "description_with_archive": "Později si budete moci zobrazit archivovanou verzi.", - "title": "❔ Jste si jistí?" - }, - "confirm_multiple": { - "description": [ - "Zareagujte pomocí ✅ pro uzavření %d tiketu.", - "Zareagujte pomocí ✅ pro uzavření %d tiketů." - ], - "title": "❔ Jste si jistí?" - }, - "confirmation_timeout": { - "description": "Potvrzení vám trvalo příliš dlouho.", - "title": "❌ Doba pro reakci vypršela" - }, - "invalid_time": { - "description": "Zadaný čas nebyl rozpoznán.", - "title": "❌ Neplatný vstup" - }, - "no_tickets": { - "description": "Nejsou zde žádné tikety, které byly aktivní po zadané časově rozmezí.", - "title": "❌ Žádné tikety k uzavření" - }, - "not_a_ticket": { - "description": "Tento příkaz prosím použijte v kanálu tiketu nebo použijte vlajku tiketu.\nZadejte `%shelp close` pro více informací.", - "title": "❌ Toto není kanál tiketu" - }, - "unresolvable": { - "description": "`%s` nebyl rozpoznán jako tiket. Zadejte prosím ID nebo číslo tiketu nebo jej označte.", - "title": "❌ Chyba" - } - } - }, - "help": { - "description": "Seznam příkazů, ke kterým máš přístup, nebo k nalezení dalších informací o příkazu", - "name": "napoveda", - "response": { - "list": { - "description": "Příkazy, ke kterým máš přístup, jsou vypsány níže. Pro více informací o příkazu zadej `{prefix}napoveda [příkaz]`. Pro vytvoření ticketu zadej `{prefix}novy [téma]`.", - "fields": { - "commands": "Příkazy" - }, - "title": "❔ Nápověda" - } - } - }, - "new": { - "description": "Vytvořit ticket", - "name": "novy", - "request_topic": { - "description": "Popiš prosím stručně několika slovy, o čem je tento ticket.", - "title": "Téma ticketu" - }, - "response": { - "created": { - "description": "Tvůj ticket byl vytvořen: %s.", - "title": "✅ Ticket vytvořen" - }, - "error": { - "title": "❌ Chyba" - }, - "has_a_ticket": { - "description": "Prosím použij již existující ticket (<#%s>) nebo jej uzavři pro vytvoření nového.", - "title": "❌ Již máš otevřený ticket" - }, - "max_tickets": { - "description": "Prosím použij `%szavrit` pro uzavření nepotřebných ticketů.\n\n%s", - "title": "❌ Již máš %d otevřených ticketů" - }, - "no_categories": { - "description": "Než budeš moci vytvořit nový ticket, musí správce serveru vytvořit alespoň jednu kategorii pro tickety.", - "title": "❌ Nelze vytvořit ticket" - }, - "select_category": { - "description": "Vyber kategorii, která je nejrelevantnější pro téma ticketu.", - "title": "🔤 Prosím vyber kategorii ticketu" - }, - "select_category_timeout": { - "description": "Výběr kategorie ticketu trval příliš dlouho.", - "title": "❌ Doba pro reakci vypršela" - } - } - }, - "panel": { - "description": "Vytvořit nový ticket panel", - "name": "panel", - "response": { - "invalid_category": { - "description": "Jedno nebo více ID kategorií je vadných.", - "title": "❌ Neplatná kategorie" - } - } - }, - "remove": { - "description": "Odebrat člena z ticketu", - "name": "odebrat", - "response": { - "no_member": { - "description": "Označ prosím člena, kterého chceš přidat.", - "title": "❌ Neznámý člen" - }, - "no_permission": { - "description": "Nejsi tvůrcem tohoto ticketu a nejsi ani členem týmu, nemůžeš z tohoto ticketu odebírat členy.", - "title": "❌ Nedostatečná oprávnění" - }, - "not_a_ticket": { - "description": "Použijte tento příkaz c kanálu tiketu nebo označte kanál.", - "title": "❌ Toto není kanál tiketu" - }, - "removed": { - "description": "%s byl/a odebrán/a z %s.", - "title": "✅ Člen odebrán" - } - } - }, - "settings": { - "description": "Konfigurace Discord Tickets", - "name": "nastaveni" - }, - "stats": { - "description": "Zobrazit statistiky ticketu", - "fields": { - "messages": "Zprávy", - "response_time": { - "minutes": "%s minut", - "title": "Průměrná doba odpovědi" - }, - "tickets": "Tickety" - }, - "name": "statistiky", - "response": { - "global": { - "description": "Statistiky všech ticketů napříč všemi servery, kde je použita tato instance bota Discord Tickets.", - "title": "📊 Globální statistiky" - }, - "guild": { - "description": "Statistiky o ticketech v tomto serveru. Tato data jsou v mezipaměti jednu hodinu.", - "title": "📊 Statistiky tohoto serveru" - } - } - }, - "survey": { - "description": "Zobrazit odpovědi dotazníku", - "name": "dotazník", - "response": { - "list": { - "title": "📃 Dotazníky" - } - } - }, - "tag": { - "description": "Použít odpověď na značku", - "name": "značka", - "response": { - "error": "❌ Chyba", - "list": { - "title": "📃 Seznam značek" - }, - "missing": "Tato značka vyžaduje následující argumenty:\n%s", - "not_a_ticket": { - "description": "Tato značka může být použita pouze v kanálu ticketu, protože používá reference na tickety.", - "title": "❌ Toto není kanál tiketu" - } - } - }, - "topic": { - "description": "Změnit téma ticketu", - "name": "tema", - "response": { - "changed": { - "description": "Téma tohoto ticketu bylo změněno.", - "title": "✅ Téma změněno" - }, - "not_a_ticket": { - "description": "Tento příkaz prosím použij kanálu ticketu, jehož téma chceš změnit.", - "title": "❌ Toto není kanál tiketu" - } - } - } - }, - "message_will_be_deleted_in": "Tato zpráva bude smazána za %d sekund", - "missing_permissions": { - "description": "K provedení tohoto příkazu nemáš dostatečná oprávnění:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s si vzal tento ticket.", - "title": "✅ Ticket vzat" - }, - "close": "Uzavřít", - "closed": { - "description": "Tento ticket byl uzavřen.\nKanál bude smazán za 5 sekund.", - "title": "✅ Ticket uzavřen" - }, - "closed_by_member": { - "description": "Tento ticket byl uzavřen uživatelem %s.\nKanál bude smazán za 5 sekund.", - "title": "✅ Ticket uzavřen" - }, - "closed_by_member_with_reason": { - "description": "Tento ticket byl uzavřen uživatelem %s: `%s`\nKanál bude smazán za 5 sekund.", - "title": "✅ Ticket uzavřen" - }, - "closed_with_reason": { - "description": "Tento ticket byl uzavřen: `%s`\nKanál bude smazán za 5 sekund.", - "title": "✅ Ticket uzavřen" - }, - "member_added": { - "description": "%s byl/a přidán/a uživatelem %s", - "title": "Člen přidán" - }, - "member_removed": { - "description": "%s byl/a odebrán/a uživatelem %s", - "title": "Člen odebrán" - }, - "opening_message": { - "content": "%s\n%s vytvořil nový ticket", - "fields": { - "topic": "Téma" - } - }, - "questions": "Prosím odpověz na následující otázky:\n\n%s", - "released": { - "description": "%s uvolnil ticket.", - "title": "✅ Ticket uvolněn" - }, - "survey": { - "complete": { - "description": "Děkujeme za zpětnou vazbu.", - "title": "✅ Děkujeme" - }, - "start": { - "description": "Zdravíme, %s. Než bude tento kanál odstraněn, nevadilo by ti vyplnit rychlý %d-otázkový dotazník?", - "title": "❔ Zpětná vazba" - } - } - } -} diff --git a/src/locales/da-DK.json b/src/locales/da-DK.json deleted file mode 100644 index b6adb8b..0000000 --- a/src/locales/da-DK.json +++ /dev/null @@ -1,610 +0,0 @@ -{ - "blacklisted": "❌ Du er blacklisted", - "bot": { - "missing_permissions": { - "description": "Discord Tickets kræver følgende tilladelser:\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s af [eartharoid](%s)" - }, - "collector_expires_in": "Udløber om %d sekunder", - "command_execution_error": { - "description": "Der opstod en uventet fejl under udførelse af kommandoer.\nBed en administrator om at kontrollere konsoloutputtet/-logfilerne for at få flere oplysninger.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Tilføj et medlem til en ticket", - "name": "add", - "options": { - "member": { - "description": "Det medlem der skal tilføjes til ticketen", - "name": "member" - }, - "ticket": { - "description": "Den ticket du vil tilføje medlemmet til", - "name": "ticket" - } - }, - "response": { - "added": { - "description": "%s er føjet til %s.", - "title": "✅ Medlem tilføjet" - }, - "no_member": { - "description": "Nævn det medlem, du vil tilføje.", - "title": "❌ Ukendt medlem" - }, - "no_permission": { - "description": "Du er ikke skaberen af denne ticket, og du er ikke et staff medlem; Du kan ikke føje medlemmer til denne ticket.", - "title": "❌ Utilstrækkelig tilladelser" - }, - "not_a_ticket": { - "description": "Brug denne kommando i ticket kanalen, eller nævn kanalen.", - "title": "❌ Dette er ikke en ticket kanal" - } - } - }, - "blacklist": { - "description": "Se eller rediger blacklisten", - "name": "blacklist", - "options": { - "add": { - "description": "Tilføj et medlem eller rolle til blacklisten", - "name": "add", - "options": { - "member_or_role": { - "description": "Det medlem eller rolle du vil tilføje til blacklisten", - "name": "member_or_role" - } - } - }, - "remove": { - "description": "Fjern et medlem eller rolle fra blacklisten", - "name": "remove", - "options": { - "member_or_role": { - "description": "Det medlem eller rolle du vil fjerne fra blacklisten", - "name": "member_or_role" - } - } - }, - "show": { - "description": "Vis medlemmer eller roller i blacklisten", - "name": "show" - } - }, - "response": { - "empty_list": { - "description": "Der er ingen medlemmer eller roller, der er blacklistet. Skriv `/blacklist add` for at tilføje et medlem eller en rolle til blacklisten.", - "title": "📃 Blacklistede medlemmer og roller" - }, - "illegal_action": { - "description": "%s er staff medlem og kan ikke blacklistes.", - "title": "❌ Du kan ikke blackliste dette medlem" - }, - "invalid": { - "description": "Denne bruger eller rolle kan ikke blive fjernet fra blacklisten eftersom de ikke er blacklisted.", - "title": "❌ Fejl" - }, - "list": { - "fields": { - "members": "Members", - "roles": "Roles" - }, - "title": "📃 Blacklistede medlemmer og roller" - }, - "member_added": { - "description": "<@%s> er blevet føjet til blacklisten. De vil ikke længere være i stand til at interagere med botten.", - "title": "✅ Føjet medlem til blacklisten" - }, - "member_removed": { - "description": "<@%s> er blevet fjernet fra den blacklisten. De kan nu bruge botten igen.", - "title": "✅ fjernet medlem fra blacklisten" - }, - "role_added": { - "description": "<@&%s> er blevet føjet til blacklisten. Medlemmer med denne rolle vil ikke længere være i stand til at interagere med botten.", - "title": "✅ Føjet rolle til blacklisten" - }, - "role_removed": { - "description": "<@&%s> er blevet fjernet fra blacklisten. Medlemmer med denne rolle kan nu bruge botten igen.", - "title": "✅ Fjernede rollen fra blacklisten" - } - } - }, - "close": { - "description": "Luk en ticketkanal", - "name": "close", - "options": { - "reason": { - "description": "Begrundelse for at lukke ticketen(e)", - "name": "reason" - }, - "ticket": { - "description": "Ticketen der skal lukkes, enten ticket nummeret eller kanal ID", - "name": "ticket" - }, - "time": { - "description": "Luk alle tickets der har været inaktive i den specificeret tid", - "name": "time" - } - }, - "response": { - "canceled": { - "description": "Du har annulleret handlingen.", - "title": "🚫 Annulleret" - }, - "closed": { - "description": "Ticket #%s er blevet lukket.", - "title": "✅ Ticket lukket" - }, - "closed_multiple": { - "description": [ - "%d Ticket er blevet lukket.", - "%d Tickets er blevet lukket." - ], - "title": [ - "✅ Ticket lukket", - "✅ Tickets lukket" - ] - }, - "confirm": { - "buttons": { - "cancel": "Annullér", - "confirm": "Bekræft" - }, - "description": "Venligst bekræft din beslutning.", - "description_with_archive": "Ticketen vil blive arkiveret for fremtidig reference.", - "title": "❔ Er du sikker?" - }, - "confirm_multiple": { - "buttons": { - "cancel": "Annullér", - "confirm": [ - "Luk %d ticket", - "Luk %d tickets" - ] - }, - "description": [ - "Du er ved at lukke %d ticket.", - "Du er ved at lukke %d tickets." - ], - "title": "❔ Er du sikker?" - }, - "confirmation_timeout": { - "description": "Du tog for lang tid på at bekræfte.", - "title": "❌ Interaktionstid er udløbet" - }, - "invalid_time": { - "description": "Den angivne tidsperiode kunne ikke parses.", - "title": "❌ Ugyldigt input" - }, - "no_permission": { - "description": "Du er ikke et staff medlem eller skaberen af denne ticket.", - "title": "❌ Der er ikke tilstrækkelig tilladelse" - }, - "no_tickets": { - "description": "Der er ingen tickets, der har været inaktive i denne periode.", - "title": "❌ Ingen tickets til at lukke" - }, - "not_a_ticket": { - "description": "Brug denne kommando i en ticketkanal, eller brug ticketflaget.\nSkriv '/help close' for at få yderligere oplysninger.", - "title": "❌ Dette er ikke en ticketkanal" - }, - "unresolvable": { - "description": "`%s` kunne ikke løses til en ticket. Angiv ticket ID/nævn eller nummer.", - "title": "❌ Fejl" - } - } - }, - "help": { - "description": "Vis de kommandoer du har adgang til", - "name": "help", - "response": { - "list": { - "description": "De kommandoer, du har adgang til, er angivet nedenfor. Hvis du vil oprette en ticket, skal du skrive **'/new'**.", - "fields": { - "commands": "commands" - }, - "title": "❔ Hjælp" - } - } - }, - "new": { - "description": "Opret en ny ticket", - "name": "new", - "options": { - "topic": { - "description": "Emnet på ticketen", - "name": "topic" - } - }, - "request_topic": { - "description": "Angiv kort, hvad denne ticket handler om, med et par ord.", - "title": "⚠️ Ticket emne" - }, - "response": { - "created": { - "description": "Din ticket er blevet oprettet: %s.", - "title": "✅ Ticket oprettet" - }, - "error": { - "title": "❌ Fejl" - }, - "has_a_ticket": { - "description": "Brug venligst din eksisterende ticket <# (%s>) eller luk den, før du opretter en anden.", - "title": "❌ Du har allerede en åben ticket" - }, - "max_tickets": { - "description": "Brug venligst '/close' for at lukke unødvendige tickets.\n\n%s", - "title": "❌ Du har allerede %d åbne tickets" - }, - "no_categories": { - "description": "En serveradministrator skal oprette mindst én ticketkategori, før en ny ticket kan åbnes.", - "title": "❌ Kan ikke oprette ticket" - }, - "select_category": { - "description": "Vælg den kategori, der er mest relevant for din tickets emne.", - "title": "🔤 Vælg ticket kategorien" - }, - "select_category_timeout": { - "description": "Du tog for lang tid om at vælge ticket kategorien.", - "title": "❌ Interaktionstid er udløbet" - } - } - }, - "panel": { - "description": "Opret et nyt ticket panel", - "name": "panel", - "options": { - "categories": { - "description": "En komma separeret liste af kategori ID'er", - "name": "categories" - }, - "description": { - "description": "Beskrivelsen for panel beskeden", - "name": "description" - }, - "image": { - "description": "Et billed URL for panel beskeden", - "name": "image" - }, - "just_type": { - "description": "Lav et \"bare skriv\" panel?", - "name": "just_type" - }, - "thumbnail": { - "description": "Et miniaturebilled -URL til panel beskeden", - "name": "thumbnail" - }, - "title": { - "description": "Titlen for panel beskeden", - "name": "title" - } - }, - "response": { - "invalid_category": { - "description": "Et eller flere af de angivne kategori-id'er er ugyldige.", - "title": "❌ Ugyldig kategori" - }, - "too_many_categories": { - "description": "\"just type\" panelet kan kun blive brugt med en enkelt kategori.", - "title": "❌ For mange kategorier" - } - } - }, - "remove": { - "description": "Fjerne et medlem fra en ticket", - "name": "remove", - "options": { - "member": { - "description": "medlemmet der skal fjernes fra ticketen", - "name": "member" - }, - "ticket": { - "description": "Den ticket du vil fjerne medlemmet fra", - "name": "ticket" - } - }, - "response": { - "no_member": { - "description": "Nævn det medlem, du vil fjerne.", - "title": "❌ Ukendt medlem" - }, - "no_permission": { - "description": "Du er ikke skaberen af denne ticket, og du er ikke et staff medlem; Du kan ikke fjerne medlemmer fra denne ticket.", - "title": "❌ Der er ikke tilstrækkelig tilladelse" - }, - "not_a_ticket": { - "description": "Brug denne kommando i ticket kanalen, eller nævn kanalen.", - "title": "❌ Dette er ikke en ticket kanal" - }, - "removed": { - "description": "%s er blevet fjernet fra %s.", - "title": "✅ Medlem fjernet" - } - } - }, - "settings": { - "description": "Konfigurer Discord Tickets", - "name": "settings", - "options": { - "categories": { - "description": "Administrer dine ticket kategorier", - "name": "categories", - "options": { - "create": { - "description": "Opret en ny kategori", - "name": "create", - "options": { - "name": { - "description": "Navnet på kategorien", - "name": "name" - }, - "roles": { - "description": "En komma separeret liste over staff rolle ID'er for denne kategori", - "name": "roles" - } - } - }, - "delete": { - "description": "Slet en kategori", - "name": "delete", - "options": { - "id": { - "description": "ID for den kategori der skal slettes", - "name": "id" - } - } - }, - "edit": { - "description": "Foretag ændringer i en kategoris konfiguration", - "name": "edit", - "options": { - "claiming": { - "description": "Vil du aktivere ticket claim?", - "name": "claiming" - }, - "id": { - "description": "ID for den kategori der skal redigeres", - "name": "id" - }, - "image": { - "description": "Et billed URL", - "name": "image" - }, - "max_per_member": { - "description": "Det maksimum antal tickets et medlem kan have i denne kategori", - "name": "max_per_member" - }, - "name": { - "description": "Kategori navnet", - "name": "name" - }, - "name_format": { - "description": "Ticket navne format", - "name": "name_format" - }, - "opening_message": { - "description": "Tekst der skal sendes når en ticket åbnes", - "name": "opening_message" - }, - "opening_questions": { - "description": "Spørgsmål at stille når en ticket er åbnet.", - "name": "opening_questions" - }, - "ping": { - "description": "En komma separeret liste over rolle-id'er, der skal pinges", - "name": "ping" - }, - "require_topic": { - "description": "Kræv at brugeren angiver emne for ticketen?", - "name": "require_topic" - }, - "roles": { - "description": "En komma separeret liste over staff rolle ID'er", - "name": "roles" - }, - "survey": { - "description": "Undersøgelse der skal bruges", - "name": "survey" - } - } - }, - "list": { - "description": "List kategorier", - "name": "list" - } - } - }, - "set": { - "description": "Indstil muligheder", - "name": "set", - "options": { - "close_button": { - "description": "Vil du aktivere lukning med en knap?", - "name": "close_button" - }, - "colour": { - "description": "Standardfarven", - "name": "colour" - }, - "error_colour": { - "description": "Fejl farven", - "name": "error_colour" - }, - "footer": { - "description": "Embed footer tekst", - "name": "footer" - }, - "locale": { - "description": "Locale (sprog)", - "name": "locale" - }, - "log_messages": { - "description": "Gem meddelelser fra tickets?", - "name": "log_messages" - }, - "success_colour": { - "description": "Succesfarven", - "name": "success_colour" - } - } - } - }, - "response": { - "category_created": "✅ `%s` ticket kategori er blevet oprettet", - "category_deleted": "✅ `%s` ticket kategori er blevet slettet", - "category_does_not_exist": "❌ Ingen kategori eksisterer med det angivet ID", - "category_list": "Ticket kategorier", - "category_updated": "✅ `%s` ticket kategori er blevet opdateret", - "settings_updated": "✅ Indstillinger er blevet opdateret" - } - }, - "stats": { - "description": "Vis ticket statistik", - "fields": { - "messages": "Meddelelser", - "response_time": { - "minutes": "%s minutter", - "title": "Gennemsnitlig svartid" - }, - "tickets": "Tickets" - }, - "name": "stats", - "response": { - "global": { - "description": "Statistik om tickets på tværs af alle guilds, hvor denne Discord Tickets instans bruges.", - "title": "📊 Global statistik" - }, - "guild": { - "description": "Statistik om tickets i dennee guild. Disse data cachelagres i en time.", - "title": "📊 Denne servers statistik" - } - } - }, - "survey": { - "description": "Vis undersøgelsessvar", - "name": "survey", - "options": { - "survey": { - "description": "Navnet på den undersøgelse du vil se svar på", - "name": "survey" - } - }, - "response": { - "list": { - "title": "📃 Undersøgelser" - } - } - }, - "tag": { - "description": "Brug et tagsvar", - "name": "tag", - "options": { - "tag": { - "description": "Navnet på det tag du vil bruge", - "name": "tag" - } - }, - "response": { - "error": "❌ Fejl", - "list": { - "title": "📃 Tag liste" - }, - "missing": "Dette tag kræver følgende argumenter:\n%s", - "not_a_ticket": { - "description": "Dette tag kan kun bruges inden for en ticket kanal, da det bruger ticket referencer.", - "title": "❌ Dette er ikke en ticket kanal" - } - } - }, - "topic": { - "description": "Skift emnet på ticketen", - "name": "topic", - "options": { - "new_topic": { - "description": "Det nye emne på ticketen", - "name": "new_topic" - } - }, - "response": { - "changed": { - "description": "Emnet for denne ticket er blevet ændret.", - "title": "✅ Emnet er ændret" - }, - "not_a_ticket": { - "description": "Brug venligst denne kommando i ticket kanalen, du ønsker at ændre emnet på.", - "title": "❌ Dette er ikke en ticket kanal" - } - } - } - }, - "message_will_be_deleted_in": "Denne meddelelse slettes om %d sekunder", - "missing_permissions": { - "description": "Du har ikke de nødvendige tilladelser til at bruge denne kommando:\n%s", - "title": "❌ Fejl" - }, - "panel": { - "create_ticket": "Opret en ticket" - }, - "ticket": { - "claim": "Claim", - "claimed": { - "description": "%s har gjort krav på denne ticket.", - "title": "✅ Ticket gjort krav på" - }, - "close": "Luk", - "closed": { - "description": "Denne ticket er blevet lukket.\nKanalen slettes om 5 sekunder.", - "title": "✅ Ticket lukket" - }, - "closed_by_member": { - "description": "Denne ticket er blevet lukket af %s.\nKanalen slettes om 5 sekunder.", - "title": "✅ Ticket lukket" - }, - "closed_by_member_with_reason": { - "description": "Denne ticket er blevet lukket af %s: '%s'\nKanalen slettes om 5 sekunder.", - "title": "✅ Ticket lukket" - }, - "closed_with_reason": { - "description": "Denne ticket er blevet lukket: '%s'\nKanalen slettes om 5 sekunder.", - "title": "✅ Ticket lukket" - }, - "member_added": { - "description": "%s er blevet tilføjet af %s", - "title": "Medlem tilføjet" - }, - "member_removed": { - "description": "%s er blevet fjernet af %s", - "title": "Medlem fjernet" - }, - "opening_message": { - "content": "%s\n%s har oprettet en ny ticket", - "fields": { - "topic": "Emne" - } - }, - "questions": "Besvar venligst følgende spørgsmål:\n\n%s", - "released": { - "description": "%s har frigivet denne ticket.", - "title": "✅ Ticket frigivet" - }, - "survey": { - "complete": { - "description": "Tak for din feedback.", - "title": "✅ Tak skal du have" - }, - "start": { - "buttons": { - "ignore": "Nej", - "start": "Start undersøgelse" - }, - "description": "Hej, %s. Før denne kanal slettes, har du noget imod at gennemføre en hurtig %d-spørgsmålsundersøgelse?", - "title": "❔ Feedback" - } - }, - "unclaim": "unclaim" - }, - "updated_permissions": "✅ Slash kommando tilladelser opdateret" -} diff --git a/src/locales/de-DE.json b/src/locales/de-DE.json deleted file mode 100644 index 0514ba5..0000000 --- a/src/locales/de-DE.json +++ /dev/null @@ -1,610 +0,0 @@ -{ - "blacklisted": "❌ Du stehst auf der Blacklist", - "bot": { - "missing_permissions": { - "description": "Discord Tickets erfordert die folgenden Berechtigungen: \n%s ", - "title": "⚠️" - }, - "version": "[Discord Tickets] (%s) v%s von [eartharoid](%s)" - }, - "collector_expires_in": "Läuft in %d Sekunden ab", - "command_execution_error": { - "description": "Ein unerwarteter Fehler ist aufgetreten.\nFrage bitte den Andministrator um Hilfe.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Füge einen Benutzer zum Ticket hinzu", - "name": "hinzufügen", - "options": { - "member": { - "description": "Das Mitglied, das dem Ticket hinzugefügt werden soll", - "name": "Mitglied" - }, - "ticket": { - "description": "Das Ticket, zu dem das Mitglied hinzugefügt werden soll", - "name": "ticket" - } - }, - "response": { - "added": { - "description": "%s wurde zu %s hinzugefügt.", - "title": "✅ Benutzer hinzugefügt" - }, - "no_member": { - "description": "Bitte erwähne den Benutzer, den Du hinzufügen möchtest.", - "title": "❌ Unbekanntes Mitglied" - }, - "no_permission": { - "description": "Du bist weder der Ersteller dieses Tickets, noch ein Support-Mitglied; Du kannst keine neuen Benutzer zu diesem Ticket hinzufügen.", - "title": "❌ Unzureichende Berechtigung" - }, - "not_a_ticket": { - "description": "Bitte führe den Befehl im Ticket-Kanal aus, oder erwähne diesen.", - "title": "❌ Dies ist kein Ticket-Kanal" - } - } - }, - "blacklist": { - "description": "Sieh dir die Blacklist an oder bearbeite sie", - "name": "blacklist", - "options": { - "add": { - "description": "Füge ein Mitglied oder eine Rolle der Blacklist hinzu", - "name": "hinzufügen", - "options": { - "member_or_role": { - "description": "Mitglied oder Rolle der Blacklist hinzufügen", - "name": "mitglied_oder_rolle" - } - } - }, - "remove": { - "description": "Entferne einen Nutzer oder eine Rolle von der Blacklist", - "name": "entfernen", - "options": { - "member_or_role": { - "description": "Der Nutzer oder die Rolle zum Entfernen von der Blacklist", - "name": "mitglied_oder_rolle" - } - } - }, - "show": { - "description": "Zeige die Nutzer und Rollen in der Blacklist", - "name": "zeigen" - } - }, - "response": { - "empty_list": { - "description": "Es sind keine Mitglieder oder Rollen auf der Blacklist. Gebe `/blacklist add` ein, um ein Mitglied oder eine Rolle zur Blacklist hinzuzufügen.", - "title": "📃 Blockierte Benutzer und Rollen" - }, - "illegal_action": { - "description": "%s ist ein Support-Mitglied und kann nicht blockiert werden.", - "title": "❌ Sie können kein Support-Mitglied blockieren" - }, - "invalid": { - "description": "Dieses Mitglied oder diese Rolle können nicht von der Blacklist entfernt werden, weil sie nicht geblacklisted sind.", - "title": "❌ Fehler" - }, - "list": { - "fields": { - "members": "Mitglieder", - "roles": "Rollen" - }, - "title": "📃 Blockierte Benutzer und Rollen" - }, - "member_added": { - "description": "<@%s> wurde blockiert. Er/Sie kann nicht mehr mit dem Bot interagieren.", - "title": "✅ Benutzer zur Blacklist hinzugefügt" - }, - "member_removed": { - "description": "<@%s> wurde von der Blacklist entfernt. Er/Sie kann den Bot wieder verwenden.", - "title": "✅ Mitglieder von der Blacklist entfernt" - }, - "role_added": { - "description": "<@&%s> wurde zur Blacklist hinzugefügt. Mitglieder mit dieser Rolle können nicht mehr mit dem Bot interagieren.", - "title": "✅ Rolle zur Blacklist hinzugefügt" - }, - "role_removed": { - "description": "<@&%s> wurde von der Blacklist entfernt. Mitglieder mit dieser Rolle können den Bot wieder benutzen.", - "title": "✅ Rolle von der Blacklist entfernt" - } - } - }, - "close": { - "description": "Schließe einen Ticket-Kanal", - "name": "schließen", - "options": { - "reason": { - "description": "Der Grund für die Schließung des Tickets", - "name": "grund" - }, - "ticket": { - "description": "Das zu schließende Ticket, entweder die Nummer oder die Kanal-ID", - "name": "ticket" - }, - "time": { - "description": "Schließe alle Tickets, die seit der angegebenen Zeit inaktiv sind", - "name": "zeit" - } - }, - "response": { - "canceled": { - "description": "Du hast den Vorgang abgebrochen.", - "title": "🚫 Abgebrochen" - }, - "closed": { - "description": "Das Ticket #%s wurde geschlossen.", - "title": "✅ Ticket geschlossen" - }, - "closed_multiple": { - "description": [ - "Das Ticket %d wurde geschlossen.", - "Die Tickets %d wurden geschlossen." - ], - "title": [ - "✅ Ticket geschlossen", - "✅ Tickets geschlossen" - ] - }, - "confirm": { - "buttons": { - "cancel": "Abbrechen", - "confirm": "Schließen" - }, - "description": "Bitte bestätige Deine Entscheidung.", - "description_with_archive": "Das Ticket wird zum späteren Nachschlagen archiviert.", - "title": "❔ Sind Sie sicher?" - }, - "confirm_multiple": { - "buttons": { - "cancel": "Abbruch", - "confirm": [ - "%d Ticket schließen", - "%d Tickets schließen" - ] - }, - "description": [ - "Du bist dabei, %d Ticket zu schließen.", - "Du bist dabei, %d Tickets zu schließen." - ], - "title": "❔ Sind Sie sicher?" - }, - "confirmation_timeout": { - "description": "Sie haben zu lange zum Bestätigen gebraucht.", - "title": "❌ Zu lange gewartet" - }, - "invalid_time": { - "description": "Die eingegebene Zeit konnte nicht verarbeitet werden.", - "title": "❌ Ungültige Eingabe" - }, - "no_permission": { - "description": "Du bist kein Teammitglied oder der Ticketersteller", - "title": "Unzureichende Rechte" - }, - "no_tickets": { - "description": "Es gibt keine inaktiven Tickets für den ausgewählten Zeitraum.", - "title": "❌ Keine Tickets zum schließen" - }, - "not_a_ticket": { - "description": "Bitte verwende diesen Befehl in einem Ticketkanal oder verwende das Ticket-Flag.\nGebe `/help close` ein, um weitere Informationen zu erhalten.", - "title": "❌ Das ist kein Ticket-Kanal" - }, - "unresolvable": { - "description": "`%s` konnte keinem Ticket zugeordnet werden. Bitte verwende die Kanal ID / Erwähnung des Kanals, oder die Nummer.", - "title": "❌ Fehler" - } - } - }, - "help": { - "description": "Liste die Befehle, auf die Du Zugriff hast", - "name": "Hilfe", - "response": { - "list": { - "description": "Die Befehle, auf die Du Zugriff hast, sind unten aufgeführt. Um ein Ticket zu erstellen, gebe **`/new`** ein.", - "fields": { - "commands": "Befehle" - }, - "title": "❔ Hilfe" - } - } - }, - "new": { - "description": "Erstelle ein neues Ticket", - "name": "neu", - "options": { - "topic": { - "description": "Das Thema des Tickets", - "name": "thema" - } - }, - "request_topic": { - "description": "Bitte gebe in wenigen Worten kurz an, worum es bei diesem Ticket geht.", - "title": "⚠️ Ticketthema" - }, - "response": { - "created": { - "description": "Dein Ticket wurde erstellt: %s.", - "title": "✅ Ticket erstellt" - }, - "error": { - "title": "❌ Fehler" - }, - "has_a_ticket": { - "description": "Bitte verwende Dein schon vorhandenes Ticket (<#%s>) oder schließe es, bevor Du ein neues erstellst.", - "title": "❌ Du hast bereits ein offenes Ticket" - }, - "max_tickets": { - "description": "Bitte verwende `/close`, um nicht benötigte Tickets zu schließen.\n\n%s", - "title": "❌ Du hast bereits %d offene Tickets" - }, - "no_categories": { - "description": "Ein Server Administrator muss zuerst eine Kategorie erstellen, bevor Du ein Ticket erstellen kannst.", - "title": "❌ Kann kein neues Ticket erstellen" - }, - "select_category": { - "description": "Wähle die Kategorie aus, die für das Thema Deines Tickets am relevantesten ist.", - "title": "🔤 Bitte wähle eine Kategorie" - }, - "select_category_timeout": { - "description": "Du hast zu lange gebraucht um eine Ticket Kategorie zu wählen.", - "title": "❌ Interaktionszeit abgelaufen" - } - } - }, - "panel": { - "description": "Erstelle ein neues Ticket-Panel", - "name": "panel", - "options": { - "categories": { - "description": "Eine durch Kommas getrennte Liste von Kategorie-IDs", - "name": "kategorien" - }, - "description": { - "description": "Die Beschreibung für die Panel-Nachricht", - "name": "beschreibung" - }, - "image": { - "description": "Eine Bild-URL für die Panel-Nachricht", - "name": "bild" - }, - "just_type": { - "description": "Ein \"just type\"-Panel erstellen?", - "name": "just_type" - }, - "thumbnail": { - "description": "Eine Miniaturbild-URL für die Panel-Nachricht", - "name": "thumbnail" - }, - "title": { - "description": "Der Titel für die Panel-Nachricht", - "name": "titel" - } - }, - "response": { - "invalid_category": { - "description": "Eine oder mehrere der angegebenen Kategorie IDs sind nicht gültig.", - "title": "❌ Ungültige Kategorie" - }, - "too_many_categories": { - "description": "Das Feld \"just type\" kann nur mit einer einzigen Kategorie verwendet werden.", - "title": "❌ Zu viele Kategorien" - } - } - }, - "remove": { - "description": "Entferne einen Benutzer von einem Ticket", - "name": "entfernen", - "options": { - "member": { - "description": "Das Mitglied, das aus dem Ticket entfernt werden soll", - "name": "mitglied" - }, - "ticket": { - "description": "Das Ticket, aus dem das Mitglied entfernt werden soll", - "name": "ticket" - } - }, - "response": { - "no_member": { - "description": "Bitte nenne den Benutzer den Du entfernen möchtest.", - "title": "❌ Unbekannter Benutzer" - }, - "no_permission": { - "description": "Du bist weder Ersteller dieses Tickets, noch Support-Mitglied; Du kannst keine Benutzer von diesem Ticket entfernen.", - "title": "❌ Unzureichende Berechtigung" - }, - "not_a_ticket": { - "description": "Bitte führe en Befehl im Ticket-Kanal aus, oder erwähne diesen.", - "title": "❌ Das ist kein Ticket-Kanal" - }, - "removed": { - "description": "%s wurde von %s entfernt.", - "title": "✅ Benutzer entfernt" - } - } - }, - "settings": { - "description": "Konfiguriere Discord Tickets", - "name": "settings", - "options": { - "categories": { - "description": "Verwalte Deine Ticketkategorien", - "name": "kategorien", - "options": { - "create": { - "description": "Erstelle eine neue Kategorie", - "name": "erstellen", - "options": { - "name": { - "description": "Der Name der Kategorie", - "name": "name" - }, - "roles": { - "description": "Eine durch Kommas getrennte Liste von Mitarbeiterrollen-IDs für diese Kategorie", - "name": "roles" - } - } - }, - "delete": { - "description": "Lösche eine Kategorie", - "name": "delete", - "options": { - "id": { - "description": "Die ID der zu löschenden Kategorie", - "name": "id" - } - } - }, - "edit": { - "description": "Nehme Änderungen an der Konfiguration einer Kategorie vor", - "name": "edit", - "options": { - "claiming": { - "description": "Ticketanspruch aktivieren?", - "name": "beanspruchen" - }, - "id": { - "description": "Die ID der zu bearbeitenden Kategorie", - "name": "id" - }, - "image": { - "description": "Eine Bild-URL", - "name": "bild" - }, - "max_per_member": { - "description": "Die maximale Anzahl an Tickets, die ein Mitglied in dieser Kategorie haben kann", - "name": "max_per_member" - }, - "name": { - "description": "Der Kategoriename", - "name": "name" - }, - "name_format": { - "description": "Das Format des Ticketnamens", - "name": "name_format" - }, - "opening_message": { - "description": "Der zu sendende Text, wenn ein Ticket geöffnet wird", - "name": "opening_message" - }, - "opening_questions": { - "description": "Fragen, die gestellt werden müssen, wenn ein Ticket eröffnet wird.", - "name": "opening_questions" - }, - "ping": { - "description": "Eine durch Kommas getrennte Liste von Rollen-IDs, die gepingt werden sollen", - "name": "ping" - }, - "require_topic": { - "description": "Den Benutzer auffordern, das Thema des Tickets anzugeben?", - "name": "require_topic" - }, - "roles": { - "description": "Eine durch Kommas getrennte Liste von Mitarbeiterrollen-IDs", - "name": "roles" - }, - "survey": { - "description": "Die zu verwendende Umfrage", - "name": "survey" - } - } - }, - "list": { - "description": "Kategorien auflisten", - "name": "list" - } - } - }, - "set": { - "description": "Optionen festlegen", - "name": "set", - "options": { - "close_button": { - "description": "Schließen per Knopfdruck aktivieren?", - "name": "close_button" - }, - "colour": { - "description": "Die Standardfarbe", - "name": "colour" - }, - "error_colour": { - "description": "Die Fehlerfarbe", - "name": "error_colour" - }, - "footer": { - "description": "Der eingebettete Fußzeilentext", - "name": "footer" - }, - "locale": { - "description": "Das Gebietsschema (Sprache)", - "name": "gebietsschema" - }, - "log_messages": { - "description": "Nachrichten von Tickets speichern?", - "name": "log_messages" - }, - "success_colour": { - "description": "Die Erfolgsfarbe", - "name": "success_colour" - } - } - } - }, - "response": { - "category_created": "✅ Die Ticketkategorie `%s` wurde erstellt", - "category_deleted": "✅ Die Ticketkategorie `%s` wurde gelöscht", - "category_does_not_exist": "❌ Es existiert keine Kategorie mit der angegebenen ID", - "category_list": "Ticketkategorien", - "category_updated": "✅ Die Ticketkategorie `%s` wurde aktualisiert", - "settings_updated": "✅ Die Einstellungen wurden aktualisiert" - } - }, - "stats": { - "description": "Ticket Statistiken ansehen", - "fields": { - "messages": "Nachrichten", - "response_time": { - "minutes": "%s Minuten", - "title": "Durchschnittliche Reaktionszeit" - }, - "tickets": "Tickets" - }, - "name": "stats", - "response": { - "global": { - "description": "Statistiken über Tickets in allen Gilden, in denen diese Discord-Tickets-Instanz verwendet wird.", - "title": "📊 Globale Statistiken" - }, - "guild": { - "description": "Statistiken von diesem Server. Die Daten sind für eine Stunde im Zwischenspeicher.", - "title": "📊 Statistiken von diesem Server" - } - } - }, - "survey": { - "description": "Umfragen-Ergebnisse ansehen", - "name": "Umfrage", - "options": { - "survey": { - "description": "Der Name der Umfrage, deren Antworten angezeigt werden sollen", - "name": "Umfrage" - } - }, - "response": { - "list": { - "title": "📃 Umfragen" - } - } - }, - "tag": { - "description": "Verwende einen 'Tag' als Antwort", - "name": "tag", - "options": { - "tag": { - "description": "Der Name des zu verwendenden Tags", - "name": "tag" - } - }, - "response": { - "error": "❌ Fehler", - "list": { - "title": "📃 Tag-Liste" - }, - "missing": "Dieser Tag benötigt die folgenden Vorraussetzungen:\n%s", - "not_a_ticket": { - "description": "Dieses Tag kann nur innerhalb eines Ticketkanals verwendet werden, da es Ticketreferenzen verwendet.", - "title": "❌ Das ist kein Ticket-Kanal" - } - } - }, - "topic": { - "description": "Änder das Anliegen Deines Tickets", - "name": "Thema", - "options": { - "new_topic": { - "description": "Das neue Thema des Tickets", - "name": "new_topic" - } - }, - "response": { - "changed": { - "description": "Das Anliegen dieses Tickets wurde erfolgreich geändert.", - "title": "✅ Anliegen geändert" - }, - "not_a_ticket": { - "description": "Bitte verwende diesen Befehl nur in einem Ticket-Kanal in dem Du das Anliegen ändern willst.", - "title": "❌ Das ist kein Ticket-Kanal" - } - } - } - }, - "message_will_be_deleted_in": "Diese Nachricht wird in %d Sekunden gelöscht.", - "missing_permissions": { - "description": "Du hast nicht die benötigten Berechtigungen um diesen Befehl zu verwenden:\n%s", - "title": "❌ Fehler" - }, - "panel": { - "create_ticket": "Ticket erstellen" - }, - "ticket": { - "claim": "Beanspruchen", - "claimed": { - "description": "Dieses Ticket gehört jetzt %s.", - "title": "✅ Ticket beansprucht" - }, - "close": "Schließen", - "closed": { - "description": "Dieses Ticket wurde geschlossen.\nDieser Kanal wird in 5 Sekunden gelöscht.", - "title": "✅ Ticket geschlossen" - }, - "closed_by_member": { - "description": "Dieses Ticket wurde von %s geschlossen.\nDieser Kanal wird in 5 Sekunden gelöscht.", - "title": "✅ Ticket geschlossen" - }, - "closed_by_member_with_reason": { - "description": "Dieses Ticket wurde von %s geschlossen: `%s`\nDieser Kanal wird in 5 Sekunden gelöscht.", - "title": "✅ Ticket geschlossen" - }, - "closed_with_reason": { - "description": "Dieses Ticket wurde geschlossen: `%s`\nDieser Kanal wird in 5 Sekunden gelöscht.", - "title": "✅ Ticket geschlossen" - }, - "member_added": { - "description": "%s wurde von %s hinzugefügt.", - "title": "Mitglied hinzugefügt" - }, - "member_removed": { - "description": "%s wurde von %s entfernt.", - "title": "Benutzer entfernt" - }, - "opening_message": { - "content": "%s\n%s hat ein neues Ticket erstellt", - "fields": { - "topic": "Anliegen" - } - }, - "questions": "Bitte beantworte die folgenden Fragen:\n\n%s", - "released": { - "description": "%s hat ein neues Ticket veröffentlicht.", - "title": "✅ Ticket veröffentlicht" - }, - "survey": { - "complete": { - "description": "Vielen Dank für Dein Feedback!", - "title": "✅ Danke" - }, - "start": { - "buttons": { - "ignore": "Nein", - "start": "Umfrage starten" - }, - "description": "Hey, %s. Würde es Dir etwas ausmachen, eine kurze %d-Fragenumfrage auszufüllen, bevor dieser Kanal gelöscht wird?", - "title": "❔ Feedback" - } - }, - "unclaim": "Freigeben" - }, - "updated_permissions": "✅ Slash-Befehlsberechtigungen aktualisiert" -} diff --git a/src/locales/en-GB.json b/src/locales/en-GB.json deleted file mode 100644 index b59ead5..0000000 --- a/src/locales/en-GB.json +++ /dev/null @@ -1,610 +0,0 @@ -{ - "blacklisted": "❌ You are blacklisted", - "bot": { - "missing_permissions": { - "description": "Discord Tickets requires the following permissions:\n%s ", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s by [eartharoid](%s)" - }, - "collector_expires_in": "Expires in %d seconds", - "command_execution_error": { - "description": "An unexpected error occurred during command execution.\nPlease ask an administrator to check the console output / logs for details.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Add a member to a ticket", - "name": "add", - "options": { - "member": { - "description": "The member to add to the ticket", - "name": "member" - }, - "ticket": { - "description": "The ticket to add the member to", - "name": "ticket" - } - }, - "response": { - "added": { - "description": "%s has been added to %s.", - "title": "✅ Member added" - }, - "no_member": { - "description": "Please mention the member you want to add.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't add members to this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "blacklist": { - "description": "View or modify the blacklist", - "name": "blacklist", - "options": { - "add": { - "description": "Add a member or role to the blacklist", - "name": "add", - "options": { - "member_or_role": { - "description": "The member or role to add to the blacklist", - "name": "member_or_role" - } - } - }, - "remove": { - "description": "Remove a member or role from the blacklist", - "name": "remove", - "options": { - "member_or_role": { - "description": "The member or role to remove from the blacklist", - "name": "member_or_role" - } - } - }, - "show": { - "description": "Show the members and roles in the blacklist", - "name": "show" - } - }, - "response": { - "empty_list": { - "description": "There are no members or roles blacklisted. Type `/blacklist add` to add a member or role to the blacklist.", - "title": "📃 Blacklisted members and roles" - }, - "illegal_action": { - "description": "%s is a staff member and cannot be blacklisted.", - "title": "❌ You can't blacklist this member" - }, - "invalid": { - "description": "This member or role can not be removed from the blacklist as they are not blacklisted.", - "title": "❌ Error" - }, - "list": { - "fields": { - "members": "Members", - "roles": "Roles" - }, - "title": "📃 Blacklisted members and roles" - }, - "member_added": { - "description": "<@%s> has been added to the blacklist. They will no longer be able to interact with the bot.", - "title": "✅ Added member to blacklist" - }, - "member_removed": { - "description": "<@%s> has been removed from the blacklist. They can now use the bot again.", - "title": "✅ Removed member from blacklist" - }, - "role_added": { - "description": "<@&%s> has been added to the blacklist. Members with this role will no longer be able to interact with the bot.", - "title": "✅ Added role to blacklist" - }, - "role_removed": { - "description": "<@&%s> has been removed from the blacklist. Members with this role can now use the bot again.", - "title": "✅ Removed role from blacklist" - } - } - }, - "close": { - "description": "Close a ticket channel", - "name": "close", - "options": { - "reason": { - "description": "The reason for closing the ticket(s)", - "name": "reason" - }, - "ticket": { - "description": "The ticket to close, either the number or the channel ID", - "name": "ticket" - }, - "time": { - "description": "Close all tickets that have been inactive for the specified time", - "name": "time" - } - }, - "response": { - "canceled": { - "description": "You canceled the operation.", - "title": "🚫 Canceled" - }, - "closed": { - "description": "Ticket #%s has been closed.", - "title": "✅ Ticket closed" - }, - "closed_multiple": { - "description": [ - "%d ticket has been closed.", - "%d tickets have been closed." - ], - "title": [ - "✅ Ticket closed", - "✅ Tickets closed" - ] - }, - "confirm": { - "buttons": { - "cancel": "Cancel", - "confirm": "Close" - }, - "description": "Please confirm your decision.", - "description_with_archive": "The ticket will be archived for future reference.", - "title": "❔ Are you sure?" - }, - "confirm_multiple": { - "buttons": { - "cancel": "Cancel", - "confirm": [ - "Close %d ticket", - "Close %d tickets" - ] - }, - "description": [ - "You are about to close %d ticket.", - "You are about to close %d tickets." - ], - "title": "❔ Are you sure?" - }, - "confirmation_timeout": { - "description": "You took too long to confirm.", - "title": "❌ Interaction time expired" - }, - "invalid_time": { - "description": "The time period provided could not be parsed.", - "title": "❌ Invalid input" - }, - "no_permission": { - "description": "You are not a staff member or the ticket creator.", - "title": "❌ Insufficient permission" - }, - "no_tickets": { - "description": "There are no tickets which have been inactive for this time period.", - "title": "❌ No tickets to close" - }, - "not_a_ticket": { - "description": "Please use this command in a ticket channel or use the ticket flag.\nType `/help close` for more information.", - "title": "❌ This isn't a ticket channel" - }, - "unresolvable": { - "description": "`%s` could not be resolved to a ticket. Please provide the ticket ID/mention or number.", - "title": "❌ Error" - } - } - }, - "help": { - "description": "List the commands you have access to", - "name": "help", - "response": { - "list": { - "description": "The commands you have access to are listed below. To create a ticket, type **`/new`**.", - "fields": { - "commands": "Commands" - }, - "title": "❔ Help" - } - } - }, - "new": { - "description": "Create a new ticket", - "name": "new", - "options": { - "topic": { - "description": "The topic of the ticket", - "name": "topic" - } - }, - "request_topic": { - "description": "Please briefly state what this ticket is about in a few words.", - "title": "⚠️ Ticket topic" - }, - "response": { - "created": { - "description": "Your ticket has been created: %s.", - "title": "✅ Ticket created" - }, - "error": { - "title": "❌ Error" - }, - "has_a_ticket": { - "description": "Please use your existing ticket (<#%s>) or close it before creating another.", - "title": "❌ You already have an open ticket" - }, - "max_tickets": { - "description": "Please use `/close` to close any unneeded tickets.\n\n%s", - "title": "❌ You already have %d open tickets" - }, - "no_categories": { - "description": "A server administrator must create at least one ticket category before a new ticket can be opened.", - "title": "❌ Can't create ticket" - }, - "select_category": { - "description": "Select the category most relevant to your ticket's topic.", - "title": "🔤 Please select the ticket category" - }, - "select_category_timeout": { - "description": "You took too long to select the ticket category.", - "title": "❌ Interaction time expired" - } - } - }, - "panel": { - "description": "Create a new ticket panel", - "name": "panel", - "options": { - "categories": { - "description": "A comma-separated list of category IDs", - "name": "categories" - }, - "description": { - "description": "The description for the panel message", - "name": "description" - }, - "image": { - "description": "An image URL for the panel message", - "name": "image" - }, - "just_type": { - "description": "Create a \"just type\" panel?", - "name": "just_type" - }, - "thumbnail": { - "description": "A thumbnail image URL for the panel message", - "name": "thumbnail" - }, - "title": { - "description": "The title for the panel message", - "name": "title" - } - }, - "response": { - "invalid_category": { - "description": "One or more of the specified category IDs is invalid.", - "title": "❌ Invalid category" - }, - "too_many_categories": { - "description": "The \"just type\" panel can only be used with a single category.", - "title": "❌ Too many categories" - } - } - }, - "remove": { - "description": "Remove a member from a ticket", - "name": "remove", - "options": { - "member": { - "description": "The member to remove from the ticket", - "name": "member" - }, - "ticket": { - "description": "The ticket to remove the member from", - "name": "ticket" - } - }, - "response": { - "no_member": { - "description": "Please mention the member you want to remove.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't remove members from this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - }, - "removed": { - "description": "%s has been removed from %s.", - "title": "✅ Member removed" - } - } - }, - "settings": { - "description": "Configure Discord Tickets", - "name": "settings", - "options": { - "categories": { - "description": "Manage your ticket categories", - "name": "categories", - "options": { - "create": { - "description": "Create a new category", - "name": "create", - "options": { - "name": { - "description": "The name of the category", - "name": "name" - }, - "roles": { - "description": "A comma-separated list of staff role IDs for this category", - "name": "roles" - } - } - }, - "delete": { - "description": "Delete a category", - "name": "delete", - "options": { - "id": { - "description": "The ID of the category to delete", - "name": "id" - } - } - }, - "edit": { - "description": "Make changes to a category's configuration", - "name": "edit", - "options": { - "claiming": { - "description": "Enable ticket claiming?", - "name": "claiming" - }, - "id": { - "description": "The ID of the category to edit", - "name": "id" - }, - "image": { - "description": "An image URL", - "name": "image" - }, - "max_per_member": { - "description": "The maximum number of tickets a member can have in this category", - "name": "max_per_member" - }, - "name": { - "description": "The category name", - "name": "name" - }, - "name_format": { - "description": "The ticket name format", - "name": "name_format" - }, - "opening_message": { - "description": "The text to send when a ticket is opened", - "name": "opening_message" - }, - "opening_questions": { - "description": "Questions to ask when a ticket is opened.", - "name": "opening_questions" - }, - "ping": { - "description": "A comma-separated list of role IDs to ping", - "name": "ping" - }, - "require_topic": { - "description": "Require the user to give the topic of the ticket?", - "name": "require_topic" - }, - "roles": { - "description": "A comma-separated list of staff role IDs", - "name": "roles" - }, - "survey": { - "description": "The survey to use", - "name": "survey" - } - } - }, - "list": { - "description": "List categories", - "name": "list" - } - } - }, - "set": { - "description": "Set options", - "name": "set", - "options": { - "close_button": { - "description": "Enable closing with a button?", - "name": "close_button" - }, - "colour": { - "description": "The standard colour", - "name": "colour" - }, - "error_colour": { - "description": "The error colour", - "name": "error_colour" - }, - "footer": { - "description": "The embed footer text", - "name": "footer" - }, - "locale": { - "description": "The locale (language)", - "name": "locale" - }, - "log_messages": { - "description": "Store messages from tickets?", - "name": "log_messages" - }, - "success_colour": { - "description": "The success colour", - "name": "success_colour" - } - } - } - }, - "response": { - "category_created": "✅ The `%s` ticket category has been created", - "category_deleted": "✅ The `%s` ticket category has been deleted", - "category_does_not_exist": "❌ No category exists with the provided ID", - "category_list": "Ticket categories", - "category_updated": "✅ The `%s` ticket category has been updated", - "settings_updated": "✅ Settings have been updated" - } - }, - "stats": { - "description": "Display ticket statistics", - "fields": { - "messages": "Messages", - "response_time": { - "minutes": "%s minutes", - "title": "Avg. response time" - }, - "tickets": "Tickets" - }, - "name": "stats", - "response": { - "global": { - "description": "Statistics about tickets across all guilds where this Discord Tickets instance is used.", - "title": "📊 Global stats" - }, - "guild": { - "description": "Statistics about tickets within this guild. This data is cached for an hour.", - "title": "📊 This server's stats" - } - } - }, - "survey": { - "description": "View survey responses", - "name": "survey", - "options": { - "survey": { - "description": "The name of the survey to view responses of", - "name": "survey" - } - }, - "response": { - "list": { - "title": "📃 Surveys" - } - } - }, - "tag": { - "description": "Use a tag response", - "name": "tag", - "options": { - "tag": { - "description": "The name of the tag to use", - "name": "tag" - } - }, - "response": { - "error": "❌ Error", - "list": { - "title": "📃 Tag list" - }, - "missing": "This tag requires the following arguments:\n%s", - "not_a_ticket": { - "description": "This tag can only be used within a ticket channel as it uses ticket references.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "topic": { - "description": "Change the topic of the ticket", - "name": "topic", - "options": { - "new_topic": { - "description": "The new topic of the ticket", - "name": "new_topic" - } - }, - "response": { - "changed": { - "description": "This ticket's topic has been changed.", - "title": "✅ Topic changed" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel you want to change the topic of.", - "title": "❌ This isn't a ticket channel" - } - } - } - }, - "message_will_be_deleted_in": "This message will be deleted in %d seconds", - "missing_permissions": { - "description": "You do not have the permissions required to use this command:\n%s", - "title": "❌ Error" - }, - "panel": { - "create_ticket": "Create a ticket" - }, - "ticket": { - "claim": "Claim", - "claimed": { - "description": "%s has claimed this ticket.", - "title": "✅ Ticket claimed" - }, - "close": "Close", - "closed": { - "description": "This ticket has been closed.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member": { - "description": "This ticket has been closed by %s.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member_with_reason": { - "description": "This ticket has been closed by %s: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_with_reason": { - "description": "This ticket has been closed: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "member_added": { - "description": "%s has been added by %s", - "title": "Member added" - }, - "member_removed": { - "description": "%s has been removed by %s", - "title": "Member removed" - }, - "opening_message": { - "content": "%s\n%s has created a new ticket", - "fields": { - "topic": "Topic" - } - }, - "questions": "Please answer the following questions:\n\n%s", - "released": { - "description": "%s has released this ticket.", - "title": "✅ Ticket released" - }, - "survey": { - "complete": { - "description": "Thank you for your feedback.", - "title": "✅ Thank you" - }, - "start": { - "buttons": { - "ignore": "No", - "start": "Start survey" - }, - "description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey?", - "title": "❔ Feedback" - } - }, - "unclaim": "Release" - }, - "updated_permissions": "✅ Slash command permissions updated" -} diff --git a/src/locales/en-US.json b/src/locales/en-US.json deleted file mode 100644 index 61a4a84..0000000 --- a/src/locales/en-US.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "bot": { - "missing_permissions": { - "description": "Discord Tickets requires the following permissions:\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s by [eartharoid](%s)" - }, - "collector_expires_in": "Expires in %d seconds", - "command_execution_error": { - "description": "An unexpected error occurred during command execution.\nPlease ask an administrator to check the console output / logs for details.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Add a member to a ticket", - "name": "add", - "response": { - "added": { - "description": "%s has been added to %s.", - "title": "✅ Member added" - }, - "no_member": { - "description": "Please mention the member you want to add.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't add members to this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "blacklist": { - "description": "Blacklist/unblacklist a member from interacting with the bot", - "name": "blacklist", - "response": { - "empty_list": { - "description": "There are no members or roles blacklisted. Type `%sblacklist ` to add a member or role to the blacklist.", - "title": "📃 Blacklisted members and roles" - }, - "illegal_action": { - "description": "%s is a staff member and cannot be blacklisted.", - "title": "❌ You can't blacklist this member" - }, - "list": { - "title": "📃 Blacklisted members and roles" - }, - "member_added": { - "description": "<@%s> has been added to the blacklist. They will no longer be able to interact with the bot.", - "title": "✅ Added member to blacklist" - }, - "member_removed": { - "description": "<@%s> has been removed from the blacklist. They can now use the bot again.", - "title": "✅ Removed member from blacklist" - }, - "role_added": { - "description": "<@&%s> has been added to the blacklist. Members with this role will no longer be able to interact with the bot.", - "title": "✅ Added role to blacklist" - }, - "role_removed": { - "description": "<@&%s> has been removed from the blacklist. Members with this role can now use the bot again.", - "title": "✅ Removed role from blacklist" - } - } - }, - "close": { - "description": "Close a ticket channel", - "name": "close", - "response": { - "closed": { - "description": "Ticket #%s has been closed.", - "title": "✅ Ticket closed" - }, - "closed_multiple": { - "description": [ - "%d ticket has been closed.", - "%d tickets have been closed." - ], - "title": [ - "✅ Ticket closed", - "✅ Tickets closed" - ] - }, - "confirm": { - "description": "React with ✅ to close this ticket.", - "description_with_archive": "You will be able to view an archived version of it after.\nReact with ✅ to close this ticket.", - "title": "❔ Are you sure?" - }, - "confirm_multiple": { - "description": [ - "React with ✅ to close %d ticket.", - "React with ✅ to close %d tickets." - ], - "title": "❔ Are you sure?" - }, - "confirmation_timeout": { - "description": "You took too long to confirm.", - "title": "❌ Reaction time expired" - }, - "invalid_time": { - "description": "The time period provided could not be parsed.", - "title": "❌ Invalid input" - }, - "no_tickets": { - "description": "There are no tickets which have been inactive for this time period.", - "title": "❌ No tickets to close" - }, - "not_a_ticket": { - "description": "Please use this command in a ticket channel or use the ticket flag.\nType `%shelp close` for more information.", - "title": "❌ This isn't a ticket channel" - }, - "unresolvable": { - "description": "`%s` could not be resolved to a ticket. Please provide the ticket ID/mention or number.", - "title": "❌ Error" - } - } - }, - "help": { - "description": "List commands you have access to, or find out more about a command", - "name": "help", - "response": { - "list": { - "description": "The commands you have access to are listed below. For more information about a command, type `{prefix}help [command]`. To create a ticket, type `{prefix}new [topic]`.", - "fields": { - "commands": "Commands" - }, - "title": "❔ Help" - } - } - }, - "new": { - "description": "Create a new ticket", - "name": "new", - "request_topic": { - "description": "Please briefly state what this ticket is about in a few words.", - "title": "Ticket topic" - }, - "response": { - "created": { - "description": "Your ticket has been created: %s.", - "title": "✅ Ticket created" - }, - "error": { - "title": "❌ Error" - }, - "has_a_ticket": { - "description": "Please use your existing ticket (<#%s>) or close it before creating another.", - "title": "❌ You already have an open ticket" - }, - "max_tickets": { - "description": "Please use `%sclose` to close any unneeded tickets.\n\n%s", - "title": "❌ You already have %d open tickets" - }, - "no_categories": { - "description": "A server administrator must create at least one ticket category before a new ticket can be opened.", - "title": "❌ Can't create ticket" - }, - "select_category": { - "description": "Select the category most relevant to your ticket's topic:\n\n%s", - "title": "🔤 Please select the ticket category" - }, - "select_category_timeout": { - "description": "You took too long to select the ticket category.", - "title": "❌ Reaction time expired" - } - } - }, - "panel": { - "description": "Create a new ticket panel", - "name": "panel", - "response": { - "invalid_category": { - "description": "One or more of the specified category IDs is invalid.", - "title": "❌ Invalid category" - } - } - }, - "remove": { - "description": "Remove a member from a ticket", - "name": "remove", - "response": { - "no_member": { - "description": "Please mention the member you want to remove.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't remove members from this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - }, - "removed": { - "description": "%s has been removed from %s.", - "title": "✅ Member removed" - } - } - }, - "settings": { - "description": "Configure Discord Tickets", - "name": "settings" - }, - "stats": { - "description": "Display ticket statistics", - "fields": { - "messages": "Messages", - "response_time": { - "minutes": "%s minutes", - "title": "Avg. response time" - }, - "tickets": "Tickets" - }, - "name": "stats", - "response": { - "global": { - "description": "Statistics about tickets across all guilds where this Discord Tickets instance is used.", - "title": "📊 Global stats" - }, - "guild": { - "description": "Statistics about tickets within this guild. This data is cached for an hour.", - "title": "📊 This server's stats" - } - } - }, - "survey": { - "description": "View survey responses", - "name": "survey", - "response": { - "list": { - "title": "📃 Surveys" - } - } - }, - "tag": { - "description": "Use a tag response", - "name": "tag", - "response": { - "error": "❌ Error", - "list": { - "title": "📃 Tag list" - }, - "missing": "This tag requires the following arguments:\n%s", - "not_a_ticket": { - "description": "This tag can only be used within a ticket channel as it uses ticket references.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "topic": { - "description": "Change the topic of the ticket", - "name": "topic", - "response": { - "changed": { - "description": "This ticket's topic has been changed.", - "title": "✅ Topic changed" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel you want to change the topic of.", - "title": "❌ This isn't a ticket channel" - } - } - } - }, - "message_will_be_deleted_in": "This message will be deleted in %d seconds", - "missing_permissions": { - "description": "You do not have the permissions required to use this command:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s has claimed this ticket.", - "title": "✅ Ticket claimed" - }, - "closed": { - "description": "This ticket has been closed.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member": { - "description": "This ticket has been closed by %s.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member_with_reason": { - "description": "This ticket has been closed by %s: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_with_reason": { - "description": "This ticket has been closed: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "member_added": { - "description": "%s has been added by %s", - "title": "Member added" - }, - "member_removed": { - "description": "%s has been removed by %s", - "title": "Member removed" - }, - "opening_message": { - "fields": { - "topic": "Topic" - } - }, - "questions": "Please answer the following questions:\n\n%s", - "released": { - "description": "%s has released this ticket.", - "title": "✅ Ticket released" - }, - "survey": { - "complete": { - "description": "Thank you for your feedback.", - "title": "✅ Thank you" - }, - "start": { - "description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey? React with ✅ to start, or ignore this message.", - "title": "❔ Feedback" - } - } - } -} diff --git a/src/locales/es-ES.json b/src/locales/es-ES.json deleted file mode 100644 index 94420d4..0000000 --- a/src/locales/es-ES.json +++ /dev/null @@ -1,612 +0,0 @@ -{ - "blacklisted": "❌ Estás vetado del sistema", - "bot": { - "missing_permissions": { - "description": "Discord Tickets requiere los siguientes permisos:\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s creado por [eartharoid](%s)" - }, - "collector_expires_in": "Expira en %d segundos", - "command_execution_error": { - "description": "Un error inesperado ha ocurrido durante la ejecución del comando.\nPor favor, pregúntale a un administrador para revisar la consola o los registros y obtener más información.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Añadir a un usuario al ticket", - "name": "add", - "options": { - "member": { - "description": "El usuario que deseas agregar al ticket", - "name": "member" - }, - "ticket": { - "description": "El ticket al cual deseas agregar el usuario", - "name": "ticket" - } - }, - "response": { - "added": { - "description": "%s ha sido añadido a %s.", - "title": "✅ Miembro añadido" - }, - "no_member": { - "description": "Por favor, menciona al usuario que deseas añadir.", - "title": "❌ Usuario desconocido" - }, - "no_permission": { - "description": "No eres el creador de este ticket y no eres parte del staff, no puedes añadir usuarios a este ticket.", - "title": "❌ Permisos insuficientes" - }, - "not_a_ticket": { - "description": "Por favor utiliza este comando en el canal de tickets, o menciona el canal.", - "title": "❌ Este no es un canal de tickets" - } - } - }, - "blacklist": { - "description": "Ver o modificar la lista negra", - "name": "blacklist", - "options": { - "add": { - "description": "Añadir un miembro o rol a la lista negra", - "name": "add", - "options": { - "member_or_role": { - "description": "El miembro o rol a añadir a la lista negra", - "name": "member_or_role" - } - } - }, - "remove": { - "description": "Quitar un miembro o rol de la lista negra", - "name": "remove", - "options": { - "member_or_role": { - "description": "El miembro o rol a quitar de la lista negra", - "name": "member_or_role" - } - } - }, - "show": { - "description": "Muestra los miembros y roles que están en la lista negra", - "name": "show" - } - }, - "response": { - "empty_list": { - "description": "No hay miembros ni roles en la lista negra. Escribe `/blacklist add` para añadir a un usuario o rol a la misma.", - "title": "📃 Usuarios y roles en la lista negra" - }, - "illegal_action": { - "description": "%s es un miembro del personal, por lo tanto no puede ser añadido a la lista negra.", - "title": "❌ No puedes añadir a la lista negra a este usuario" - }, - "invalid": { - "description": "Este miembro o rol no puede ser quitado de la lista negra porque no está dentro de ella.", - "title": "❌ Error" - }, - "list": { - "fields": { - "members": "Miembros", - "roles": "Roles" - }, - "title": "📃 Usuarios y roles en la lista negra" - }, - "member_added": { - "description": "<@%s> fue añadido a la lista negra. Ya no podrá interactuar con el bot, a menos de que sea removido de la lista.", - "title": "✅ Usuario añadido a la lista negra" - }, - "member_removed": { - "description": "<@%s> fue eliminado de la lista negra. Ahora puede usar el bot.", - "title": "✅ Usuario eliminado de la lista negra" - }, - "role_added": { - "description": "<@&%s> fue añadido a la lista negra. Los usuarios que tengan este rol no podrán interactuar con el bot hasta ser removidos de la lista.", - "title": "✅ Rol añadido a la lista negra" - }, - "role_removed": { - "description": "<@&%s> fue eliminado de la lista negra. Los usuarios que tengan este rol podrán volver a hacer uso del bot.", - "title": "✅ Rol eliminado de la lista negra" - } - } - }, - "close": { - "description": "Cerrar un canal de ayuda", - "name": "close", - "options": { - "reason": { - "description": "El motivo por cerrar el/los ticket(s)", - "name": "reason" - }, - "ticket": { - "description": "El ticket a cerrar, sea el numero o la ID del canal", - "name": "ticket" - }, - "time": { - "description": "Cerrar todos los tickets que han estado inactivos durante un tiempo determinado", - "name": "time" - } - }, - "response": { - "canceled": { - "description": "Has cancelado la operación.", - "title": "🚫 Cancelado" - }, - "closed": { - "description": "El ticket #%s fue cerrado.", - "title": "✅ Ticket resuelto" - }, - "closed_multiple": { - "description": [ - "El ticket %d fue resuelto.", - "Se han cerrado %d tickets." - ], - "title": [ - "✅ Ticket resuelto", - "✅ Tickets cerrados" - ] - }, - "confirm": { - "buttons": { - "cancel": "Cancelar", - "confirm": "Cerrar" - }, - "description": "Por favor, confirm tu selección.", - "description_with_archive": "El ticket será archivado para referencia en el futuro.", - "title": "❔ ¿Estás seguro?" - }, - "confirm_multiple": { - "buttons": { - "cancel": "Cancelar", - "confirm": [ - "Cerrar %d ticket", - "Cerrar %d tickets" - ] - }, - "description": [ - "Reacciona con ✅ para cerrar el ticket %d.", - "Estás a punto de cerrar %d tickets." - ], - "title": "❔ ¿Estás seguro?" - }, - "confirmation_timeout": { - "description": "Has tardado demasiado en confirmar.", - "title": "❌ El tiempo de reacción ha expirado" - }, - "invalid_time": { - "description": "El período de tiempo ingresado no pudo ser analizado.", - "title": "❌ Entrada inválida" - }, - "no_permission": { - "description": "No eres un miembro del staff ni el creador del ticket..", - "title": "❌ Permisos insuficientes" - }, - "no_tickets": { - "description": "No hay tickets que hayan estado inactivos durante este período de tiempo.", - "title": "❌ No hay tickets para cerrar" - }, - "not_a_ticket": { - "description": "Por favor, utiliza este comando en un canal de tickets, o usa la bandera de tickets.\nEjecuta `%shelp close` para más información.", - "title": "❌ Este no es un canal de tickets" - }, - "unresolvable": { - "description": "`%s` no pudo ser resuelto como un ticket. Por favor, especifica el ID o número del ticket.", - "title": "❌ Error" - } - } - }, - "help": { - "description": "Listar comandos a los que tienes acceso", - "name": "help", - "options": {}, - "response": { - "list": { - "description": "Los comandos a los que tienes acceso están listados debajo. Para más información sobre un comando, ejecuta `{prefix}help [command]`. para crear un ticket, ejecuta `{prefix}new [topic]`.", - "fields": { - "commands": "Comandos" - }, - "title": "❔ Ayuda" - } - } - }, - "new": { - "description": "Crear un nuevo ticket", - "name": "new", - "options": { - "topic": { - "description": "Categoria del ticket", - "name": "topic" - } - }, - "request_topic": { - "description": "Por favor, indica de qué se trata este ticket en pocas palabras.", - "title": "Tema del ticket" - }, - "response": { - "created": { - "description": "Tu ticket fue creado: %s.", - "title": "✅ Ticket creado" - }, - "error": { - "title": "❌ Error" - }, - "has_a_ticket": { - "description": "Por favor, utiliza tu ticket existente (<#%s>) o ciérralo antes de crear otro.", - "title": "❌ Ya tienes un ticket abierto" - }, - "max_tickets": { - "description": "Por favor, utiliza el comando `%sclose` ´para cerrar tickets inatendidos.\n\n%s", - "title": "❌ Ya tienes %d tickets abiertos" - }, - "no_categories": { - "description": "Un administrador del servidor debe crear al menos una categoría de tickets antes de que un nuevo ticket pueda ser abierto.", - "title": "❌ No se puede crear el ticket" - }, - "select_category": { - "description": "Selecciona la categoría más relevante para el tema de tu ticket:\n\n%s", - "title": "🔤 Por favor, selecciona la categoría de tu ticket" - }, - "select_category_timeout": { - "description": "Has tardado demasiado tiempo en seleccionar la categoría de tickets.", - "title": "❌ El tiempo de la reacción ha expirado" - } - } - }, - "panel": { - "description": "Crear un nuevo panel de tickets", - "name": "panel", - "options": { - "categories": { - "description": "Una lista separada por comas de IDs de las categorías", - "name": "categories" - }, - "description": { - "description": "La descripción para el mensaje del panel", - "name": "description" - }, - "image": { - "description": "Una URL de imagen para el mensaje del panel", - "name": "image" - }, - "just_type": { - "description": "Crear un \"tipo\" de panel?", - "name": "just_type" - }, - "title": { - "description": "Titulo del mensaje del panel", - "name": "title" - }, - "thumbnail": { - "description": "Url de una imagen en miniatura para el mensaje del panel", - "name": "thumbnail" - } - }, - "response": { - "invalid_category": { - "description": "Una o más de las ID de categoría ingresadas no son válidas.", - "title": "❌ Categoría inválida" - }, - "too_many_categories": { - "description": "El \"tipo\" solo se puede utilizar para un tipo de categoria.", - "title": "❌ Demasiadas Categorias" - } - } - }, - "remove": { - "description": "Eliminar a un miembro de un ticket", - "name": "remove", - "options": { - "member": { - "description": "Miembro a eliminar del ticket", - "name": "member" - }, - "ticket": { - "description": "Miembro a eliminar del ticket", - "name": "ticket" - } - }, - "response": { - "no_member": { - "description": "Por favor, menciona al miembro que deseas eliminar.", - "title": "❌ Miembro desconocido" - }, - "no_permission": { - "description": "Tú no eres el creador de este ticket ni un miembro del personal, por lo que no puedes eliminar miembros de este ticket.", - "title": "❌ Permisos insuficientes" - }, - "not_a_ticket": { - "description": "Por favor utiliza este comando en el canal de tickets, o menciona el canal.", - "title": "❌ Este no es un canal de tickets" - }, - "removed": { - "description": "%s fue eliminado de %s.", - "title": "✅ Miembro eliminado" - } - } - }, - "settings": { - "description": "Configurar Discord Tickets", - "name": "settings", - "options": { - "categories": { - "description": "Gestiona tus categoría", - "name": "categories", - "options": { - "create": { - "description": "Crear una nueva categoria", - "name": "create", - "options": { - "name": { - "description": "Nombre de la categoria", - "name": "name" - }, - "roles": { - "description": "Una lista separada por comas de los ID de funciones del staff para esta categoría", - "name": "roles" - } - } - }, - "delete": { - "description": "Borrar Categoria", - "name": "delete", - "options": { - "id": { - "description": "ID de la categoria a borrar", - "name": "id" - } - } - }, - "edit": { - "description": "Realizar cambios en la configuración de una categoría", - "name": "edit", - "options": { - "claiming": { - "description": "Habilitar reclamar un ticket?", - "name": "claiming" - }, - "id": { - "description": "ID de categoria a editar", - "name": "id" - }, - "image": { - "description": "URL de una imagen", - "name": "image" - }, - "max_per_member": { - "description": "El número máximo de tickets que un miembro puede tener en esta categoría", - "name": "max_per_member" - }, - "name": { - "description": "Nombre de la categoria", - "name": "name" - }, - "name_format": { - "description": "Formato del ticket", - "name": "name_format" - }, - "opening_message": { - "description": "Texto a enviar tras abrir el ticket", - "name": "opening_message" - }, - "opening_questions": { - "description": "Preguntas que realizar tras abrir un ticket.", - "name": "opening_questions" - }, - "ping": { - "description": "Una lista separada por comas de ID de roles para hacer ping", - "name": "ping" - }, - "require_topic": { - "description": "¿Requerir al usuario dar el tema del ticket?", - "name": "require_topic" - }, - "roles": { - "description": "Una lista separada por comas de ID de funciones del personal", - "name": "roles" - }, - "survey": { - "description": "La encuesta a utilizar", - "name": "survey" - } - } - }, - "list": { - "description": "Lista de categorias", - "name": "list" - } - } - }, - "set": { - "description": "Setear Opciones", - "name": "set", - "options": { - "close_button": { - "description": "Habilitar el cierre del ticket con un botón?", - "name": "close_button" - }, - "colour": { - "description": "Color estandar", - "name": "colour" - }, - "error_colour": { - "description": "Color de error", - "name": "error_colour" - }, - "footer": { - "description": "El texto del pie de página incrustado", - "name": "footer" - }, - "locale": { - "description": "Idioma", - "name": "locale" - }, - "log_messages": { - "description": "¿Almacenar mensajes de tickets?", - "name": "log_messages" - }, - "success_colour": { - "description": "El color de acierto", - "name": "success_colour" - } - } - } - }, - "response": { - "category_created": "✅ La `%s` categoria del sistema de ticket fue creada", - "category_deleted": "✅ La `%s` categoria del sistema de ticket fue borrada", - "category_does_not_exist": "❌ No existe la categoria con esa id", - "category_updated": "✅ La `%s` categoria del sistema de ticket fue actualizada", - "category_list": "Categoria de tickets", - "settings_updated": "✅ Los cambios fueron guardados" - } - }, - "stats": { - "description": "Mostrar estadísticas del ticket", - "fields": { - "messages": "Mensajes", - "response_time": { - "minutes": "%s minutos", - "title": "Tiempo de respuesta promedio" - }, - "tickets": "Tickets" - }, - "name": "stats", - "options": {}, - "response": { - "global": { - "description": "Estadísticas sobre los tickets en todos los servidores donde se utiliza esta instancia de Discord Tickets.", - "title": "📊 Estadísticas globales" - }, - "guild": { - "description": "Estadísticas sobre los tickets de este servidor. Estos datos son almacenados en la caché durante una hora.", - "title": "📊 Estadísticas de este servidor" - } - } - }, - "survey": { - "description": "Ver respuestas de la encuesta", - "name": "survey", - "options": { - "survey": { - "description": "El nombre de la encuesta para ver las respuestas de", - "name": "survey" - } - }, - "response": { - "list": { - "title": "📃 Encuestas" - } - } - }, - "tag": { - "description": "Usar una respuesta de etiqueta", - "name": "tag", - "options": { - "tag": { - "description": "El nombre de la etiqueta a usar", - "name": "tag" - } - }, - "response": { - "error": "❌ Error", - "list": { - "title": "📃 Lista de etiquetas" - }, - "missing": "Esta etiqueta requiere los siguientes argumentos:\n%s", - "not_a_ticket": { - "description": "Esta etiqueta solo puede utilizarse dentro de un canal de tickets, ya que utiliza referencias de éste.", - "title": "❌ Este no es un canal de tickets" - } - } - }, - "topic": { - "description": "Cambiar el tema del ticket", - "name": "topic", - "options": { - "new_topic": { - "description": "Nuevo tema de los tickets", - "name": "new_topic" - } - }, - "response": { - "changed": { - "description": "El tema de este ticket ha sido cambiado.", - "title": "✅ Tema cambiado" - }, - "not_a_ticket": { - "description": "Por favor, utiliza este comando en el canal de ticket en el que deseas cambiar el tema.", - "title": "❌ Este no es un canal de tickets" - } - } - } - }, - "message_will_be_deleted_in": "Este mensaje será eliminado en %d segundos", - "missing_permissions": { - "description": "No tienes los permisos requeridos para ejecutar este comando:\n%s", - "title": "❌" - }, - "panel": { - "create_ticket": "Crear un ticket" - }, - "ticket": { - "claim": "Reclamar", - "claimed": { - "description": "%s ha reclamado el ticket.", - "title": "✅ Ticket reclamado" - }, - "close": "Cerrar", - "closed": { - "description": "Este ticket fue cerrado.\nEl canal será eliminado en 5 segundos.", - "title": "✅ Ticket cerrado" - }, - "closed_by_member": { - "description": "Este ticket ha sido cerrado por %s.\nEl canal será eliminado en 5 segundos.", - "title": "✅ Ticket cerrado" - }, - "closed_by_member_with_reason": { - "description": "Este ticket ha sido cerrado por %s: `%s`\nEl canal será eliminado en 5 segundos.", - "title": "✅ Ticket cerrado" - }, - "closed_with_reason": { - "description": "Este ticket ha sido cerrado: `%s`\nEl canal será eliminado en 5 segundos.", - "title": "✅ Ticket cerrado" - }, - "member_added": { - "description": "%s fue añadido por %s", - "title": "Miembro añadido" - }, - "member_removed": { - "description": "%s fue eliminado por %s", - "title": "Miembro removido" - }, - "opening_message": { - "content": "%s\n%s ha creado un nuevo ticket", - "fields": { - "topic": "Tema" - } - }, - "questions": "Por favor, responde las siguientes preguntas:\n\n%s", - "released": { - "description": "%s ha lanzado este ticket.", - "title": "✅ Ticket lanzado" - }, - "survey": { - "complete": { - "description": "Gracias por tus comentarios.", - "title": "✅ Gracias" - }, - "start": { - "buttons": { - "start": "Empezar encuesta", - "ignore": "No" - }, - "description": "Hey, %s. Antes de que se elimine este canal, ¿te importaría completar una encuesta rápida de %d preguntas?", - "title": "❔ Feedback" - } - }, - "unclaim": "Release" - }, - "updated_permissions": "✅ Se han actualizado los permisos" -} \ No newline at end of file diff --git a/src/locales/fr-FR.json b/src/locales/fr-FR.json deleted file mode 100644 index ec2e0e1..0000000 --- a/src/locales/fr-FR.json +++ /dev/null @@ -1,566 +0,0 @@ -{ - "blacklisted": "❌ Vous êtes sur liste noire", - "bot": { - "missing_permissions": { - "description": "Discord Tickets nécessite les autorisations suivantes :\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s par [eartharoid](%s)" - }, - "collector_expires_in": "Expire dans %d secondes", - "command_execution_error": { - "description": "Une erreur inattendue s’est produite lors de l’exécution de commande.\nVeuillez demander à un administrateur de vérifier la console / les journaux pour plus de détails.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Ajouter un membre à un billet", - "name": "ajouter", - "options": { - "member": { - "description": "Le membre à ajouter au billet", - "name": "membre" - }, - "ticket": { - "description": "Le billet auquel ajouter le membre", - "name": "billet" - } - }, - "response": { - "added": { - "description": "%s a été ajouté à %s.", - "title": "✅ Membre ajouté" - }, - "no_member": { - "description": "Veuillez mentionner le membre que vous souhaitez ajouter.", - "title": "❌ Membre inconnu" - }, - "no_permission": { - "description": "Vous n’êtes pas le créateur de ce billet et vous n’êtes pas un membre du personnel; vous ne pouvez pas ajouter de membre à ce billet .", - "title": "❌ Permissions insuffisantes" - }, - "not_a_ticket": { - "description": "Veuillez utiliser cette commande dans le canal des billets, ou mentionner le salon.", - "title": "❌ Ce n’est pas un salon de billet" - } - } - }, - "blacklist": { - "description": "Voir ou modifier la liste noire", - "name": "liste noire", - "options": { - "add": { - "description": "Ajouter un membre ou un rôle à la liste noire", - "name": "ajouter", - "options": { - "member_or_role": { - "description": "Le membre ou rôle à ajouter à la liste noire", - "name": "membre_ou_rôle" - } - } - }, - "remove": { - "description": "Retirer un membre ou un rôle de la liste noire", - "name": "retirer", - "options": { - "member_or_role": { - "description": "Le membre ou le rôle à retirer de la liste noire", - "name": "membre_ou_rôle" - } - } - }, - "show": { - "description": "Voir les membres et rôles dans la liste noire", - "name": "voir" - } - }, - "response": { - "empty_list": { - "description": "Il n'y a aucun membre ou rôle dans la liste noir. Tapez `/blacklist add` pour ajouter un membre ou un rôle à la liste noire.", - "title": "📃 Membres et rôles sur la liste noire" - }, - "illegal_action": { - "description": "%s est un membre du personnel et ne peut pas être mis sur la liste noire.", - "title": "❌ Vous ne pouvez pas ajouter ce membre à la liste noire" - }, - "invalid": { - "description": "Ce membre ou rôle ne peut pas être retiré de la liste noire car il n'y est pas présent.", - "title": "❌ Erreur" - }, - "list": { - "fields": { - "members": "Membres", - "roles": "Rôles" - }, - "title": "📃 Membres et rôles sur la liste noire" - }, - "member_added": { - "description": "<@%s> a été ajouté à la liste noire. Ils ne seront plus en mesure d’interagir avec le bot.", - "title": "✅ Membre ajouté à la liste noire" - }, - "member_removed": { - "description": "<@%s> a été retiré de la liste noire. Ils peuvent désormais utiliser à nouveau le bot.", - "title": "✅ Membre retiré de la liste noire" - }, - "role_added": { - "description": "<@&%s> a été ajouté à la liste noire. Les membres avec ce rôle ne seront plus en mesure d’interagir avec le bot.", - "title": "✅ Rôle ajouté à la liste noire" - }, - "role_removed": { - "description": "<@&%s> a été retiré de la liste noire. Les membres avec ce rôle peuvent maintenant utiliser le bot à nouveau.", - "title": "✅ membre ajouté à la liste noire" - } - } - }, - "close": { - "description": "Fermer un canal de billets", - "name": "fermer", - "options": { - "reason": { - "description": "La raison de fermer les tickets", - "name": "raison" - }, - "ticket": { - "description": "Le ticket à fermer, soit le numéro ou l'ID du salon", - "name": "ticket" - }, - "time": { - "description": "Fermer tous les tickets qui ont été inactifs dans un temps impartis", - "name": "temps" - } - }, - "response": { - "canceled": { - "description": "Vous avez annulé l'opération.", - "title": "🚫 Annulé" - }, - "closed": { - "description": "Le billet #%s a été fermé.", - "title": "✅ billet fermé" - }, - "closed_multiple": { - "description": [ - "le billet #%s a été fermé.", - "le billet #%s a été fermé." - ], - "title": [ - "✅ billet fermé", - "✅ billet fermé" - ] - }, - "confirm": { - "buttons": { - "cancel": "Annuler", - "confirm": "Fermer" - }, - "description": "Veuillez confirmer votre décision.", - "description_with_archive": "Le billet sera archivé pour référence future.", - "title": "❔ En êtes-vous sûr ?" - }, - "confirm_multiple": { - "buttons": { - "cancel": "Annuler", - "confirm": [ - "Fermer %d ticket", - "Fermer %d tickets" - ] - }, - "description": [ - "Vous êtes sur le point de fermer le billet %d.", - "Vous allez supprimer %d tickets, réagissez avec ✅ pour confirmer." - ], - "title": "❔ En êtes-vous sûr ?" - }, - "confirmation_timeout": { - "description": "Tu as mis trop de temps à confirmer.", - "title": "❌ Le temps de réaction a expiré" - }, - "invalid_time": { - "description": "La période prévue n’a pas pu être analysée.", - "title": "❌ entrée invalide" - }, - "no_permission": { - "description": "Vous n'êtes pas un membre du staff ou le créateur du ticket.", - "title": "❌ Permissions insuffisantes" - }, - "no_tickets": { - "description": "Il n’y a pas de billets inactifs pour cette période.", - "title": "❌ pas de billets à fermer" - }, - "not_a_ticket": { - "description": "Veuillez utiliser cette commande dans un salon de ticket ou utilisez le drapeau ticket.\nTapez «/help close» pour plus d’informations.", - "title": "❌ Ce n’est pas une chaîne de billets" - }, - "unresolvable": { - "description": "'%s' ne pouvait pas être résolu à un billet. Veuillez fournir l’iD/mention ou le numéro du billet.", - "title": "❌ erreur" - } - } - }, - "help": { - "description": "Listez les commandes dont vous avez accès", - "name": "aide", - "response": { - "list": { - "description": "Les commandes dont vous avez accès sont listées ci-dessous. Pour créer un ticket, tapez **`/new`**", - "fields": { - "commands": "Commandes" - }, - "title": "❔ Aide" - } - } - }, - "new": { - "description": "Créer un nouveau billet", - "name": "nouveau", - "options": { - "topic": { - "description": "La sujet du ticket", - "name": "sujet" - } - }, - "request_topic": { - "description": "Veuillez indiquer brièvement l'objet de ce billet en quelques mots.", - "title": "⚠️ Sujet du ticket" - }, - "response": { - "created": { - "description": "Votre billet a été créé : %s.", - "title": "✅ Billet créé" - }, - "error": { - "title": "❌ erreur" - }, - "has_a_ticket": { - "description": "S’il vous plaît utiliser votre billet <# existant (%s>) ou le fermer avant de créer un autre.", - "title": "❌ Vous avez déjà un billet ouvert" - }, - "max_tickets": { - "description": "Utilisez **`/close`** pour fermer tous les tickets inutiles.\n\n%s", - "title": "❌ Vous avez déjà un billet ouvert" - }, - "no_categories": { - "description": "Un administrateur serveur doit créer au moins une catégorie de billets avant qu’un nouveau billet puisse être ouvert.", - "title": "❌ Impossible de créer un ticket" - }, - "select_category": { - "description": "Sélectionnez la catégorie la plus pertinente pour le sujet de votre billet.", - "title": "🔤 Veuillez sélectionner la catégorie de billets" - }, - "select_category_timeout": { - "description": "Vous avez mis trop de temps à sélectionner la catégorie de billets.", - "title": "❌Le temps de réaction a expiré" - } - } - }, - "panel": { - "description": "Créer un nouveau panneau de billet", - "name": "panneau", - "options": { - "categories": { - "description": "La liste d'ID de catégorie séparé par des virgules", - "name": "catégories" - }, - "description": { - "description": "La description du message du panneau d'affichage", - "name": "description" - }, - "image": { - "description": "L'URL d'une image pour le panneau d'affichage", - "name": "image" - }, - "just_type": { - "description": "Créer un panneau d'affichage \"juste écrire\"?", - "name": "juste_ecrire" - }, - "thumbnail": { - "description": "Une URL d'image de bannière pour le panneau d'affichage", - "name": "bannière" - }, - "title": { - "description": "Le titre du panneau d'affichage", - "name": "titre" - } - }, - "response": { - "invalid_category": { - "description": "Un ou plusieurs des ID de catégorie spécifiés sont invalides.", - "title": "❌ Catégorie invalide" - }, - "too_many_categories": { - "description": "Le panneau d'affichage \"juste écrire\" ne peut être utilisé que dans une seule catégorie.", - "title": "❌ Trop de catégories" - } - } - }, - "remove": { - "description": "Retirer un membre d’un billet", - "name": "supprimer", - "options": { - "member": { - "description": "Le membre à retirer du ticket", - "name": "membre" - }, - "ticket": { - "description": "Le ticket où retirer le membre", - "name": "ticket" - } - }, - "response": { - "no_member": { - "description": "Veuillez mentionner le membre que vous souhaitez ajouter.", - "title": "❌ membre inconnu" - }, - "no_permission": { - "description": "Vous n’êtes pas le créateur de ce billet et vous n’êtes pas un membre du personnel ; vous ne pouvez pas ajouter de membres à ce billet.", - "title": "❌ autorisation insuffisante" - }, - "not_a_ticket": { - "description": "S’il vous plaît utiliser cette commande dans le canal de billet, ou mentionner le canal.", - "title": "❌ Ce n’est pas une chaîne de billets" - }, - "removed": { - "description": "%s a été ajouté à %s.", - "title": "✅ Membre retiré" - } - } - }, - "settings": { - "description": "Configurer Discord Tickets", - "name": "paramètres", - "options": { - "categories": { - "description": "Gérer les catégories des tickets", - "name": "catégories", - "options": { - "create": { - "description": "Créez une nouvelle catégorie", - "name": "créer", - "options": { - "name": { - "description": "Le nom de la catégorie", - "name": "nom" - }, - "roles": { - "name": "rôles" - } - } - }, - "delete": { - "description": "Supprimez une catégorie", - "name": "supprimer", - "options": { - "id": { - "description": "L'ID de la catégorie a supprimer", - "name": "id" - } - } - }, - "edit": { - "description": "Faire des changement sur la configuration d'une catégorie", - "name": "éditer", - "options": { - "claiming": { - "description": "Activer la réception d'un ticket ?", - "name": "réception" - }, - "id": { - "description": "L'ID de la catégorie à éditer", - "name": "id" - }, - "image": { - "description": "L'URL d'une image", - "name": "image" - }, - "max_per_member": { - "description": "Le maximum de tickets qu'un membre peut avoir dans cette catégorie", - "name": "max_par_membre" - }, - "name": { - "description": "Le nom de la catégorie", - "name": "nom" - }, - "name_format": { - "description": "Le format du nom des tickets", - "name": "format_nom" - }, - "opening_message": { - "description": "Le texte à envoyer quand un ticket est ouvert", - "name": "ouverture_message" - }, - "opening_questions": { - "description": "Questions à demander quand un ticket est ouvert.", - "name": "questions_ouverture" - }, - "ping": { - "name": "ping" - }, - "require_topic": { - "description": "Obliger le membre à donner le sujet du ticket ?", - "name": "requiert_sujet" - }, - "roles": { - "name": "rôles" - } - } - }, - "list": { - "description": "Lister les catégories", - "name": "lister" - } - } - }, - "set": { - "description": "Définir les options", - "name": "définir", - "options": { - "close_button": { - "description": "Activer la fermeture avec un bouton ?", - "name": "fermer_bouton" - }, - "colour": { - "description": "La couleur de base", - "name": "couleur" - }, - "error_colour": { - "description": "La couleur des eurreurs", - "name": "couleur_erreur" - }, - "footer": { - "description": "Le texte de fin de l'embed", - "name": "texte de fin" - }, - "log_messages": { - "description": "Stocker les messages des tickets ?", - "name": "stocker_messages" - }, - "success_colour": { - "description": "La couleur pour la réussite", - "name": "couleur_réussite" - } - } - } - }, - "response": { - "category_created": "✅ La catégorie de tickets `%s` à été créée", - "category_deleted": "✅ La catégorie de tickets `%s` à bien été supprimée", - "category_does_not_exist": "❌ La catégorie avec cet ID n'existe pas" - } - }, - "stats": { - "description": "Afficher les statistiques des billets", - "fields": { - "messages": "Messages", - "response_time": { - "minutes": "%s minutes", - "title": "Temps de réponse moyen" - }, - "tickets": "Billet" - }, - "name": "statistiques", - "response": { - "global": { - "description": "Statistiques sur les billets dans toutes les guildes où cette instance Discord Tickets est utilisée.", - "title": "📊 statistiques mondiales" - }, - "guild": { - "description": "Statistiques sur les billets au sein de cette guilde. Ces données sont mises en cache pendant une heure.", - "title": "📊 statistiques de ce serveur" - } - } - }, - "survey": { - "description": "Voir les réponses au sondage", - "name": "enquêtes", - "response": { - "list": { - "title": "📃 enquêtes" - } - } - }, - "tag": { - "description": "Utiliser une réponse d’étiquette", - "name": "étiquettes", - "response": { - "error": "❌ erreur", - "list": { - "title": "📃 Liste d'étiquettes" - }, - "missing": "Cette balise nécessite les arguments suivants :\n%s", - "not_a_ticket": { - "description": "Cette balise ne peut être utilisée que dans un canal de billets car elle utilise des références de billets.", - "title": "❌ Ce n’est pas un canal de billet" - } - } - }, - "topic": { - "description": "Le sujet du billet", - "name": "sujet", - "response": { - "changed": { - "description": "Le sujet de ce billet a été changé.", - "title": "✅ sujet changé" - }, - "not_a_ticket": { - "description": "S’il vous plaît utiliser cette commande dans le canal de billet, ou mentionner le canal.", - "title": "❌ Ce n’est pas un canal de billet" - } - } - } - }, - "message_will_be_deleted_in": "Ce message sera supprimé dans %d secondes", - "missing_permissions": { - "description": "Vous n’avez pas les autorisations requises pour utiliser cette commande :\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s a réclamé ce billet.", - "title": "✅ billet fermé" - }, - "closed": { - "description": "Ce billet a été fermé.\nLe canal sera supprimé en 5 secondes.", - "title": "✅ billet fermé" - }, - "closed_by_member": { - "description": "Ce billet a été fermé.\nLe canal sera supprimé en 5 secondes.", - "title": "✅ billet fermé" - }, - "closed_by_member_with_reason": { - "description": "Ce billet a été fermé.\nLe canal sera supprimé en 5 secondes.", - "title": "✅ billet fermé" - }, - "closed_with_reason": { - "description": "Ce billet a été fermé.\nLe canal sera supprimé en 5 secondes.", - "title": "✅ billet fermé" - }, - "member_added": { - "description": "%s a été ajouté à %s", - "title": "Membre ajouté" - }, - "member_removed": { - "description": "%s a été ajouté à %s", - "title": "Membre retiré" - }, - "opening_message": { - "fields": { - "topic": "Sujet" - } - }, - "questions": "S’il vous plaît répondre aux questions suivantes :\n\n%s", - "released": { - "description": "%s a abandonné ce billet.", - "title": "✅ billet abandonné" - }, - "survey": { - "complete": { - "description": "Merci pour vos commentaires.", - "title": "✅ Merci" - }, - "start": { - "description": "Hé, %s. Avant que ce canal soit supprimé, pourriez-vous répondre à %d question(s) ?", - "title": "❔ commentaires" - } - } - } -} diff --git a/src/locales/he-IL.json b/src/locales/he-IL.json deleted file mode 100644 index 5d973e7..0000000 --- a/src/locales/he-IL.json +++ /dev/null @@ -1,141 +0,0 @@ -{ - "blacklisted": "❌ אתה חסום", - "bot": { - "missing_permissions": { - "description": "דיסקורד-טיקטס דורש ממך להיות בעל ההרשאות הבאות:\n%s ", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s ע\"י [eartharoid](%s)" - }, - "collector_expires_in": "פג תוקף בעוד %d שניות", - "command_execution_error": { - "description": "שגיאה לא צפויה התחוללה!\nאנא צרו קשר עם האדמין על מנת שיבדוק את הקונסול לעוד פרטים.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "הוסף משתמש לטיקט", - "name": "הוסף", - "options": { - "member": { - "description": "המשתמש הוסף לטיקט", - "name": "משתמש" - }, - "ticket": { - "description": "הטיקט שתרצו לצרף אליו משתמש", - "name": "טיקט" - } - }, - "response": { - "added": { - "description": "%s צורף ל %s.", - "title": "✅ משתמש צורף" - }, - "no_member": { - "description": "אנא תייג את המשתמש שתרצה להוסיף.", - "title": "❌ משתמש לא ידוע" - }, - "no_permission": { - "description": "אינך יוצר הטיקט ולא בעל הרשאות אדמין, לכן אינך מורשה להוסיף משתמשים נוספים לטיקט.", - "title": "❌הרשאות לא מספיקות" - }, - "not_a_ticket": { - "description": "אנא השתמש בפקודה זו בערוץ הטיקטים, או ציין את הערוץ.", - "title": "❌ זה איננו ערוץ טיקטים" - } - } - }, - "blacklist": { - "description": "צפה או ערוץ את רשימת החסומים", - "name": "רשימת החסומים", - "options": { - "add": { - "description": "הוסף משתמש או רול לרשימת החסומים", - "name": "הוסף", - "options": { - "member_or_role": { - "description": "המשתמש או הרול להוסיף לרשימת החסומים", - "name": "משתמש_או_רול" - } - } - }, - "remove": { - "description": "הסר משתמש או רול מרשימת החסומים", - "name": "הסר", - "options": { - "member_or_role": { - "description": "המשתמש או הרול להסיר מרשימת החסומים", - "name": "משתמש_או_רול" - } - } - }, - "show": { - "description": "הצג את המשתמשים והרולים ברשימת החסומים", - "name": "הצג" - } - }, - "response": { - "empty_list": { - "description": "אין משתמשים או רולים ברשימת החסומים, כתוב `/blacklist add` על מנת להוסיף משתמשים או רולים לרשימה.", - "title": "📃רשימת חסומים של משתמשים ורולים" - }, - "illegal_action": { - "description": "%s הינו חבר צוות ואינו יכול להיחסם.", - "title": "❌לא ניתן לחסום משתמש זה" - }, - "invalid": { - "description": "לא ניתן להסיר את המשתמש או הרול הללו מן הרשימה מאחר ואינם חסומים.", - "title": "❌שגיאה" - }, - "list": { - "fields": { - "members": "משתמשים", - "roles": "רולים" - }, - "title": "📃משתמשים ורולים ברשימת החסומים" - }, - "member_added": { - "description": "<@%s> נחסם ולא יוכל לתקשר עם הבוט.", - "title": "✅משתמש הוסף לרשימת החסומים" - } - } - }, - "settings": { - "options": { - "categories": { - "options": { - "edit": { - "name": "ערוך", - "options": { - "id": { - "name": "איידי" - }, - "image": { - "name": "תמונה" - }, - "max_per_member": { - "name": "מקס_עבור_משתמש" - }, - "name": { - "name": "שם" - }, - "name_format": { - "name": "פורמט_שם" - }, - "opening_message": { - "name": "הודעת_פתיחה" - }, - "opening_questions": { - "name": "שאלות_פתיחה" - }, - "ping": { - "name": "פינג" - } - } - } - } - } - } - } - } -} diff --git a/src/locales/hi-IN.json b/src/locales/hi-IN.json deleted file mode 100644 index b3353a5..0000000 --- a/src/locales/hi-IN.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "bot": { - "missing_permissions": { - "description": "कलह टिकट निम्नलिखित अनुमतियों की आवश्यकता है:\n%s", - "title": "⚠️" - }, - "version": "[Discord टिकट] (%s) v%s [eartharoid](%s) द्वारा" - }, - "collector_expires_in": "%d सेकंड में समाप्त हो रहा है", - "command_execution_error": { - "description": "An unexpected error occurred during command execution.\nPlease ask an administrator to check the console output / logs for details.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Add a member to a ticket", - "name": "add", - "response": { - "added": { - "description": "%s has been added to %s.", - "title": "✅ Member added" - }, - "no_member": { - "description": "Please mention the member you want to add.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't add members to this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "blacklist": { - "description": "Blacklist/unblacklist a member from interacting with the bot", - "name": "blacklist", - "response": { - "empty_list": { - "description": "There are no members or roles blacklisted. Type `%sblacklist ` to add a member or role to the blacklist.", - "title": "📃 Blacklisted members and roles" - }, - "illegal_action": { - "description": "%s is a staff member and cannot be blacklisted.", - "title": "❌ You can't blacklist this member" - }, - "list": { - "title": "📃 Blacklisted members and roles" - }, - "member_added": { - "description": "<@%s> has been added to the blacklist. They will no longer be able to interact with the bot.", - "title": "✅ Added member to blacklist" - }, - "member_removed": { - "description": "<@%s> has been removed from the blacklist. They can now use the bot again.", - "title": "✅ Removed member from blacklist" - }, - "role_added": { - "description": "<@&%s> has been added to the blacklist. Members with this role will no longer be able to interact with the bot.", - "title": "✅ Added role to blacklist" - }, - "role_removed": { - "description": "<@&%s> has been removed from the blacklist. Members with this role can now use the bot again.", - "title": "✅ Removed role from blacklist" - } - } - }, - "close": { - "description": "Close a ticket channel", - "name": "close", - "response": { - "closed": { - "description": "Ticket #%s has been closed.", - "title": "✅ Ticket closed" - }, - "closed_multiple": { - "description": [ - "%d ticket has been closed.", - "%d tickets have been closed." - ], - "title": [ - "✅ Ticket closed", - "✅ Tickets closed" - ] - }, - "confirm": { - "description": "React with ✅ to close this ticket.", - "description_with_archive": "You will be able to view an archived version of it after.\nReact with ✅ to close this ticket.", - "title": "❔ Are you sure?" - }, - "confirm_multiple": { - "description": [ - "React with ✅ to close %d ticket.", - "React with ✅ to close %d tickets." - ], - "title": "❔ Are you sure?" - }, - "confirmation_timeout": { - "description": "You took too long to confirm.", - "title": "❌ Reaction time expired" - }, - "invalid_time": { - "description": "The time period provided could not be parsed.", - "title": "❌ Invalid input" - }, - "no_tickets": { - "description": "There are no tickets which have been inactive for this time period.", - "title": "❌ No tickets to close" - }, - "not_a_ticket": { - "description": "Please use this command in a ticket channel or use the ticket flag.\nType `%shelp close` for more information.", - "title": "❌ This isn't a ticket channel" - }, - "unresolvable": { - "description": "`%s` could not be resolved to a ticket. Please provide the ticket ID/mention or number.", - "title": "❌ Error" - } - } - }, - "help": { - "description": "List commands you have access to, or find out more about a command", - "name": "help", - "response": { - "list": { - "description": "The commands you have access to are listed below. For more information about a command, type `{prefix}help [command]`. To create a ticket, type `{prefix}new [topic]`.", - "fields": { - "commands": "Commands" - }, - "title": "❔ Help" - } - } - }, - "new": { - "description": "Create a new ticket", - "name": "new", - "request_topic": { - "description": "Please briefly state what this ticket is about in a few words.", - "title": "Ticket topic" - }, - "response": { - "created": { - "description": "Your ticket has been created: %s.", - "title": "✅ Ticket created" - }, - "error": { - "title": "❌ Error" - }, - "has_a_ticket": { - "description": "Please use your existing ticket (<#%s>) or close it before creating another.", - "title": "❌ You already have an open ticket" - }, - "max_tickets": { - "description": "Please use `%sclose` to close any unneeded tickets.\n\n%s", - "title": "❌ You already have %d open tickets" - }, - "no_categories": { - "description": "A server administrator must create at least one ticket category before a new ticket can be opened.", - "title": "❌ Can't create ticket" - }, - "select_category": { - "description": "Select the category most relevant to your ticket's topic:\n\n%s", - "title": "🔤 Please select the ticket category" - }, - "select_category_timeout": { - "description": "You took too long to select the ticket category.", - "title": "❌ Reaction time expired" - } - } - }, - "panel": { - "description": "Create a new ticket panel", - "name": "panel", - "response": { - "invalid_category": { - "description": "One or more of the specified category IDs is invalid.", - "title": "❌ Invalid category" - } - } - }, - "remove": { - "description": "Remove a member from a ticket", - "name": "remove", - "response": { - "no_member": { - "description": "Please mention the member you want to remove.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't remove members from this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - }, - "removed": { - "description": "%s has been removed from %s.", - "title": "✅ Member removed" - } - } - }, - "settings": { - "description": "Configure Discord Tickets", - "name": "settings" - }, - "stats": { - "description": "Display ticket statistics", - "fields": { - "messages": "Messages", - "response_time": { - "minutes": "%s minutes", - "title": "Avg. response time" - }, - "tickets": "Tickets" - }, - "name": "stats", - "response": { - "global": { - "description": "Statistics about tickets across all guilds where this Discord Tickets instance is used.", - "title": "📊 Global stats" - }, - "guild": { - "description": "Statistics about tickets within this guild. This data is cached for an hour.", - "title": "📊 This server's stats" - } - } - }, - "survey": { - "description": "View survey responses", - "name": "survey", - "response": { - "list": { - "title": "📃 Surveys" - } - } - }, - "tag": { - "description": "Use a tag response", - "name": "tag", - "response": { - "error": "❌ Error", - "list": { - "title": "📃 Tag list" - }, - "missing": "This tag requires the following arguments:\n%s", - "not_a_ticket": { - "description": "This tag can only be used within a ticket channel as it uses ticket references.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "topic": { - "description": "Change the topic of the ticket", - "name": "topic", - "response": { - "changed": { - "description": "This ticket's topic has been changed.", - "title": "✅ Topic changed" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel you want to change the topic of.", - "title": "❌ This isn't a ticket channel" - } - } - } - }, - "message_will_be_deleted_in": "This message will be deleted in %d seconds", - "missing_permissions": { - "description": "You do not have the permissions required to use this command:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s has claimed this ticket.", - "title": "✅ Ticket claimed" - }, - "closed": { - "description": "This ticket has been closed.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member": { - "description": "This ticket has been closed by %s.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member_with_reason": { - "description": "This ticket has been closed by %s: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_with_reason": { - "description": "This ticket has been closed: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "member_added": { - "description": "%s has been added by %s", - "title": "Member added" - }, - "member_removed": { - "description": "%s has been removed by %s", - "title": "Member removed" - }, - "opening_message": { - "fields": { - "topic": "Topic" - } - }, - "questions": "Please answer the following questions:\n\n%s", - "released": { - "description": "%s has released this ticket.", - "title": "✅ Ticket released" - }, - "survey": { - "complete": { - "description": "Thank you for your feedback.", - "title": "✅ Thank you" - }, - "start": { - "description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey? React with ✅ to start, or ignore this message.", - "title": "❔ Feedback" - } - } - } -} diff --git a/src/locales/hu.json b/src/locales/hu.json deleted file mode 100644 index a3cd10a..0000000 --- a/src/locales/hu.json +++ /dev/null @@ -1,610 +0,0 @@ -{ - "blacklisted": "❌Feketelistázva vagy", - "bot": { - "missing_permissions": { - "description": "Discord Ticketek az alábbi engedélyeket igényli:\n%s", - "title": "⚠️" - }, - "version": "[Discord Ticketek](%s) v%s által [eartharoid](%s)" - }, - "collector_expires_in": "%d másodperc múlva lejár", - "command_execution_error": { - "description": "Egy váratlan hiba lépett fel a parancs futtatása közben\nKérj meg egy adminisztrátort hogy nézze meg a parancssort / hibanaplókat további információkért.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Felhasználó hozzáadása a Hibajegyhez", - "name": "hozzáad", - "options": { - "member": { - "description": "A felhasználó akit hozzá szeretnél adni a Tickethez", - "name": "tag" - }, - "ticket": { - "description": "A Ticket amihez hozzá szeretnéd adni a felhasználót", - "name": "ticket" - } - }, - "response": { - "added": { - "description": "%s felhasználó hozzáadva %s.", - "title": "✅ Felhasználó hozzáadva" - }, - "no_member": { - "description": "Kérlek, említsd meg a hozzáadni kívánt felhasználót.", - "title": "❌ Ismeretlen felhasználó" - }, - "no_permission": { - "description": "Nem ennek a Ticket alkotója, és nem munkatársa, így nem adhatsz hozzá felhasználókat ehhez a Hibajegyhez.", - "title": "❌ Nincs engedélyed ehhez" - }, - "not_a_ticket": { - "description": "Kérlek, használd ezt a parancsot a Hibajegy csatornában, vagy említsd meg a csatornát.", - "title": "❌ Ez nem Ticket csatorna" - } - } - }, - "blacklist": { - "description": "A feketelista megtekintése vagy módosítása", - "name": "feketelista", - "options": { - "add": { - "description": "Rang vagy felhasználó hozzáadása a feketelistához", - "name": "hozzáadás", - "options": { - "member_or_role": { - "description": "A feketelistára felveendő felhasználó vagy rang", - "name": "felhazsnálo_vagy_rang" - } - } - }, - "remove": { - "description": "Rang vagy felhasználó eltávolítása a feketelistáról", - "name": "eltávolítás", - "options": { - "member_or_role": { - "description": "A feketelistáról eltávolítandó felhasználó vagy rang", - "name": "felhasználó_vagy_rang" - } - } - }, - "show": { - "description": "Megmutatja a feketelistán szereplő felhasználóka és rangokat", - "name": "megmutatás" - } - }, - "response": { - "empty_list": { - "description": "Nincsenek felhasználok vagy rangok feketelistán. Írd be a(z) \"/blacklist add\" parancsot, ha felhasználót vagy rangot szeretnél hozzáadni a feketelistához.", - "title": "📃 Feketelistára került felhasználók és rangok" - }, - "illegal_action": { - "description": "%s csapattag, és nem lehet feketelistára tenni.", - "title": "❌ Ezt a felhasználót nem lehet feketelistára tenni" - }, - "invalid": { - "description": "Ez a felhasználó vagy rang nem távolítható el a feketelistáról, mivel nincsenek feketelistán.", - "title": "❌ Hiba" - }, - "list": { - "fields": { - "members": "Felhasználók", - "roles": "Rangok" - }, - "title": "📃 Feketelistára került felhasználók és rangok" - }, - "member_added": { - "description": "<@%s> felkerült a feketelistára. Többé nem léphet kapcsolatba a bottal.", - "title": "✅ Felhasználó hozzáadva a feketelistára" - }, - "member_removed": { - "description": "<@%s> eltávolítva a feketelistáról. Most újra használhatja a botot.", - "title": "✅ Felhasználó eltávolítva a fektelistáról" - }, - "role_added": { - "description": "<@&%s> felkerült a feketelistára. Az ezzel a rangal rendelkező felhasználók többé nem léphetnek kapcsolatba a bottal.", - "title": "✅ Rang hozzáadva a feketelistára" - }, - "role_removed": { - "description": "<@&%s> eltávolítva a feketelistáról. Az ezzel a rangal rendelkező felhasználók most újra használhatják a botot.", - "title": "✅ Rang eltávolítva a feketelistáról" - } - } - }, - "close": { - "description": "Zárd be a Ticket csatornát", - "name": "bezárás", - "options": { - "reason": { - "description": "A ticket lezárásának indoka", - "name": "indok" - }, - "ticket": { - "description": "A záró ticket, akár a szám, akár a csatorna ID", - "name": "ticket" - }, - "time": { - "description": "Zárj be minden ticketet, amely a megadott ideig inaktív volt", - "name": "idő" - } - }, - "response": { - "canceled": { - "description": "Töröltél a műveletet.", - "title": "🚫 Törölve" - }, - "closed": { - "description": "Ticket #%s lezárva.", - "title": "✅ Ticketek lezárva" - }, - "closed_multiple": { - "description": [ - "%d Ticketek lezárták.", - "%d ticketet lezárták." - ], - "title": [ - "✅ Ticket lezárva", - "✅ Ticketek lezárva" - ] - }, - "confirm": { - "buttons": { - "cancel": "Mégse", - "confirm": "Bezárás" - }, - "description": "Kérlek, erősítsd meg döntésed.", - "description_with_archive": "A ticketet archiváljuk a későbbi hivatkozás érdekében.", - "title": "❔ Biztos vagy ebben?" - }, - "confirm_multiple": { - "buttons": { - "cancel": "Mégse", - "confirm": [ - "%d ticket bezárása", - "%d ticketek bezárása" - ] - }, - "description": [ - "Arra készülsz, hogy lezárod %d ticketét.", - "Arra készülsz, hogy lezárod %d ticketjüket." - ], - "title": "❔ Biztos vagy benne?" - }, - "confirmation_timeout": { - "description": "Túl sokáig tartott a megerősítés.", - "title": "❌ Az interakciós idő lejárt" - }, - "invalid_time": { - "description": "A megadott időszakot nem lehetett elemezni.", - "title": "❌ Érvénytelen bemenet" - }, - "no_permission": { - "description": "Nem vagy személyzeti felhasználó vagy ticket gyártója.", - "title": "❌ Nincs elegendő engedélyed" - }, - "no_tickets": { - "description": "Nincs olyan ticket, amely inaktív lenne ebben az időszakban.", - "title": "❌Ticketeknek nincs zárható ticketjük" - }, - "not_a_ticket": { - "description": "Kérlek, használd ezt a parancsot egy ticket csatornában, vagy használd a ticket zászlót.\nTovábbi információért írd be a(z) \"/help close\" parancsot.", - "title": "❌ Ez nem ticket csatorna" - }, - "unresolvable": { - "description": "`%s` nem lehet feloldani ticketjét. Kérlek, add meg a ticket ID-ját/említését vagy számát.", - "title": "❌ Hiba" - } - } - }, - "help": { - "description": "Felsorolja azokat a parancsokat, amelyekhez hozzáférsz", - "name": "segítség", - "response": { - "list": { - "description": "Az alábbiakban felsoroljuk a hozzáférhető parancsokat. Ticket létrehozásához írd be a **`/new`** parancsot.", - "fields": { - "commands": "Parancsok" - }, - "title": "❔ Segítség" - } - } - }, - "new": { - "description": "Új Ticket létrehozása", - "name": "új", - "options": { - "topic": { - "description": "A ticket témája", - "name": "téma" - } - }, - "request_topic": { - "description": "Kérlek, néhány szóban röviden írd le, miről szól ez a ticket.", - "title": "⚠️ Ticket témája" - }, - "response": { - "created": { - "description": "Tickete létrehozva: %s.", - "title": "✅ Ticket létrehozva" - }, - "error": { - "title": "❌ Hiba" - }, - "has_a_ticket": { - "description": "Kérlek, használd meglévő ticketed (<#%s>), vagy zárd be, mielőtt újat hozol létre.", - "title": "❌ Már van nyitott ticketed" - }, - "max_tickets": { - "description": "A \"/close\" parancsal zárd be a szükségtelen ticketeket.\n\n%s", - "title": "❌ Már van %d nyitott ticketed" - }, - "no_categories": { - "description": "A szerver adminisztrátorának legalább egy ticket kategóriát kell létrehoznia, mielőtt új ticketet nyithatnál.", - "title": "❌ Nem lehet ticketet létrehozni" - }, - "select_category": { - "description": "Válaszd ki a ticket témájának leginkább megfelelő kategóriáját.", - "title": "🔤 Kérlek, válaszd ki a ticket kategóriáját" - }, - "select_category_timeout": { - "description": "Túl sokáig tartott a ticket kategória kiválasztása.", - "title": "❌ Az interakciós idő lejárt" - } - } - }, - "panel": { - "description": "Hozz létre új ticket panelt", - "name": "panel", - "options": { - "categories": { - "description": "A ID-kat vesszővel elválasztott listája", - "name": "kategóriák" - }, - "description": { - "description": "A panelüzenet leírása", - "name": "leírás" - }, - "image": { - "description": "A panelüzenet kép URL -je", - "name": "kép" - }, - "just_type": { - "description": "Létrehozol egy \"csak típus\" panelt?", - "name": "csak_gépelj" - }, - "thumbnail": { - "description": "A panelüzenet miniatűr képének URL -je", - "name": "miniatűr kép" - }, - "title": { - "description": "A panelüzenet címe", - "name": "cím" - } - }, - "response": { - "invalid_category": { - "description": "A megadott ID közül egy vagy több érvénytelen.", - "title": "❌ Érvénytelen kategória" - }, - "too_many_categories": { - "description": "A \"csak típus\" panel csak egyetlen kategóriával használható.", - "title": "❌ Túl sok kategória" - } - } - }, - "remove": { - "description": "Felhasználó eltávolítása a ticketből", - "name": "eltávolítás", - "options": { - "member": { - "description": "A felhasználó, hogy távolítsd el a ticketet", - "name": "felhasználó" - }, - "ticket": { - "description": "Felhasználó eltávolítása a ticketből", - "name": "ticket" - } - }, - "response": { - "no_member": { - "description": "Kérlek, említsd meg az eltávolítani kívánt felhasználót.", - "title": "❌ Ismeretlen felhasználó" - }, - "no_permission": { - "description": "Nem vagy ennek a ticketnek az alkotója, és nem munkatársa; nem távolíthatsz el a felhasználókat ebből a ticketől.", - "title": "❌ Nincs elegendő engedélyed" - }, - "not_a_ticket": { - "description": "Kérlek, használd ezt a parancsot a ticket csatornában, vagy említsd meg a csatornát.", - "title": "❌ Ez nem ticket csatorna" - }, - "removed": { - "description": "%s eltávolítva innen: %s.", - "title": "✅ Felhasználó eltávolítva" - } - } - }, - "settings": { - "description": "Állítsd be a Discord Ticketeket", - "name": "beállítások", - "options": { - "categories": { - "description": "Kezeld ticket kategóriákat", - "name": "kategóriák", - "options": { - "create": { - "description": "Új kategória létrehozása", - "name": "létrehozás", - "options": { - "name": { - "description": "A kategória neve", - "name": "név" - }, - "roles": { - "description": "A kategória személyi szerepkör-ID-nak vesszővel elválasztott listája", - "name": "rangok" - } - } - }, - "delete": { - "description": "Kategória törlése", - "name": "törlés", - "options": { - "id": { - "description": "A törölni kívánt kategória ID-ja", - "name": "id" - } - } - }, - "edit": { - "description": "Módosítsd a kategória konfigurációját", - "name": "szerkesztés", - "options": { - "claiming": { - "description": "Engedélyezed a ticket igénylését?", - "name": "igénylés" - }, - "id": { - "description": "A szerkeszteni kívánt kategória ID-ja", - "name": "id" - }, - "image": { - "description": "Kép URL", - "name": "kép" - }, - "max_per_member": { - "description": "Ebben a kategóriában a ticketek maximális száma, amelyet egy felhasználó kaphat", - "name": "felhasználónként_maximális" - }, - "name": { - "description": "A kategória neve", - "name": "név" - }, - "name_format": { - "description": "A ticket nevének a formátuma", - "name": "név_formátum" - }, - "opening_message": { - "description": "A szöveg, amelyet el kell küldeni a ticket kinyitásakor", - "name": "nyitó_üzenet" - }, - "opening_questions": { - "description": "Kérdések, amelyeket fel kell tenni a ticket kinyitásakor.", - "name": "nyitó_kérdések" - }, - "ping": { - "description": "A pingelni kívánt rang ID vesszővel elválasztott listája", - "name": "ping" - }, - "require_topic": { - "description": "Követeld a felhasználótól, hogy adja meg a ticket témáját?", - "name": "témát_igényel" - }, - "roles": { - "description": "A személyzeti rang ID vesszővel elválasztott listája", - "name": "rangok" - }, - "survey": { - "description": "A felmérés használható", - "name": "felmérés" - } - } - }, - "list": { - "description": "Kategóriák listája", - "name": "lista" - } - } - }, - "set": { - "description": "Beállítások beállítása", - "name": "beállítás", - "options": { - "close_button": { - "description": "Engedélyezed a gombbal történő zárást?", - "name": "záró_gomb" - }, - "colour": { - "description": "A standard szín", - "name": "szín" - }, - "error_colour": { - "description": "A hiba színe", - "name": "hiba_színe" - }, - "footer": { - "description": "A lábléc beágyazása", - "name": "lábléc" - }, - "locale": { - "description": "A területi beállítás (nyelv)", - "name": "nyelv" - }, - "log_messages": { - "description": "Tárold a ticketekből származó üzeneteket?", - "name": "napló_üzenetek" - }, - "success_colour": { - "description": "A siker színe", - "name": "siker_színe" - } - } - } - }, - "response": { - "category_created": "✅ Létrehozva a(z) `%s` ticket kategória", - "category_deleted": "✅ Törölve a(z) `%s` ticket kategória", - "category_does_not_exist": "❌ A megadott ID-val nem létezik kategória", - "category_list": "Ticket kategóriák", - "category_updated": "✅ A(z) `%s` ticket kategória frissítve", - "settings_updated": "✅ A beállítások sikeresen frissítve" - } - }, - "stats": { - "description": "Ticket statisztikák megjelenítése", - "fields": { - "messages": "Üzenetek", - "response_time": { - "minutes": "%s perc", - "title": "Átl. válaszidő" - }, - "tickets": "Ticketek" - }, - "name": "statisztika", - "response": { - "global": { - "description": "Statisztikák a ticketekről az összes szerveren, ahol ezt a Discord Ticketek példányt használják.", - "title": "📊 Globális statisztika" - }, - "guild": { - "description": "Statisztikák a szerveren belüli ticketekről. Ezeket az adatokat egy órán keresztül tárolja a gyorsítótárban.", - "title": "📊 Szerver statisztikája" - } - } - }, - "survey": { - "description": "Felmérési válaszok megtekintése", - "name": "felmérés", - "options": { - "survey": { - "description": "A válaszok megtekintéséhez szükséges felmérés neve", - "name": "felmérés" - } - }, - "response": { - "list": { - "title": "📃 Felmérések" - } - } - }, - "tag": { - "description": "Címke válasz használata", - "name": "címke", - "options": { - "tag": { - "description": "A használni kívánt címke neve", - "name": "címke" - } - }, - "response": { - "error": "❌ Hiba", - "list": { - "title": "📃 Címke lista" - }, - "missing": "Ez a címke a következő érveket igényli:\n%s", - "not_a_ticket": { - "description": "Ez a címke csak ticket csatornán belül használható, mivel ticket referenciákat használ.", - "title": "❌ Ez nem ticket csatorna" - } - } - }, - "topic": { - "description": "Változtasd meg a ticket témáját", - "name": "téma", - "options": { - "new_topic": { - "description": "A ticket új témája", - "name": "új_téma" - } - }, - "response": { - "changed": { - "description": "Ennek a ticketnek a témája megváltozott.", - "title": "✅ Téma változtatva" - }, - "not_a_ticket": { - "description": "Kérlek, használd ezt a parancsot a ticket csatornában, amelynek témáját módosítani szeretnéd.", - "title": "❌ Ez nem ticket csatorna" - } - } - } - }, - "message_will_be_deleted_in": "Ez az üzenet %d másodperc múlva törlődik", - "missing_permissions": { - "description": "Nem rendelkezel a parancs használatához szükséges jogosultságokkal:\n%s", - "title": "❌ Hiba" - }, - "panel": { - "create_ticket": "Ticket létrehozása" - }, - "ticket": { - "claim": "Igénylés", - "claimed": { - "description": "%s igényelt egy ticketet.", - "title": "✅ Ticket igényelve" - }, - "close": "Bezárás", - "closed": { - "description": "Ezt a ticketet lezárták.\nA csatorna 5 másodpercen belül törlődik.", - "title": "✅ Ticket lezárva" - }, - "closed_by_member": { - "description": "Ezt a ticketet lezárta %s.\nA csatorna 5 másodpercen belül törlődik.", - "title": "✅ Ticket lezárva" - }, - "closed_by_member_with_reason": { - "description": "Ezt a ticketet %s lezárta: ` %s`\nA csatorna 5 másodpercen belül törlődik.", - "title": "✅ Ticket lezárva" - }, - "closed_with_reason": { - "description": "Ezt a ticketet lezárta: `%s`\nA csatorna 5 másodpercen belül törlődik.", - "title": "✅ Ticket lezárva" - }, - "member_added": { - "description": "%s hozzáadva %s által", - "title": "Felhasználó hozzáadva" - }, - "member_removed": { - "description": "%s eltávolítva %s által", - "title": "Felhasználó eltávolítva" - }, - "opening_message": { - "content": "%s\n%s létrehozott egy új ticketet", - "fields": { - "topic": "Téma" - } - }, - "questions": "Kérlek, válaszolj az alábbi kérdésekre:\n\n%s", - "released": { - "description": "%s kiadta ezt a ticketet.", - "title": "✅ Ticket kiadva" - }, - "survey": { - "complete": { - "description": "Köszönjük a visszajelzést.", - "title": "✅ Köszönjük" - }, - "start": { - "buttons": { - "ignore": "Nem", - "start": "Indítsd el a felmérést" - }, - "description": "Szia, %s. Mielőtt törölnéd ezt a csatornát, szeretnél kitölteni egy gyors %d-question felmérést?", - "title": "❔Visszajelzés" - } - }, - "unclaim": "Kiadás" - }, - "updated_permissions": "✅ A Slash parancs engedélyei frissítve" -} diff --git a/src/locales/id-ID.json b/src/locales/id-ID.json deleted file mode 100644 index 1bfdb0c..0000000 --- a/src/locales/id-ID.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "bot": { - "missing_permissions": { - "description": "Discord Tickets requires the following permissions:\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) versi %s oleh [eartharoid](%s)" - }, - "collector_expires_in": "Kadaluwarsa dalam %d detik", - "command_execution_error": { - "description": "Eror tak terduga ketika mengeksekusi command.\nTolong tanya seorang adminstrator untuk memeriksa console atau log konsol untuk detail.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Add a member to a ticket", - "name": "add", - "response": { - "added": { - "description": "%s has been added to %s.", - "title": "✅ Member added" - }, - "no_member": { - "description": "Please mention the member you want to add.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't add members to this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "blacklist": { - "description": "Blok/hapus blok seorang anggota dari berinteraksi dengan bot ini", - "name": "blok", - "response": { - "empty_list": { - "description": "Tidak ada anggota atau role yang di blok. Ketik `%sblok ` untuk menambahkan member atau role ke daftar blokir.", - "title": "📃 Daftar anggota-anggota yang di blok" - }, - "illegal_action": { - "description": "%s adalah seorang anggota staf dan tidak bisa di blokir.", - "title": "❌ Anda tidak bisa blokir anggota ini" - }, - "list": { - "title": "📃 Daftar anggota-anggota yang di blok" - }, - "member_added": { - "description": "<@%s> telah ditambahkan ke daftar blokir. Mereka tidak akan lagi dapat berinteraksi dengan bot ini.", - "title": "✅ Berhasil menambahkan anggota ke daftar blokir" - }, - "member_removed": { - "description": "<@%s> telah dihapus dari daftar blokir. Sekarang mereka dapat menggunakan bot ini lagi.", - "title": "✅ Berhasil menghapus anggota dari daftar blokir" - }, - "role_added": { - "description": "<@&%s> telah ditambahkan ke daftar blokir. Anggota-anggota dengan role ini tidak akan lagi dapat berinteraksi dengan bot ini.", - "title": "✅ Berhasil menambahkan role ke daftar blokir" - }, - "role_removed": { - "description": "<@&%s> telah dihapus dari daftar blokir. Anggota-anggota dengan role ini sekarang dapat menggunakan bot ini lagi.", - "title": "✅ Berhasil menghapus role dari daftar blokir" - } - } - }, - "close": { - "description": "Tutup sebuah channel tiket", - "name": "tutup", - "response": { - "closed": { - "description": "Ticket #%s has been closed.", - "title": "✅ Tiket ditutup" - }, - "closed_multiple": { - "description": [ - "%d ticket has been closed.", - "%d tickets have been closed." - ], - "title": [ - "✅ Tiket ditutup", - "✅ Tickets closed" - ] - }, - "confirm": { - "description": "React with ✅ to close this ticket.", - "description_with_archive": "You will be able to view an archived version of it after.\nReact with ✅ to close this ticket.", - "title": "❔ Are you sure?" - }, - "confirm_multiple": { - "description": [ - "React with ✅ to close %d ticket.", - "React with ✅ to close %d tickets." - ], - "title": "❔ Are you sure?" - }, - "confirmation_timeout": { - "description": "You took too long to confirm.", - "title": "❌ Waktu reaksi habis" - }, - "invalid_time": { - "description": "The time period provided could not be parsed.", - "title": "❌ Invalid input" - }, - "no_tickets": { - "description": "There are no tickets which have been inactive for this time period.", - "title": "❌ No tickets to close" - }, - "not_a_ticket": { - "description": "Please use this command in a ticket channel or use the ticket flag.\nType `%shelp close` for more information.", - "title": "❌ This isn't a ticket channel" - }, - "unresolvable": { - "description": "`%s` could not be resolved to a ticket. Please provide the ticket ID/mention or number.", - "title": "❌ Error" - } - } - }, - "help": { - "description": "List commands you have access to, or find out more about a command", - "name": "help", - "response": { - "list": { - "description": "The commands you have access to are listed below. For more information about a command, type `{prefix}help [command]`. To create a ticket, type `{prefix}new [topic]`.", - "fields": { - "commands": "Commands" - }, - "title": "❔ Help" - } - } - }, - "new": { - "description": "Buat tiket baru", - "name": "baru", - "request_topic": { - "description": "Tolong beri tahu secara singkat untuk apa tiket ini dibuat dalam beberapa kata.", - "title": "Topik tiket" - }, - "response": { - "created": { - "description": "Tiket anda telah dibuat: %s.", - "title": "✅ Tiket dibuat" - }, - "error": { - "title": "❌ Error" - }, - "has_a_ticket": { - "description": "Silahkan gunakan tiket yang ada (<#%s>) atau tutup tiket tersebut sebelum membuat tiket lain.", - "title": "❌ Anda sudah memiliki tiket yang buka" - }, - "max_tickets": { - "description": "Tolong gunakan `%stutup` untuk menutup tiket yang tidak perlu.\n\n%s", - "title": "❌ Anda sudah memiliki %d tiket yang buka" - }, - "no_categories": { - "description": "Seorang administrator server harus membuat setidaknya satu kategori tikey sebelum tiket baru dapat dibuka.", - "title": "❌ Tidak dapat membuat tiket" - }, - "select_category": { - "description": "Pilih kategory yang paling relevan denga topik tiket anda:\n\n%s", - "title": "🔤 Tolong pilih kategori tiket" - }, - "select_category_timeout": { - "description": "Anda terlalu lama untuk memilih kategori tiket.", - "title": "❌ Waktu reaksi habis" - } - } - }, - "panel": { - "description": "Create a new ticket panel", - "name": "panel", - "response": { - "invalid_category": { - "description": "One or more of the specified category IDs is invalid.", - "title": "❌ Invalid category" - } - } - }, - "remove": { - "description": "Remove a member from a ticket", - "name": "remove", - "response": { - "no_member": { - "description": "Please mention the member you want to remove.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't remove members from this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - }, - "removed": { - "description": "%s has been removed from %s.", - "title": "✅ Member removed" - } - } - }, - "settings": { - "description": "Konfigurasi Discord Tickets", - "name": "pengaturan" - }, - "stats": { - "description": "Display ticket statistics", - "fields": { - "messages": "Messages", - "response_time": { - "minutes": "%s minutes", - "title": "Avg. response time" - }, - "tickets": "Tickets" - }, - "name": "stats", - "response": { - "global": { - "description": "Statistics about tickets across all guilds where this Discord Tickets instance is used.", - "title": "📊 Global stats" - }, - "guild": { - "description": "Statistics about tickets within this guild. This data is cached for an hour.", - "title": "📊 This server's stats" - } - } - }, - "survey": { - "description": "View survey responses", - "name": "survey", - "response": { - "list": { - "title": "📃 Surveys" - } - } - }, - "tag": { - "description": "Use a tag response", - "name": "tag", - "response": { - "error": "❌ Error", - "list": { - "title": "📃 Tag list" - }, - "missing": "This tag requires the following arguments:\n%s", - "not_a_ticket": { - "description": "This tag can only be used within a ticket channel as it uses ticket references.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "topic": { - "description": "Change the topic of the ticket", - "name": "topik", - "response": { - "changed": { - "description": "This ticket's topic has been changed.", - "title": "✅ Topic changed" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel you want to change the topic of.", - "title": "❌ This isn't a ticket channel" - } - } - } - }, - "message_will_be_deleted_in": "Pesan ini akan dihapus dalam %d detik", - "missing_permissions": { - "description": "You do not have the permissions required to use this command:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s has claimed this ticket.", - "title": "✅ Ticket claimed" - }, - "closed": { - "description": "This ticket has been closed.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Tiket ditutup" - }, - "closed_by_member": { - "description": "This ticket has been closed by %s.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Tiket ditutup" - }, - "closed_by_member_with_reason": { - "description": "This ticket has been closed by %s: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Tiket ditutup" - }, - "closed_with_reason": { - "description": "This ticket has been closed: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Tiket ditutup" - }, - "member_added": { - "description": "%s has been added by %s", - "title": "Member added" - }, - "member_removed": { - "description": "%s has been removed by %s", - "title": "Member removed" - }, - "opening_message": { - "fields": { - "topic": "Topic" - } - }, - "questions": "Please answer the following questions:\n\n%s", - "released": { - "description": "%s has released this ticket.", - "title": "✅ Ticket released" - }, - "survey": { - "complete": { - "description": "Thank you for your feedback.", - "title": "✅ Thank you" - }, - "start": { - "description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey? React with ✅ to start, or ignore this message.", - "title": "❔ Feedback" - } - } - } -} diff --git a/src/locales/it-IT.json b/src/locales/it-IT.json deleted file mode 100644 index 9e84c42..0000000 --- a/src/locales/it-IT.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "bot": { - "missing_permissions": { - "description": "Discord Tickets necessita i seguenti permessi.\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s di [eartharoid](%s)" - }, - "collector_expires_in": "Scade tra %d secondi", - "command_execution_error": { - "description": "Errore imprevisto durante l'esecuzione del comando.\nChiedere a un amministratore di controllare l'output / i registri della console per i dettagli.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Aggiungi un membro ad un ticket", - "name": "aggiungi", - "response": { - "added": { - "description": "%s è stato aggiunto a %s.", - "title": "✅ Membro aggiunto" - }, - "no_member": { - "description": "Si prega di menzionare il membro che si desidera aggiungere.", - "title": "❌ Membro sconosciuto" - }, - "no_permission": { - "description": "Non sei il creatore di questo ticket e non sei neanche un membro dello staff, di conseguenza non puoi aggiungere membri a questo ticket.", - "title": "❌ Permessi insufficienti" - }, - "not_a_ticket": { - "description": "Usa questo comando in un ticket, o menziona il canale.", - "title": "❌ Questo canale non è un ticket" - } - } - }, - "blacklist": { - "description": "Blacklista o unblacklista un utente dal bot", - "name": "lista nera", - "response": { - "empty_list": { - "description": "Non ci sono membri o ruoli blacklistati. Digita %sblacklist ` per aggiungere un membro o un ruolo alla blacklist.", - "title": "📃 Membri e ruoli nella blacklist" - }, - "illegal_action": { - "description": "%s è un membro dello staff e non può essere blacklistato.", - "title": "❌ Non puoi blacklistare questo utente" - }, - "list": { - "title": "📃 Membri e ruoli nella blacklist" - }, - "member_added": { - "description": "<@%s> è stato aggiunto alla blacklist. Non saranno più in grado di interagire con il bot.", - "title": "✅ Aggiunto membro alla blacklist" - }, - "member_removed": { - "description": "<@%s> è stato rimosso dalla blacklist. Da ora potrà ritornare ad usare il bot.", - "title": "✅ Membro rimosso dalla blacklist" - }, - "role_added": { - "description": "<@&%s> è stato aggiunto alla blacklist. I membri con questo ruolo non saranno più in grado di interagire con il bot.", - "title": "✅ Aggiunto ruolo alla blacklist" - }, - "role_removed": { - "description": "<@&%s> è stato rimosso dalla blacklist. I membri con questo ruolo da ora potranno ritornare ad usare il bot.", - "title": "✅ Ruolo rimosso dalla blacklist" - } - } - }, - "close": { - "description": "Chiudi un ticket", - "name": "chiudi", - "response": { - "closed": { - "description": "Il ticket #%s è stato chiuso.", - "title": "✅ Ticket chiuso" - }, - "closed_multiple": { - "description": [ - "%d è stato chiuso.", - "%d ticket sono stati chiusi." - ], - "title": [ - "✅ Ticket chiuso", - "✅ Ticket chiusi" - ] - }, - "confirm": { - "description": "Reagisci con ✅ per chiudere questo ticket.", - "description_with_archive": "Sarai in grado di visualizzarla dopo una versione archiviata.\nreagisci con ✅ per chiudere questo ticket.", - "title": "❔ Sei sicuro?" - }, - "confirm_multiple": { - "description": [ - "Reagisci con ✅ per chiudere %d ticket.", - "Reagisci con ✅ per chiudere %d ticket." - ], - "title": "❔ Sei sicuro?" - }, - "confirmation_timeout": { - "description": "Hai impiegato troppo tempo per confermare l'azione.", - "title": "❌ Tempo scaduto" - }, - "invalid_time": { - "description": "Impossibile analizzare il periodo di tempo fornito.", - "title": "❌ Input invalido" - }, - "no_tickets": { - "description": "Non ci sono ticket che sono stati inattivi per questo periodo di tempo.", - "title": "❌ Nessun ticket da chiudere" - }, - "not_a_ticket": { - "description": "Si prega di utilizzare questo comando in un ticket o di usare il contrassegno del ticket.\nDigita `%shelp close` per ulteriori informazioni.", - "title": "❌ Questo canale non è un ticket" - }, - "unresolvable": { - "description": "`%s` non poteva essere risolto in un ticket. Si prega di fornire un ID/menzione del ticket o il numero.", - "title": "❌ Errore" - } - } - }, - "help": { - "description": "Lista dei comandi a cui hai accesso o per saperne di più su un comando", - "name": "aiuto", - "response": { - "list": { - "description": "I comandi a cui hai accesso sono elencati di seguito. Per ulteriori informazioni su un comando, digita '{prefix}help [command]'. Per creare un ticket, digita '{prefix}new [topic]'.", - "fields": { - "commands": "Comandi" - }, - "title": "❔ Aiuto" - } - } - }, - "new": { - "description": "Crea un nuovo ticket", - "name": "nuovo", - "request_topic": { - "description": "Descrivi brevemente di cosa hai bisogno.", - "title": "Argomento del ticket" - }, - "response": { - "created": { - "description": "Il tuo ticket è stato creato: %s.", - "title": "✅ Ticket creato" - }, - "error": { - "title": "❌ Errore" - }, - "has_a_ticket": { - "description": "Ti preghiamo di utilizzare il tuo ticket già creato (<#%s>) o di chiuderlo prima di crearne un altro.", - "title": "❌ Hai già un ticket aperto" - }, - "max_tickets": { - "description": "Per favore utilizza `%sclose` per chiudere i ticket non necessari.\n\n%s", - "title": "❌ Hai già %d ticket aperti" - }, - "no_categories": { - "description": "Un amministratore del server deve creare almeno una categoria per i ticket prima di aprire un nuovo ticket.", - "title": "❌ Impossibile creare un ticket" - }, - "select_category": { - "description": "Seleziona la categoria che si attiene di più all'argomento del tuo ticket:\n\n%s", - "title": "🔤 Seleziona la categoria dei ticket" - }, - "select_category_timeout": { - "description": "Hai impiegato troppo tempo per selezionare la categoria del ticket.", - "title": "❌ Tempo scaduto" - } - } - }, - "panel": { - "description": "Crea un nuovo pannello dei ticket", - "name": "pannello", - "response": { - "invalid_category": { - "description": "Uno o più ID di categoria specificati non sono validi.", - "title": "❌ Categoria invalida" - } - } - }, - "remove": { - "description": "Rimuovi un membro dal ticket", - "name": "rimuovi", - "response": { - "no_member": { - "description": "Si prega di menzionare il membro che si desidera rimuovere.", - "title": "❌ Membro sconosciuto" - }, - "no_permission": { - "description": "Non sei il creatore di questo ticket e non sei neanche un membro dello staff, di conseguenza non puoi rimuovere membri da questo ticket.", - "title": "❌ Permessi insufficienti" - }, - "not_a_ticket": { - "description": "Usa questo comando in un ticket, o menziona il canale.", - "title": "❌ Questo canale non è un ticket" - }, - "removed": { - "description": "%s è stato rimosso da %s.", - "title": "✅ Membro rimosso" - } - } - }, - "settings": { - "description": "Configura il bot discord dei ticket", - "name": "impostazioni" - }, - "stats": { - "description": "Mostra le statistiche dei ticket", - "fields": { - "messages": "Messaggi", - "response_time": { - "minutes": "%s minuti", - "title": "Tempo di risposta media" - }, - "tickets": "Ticket" - }, - "name": "statistiche", - "response": { - "global": { - "description": "Statistiche sui ticket in tutti i server in cui viene utilizzata questa istanza di Discord Tickets.", - "title": "📊 Statistiche globali" - }, - "guild": { - "description": "Statistiche sui ticket all'interno di questo server. Questi dati vengono salvati nella cache per un'ora.", - "title": "📊 Statistiche di questo server" - } - } - }, - "survey": { - "description": "Guarda le risposte dei sondaggi", - "name": "sondaggio", - "response": { - "list": { - "title": "📃 Sondaggi" - } - } - }, - "tag": { - "description": "Usa un tag per rispondere", - "name": "tag", - "response": { - "error": "❌ Errore", - "list": { - "title": "📃 Lista dei tag" - }, - "missing": "Questo tag richiede i seguenti argomenti:\n%s", - "not_a_ticket": { - "description": "Questo tag può essere utilizzato solo all'interno di un ticket in quanto utilizza i riferimenti ai ticket.", - "title": "❌ Questo canale non è un ticket" - } - } - }, - "topic": { - "description": "Cambia l'argomento di un ticket", - "name": "argomento", - "response": { - "changed": { - "description": "L'argomento di questo biglietto è stato modificato.", - "title": "✅ Argomento cambiato" - }, - "not_a_ticket": { - "description": "Si prega di utilizzare questo comando in un ticket di cui si desidera modificare l'argomento.", - "title": "❌ Questo canale non è un ticket" - } - } - } - }, - "message_will_be_deleted_in": "Questo messaggio verrà eliminato in %d secondi", - "missing_permissions": { - "description": "Non hai i permessi richiesti per utilizzare questo comando:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "Il ticket è in revisione da parte di %s.", - "title": "✅ Ticket assegnato" - }, - "closed": { - "description": "Questo ticket è stato chiuso.\nIl canale verrà eliminato tra 5 secondi.", - "title": "✅ Ticket chiuso" - }, - "closed_by_member": { - "description": "Questo ticket è stato chiuso da %s.\nIl canale verrà eliminato tra 5 secondi.", - "title": "✅ Ticket chiuso" - }, - "closed_by_member_with_reason": { - "description": "Il ticket è stato chiuso da %s: `%s`\nIl canale verrà rimosso tra 5 secondi.", - "title": "✅ Ticket chiuso" - }, - "closed_with_reason": { - "description": "Il ticket è stato chiuso da: `%s`\nIl canale verrà eliminato tra 5 secondi.", - "title": "✅ Ticket chiuso" - }, - "member_added": { - "description": "%s è stato aggiunto da %s", - "title": "Membro aggiunto" - }, - "member_removed": { - "description": "%s è stato rimosso da %s", - "title": "Membro rimosso" - }, - "opening_message": { - "fields": { - "topic": "Argomento" - } - }, - "questions": "Si prega di rispondere alle seguenti domande:\n\n%s", - "released": { - "description": "%s ha rilasciato il ticket.", - "title": "✅ Ticket rilasciato" - }, - "survey": { - "complete": { - "description": "Grazie per il tuo feedback.", - "title": "✅ Grazie" - }, - "start": { - "description": "Ehi, %s. Prima che questo canale sia eliminato, ti dispiacerebbe completare un sondaggio %ddomande rapide? Reagisci con ✅ per avviare il sondaggio, altrimenti ignora questo messaggio.", - "title": "❔ Feedback" - } - } - } -} diff --git a/src/locales/ko-KR.json b/src/locales/ko-KR.json deleted file mode 100644 index b17054a..0000000 --- a/src/locales/ko-KR.json +++ /dev/null @@ -1,344 +0,0 @@ -{ - "bot": { - "missing_permissions": { - "description": "디스코드 티켓은 해당 권한이 필요합니다:\n%s", - "title": "⚠️" - }, - "version": "[디스코드 티켓](%s) v%s by [eartharoid](%s)" - }, - "collector_expires_in": "%d 초 안에 만료됨", - "command_execution_error": { - "description": "명령을 실행하는 동안 예기치 않은 오류가 발생했습니다.\n자세한 내용은 관리자에게 콘솔 출력/로그를 확인 하도록 요청하십시오.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "티켓에 멤버 추가하기", - "name": "추가", - "options": { - "member": { - "description": "티켓에 추가할 멤버", - "name": "맴버" - }, - "ticket": { - "description": "멤버를 추가할 티켓", - "name": "티켓" - } - }, - "response": { - "added": { - "description": "%s 님에게 %s 를 추가 했습니다.", - "title": "✅ 맴버가 추가됨" - }, - "no_member": { - "description": "추가하고 싶은 멤버를 멘션해 주세요.", - "title": "❌ 알수없는 맴버" - }, - "no_permission": { - "description": "이 티켓의 제작자 및 직원이 아니므로, 회원을 추가할 수 없습니다.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ 여긴 티켓 채널이 아닙니다." - } - } - }, - "blacklist": { - "description": "블랙리스트 보기 또는 수정", - "name": "블랙리스트", - "options": { - "remove": { - "name": "제거함", - "options": { - "member_or_role": { - "name": "맴버 또는 역할" - } - } - } - }, - "response": { - "empty_list": { - "description": "There are no members or roles blacklisted. Type `%sblacklist ` to add a member or role to the blacklist.", - "title": "📃 Blacklisted members and roles" - }, - "illegal_action": { - "description": "%s is a staff member and cannot be blacklisted.", - "title": "❌ 당신은 맴버를 블랙리스트 화 시킬수 없습니다" - }, - "list": { - "title": "📃 Blacklisted members and roles" - }, - "member_added": { - "description": "<@%s> has been added to the blacklist. They will no longer be able to interact with the bot.", - "title": "✅ Added member to blacklist" - }, - "member_removed": { - "description": "<@%s> has been removed from the blacklist. They can now use the bot again.", - "title": "✅ Removed member from blacklist" - }, - "role_added": { - "description": "<@&%s> has been added to the blacklist. Members with this role will no longer be able to interact with the bot.", - "title": "✅ Added role to blacklist" - }, - "role_removed": { - "description": "<@&%s> has been removed from the blacklist. Members with this role can now use the bot again.", - "title": "✅ Removed role from blacklist" - } - } - }, - "close": { - "description": "Close a ticket channel", - "name": "close", - "response": { - "closed": { - "description": "Ticket #%s has been closed.", - "title": "✅ Ticket closed" - }, - "closed_multiple": { - "description": [ - "%d ticket has been closed.", - "%d tickets have been closed." - ], - "title": [ - "✅ Ticket closed", - "✅ Tickets closed" - ] - }, - "confirm": { - "description": "React with ✅ to close this ticket.", - "description_with_archive": "You will be able to view an archived version of it after.\nReact with ✅ to close this ticket.", - "title": "❔ Are you sure?" - }, - "confirm_multiple": { - "description": [ - "React with ✅ to close %d ticket.", - "React with ✅ to close %d tickets." - ], - "title": "❔ Are you sure?" - }, - "confirmation_timeout": { - "description": "You took too long to confirm.", - "title": "❌ Reaction time expired" - }, - "invalid_time": { - "description": "The time period provided could not be parsed.", - "title": "❌ Invalid input" - }, - "no_tickets": { - "description": "There are no tickets which have been inactive for this time period.", - "title": "❌ No tickets to close" - }, - "not_a_ticket": { - "description": "Please use this command in a ticket channel or use the ticket flag.\nType `%shelp close` for more information.", - "title": "❌ This isn't a ticket channel" - }, - "unresolvable": { - "description": "`%s` could not be resolved to a ticket. Please provide the ticket ID/mention or number.", - "title": "❌ Error" - } - } - }, - "help": { - "description": "List commands you have access to, or find out more about a command", - "name": "help", - "response": { - "list": { - "description": "The commands you have access to are listed below. For more information about a command, type `{prefix}help [command]`. To create a ticket, type `{prefix}new [topic]`.", - "fields": { - "commands": "Commands" - }, - "title": "❔ Help" - } - } - }, - "new": { - "description": "Create a new ticket", - "name": "new", - "request_topic": { - "description": "Please briefly state what this ticket is about in a few words.", - "title": "Ticket topic" - }, - "response": { - "created": { - "description": "Your ticket has been created: %s.", - "title": "✅ Ticket created" - }, - "error": { - "title": "❌ Error" - }, - "has_a_ticket": { - "description": "Please use your existing ticket (<#%s>) or close it before creating another.", - "title": "❌ You already have an open ticket" - }, - "max_tickets": { - "description": "Please use `%sclose` to close any unneeded tickets.\n\n%s", - "title": "❌ You already have %d open tickets" - }, - "no_categories": { - "description": "A server administrator must create at least one ticket category before a new ticket can be opened.", - "title": "❌ Can't create ticket" - }, - "select_category": { - "description": "Select the category most relevant to your ticket's topic:\n\n%s", - "title": "🔤 Please select the ticket category" - }, - "select_category_timeout": { - "description": "You took too long to select the ticket category.", - "title": "❌ Reaction time expired" - } - } - }, - "panel": { - "description": "Create a new ticket panel", - "name": "panel", - "response": { - "invalid_category": { - "description": "One or more of the specified category IDs is invalid.", - "title": "❌ Invalid category" - } - } - }, - "remove": { - "description": "Remove a member from a ticket", - "name": "remove", - "response": { - "no_member": { - "description": "Please mention the member you want to remove.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't remove members from this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - }, - "removed": { - "description": "%s has been removed from %s.", - "title": "✅ Member removed" - } - } - }, - "settings": { - "description": "Configure Discord Tickets", - "name": "settings" - }, - "stats": { - "description": "Display ticket statistics", - "fields": { - "messages": "Messages", - "response_time": { - "minutes": "%s minutes", - "title": "Avg. response time" - }, - "tickets": "Tickets" - }, - "name": "stats", - "response": { - "global": { - "description": "Statistics about tickets across all guilds where this Discord Tickets instance is used.", - "title": "📊 Global stats" - }, - "guild": { - "description": "Statistics about tickets within this guild. This data is cached for an hour.", - "title": "📊 This server's stats" - } - } - }, - "survey": { - "description": "View survey responses", - "name": "survey", - "response": { - "list": { - "title": "📃 Surveys" - } - } - }, - "tag": { - "description": "Use a tag response", - "name": "tag", - "response": { - "error": "❌ Error", - "list": { - "title": "📃 Tag list" - }, - "missing": "This tag requires the following arguments:\n%s", - "not_a_ticket": { - "description": "This tag can only be used within a ticket channel as it uses ticket references.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "topic": { - "description": "Change the topic of the ticket", - "name": "topic", - "response": { - "changed": { - "description": "This ticket's topic has been changed.", - "title": "✅ Topic changed" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel you want to change the topic of.", - "title": "❌ This isn't a ticket channel" - } - } - } - }, - "message_will_be_deleted_in": "This message will be deleted in %d seconds", - "missing_permissions": { - "description": "You do not have the permissions required to use this command:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s has claimed this ticket.", - "title": "✅ Ticket claimed" - }, - "closed": { - "description": "This ticket has been closed.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member": { - "description": "This ticket has been closed by %s.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member_with_reason": { - "description": "This ticket has been closed by %s: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_with_reason": { - "description": "This ticket has been closed: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "member_added": { - "description": "%s has been added by %s", - "title": "Member added" - }, - "member_removed": { - "description": "%s has been removed by %s", - "title": "Member removed" - }, - "opening_message": { - "fields": { - "topic": "Topic" - } - }, - "questions": "Please answer the following questions:\n\n%s", - "released": { - "description": "%s has released this ticket.", - "title": "✅ Ticket released" - }, - "survey": { - "complete": { - "description": "Thank you for your feedback.", - "title": "✅ Thank you" - }, - "start": { - "description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey? React with ✅ to start, or ignore this message.", - "title": "❔ Feedback" - } - } - } -} diff --git a/src/locales/nl-NL.json b/src/locales/nl-NL.json deleted file mode 100644 index 431b3d7..0000000 --- a/src/locales/nl-NL.json +++ /dev/null @@ -1,402 +0,0 @@ -{ - "blacklisted": "❌ Je staat op de zwarte lijst", - "bot": { - "missing_permissions": { - "description": "Discord Tickets vereist de volgende machtigingen:\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets] (%s) v%s door [eartharoid](%s)" - }, - "collector_expires_in": "Verloopt over %d seconden", - "command_execution_error": { - "description": "Er is een onverwachte fout opgetreden tijdens de uitvoering van de opdracht.\nVraag een beheerder om de console-uitvoer / logboeken te controleren voor meer informatie.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Een lid toevoegen aan een ticket", - "name": "toevoegen", - "options": { - "member": { - "description": "Het lid is toegevoegd aan het ticket", - "name": "lid" - }, - "ticket": { - "description": "Het ticket om het lid aan toe te voegen", - "name": "ticket" - } - }, - "response": { - "added": { - "description": "%s is toegevoegd aan %s.", - "title": "✅ lid toegevoegd" - }, - "no_member": { - "description": "Vermeld het lid dat u wilt toevoegen.", - "title": "❌ Onbekend lid" - }, - "no_permission": { - "description": "U bent niet de maker van deze ticket en u bent geen werknemer, je kunt geen leden toevoegen aan deze ticket.", - "title": "❌ Onvoldoende rechten" - }, - "not_a_ticket": { - "description": "Gebruik deze commando in het ticketkanaal of vermeld het kanaal.", - "title": "❌ Dit is geen ticketkanaal" - } - } - }, - "blacklist": { - "description": "Bekijk of pas de zwarte lijst aan", - "name": "Blacklist", - "options": { - "add": { - "description": "Een member of rol aan de zwarte lijst", - "name": "toevoegen", - "options": { - "member_or_role": { - "description": "Een lid of rol toevoegen aan de zwarte lijst", - "name": "member_of_rol" - } - } - }, - "remove": { - "description": "Verwijder een lid of rol van de zwarte lijst", - "name": "verwijder", - "options": { - "member_or_role": { - "description": "Een lid of rol verwijderen van de zwarte lijst", - "name": "lid_of_rol" - } - } - }, - "show": { - "description": "Laat de leden en rollen zien die lid zijn van de zwarte lijst", - "name": "zien" - } - }, - "response": { - "empty_list": { - "description": "Er zijn geen leden of rollen op de zwarte lijst. Type '%sblacklist ' om een lid of rol aan de zwarte lijst toe te voegen.", - "title": "📃 leden en rollen op de blacklist" - }, - "illegal_action": { - "description": "%s is een personeelslid en kan niet op de blacklist worden geplaatst.", - "title": "❌ Je kunt dit lid niet op de blacklist zetten" - }, - "invalid": { - "description": "Dit lid of rol kan niet van de zwarte lijst worden gehaald omdat ze er niet op staan.", - "title": "❌ Fout" - }, - "list": { - "fields": { - "members": "Leden", - "roles": "Rollen" - }, - "title": "📃 leden en rollen op de blacklist" - }, - "member_added": { - "description": "<@%s> is toegevoegd aan de blacklist. Ze kunnen niet langer communiceren met de bot.", - "title": "✅ Lid toegevoegd aan blacklist" - }, - "member_removed": { - "description": "<@%s> is van de blacklist verwijderd. Ze kunnen de bot nu weer gebruiken.", - "title": "✅ Lid van blacklist verwijderd" - }, - "role_added": { - "description": "<@&%s> is toegevoegd aan de blacklist. Leden met deze rol kunnen niet langer communiceren met de bot.", - "title": "✅ Rol toegevoegd aan blacklist" - }, - "role_removed": { - "description": "<@&%s> is van de zwartelijst verwijderd. Leden met deze rol kunnen de bot nu opnieuw gebruiken.", - "title": "✅ Rol van blacklist verwijderd" - } - } - }, - "close": { - "description": "Een ticketkanaal sluiten", - "name": "sluiten", - "options": { - "reason": { - "description": "De reden waarom je dit/deze ticket(s) sluit", - "name": "reden" - }, - "ticket": { - "description": "Het ticket op te sluiten, of het nummer of het channel ID", - "name": "ticket" - }, - "time": { - "description": "Sluit alle tickets inactieve tickets voor een gespecificieerde tijd", - "name": "tijd" - } - }, - "response": { - "canceled": { - "description": "Je hebt de opdracht geannuleerd", - "title": "🚫 Geannuleerd" - }, - "closed": { - "description": "Ticket #%s is gesloten.", - "title": "✅ Ticket gesloten" - }, - "closed_multiple": { - "description": [ - "%d ticket is gesloten.", - "%d tickets zijn gesloten." - ], - "title": [ - "✅ Ticket gesloten", - "✅ Tickets gesloten" - ] - }, - "confirm": { - "buttons": { - "cancel": "Annuleren", - "confirm": "Sluiten" - }, - "description": "Reageer met ✅ om dit ticket af te sluiten.", - "description_with_archive": "U kunt er daarna een gearchiveerde versie van bekijken. Reageer met ✅ om dit ticket af te sluiten.", - "title": "❔ Weet je het zeker?" - }, - "confirm_multiple": { - "buttons": { - "cancel": "Annuleren", - "confirm": [ - "Sluit %d ticket", - "Sluit %d tickets" - ] - }, - "description": [ - "Reageer met ✅ om %d ticket af te sluiten.", - "Reageer met ✅ om %d tickets af te sluiten." - ], - "title": "❔ Weet je het zeker?" - }, - "confirmation_timeout": { - "description": "Je deed er te lang over om het te bevestigen.", - "title": "❌ Interactietijd verstreken" - }, - "invalid_time": { - "description": "De opgegeven periode kan niet worden geparseerd.", - "title": "❌ ongeldige invoer" - }, - "no_permission": { - "description": "Je bent geen stafflid of eigenaar van dit ticket.", - "title": "❌ Onvoldoende rechten" - }, - "no_tickets": { - "description": "Er zijn geen tickets die inactief zijn geweest voor deze periode.", - "title": "❌ Geen tickets om te sluiten" - }, - "not_a_ticket": { - "description": "Gebruik deze commando in een ticketkanaal of gebruik de ticket emoji.\nType '%shelp sluiten' voor meer informatie.", - "title": "❌ Dit is geen ticketkanaal" - }, - "unresolvable": { - "description": "'%s' kon niet worden omgezet in een ticket. Geef het ticket-ID/vermelding of nummer op.", - "title": "❌ fout" - } - } - }, - "help": { - "description": "Opdrachten weergeven waar u toegang toe heeft, of meer informatie over een opdracht", - "name": "help", - "response": { - "list": { - "description": "De commandos waar u toegang toe hebt, worden hieronder vermeld. Typ '{prefix}help [command]' voor meer informatie over een commando. Als u een ticket wilt maken, typt u '{prefix}new [topic]'.", - "fields": { - "commands": "Commandos" - }, - "title": "❔ Help" - } - } - }, - "new": { - "description": "Een nieuw ticket maken", - "name": "new", - "request_topic": { - "description": "Geef in een paar woorden kort aan waar deze ticket over gaat.", - "title": "Ticket onderwerp" - }, - "response": { - "created": { - "description": "Uw ticket is aangemaakt: %s.", - "title": "✅ ticket gemaakt" - }, - "error": { - "title": "❌ fout" - }, - "has_a_ticket": { - "description": "Gebruik uw bestaande ticket (<#%s>) of sluit uw oude ticket voordat u een ander ticket maakt.", - "title": "❌ Je hebt al een ticket open staan" - }, - "max_tickets": { - "description": "Gebruik '%ssluiten' om onnodige tickets te sluiten.\n\n%s", - "title": "❌ Je hebt al %d geopende tickets" - }, - "no_categories": { - "description": "Een serverbeheerder moet ten minste één ticketcategorie maken voordat er een nieuw ticket kan worden geopend.", - "title": "❌ Kan geen ticket maken" - }, - "select_category": { - "description": "Selecteer de categorie die het meest relevant is voor het onderwerp van uw ticket:\n\n%s", - "title": "🔤 Selecteer de ticketcategorie" - }, - "select_category_timeout": { - "description": "Het duurde te lang om de ticketcategorie te selecteren.", - "title": "❌ Reactietijd verstreken" - } - } - }, - "panel": { - "description": "Een nieuw ticketpaneel maken", - "name": "paneel", - "response": { - "invalid_category": { - "description": "Een of meer van de opgegeven categorie-ID's zijn ongeldig.", - "title": "❌ ongeldige categorie" - } - } - }, - "remove": { - "description": "Een lid uit een ticket verwijderen", - "name": "verwijderen", - "response": { - "no_member": { - "description": "Vermeld het lid dat u wilt verwijderen.", - "title": "❌ Onbekend lid" - }, - "no_permission": { - "description": "U bent niet de maker van dit ticket en u bent geen werknemer, je kunt geen leden van deze ticket verwijderen.", - "title": "❌ Onvoldoende rechten" - }, - "not_a_ticket": { - "description": "Gebruik deze commando in het ticketkanaal of vermeld het kanaal.", - "title": "❌ Dit is geen ticketkanaal" - }, - "removed": { - "description": "%s is verwijderd uit %s.", - "title": "✅ lid verwijderd" - } - } - }, - "settings": { - "description": "Discord-tickets configureren", - "name": "instellingen" - }, - "stats": { - "description": "Ticketstatistieken weergeven", - "fields": { - "messages": "Berichten", - "response_time": { - "minutes": "%s minuten", - "title": "Gemiddelde reactietijd" - }, - "tickets": "Tickets" - }, - "name": "statistieken", - "response": { - "global": { - "description": "Statistieken over tickets in alle gilden waar dit Discord Tickets-exemplaar wordt gebruikt.", - "title": "📊 Wereldwijde statistieken" - }, - "guild": { - "description": "Statistieken over tickets binnen deze gilde. Deze gegevens worden een uur in de cache opgeslagen.", - "title": "📊 De statistieken van deze server" - } - } - }, - "survey": { - "description": "Enquêtereacties weergeven", - "name": "enquête", - "response": { - "list": { - "title": "📃 enquêtes" - } - } - }, - "tag": { - "description": "Een tagrespons gebruiken", - "name": "label", - "response": { - "error": "❌ fout", - "list": { - "title": "📃 Tag lijst" - }, - "missing": "Deze tag vereist de volgende argumenten:\n%s", - "not_a_ticket": { - "description": "Deze tag kan alleen worden gebruikt binnen een ticketkanaal omdat deze ticketreferenties gebruikt.", - "title": "❌ Dit is geen ticketkanaal" - } - } - }, - "topic": { - "description": "Het onderwerp van het ticket wijzigen", - "name": "onderwerp", - "response": { - "changed": { - "description": "Het onderwerp van dit ticket is gewijzigd.", - "title": "✅ Onderwerp gewijzigd" - }, - "not_a_ticket": { - "description": "Gebruik deze opdracht in het ticketkanaal waarvan u het onderwerp wilt wijzigen.", - "title": "❌ Dit is geen ticketkanaal" - } - } - } - }, - "message_will_be_deleted_in": "Dit bericht wordt binnen %d seconden verwijderd", - "missing_permissions": { - "description": "U beschikt niet over de machtigingen die nodig zijn om deze opdracht te gebruiken:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s heeft dit ticket opgeëist.", - "title": "✅ Ticket geclaimd" - }, - "closed": { - "description": "Deze ticket is gesloten.\nHet kanaal wordt binnen 5 seconden verwijderd.", - "title": "✅ Ticket gesloten" - }, - "closed_by_member": { - "description": "Deze ticket is gesloten door %s.\nHet kanaal wordt binnen 5 seconden verwijderd.", - "title": "✅ Ticket gesloten" - }, - "closed_by_member_with_reason": { - "description": "Deze ticket is gesloten door %s: '%s'\nHet kanaal wordt binnen 5 seconden verwijderd.", - "title": "✅ Ticket gesloten" - }, - "closed_with_reason": { - "description": "Deze ticket is gesloten: '%s'\nHet kanaal wordt binnen 5 seconden verwijderd.", - "title": "✅ Ticket gesloten" - }, - "member_added": { - "description": "%s is toegevoegd door %s", - "title": "Lid toegevoegd" - }, - "member_removed": { - "description": "%s is verwijderd door %s", - "title": "Lid verwijderd" - }, - "opening_message": { - "fields": { - "topic": "Onderwerp" - } - }, - "questions": "Beantwoord de volgende vragen:\n\n%s", - "released": { - "description": "%s heeft deze ticket vrijgegeven.", - "title": "✅ Ticket vrijgegeven" - }, - "survey": { - "complete": { - "description": "Bedankt voor uw feedback.", - "title": "✅ Dank u" - }, - "start": { - "description": "Hey %s. Voordat dit kanaal wordt verwijderd, wilt u een snelle %d-vragen enquête invullen? Reageer met ✅ om dit bericht te starten of negeer dit bericht.", - "title": "❔ Feedback" - } - } - } -} diff --git a/src/locales/no-NO.json b/src/locales/no-NO.json deleted file mode 100644 index da2533f..0000000 --- a/src/locales/no-NO.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "bot": { - "missing_permissions": { - "description": "Discord-billetter krever følgende tilganger:\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s av [eartharoid](%s)" - }, - "collector_expires_in": "Utløper om %d sekunder", - "command_execution_error": { - "description": "Det oppstod en uventet feil under kommandoutførelsen.\nBe en administrator kontrollere konsollutdataene/-loggene for mer informasjon.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Add a member to a ticket", - "name": "legg til", - "response": { - "added": { - "description": "%s har blitt lagt til i %s.", - "title": "✅ Medlem lagt til" - }, - "no_member": { - "description": "Nevn medlemmet du ønsker å legge til.", - "title": "❌ Ukjent medlem" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't add members to this ticket.", - "title": "❌ Utilstrekkelige tilganger" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "blacklist": { - "description": "Svarteliste/fjerne fra svarteliste et medlem fra og bruke denne botten", - "name": "svarteliste", - "response": { - "empty_list": { - "description": "Det er ingen medlemmer eller roller svartelistet. Skriv `%sblacklist ` for og legge til et medlem eller rolle til svartelisten.", - "title": "📃 Svartelistet medlemmer og roller" - }, - "illegal_action": { - "description": "%s er en moderator og kan ikke bli svartelistet.", - "title": "❌ Du kan ikke svarteliste dette medlemmet" - }, - "list": { - "title": "📃 Svartelistet medlemmer og roller" - }, - "member_added": { - "description": "<@%s> har blitt lagt til i svartelisten. De kan ikke lengre bruke botten.", - "title": "✅ La til medlemmet i svartelisten" - }, - "member_removed": { - "description": "<@%s> har blitt fjernet fra svartelisten. De kan nå bruke botten igjen.", - "title": "✅ Fjernet medlemmet fra svartelisten" - }, - "role_added": { - "description": "<@&%s> er lagt til i svartelisten. Medlemmer med denne rollen vil ikke lenger kunne bruke botten.", - "title": "✅ La til rollen i svartelisten" - }, - "role_removed": { - "description": "<@&%s> er fjernet fra svartelisten. Medlemmer med denne rollen kan nå bruke botten igjen.", - "title": "✅ Fjernet rollen fra svartelisten" - } - } - }, - "close": { - "description": "Lukke en billettkanal", - "name": "lukk", - "response": { - "closed": { - "description": "Billett #%s har blitt stengt.", - "title": "✅ Billetten er stengt" - }, - "closed_multiple": { - "description": [ - "%d billett har blitt stengt.", - "%d billetter har blitt stengt." - ], - "title": [ - "✅ Billetten er stengt", - "✅ Billetter lukket" - ] - }, - "confirm": { - "description": "Reager med ✅ for å stenge denne billetten.", - "description_with_archive": "You will be able to view an archived version of it after.\nReact with ✅ to close this ticket.", - "title": "❔ Er du sikker?" - }, - "confirm_multiple": { - "description": [ - "Reager med ✅ for å stenge %d billett.", - "Reager med ✅ for å stenge %d billetter." - ], - "title": "❔ Er du sikker?" - }, - "confirmation_timeout": { - "description": "Du brukte for lang tid på å bekrefte.", - "title": "❌ Reaksjonstiden har utløpt" - }, - "invalid_time": { - "description": "The time period provided could not be parsed.", - "title": "❌ Ugyldig inndata" - }, - "no_tickets": { - "description": "There are no tickets which have been inactive for this time period.", - "title": "❌ Ingen billetter å lukke" - }, - "not_a_ticket": { - "description": "Please use this command in a ticket channel or use the ticket flag.\nType `%shelp close` for more information.", - "title": "❌ Dette er ikke en billett-kanal" - }, - "unresolvable": { - "description": "`%s` could not be resolved to a ticket. Please provide the ticket ID/mention or number.", - "title": "❌ Feil" - } - } - }, - "help": { - "description": "List commands you have access to, or find out more about a command", - "name": "hjelp", - "response": { - "list": { - "description": "The commands you have access to are listed below. For more information about a command, type `{prefix}help [command]`. To create a ticket, type `{prefix}new [topic]`.", - "fields": { - "commands": "Kommandoer" - }, - "title": "❔ Hjelp" - } - } - }, - "new": { - "description": "Opprett en ny billett", - "name": "ny", - "request_topic": { - "description": "Vennligst oppgi kort hva denne billetten handler om med noen få ord.", - "title": "Billettemne" - }, - "response": { - "created": { - "description": "Billetten er opprettet: %s.", - "title": "✅ Billetten er opprettet" - }, - "error": { - "title": "❌ Feil" - }, - "has_a_ticket": { - "description": "Bruk din eksisterende billett ( <#%s>) eller lukk den før du oppretter en ny.", - "title": "❌ Du har allerede en åpen billett" - }, - "max_tickets": { - "description": "Bruk «%sclose» for å lukke unødvendige billetter.\n\n%s", - "title": "❌ Du har allerede %d åpne billetter" - }, - "no_categories": { - "description": "En tjeneradministrator må opprette minst én billettkategori før en ny billett kan åpnes.", - "title": "❌ Kan ikke opprette billett" - }, - "select_category": { - "description": "Velg kategorien som er mest relevant for billettens emne:\n\n%s", - "title": "🔤 Velg billettkategori" - }, - "select_category_timeout": { - "description": "Det tok for lang tid å velge billettkategorien.", - "title": "❌ Reaksjonstiden har utløpt" - } - } - }, - "panel": { - "description": "Create a new ticket panel", - "name": "panel", - "response": { - "invalid_category": { - "description": "One or more of the specified category IDs is invalid.", - "title": "❌ Ugyldig kategori" - } - } - }, - "remove": { - "description": "Fjern et medlem fra en billett", - "name": "fjern", - "response": { - "no_member": { - "description": "Nevn medlemmet du ønsker å fjerne.", - "title": "❌ Ukjent medlem" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't remove members from this ticket.", - "title": "❌ Utilstrekkelige tilganger" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ Dette er ikke en billettkanal" - }, - "removed": { - "description": "%s har blitt fjernet fra %s.", - "title": "✅ Medlem fjernet" - } - } - }, - "settings": { - "description": "Sett opp Discord-billetter", - "name": "innstillinger" - }, - "stats": { - "description": "Vis billett-statistikk", - "fields": { - "messages": "Meldinger", - "response_time": { - "minutes": "%s minutter", - "title": "Avg. response time" - }, - "tickets": "Billetter" - }, - "name": "statistikk", - "response": { - "global": { - "description": "Statistics about tickets across all guilds where this Discord Tickets instance is used.", - "title": "📊 Global stats" - }, - "guild": { - "description": "Statistics about tickets within this guild. This data is cached for an hour.", - "title": "📊 This server's stats" - } - } - }, - "survey": { - "description": "View survey responses", - "name": "survey", - "response": { - "list": { - "title": "📃 Surveys" - } - } - }, - "tag": { - "description": "Use a tag response", - "name": "tag", - "response": { - "error": "❌ Error", - "list": { - "title": "📃 Tag list" - }, - "missing": "This tag requires the following arguments:\n%s", - "not_a_ticket": { - "description": "This tag can only be used within a ticket channel as it uses ticket references.", - "title": "❌ Dette er ikke en billettkanal" - } - } - }, - "topic": { - "description": "Change the topic of the ticket", - "name": "emne", - "response": { - "changed": { - "description": "This ticket's topic has been changed.", - "title": "✅ Emne endret" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel you want to change the topic of.", - "title": "❌ Dette er ikke en billettkanal" - } - } - } - }, - "message_will_be_deleted_in": "Denne meldingen vil bli slettet om %d sekunder", - "missing_permissions": { - "description": "You do not have the permissions required to use this command:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s has claimed this ticket.", - "title": "✅ Ticket claimed" - }, - "closed": { - "description": "This ticket has been closed.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Billetten er stengt" - }, - "closed_by_member": { - "description": "This ticket has been closed by %s.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Billetten er stengt" - }, - "closed_by_member_with_reason": { - "description": "This ticket has been closed by %s: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Billetten er stengt" - }, - "closed_with_reason": { - "description": "This ticket has been closed: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Billetten er stengt" - }, - "member_added": { - "description": "%s has been added by %s", - "title": "Member added" - }, - "member_removed": { - "description": "%s has been removed by %s", - "title": "Member removed" - }, - "opening_message": { - "fields": { - "topic": "Emne" - } - }, - "questions": "Please answer the following questions:\n\n%s", - "released": { - "description": "%s has released this ticket.", - "title": "✅ Ticket released" - }, - "survey": { - "complete": { - "description": "Takk for din tilbakemelding.", - "title": "✅ Takk" - }, - "start": { - "description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey? React with ✅ to start, or ignore this message.", - "title": "❔ Tilbakemelding" - } - } - } -} diff --git a/src/locales/pl-PL.json b/src/locales/pl-PL.json deleted file mode 100644 index 952dbc5..0000000 --- a/src/locales/pl-PL.json +++ /dev/null @@ -1,348 +0,0 @@ -{ - "blacklisted": "❌Jesteś na czarnej liście", - "bot": { - "missing_permissions": { - "description": "DiscordTickets potrzebuje tych uprawnień:\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s by [eartharoid](%s)" - }, - "collector_expires_in": "Wygasa w ciągu %d sekund", - "command_execution_error": { - "description": "Wystąpił nieoczekiwany błąd podczas wykonywania polecenia.\nPoproś administratora o sprawdzenie konsoli / logów w celu uzyskania szczegółów.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Dodaj użytkownika do ticketu", - "name": "dodaj", - "response": { - "added": { - "description": "%s został dodany do %s.", - "title": "✅ Użytkownik dodany" - }, - "no_member": { - "description": "Wspomnij o osobie, którą chcesz dodać.", - "title": "❌ Nieznany użytkownik" - }, - "no_permission": { - "description": "Nie jesteś twórca tego ticketum, ani nie jesteś administratorem. Nie możesz dodać osób do tego ticketu.", - "title": "❌ Niewystarczające uprawnienia" - }, - "not_a_ticket": { - "description": "Użyj tej komendy na kanale ticketu lub wspomnij o kanale.", - "title": "❌ To nie jest kanał ticketu" - } - } - }, - "blacklist": { - "description": "Wyświetl lub zmodyfikuj czarną listę", - "name": "blacklist", - "options": { - "show": { - "description": "Pokaż użytkowników i role na czarnej liście", - "name": "pokaż" - } - }, - "response": { - "empty_list": { - "description": "Nie ma żadnych osób i roli, które są zblacklistowane. Wpisz `%sblacklist `, aby dodać osobę lub role do blacklisty.", - "title": "📃 osoby i role, które są zblacklistowane" - }, - "illegal_action": { - "description": "%s jest administratorem i nie może zostać zblacklistowany.", - "title": "❌ Nie możesz zblacklistować tej osoby" - }, - "invalid": { - "title": "❌ Błąd" - }, - "list": { - "title": "📃 osoby i role, które są zblacklistowane" - }, - "member_added": { - "description": "<@%s> została dodana do czarnej listy. Nie będą już mogli wchodzić w interakcje z botem.", - "title": "✅ Dodano osobę do czarnej listy" - }, - "member_removed": { - "description": "<@%s> został usunięty z czarnej listy. Teraz mogą ponownie użyć bota.", - "title": "✅ Osoba została usunięta z blacklisty" - }, - "role_added": { - "description": "<@&%s> została dodana do blacklisty. Osoby z tej roli nie będą już mogli wchodzić w interakcje z botem.", - "title": "✅ Dodano rolę do blacklisty" - }, - "role_removed": { - "description": "<@&%s> został usunięty z blacklisty. Osoby z tej roli mogą teraz użyć bota ponownie.", - "title": "✅ Usunięto rolę z blacklisty" - } - } - }, - "close": { - "description": "Close a ticket channel", - "name": "zamknij", - "options": { - "reason": { - "name": "powód" - } - }, - "response": { - "canceled": { - "title": "🚫 Anulowane" - }, - "closed": { - "description": "Ticket #%s has been closed.", - "title": "✅ Ticket closed" - }, - "closed_multiple": { - "description": [ - "%d ticket has been closed.", - "%d tickets have been closed." - ], - "title": [ - "✅ Ticket zamknięty", - "✅ Tickets closed" - ] - }, - "confirm": { - "buttons": { - "cancel": "Anuluj" - }, - "description": "Proszę potwierdź swoją decyzję.", - "description_with_archive": "You will be able to view an archived version of it after.\nReact with ✅ to close this ticket.", - "title": "❔ Jesteś pewien?" - }, - "confirm_multiple": { - "description": [ - "React with ✅ to close %d ticket.", - "React with ✅ to close %d tickets." - ], - "title": "❔ Jesteś pewien?" - }, - "confirmation_timeout": { - "description": "You took too long to confirm.", - "title": "❌ Reaction time expired" - }, - "invalid_time": { - "description": "The time period provided could not be parsed.", - "title": "❌ Invalid input" - }, - "no_permission": { - "title": "❌ Niewystarczające pozwolenie" - }, - "no_tickets": { - "description": "There are no tickets which have been inactive for this time period.", - "title": "❌ No tickets to close" - }, - "not_a_ticket": { - "description": "Please use this command in a ticket channel or use the ticket flag.\nType `%shelp close` for more information.", - "title": "❌ To nie jest kanał ticketu" - }, - "unresolvable": { - "description": "`%s` could not be resolved to a ticket. Please provide the ticket ID/mention or number.", - "title": "❌ Error" - } - } - }, - "help": { - "description": "List commands you have access to, or find out more about a command", - "name": "help", - "response": { - "list": { - "description": "The commands you have access to are listed below. For more information about a command, type `{prefix}help [command]`. To create a ticket, type `{prefix}new [topic]`.", - "fields": { - "commands": "Komendy" - }, - "title": "❔ Help" - } - } - }, - "new": { - "description": "Create a new ticket", - "name": "new", - "request_topic": { - "description": "Proszę krótko opisać ten ticket.", - "title": "Temat ticketu" - }, - "response": { - "created": { - "description": "Your ticket has been created: %s.", - "title": "✅ Ticket utworzony" - }, - "error": { - "title": "❌ Błąd" - }, - "has_a_ticket": { - "description": "Użyj istniejącego ticketu <#(%s>) lub zamknij go przed utworzeniem innego.", - "title": "❌ Masz już otwarty ticket" - }, - "max_tickets": { - "description": "Użyj `%sclose`, aby zamknąć niepotrzebne tickety.\n\n%s", - "title": "❌ Masz już %d otwartych ticketów" - }, - "no_categories": { - "description": "Administrator serwera musi utworzyć co najmniej jedną kategorię ticketu przed otwarciem nowego.", - "title": "❌ Nie można stworzyć ticketu" - }, - "select_category": { - "description": "Wybierz kategorię najbardziej odpowiednią dla tematu ticketu:\n\n%s", - "title": "🔤 Proszę wybrać kategorię ticketu" - }, - "select_category_timeout": { - "description": "Wybór kategorii ticketu trwał zbyt długo.", - "title": "❌ Reaction time expired" - } - } - }, - "panel": { - "description": "Tworzenie nowego panelu ticketów", - "name": "panel", - "response": { - "invalid_category": { - "description": "Co najmniej jeden z określonych ID kategorii jest nieprawidłowy.", - "title": "❌ Nieprawidłowa kategoria" - } - } - }, - "remove": { - "description": "Usuń osobę z ticketu", - "name": "usuń", - "response": { - "no_member": { - "description": "Proszę wspomnieć o osobie, którą chcesz usunąć.", - "title": "❌ Nieznany użytkownik" - }, - "no_permission": { - "description": "Nie jesteś twórcą tego biletu i nie jesteś administratorem; nie można usunąć osób z tego ticketu.", - "title": "❌ Niewystarczające uprawnienia" - }, - "not_a_ticket": { - "description": "Użyj tej komendy na kanale ticketu lub wspomnij o kanale.", - "title": "❌ To nie jest kanał ticketu" - }, - "removed": { - "description": "%s został usunięty z %s.", - "title": "✅ osoba usunięta" - } - } - }, - "settings": { - "description": "Configure Discord Tickets", - "name": "settings" - }, - "stats": { - "description": "Display ticket statistics", - "fields": { - "messages": "Messages", - "response_time": { - "minutes": "%s minutes", - "title": "Avg. response time" - }, - "tickets": "Tickets" - }, - "name": "stats", - "response": { - "global": { - "description": "Statistics about tickets across all guilds where this Discord Tickets instance is used.", - "title": "📊 Global stats" - }, - "guild": { - "description": "Statistics about tickets within this guild. This data is cached for an hour.", - "title": "📊 This server's stats" - } - } - }, - "survey": { - "description": "View survey responses", - "name": "survey", - "response": { - "list": { - "title": "📃 Surveys" - } - } - }, - "tag": { - "description": "Use a tag response", - "name": "tag", - "response": { - "error": "❌ Error", - "list": { - "title": "📃 Tag list" - }, - "missing": "This tag requires the following arguments:\n%s", - "not_a_ticket": { - "description": "This tag can only be used within a ticket channel as it uses ticket references.", - "title": "❌ To nie jest kanał ticketu" - } - } - }, - "topic": { - "description": "Change the topic of the ticket", - "name": "topic", - "response": { - "changed": { - "description": "This ticket's topic has been changed.", - "title": "✅ Topic changed" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel you want to change the topic of.", - "title": "❌ To nie jest kanał ticketu" - } - } - } - }, - "message_will_be_deleted_in": "This message will be deleted in %d seconds", - "missing_permissions": { - "description": "You do not have the permissions required to use this command:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s has claimed this ticket.", - "title": "✅ Ticket claimed" - }, - "closed": { - "description": "This ticket has been closed.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member": { - "description": "This ticket has been closed by %s.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member_with_reason": { - "description": "This ticket has been closed by %s: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_with_reason": { - "description": "This ticket has been closed: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "member_added": { - "description": "%s has been added by %s", - "title": "Member added" - }, - "member_removed": { - "description": "%s has been removed by %s", - "title": "Member removed" - }, - "opening_message": { - "fields": { - "topic": "Topic" - } - }, - "questions": "Please answer the following questions:\n\n%s", - "released": { - "description": "%s has released this ticket.", - "title": "✅ Ticket released" - }, - "survey": { - "complete": { - "description": "Thank you for your feedback.", - "title": "✅ Thank you" - }, - "start": { - "description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey? React with ✅ to start, or ignore this message.", - "title": "❔ Feedback" - } - } - } -} diff --git a/src/locales/pt-BR.json b/src/locales/pt-BR.json deleted file mode 100644 index 41074ae..0000000 --- a/src/locales/pt-BR.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "bot": { - "missing_permissions": { - "description": "O Bot necessita das seguintes permissões:\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s feito por [eartharoid](%s)" - }, - "collector_expires_in": "Expira em %d segundos", - "command_execution_error": { - "description": "Ocorreu um erro inesperado durante a execução do comando. \nPor favor, peça a um administrador para verificar os logs do console para obter detalhes.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Adicionar um usuário ao ticket", - "name": "adicionar", - "response": { - "added": { - "description": "%s foi adicionado ao ticket %s.", - "title": "✅ Membro adicionado" - }, - "no_member": { - "description": "Por favor, mencione o membro que quer adicionar.", - "title": "❌ Membro não encontrado" - }, - "no_permission": { - "description": "Você não é o criador deste ticket e não é um membro da staff; você não pode adicionar membros a este ticket.", - "title": "❌ Você não tem permissão" - }, - "not_a_ticket": { - "description": "Use este comando no canal do ticket ou mencione o canal.", - "title": "❌ Este não é um canal de ticket" - } - } - }, - "blacklist": { - "description": "Blacklist/unblacklist um membro de interagir com o bot", - "name": "blacklist", - "response": { - "empty_list": { - "description": "Não há membros ou cargos na blacklist. Digite '%sblacklist ' para adicionar um membro ou cargo à blacklist.", - "title": "📃 Utilizadores e cargos na blacklist" - }, - "illegal_action": { - "description": "%s é um membro da staff e não pode ser colocado na blacklist.", - "title": "❌ Você não pode colocar este membro na blacklist" - }, - "list": { - "title": "📃 Usuários da blacklist" - }, - "member_added": { - "description": "<@%s> foi adicionado à blacklist. Eles não serão mais capazes de interagir com o bot.", - "title": "✅ Membro adicionado à blacklist" - }, - "member_removed": { - "description": "<@%s> foi removido da blacklist. Agora eles podem usar o bot novamente.", - "title": "✅ Membro removido da lista negra" - }, - "role_added": { - "description": "<@&%s> foi adicionado à blacklist. Eles não serão mais capazes de interagir com o bot.", - "title": "✅ Cargo adicionado à blacklist" - }, - "role_removed": { - "description": "<@&%s> foi removido da blacklist. Membros com esta função agora podem usar o bot novamente.", - "title": "✅ Cargo removido da lista negra" - } - } - }, - "close": { - "description": "Feche um canal de ticket", - "name": "fechar", - "response": { - "closed": { - "description": "Ticket #%s foi fechado.", - "title": "✅ Ticket fechado" - }, - "closed_multiple": { - "description": [ - "%d ticket foi fechado", - "%d tickets foram fechados" - ], - "title": [ - "✅ Ticket fechado", - "✅ Tickets fechados" - ] - }, - "confirm": { - "description": "Reaja com ✅ para confirmar", - "description_with_archive": "Você poderá ver uma versão arquivada dele depois.\nReaja com ✅ para fechar o ticket", - "title": "❔ Deseja realmente fechar o ticket?" - }, - "confirm_multiple": { - "description": [ - "Reaja com ✅ para fechar %d ticket.", - "Reaja com ✅ para fechar %d tickets." - ], - "title": "❔ Deseja realmente fechar o ticket?" - }, - "confirmation_timeout": { - "description": "Você demorou muito para confirmar.", - "title": "❌ Tempo de reação expirado" - }, - "invalid_time": { - "description": "O período de tempo não pode ser análisado", - "title": "❌ Entrada inválida" - }, - "no_tickets": { - "description": "Não há tickets inativos neste período.", - "title": "❌ Nenhum ticket para fechar" - }, - "not_a_ticket": { - "description": "Use este comando em um canal de ticket ou use o sinalizador de ticket.\nUse `%shelp close` para mais informações", - "title": "❌ Este não é um canal de ticket" - }, - "unresolvable": { - "description": "`%s` não pôde ser resolvido para um ticket. Forneça a ID / menção ou número do ticket.", - "title": "❌ Erro" - } - } - }, - "help": { - "description": "Lista de comandos que tens acesso, ou encontra mais informações sobre um comando", - "name": "ajuda", - "response": { - "list": { - "description": "Os comandos que tens acesso estão os que estão abaixo. Para mais informações acerca de um comando, escreve `{prefix}ajuda [comando]`. Para criar um ticket, escreve `{prefix}novo [motivo]`.", - "fields": { - "commands": "Comandos" - }, - "title": "❔ Ajuda" - } - } - }, - "new": { - "description": "Criar um novo Ticket", - "name": "novo", - "request_topic": { - "description": "Por favor, descreva resumidamente do que se trata este ticket em poucas palavras.", - "title": "Assunto do Ticket" - }, - "response": { - "created": { - "description": "Seu ticket foi criado: %s.", - "title": "✅ Ticket criado" - }, - "error": { - "title": "❌ Erro" - }, - "has_a_ticket": { - "description": "Por favor, use o seu ticket existente (<#%s>) ou feche-o antes de criar outro.", - "title": "❌ Você já tem um ticket aberto" - }, - "max_tickets": { - "description": "Use `%sclose` para fechar todos os tickets desnecessários.\n\n%s", - "title": "❌ Você já tem %d tickets abertos" - }, - "no_categories": { - "description": "Um administrador do servidor deve criar pelo menos uma categoria de ticket antes que um novo ticket possa ser aberto.", - "title": "❌ Não é possível criar ticket" - }, - "select_category": { - "description": "Selecione a categoria mais relevante para o tópico do seu ticket:\n\n%s", - "title": "🔤 Selecione a categoria do ticket" - }, - "select_category_timeout": { - "description": "Você demorou muito para selecionar a categoria do ticket.", - "title": "❌ Tempo de reação expirado" - } - } - }, - "panel": { - "description": "Cria um novo painel de tickets", - "name": "painel", - "response": { - "invalid_category": { - "description": "Um ou mais IDs de categorias são inválidos.", - "title": "❌ Categoria Inválida" - } - } - }, - "remove": { - "description": "Remove um mebro de um ticket", - "name": "remover", - "response": { - "no_member": { - "description": "Por favor menciona o membro que queres remover.", - "title": "❌ Membro não encontrado" - }, - "no_permission": { - "description": "Não és o criador do ticket nem um mebro da staff; não consegues remover membros deste ticket.", - "title": "❌ Você não tem permissões" - }, - "not_a_ticket": { - "description": "Use este comando no canal do ticket ou mencione o canal.", - "title": "❌ Este não é um canal de tickets" - }, - "removed": { - "description": "%s foi removido de %s.", - "title": "✅ Membro removido" - } - } - }, - "settings": { - "description": "Configurar o BOT", - "name": "settings" - }, - "stats": { - "description": "Mostra as estatísticas dos tickets", - "fields": { - "messages": "Mensagens", - "response_time": { - "minutes": "%s minutos", - "title": "Tempo médio de resposta" - }, - "tickets": "Tickets" - }, - "name": "Estatísticas", - "response": { - "global": { - "description": "Estatísticas sobre tickets em todos os servidores onde esta instância do bot de tickets é usada.", - "title": "📊 Estatísticas globais" - }, - "guild": { - "description": "Estatísticas acerca dos tickets neste servidor. Esta informação é guardada na cache por 1 hora.", - "title": "📊 Estatísticas deste servidor" - } - } - }, - "survey": { - "description": "Vê respostas dos questionários", - "name": "questionario", - "response": { - "list": { - "title": "📃 Questionários" - } - } - }, - "tag": { - "description": "Usa uma resposta de tag", - "name": "tag", - "response": { - "error": "❌ Erro", - "list": { - "title": "📃 Lista de Tags" - }, - "missing": "Esta tag necessita dos seguintes argumentos:\n%s", - "not_a_ticket": { - "description": "Esta tag só pode ser usada num ticket pois usa referências do mesmo.", - "title": "❌ Este não é um canal de ticket" - } - } - }, - "topic": { - "description": "Altera o motivo do ticket", - "name": "motivo", - "response": { - "changed": { - "description": "O motivo do ticket foi alterado", - "title": "✅ Motivo alterado" - }, - "not_a_ticket": { - "description": "Por favor usa este comando no ticket ao qual queres mudar o motivo.", - "title": "❌ Este não é um canal de ticket" - } - } - } - }, - "message_will_be_deleted_in": "Essa mensagem será apagada em %d segundos", - "missing_permissions": { - "description": "Você não tem as permissões necessárias para usar este comando:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s has claimed this ticket.", - "title": "✅ Ticket reivindicado" - }, - "closed": { - "description": "Ticket fechado.\nO canal será apagado daqui a 5 segundos", - "title": "✅ Ticket fechado" - }, - "closed_by_member": { - "description": "Ticket fechado por %s.\nO ticket será apagado daqui a 5 segundos", - "title": "✅ Ticket fechado" - }, - "closed_by_member_with_reason": { - "description": "Ticket fechado por %s, motivo: `%s`\nO canal será apagado daqui a 5 segundos", - "title": "✅ Ticket fechado" - }, - "closed_with_reason": { - "description": "Ticket foi fechado, motivo: `%s`\nO canal será apagado daqui a 5 segundos", - "title": "✅ Ticket fechado" - }, - "member_added": { - "description": "%s foi adicionado ao ticket %s", - "title": "Utilizador adicionado" - }, - "member_removed": { - "description": "%s foi removido do ticket por %s", - "title": "Utilizador removido" - }, - "opening_message": { - "fields": { - "topic": "Motivo" - } - }, - "questions": "Por favor responda as seguintes questões:\n\n%s", - "released": { - "description": "%s libertou o ticket.", - "title": "✅ Ticket libertado" - }, - "survey": { - "complete": { - "description": "Obrigado pelo seu feedback.", - "title": "✅ Obrigado" - }, - "start": { - "description": "Olha, %s. Antes deste canal ser apagado, você se importaria de responder a um questionário rápido com %d perguntas? Reaja com ✅ para iniciar ou ignore esta mensagem.", - "title": "❔ Feedback" - } - } - } -} diff --git a/src/locales/pt.json b/src/locales/pt.json deleted file mode 100644 index 6c07619..0000000 --- a/src/locales/pt.json +++ /dev/null @@ -1,598 +0,0 @@ -{ - "blacklisted": "❌ Estás na blacklist", - "bot": { - "missing_permissions": { - "description": "Discord Ticket precisa das seguintes permissões:\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s por [eartharoid](%s)" - }, - "collector_expires_in": "Expira daqui a %d segundos", - "command_execution_error": { - "description": "Um erro desconhecido ocorreu durante a execução do comano.\nPor favor pergunta a um administrador para verificar os outputs/logs da consola para mais detalhes.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Adicionar um membro a um ticket", - "name": "adicionar", - "options": { - "member": { - "description": "O membro para adicionar ao ticket", - "name": "membro" - }, - "ticket": { - "description": "O ticket para adicionar o membro", - "name": "ticket" - } - }, - "response": { - "added": { - "description": "%s foi adicionado ao %s.", - "title": "✅ Membro adicionado" - }, - "no_member": { - "description": "Por favor menciona o membro que queres adicionar.", - "title": "❌ Membro desconhecido" - }, - "no_permission": { - "description": "Não és o criador deste ticket nem um membro da staff; não podes adicionar membros a este ticket.", - "title": "❌ Permissões insuficientes" - }, - "not_a_ticket": { - "description": "Por favor usa este comando num ticket, ou menciona o canal.", - "title": "❌ Este canal não é um ticket" - } - } - }, - "blacklist": { - "description": "Permite, ou não, um membro de interagir com o bot", - "name": "blacklist", - "options": { - "add": { - "description": "Adiciona um membro ou cargo á blacklist", - "name": "adicionar", - "options": { - "member_or_role": { - "description": "O membro ou cargo para adicionar á blacklist", - "name": "membro_ou_cargo" - } - } - }, - "remove": { - "description": "Remove um membro ou cargo da blacklist", - "name": "remover", - "options": { - "member_or_role": { - "description": "O membro ou cargo para remover da blacklist", - "name": "membro_ou_cargo" - } - } - }, - "show": { - "description": "Mostra os membros e cargos que estão na blacklist", - "name": "mostrar" - } - }, - "response": { - "empty_list": { - "description": "Não existem mebros ou cargos na blacklist. Escreve `%sblacklist ` para adicionao um membro ou cargo á blacklist.", - "title": "📃 Membros e cargos na blacklist" - }, - "illegal_action": { - "description": "%s é um membro da staff e não pode estar na blacklist.", - "title": "❌ Não podes adicionar este membro á blacklist" - }, - "invalid": { - "description": "Este membro ou cargo não pode ser removido da blacklist visto que não estão na mesma.", - "title": "❌ Erro" - }, - "list": { - "fields": { - "members": "Membros", - "roles": "Cargos" - }, - "title": "📃 Membros e cargos na blacklist" - }, - "member_added": { - "description": "<@%s> foi adicionado á blacklist. Ele não vai conseguir interagir com o bot.", - "title": "✅ Membro adicionado á blacklist" - }, - "member_removed": { - "description": "<@%s> foi removido da blacklist. Ele pode usar agora o bot.", - "title": "✅ Membro removido da blacklist" - }, - "role_added": { - "description": "<@&%s> foi adicionada á blacklist. Membros com este cargo já não podem interagir com o bot.", - "title": "✅ Cargo adicionado á blacklist" - }, - "role_removed": { - "description": "<@&%s> foi removida da blacklist. Membros com este cargo já podes interagir com o bot.", - "title": "✅ Cargo removido da blacklist" - } - } - }, - "close": { - "description": "Fecha um ticket", - "name": "fechar", - "options": { - "reason": { - "description": "O motivo para fechar o(s) ticket(s)", - "name": "motivo" - }, - "ticket": { - "description": "O ticket para fechar, ou o número ou o ID do canal", - "name": "ticket" - }, - "time": { - "description": "Fecha todos os tickets que estão inativos pelo tempo fornecido", - "name": "tempo" - } - }, - "response": { - "canceled": { - "description": "Cancelas-te a operação.", - "title": "🚫 Cancelado" - }, - "closed": { - "description": "O ticket #%s foi fechado.", - "title": "✅ Ticket fechado" - }, - "closed_multiple": { - "description": [ - "&d ticket foi fechado.", - "%d tickets foram fechados." - ], - "title": [ - "✅ Ticket fechado", - "✅ Tickets fechados" - ] - }, - "confirm": { - "buttons": { - "cancel": "Cencelar", - "confirm": "Fechar" - }, - "description": "Por favor confirma a tua decisão.", - "description_with_archive": "O ticket vai ser arquivado para referências futuras.", - "title": "❔ Tens a certeza?" - }, - "confirm_multiple": { - "buttons": { - "cancel": "Cancelar", - "confirm": [ - "Fechar %d ticket", - "Fechar %d tickets" - ] - }, - "description": [ - "Estás prestes a fechar %d ticket.", - "Estás prestes a fechar %d tickets." - ], - "title": "❔ Tens a certeza?" - }, - "confirmation_timeout": { - "description": "Demoras-te muito tempo para confirmar.", - "title": "❌ Tempo de interação expirado" - }, - "invalid_time": { - "description": "O período de tempo fornecido não pôde ser analisado.", - "title": "❌ Entrada inválida" - }, - "no_tickets": { - "description": "Não existem ticket que estiveram inativos por este período de tempo.", - "title": "❌ Sem tickets para fechar" - }, - "not_a_ticket": { - "description": "Usa este comando num ticket ou use um ticket flag.\nEscreve `/help close` para mais informações.", - "title": "❌ Este canal não é um ticket" - }, - "unresolvable": { - "description": "`%s` não pôde ser comparado com um ticket. Por favor fornece um ID/menção do ticket ou o número.", - "title": "❌ Erro" - } - } - }, - "help": { - "description": "Lista dos comandos que tens acesso", - "name": "ajuda", - "response": { - "list": { - "description": "Os comandos que tens acesso estão listados em baixo. Para criar um ticket, escreve **`/novo`**.", - "fields": { - "commands": "Comandos" - }, - "title": "❔ Ajuda" - } - } - }, - "new": { - "description": "Cria um novo ticket", - "name": "novo", - "options": { - "topic": { - "description": "O motivo do ticket", - "name": "motivo" - } - }, - "request_topic": { - "description": "Por favor escreve em poucas palavras o porquê da abertura deste ticket.", - "title": "⚠️ Motivo do Ticket" - }, - "response": { - "created": { - "description": "O teu ticket foi criado: %s.", - "title": "✅ Ticket criado" - }, - "error": { - "title": "❌ Erro" - }, - "has_a_ticket": { - "description": "Por favor usa o teu ticket já existente (<#%s>) ou fecha-o antes de criares outro.", - "title": "❌ Já tens um ticket aberto" - }, - "max_tickets": { - "description": "Por favor usa `/fechar` para fechares tickets desnecessários.\n\n%s", - "title": "❌ Já tens %d tickets abertos" - }, - "no_categories": { - "description": "Um administrador do servidor tem que criar pelo menos uma categoria de tickets antes que qualquer ticket possa ser aberto.", - "title": "❌ Não podes criar tickets" - }, - "select_category": { - "description": "Seleciona a categoria em que o teu motivo mais se enquadra.", - "title": "🔤 Por favor seleciona a categoria do ticket" - }, - "select_category_timeout": { - "description": "Demoras-te muito tempo a selecionar uma categoria.", - "title": "❌ Tempo de interação expirado" - } - } - }, - "panel": { - "description": "Cria um novo painel de ticket", - "name": "painel", - "options": { - "categories": { - "description": "A vírgula separa a lista dos IDs das categorias", - "name": "categorias" - }, - "description": { - "description": "A descrição para a mensagem do painel", - "name": "descrição" - }, - "image": { - "description": "Um link de imagem para a mensagem do painel", - "name": "imagem" - }, - "just_type": { - "description": "Criar um painel \"só escrever\"?", - "name": "so_escrever" - }, - "thumbnail": { - "description": "Um link para a thumbnail da mensagem do painel", - "name": "thumbnail" - }, - "title": { - "description": "O título da mensagem do painel", - "name": "título" - } - }, - "response": { - "invalid_category": { - "description": "Um ou mais IDs das categorias fornecidas são inválidas.", - "title": "❌ Categoria inválida" - }, - "too_many_categories": { - "description": "O painel \"só escrever\" só pode ser usado para uma categoria.", - "title": "❌ Demasiadas Categorias" - } - } - }, - "remove": { - "description": "Remove um membro de um ticket", - "name": "remover", - "options": { - "member": { - "description": "O membro para remover do ticket", - "name": "membro" - }, - "ticket": { - "description": "O ticket para remover o membro", - "name": "ticket" - } - }, - "response": { - "no_member": { - "description": "Por favor menciona o membro que queres remover.", - "title": "❌ Membro desconhecido" - }, - "no_permission": { - "description": "Não és o criador deste ticket nem um membro da staff; não podes remover membros deste ticket.", - "title": "❌ Permissões insuficientes" - }, - "not_a_ticket": { - "description": "Por favor usa este comando num ticket, ou menciona o canal.", - "title": "❌ Este canal não é um ticket" - }, - "removed": { - "description": "%s foi removido de %s.", - "title": "✅ Membro removido" - } - } - }, - "settings": { - "description": "Configura o Discord Tickets", - "name": "definicoes", - "options": { - "categories": { - "description": "Gere a categoria dos tickets", - "name": "categorias", - "options": { - "create": { - "description": "Cria uma nova categoria", - "name": "criar", - "options": { - "name": { - "description": "O nome da categoria", - "name": "nome" - }, - "roles": { - "description": "Uma vírgula separa a lista dos IDs dos cargos de staff para a categoria", - "name": "cargos" - } - } - }, - "delete": { - "description": "Apaga a categoria", - "name": "apagar", - "options": { - "id": { - "description": "O ID da categoria para apagar", - "name": "id" - } - } - }, - "edit": { - "description": "Faz alterações na configuração de uma categoria", - "name": "editar", - "options": { - "claiming": { - "description": "Ativar a reivindicação de tickets?", - "name": "reivindicação" - }, - "id": { - "description": "O ID da categoria para editar", - "name": "id" - }, - "image": { - "description": "Um link de imagem", - "name": "imagem" - }, - "max_per_member": { - "description": "O número máximo de tickets que um membro pode ter abertos nesta categoria", - "name": "max_por_membro" - }, - "name": { - "description": "O nome da categoria", - "name": "nome" - }, - "name_format": { - "description": "O formato do nome do ticket", - "name": "formato_do_nome" - }, - "opening_message": { - "description": "O texto para enviar quando um ticket é aberto", - "name": "mensagem_de_abertura" - }, - "ping": { - "description": "Uma vírgula separa a lista dos IDs dos cargos para mencionar", - "name": "mencionar" - }, - "require_topic": { - "description": "É necessário o membro dar um motivo do ticket?", - "name": "motivo_necessario" - }, - "roles": { - "description": "Uma vírgula separa a lista dos IDs do cargo de staff", - "name": "cargos" - }, - "survey": { - "description": "O questionário para usar", - "name": "questionário" - } - } - }, - "list": { - "description": "Lista de Categorias", - "name": "lista" - } - } - }, - "set": { - "description": "Definir Opções", - "name": "definir", - "options": { - "close_button": { - "description": "Ativar o botão de fechar o ticket?", - "name": "botao_fechar" - }, - "colour": { - "description": "A cor padrão", - "name": "cor" - }, - "error_colour": { - "description": "A cor de erro", - "name": "cor_erro" - }, - "footer": { - "description": "Texto para a legenda do embed", - "name": "legenda" - }, - "locale": { - "description": "O locale (idioma/linguagem)", - "name": "linguagem" - }, - "log_messages": { - "description": "Guardar mensagens dos tickets?", - "name": "guardar_msg" - }, - "success_colour": { - "description": "A cor de sucesso", - "name": "cor_sucesso" - } - } - } - }, - "response": { - "category_created": "✅ A categoria de ticket `%s` foi criada", - "category_deleted": "✅ A categoria de ticket `%s` foi apagada", - "category_does_not_exist": "❌ Nenhuma categoria existente tem este ID", - "category_list": "Categorias de Tickets", - "category_updated": "✅ A categoria do ticket `%s` foi atualizada", - "settings_updated": "✅ Definições foram atualizadas" - } - }, - "stats": { - "description": "Mostra as estatísticas dos tickets", - "fields": { - "messages": "Mensagens", - "response_time": { - "minutes": "%s minutos", - "title": "Tempo médio de resposta" - }, - "tickets": "Tickets" - }, - "name": "estatisticas", - "response": { - "global": { - "description": "Estatisticas dis tickets de todos os servidores onde esta versão do Discord Tickets é usada.", - "title": "📊 Estatisticas gerais" - }, - "guild": { - "description": "Estatisticas acerca dos tickets neste servidor. Esta informação é mantida na cache por 1 hora.", - "title": "📊 Estatisticas deste servidor" - } - } - }, - "survey": { - "description": "Vê as respostas do questionário", - "name": "questionario", - "options": { - "survey": { - "description": "O nome do questionário para ver as respostas", - "name": "questionário" - } - }, - "response": { - "list": { - "title": "📃 Questionários" - } - } - }, - "tag": { - "description": "Usa uma resposta em tag", - "name": "tag", - "options": { - "tag": { - "description": "O nome da tag para usar", - "name": "tag" - } - }, - "response": { - "error": "❌ Erro", - "list": { - "title": "📃 Lista de tags" - }, - "missing": "Esta tag necessita dos seguintes argumentos:\n%s", - "not_a_ticket": { - "description": "Esta tag só pode ser usada num ticket visto que usa referências do ticket.", - "title": "❌ Não é um ticket" - } - } - }, - "topic": { - "description": "Altera o motivo do ticket", - "name": "motivo", - "options": { - "new_topic": { - "description": "O novo motivo do ticket", - "name": "novo_motivo" - } - }, - "response": { - "changed": { - "description": "O motivo do ticket foi alterado.", - "title": "✅ Motivo alterado" - }, - "not_a_ticket": { - "description": "Por favor usa este comando no ticket que queres trocar o motivo.", - "title": "❌ Não é um ticket" - } - } - } - }, - "message_will_be_deleted_in": "Esta mensagem vai ser apagada daqui a %d segundos", - "missing_permissions": { - "description": "Não tens permissões suficientes para usar este comando.\n%s", - "title": "❌ Erro" - }, - "panel": { - "create_ticket": "Cria um ticket" - }, - "ticket": { - "claim": "Reivindicar", - "claimed": { - "description": "%s reivindicou o ticket.", - "title": "✅ Ticket reivindicado" - }, - "close": "Fechar", - "closed": { - "description": "Este ticket foi fechado.\nO canal vai ser apagado dentro de 5 segundos.", - "title": "✅ Ticket fechado" - }, - "closed_by_member": { - "description": "Este ticket foi fechado por %s.\nEste canal vai ser apagado dentro de 5 segundos.", - "title": "✅ Ticket fechado" - }, - "closed_by_member_with_reason": { - "description": "Este ticket foi fechado por %s: `%s`\nEste canal vai ser apagado dentro de 5 segundos.", - "title": "✅ Ticket fechado" - }, - "closed_with_reason": { - "description": "Este ticket foi fechado: `%s`\nEste canal vai ser apagado dentro de 5 segundos.", - "title": "✅ Ticket fechado" - }, - "member_added": { - "description": "%s foi adicionado por %s", - "title": "Membro adicionado" - }, - "member_removed": { - "description": "%s foi removido por %s", - "title": "Membro removido" - }, - "opening_message": { - "content": "%s\n%s criou um novo ticket", - "fields": { - "topic": "Motivo" - } - }, - "questions": "Por favor responde ás seguintes questões:\n\n%s", - "released": { - "description": "%s libertou este ticket.", - "title": "✅ Ticket libertado" - }, - "survey": { - "complete": { - "description": "Obrigado pelo teu feedback.", - "title": "✅ Obrigado" - }, - "start": { - "description": "Olha, %s. Antes deste canal ser apagado, importas-te de completar um questionário super rápido de %d-question questões? Reage com ✅ para começar, ou ignora esta mensagem.", - "title": "❔ Feedback" - } - }, - "unclaim": "Libertar" - }, - "updated_permissions": "✅ Atualizado permissões dos Slash commands" -} diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json deleted file mode 100644 index 6bb7a3f..0000000 --- a/src/locales/ru-RU.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "bot": { - "missing_permissions": { - "description": "Discord Tickets требует следующих разрешений:\n%s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s от [eartharoid](%s)" - }, - "collector_expires_in": "Истекает через %d секунд", - "command_execution_error": { - "description": "An unexpected error occurred during command execution.\nPlease ask an administrator to check the console output / logs for details.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Add a member to a ticket", - "name": "add", - "response": { - "added": { - "description": "%s has been added to %s.", - "title": "✅ Member added" - }, - "no_member": { - "description": "Please mention the member you want to add.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't add members to this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ Это не канал тикета" - } - } - }, - "blacklist": { - "description": "Запретить/разрешить участнику использовать бота", - "name": "blacklist", - "response": { - "empty_list": { - "description": "В черный список не внесли ни участников, ни ролей. Напишите `%sblacklist `, чтобы добавить пользователя или роль в черный список.", - "title": "📃 Черный список пользователей и ролей" - }, - "illegal_action": { - "description": "%s есть персоналом и не может быть внесен в черный список.", - "title": "❌ Вы не можете добавить в черный список этого участника" - }, - "list": { - "title": "📃 Черный список пользователей и ролей" - }, - "member_added": { - "description": "<@%s> добавлен в черный список. Они больше не смогут взаимодействовать с ботом.", - "title": "✅ Пользователь добавлен в черный список" - }, - "member_removed": { - "description": "<@%s> был убран из черного списка. Теперь он может использовать бота снова.", - "title": "✅ Пользователь убран из черного списка" - }, - "role_added": { - "description": "<@&%s> добавлена в черный список. Участники с этой ролью больше не смогут взаимодействовать с ботом.", - "title": "✅ Добавлена роль в черный список" - }, - "role_removed": { - "description": "<@&%s> была убран из черного списка. Теперь он может использовать бота снова.", - "title": "✅ Роль убрана из черного списка" - } - } - }, - "close": { - "description": "Закрыть тикет-канал", - "name": "закрыть", - "response": { - "closed": { - "description": "Тикет #%s был закрыт.", - "title": "✅ Тикет закрыт" - }, - "closed_multiple": { - "description": [ - "Тикет %d был закрыт.", - "%d тикетов было закрыто." - ], - "title": [ - "✅ Тикет закрыт", - "✅ Тикеты закрыты" - ] - }, - "confirm": { - "description": "Поставьте реакцию ✅, чтобы закрыть этот тикет.", - "description_with_archive": "Вы сможете просмотреть архивную версию этого тикета.\nПоставьте реакцию ✅, чтобы закрыть этот тикет.", - "title": "❔ Вы уверены?" - }, - "confirm_multiple": { - "description": [ - "Поставьте реакцию ✅, чтобы закрыть тикет %d.", - "Поставьте реакцию ✅, чтобы закрыть тикеты %d." - ], - "title": "❔ Вы уверены?" - }, - "confirmation_timeout": { - "description": "Подтверждение заняло слишком много времени.", - "title": "❌ Время реакции истекло" - }, - "invalid_time": { - "description": "Предоставленный период времени не может быть разобран.", - "title": "❌ Недействительный ввод" - }, - "no_tickets": { - "description": "Нет тикетов, которые были неактивны в течение этого периода времени.", - "title": "❌ Нет тикетов для закрытия" - }, - "not_a_ticket": { - "description": "Please use this command in a ticket channel or use the ticket flag.\nType `%shelp close` for more information.", - "title": "❌ Это не канал тикета" - }, - "unresolvable": { - "description": "`%s` could not be resolved to a ticket. Please provide the ticket ID/mention or number.", - "title": "❌ Ошибка" - } - } - }, - "help": { - "description": "List commands you have access to, or find out more about a command", - "name": "help", - "response": { - "list": { - "description": "The commands you have access to are listed below. For more information about a command, type `{prefix}help [command]`. To create a ticket, type `{prefix}new [topic]`.", - "fields": { - "commands": "Commands" - }, - "title": "❔ Help" - } - } - }, - "new": { - "description": "Создание нового тикета", - "name": "новый", - "request_topic": { - "description": "Please briefly state what this ticket is about in a few words.", - "title": "Тема тикета" - }, - "response": { - "created": { - "description": "Ваш тикет был создан: %s.", - "title": "✅ Тикет создан" - }, - "error": { - "title": "❌ Ошибка" - }, - "has_a_ticket": { - "description": "Пожалуйста, используйте существующий тикет (<#%s>) или закройте его перед созданием нового.", - "title": "❌ У вас уже есть открытый тикет" - }, - "max_tickets": { - "description": "Please use `%sclose` to close any unneeded tickets.\n\n%s", - "title": "❌ У вас уже есть %d открытых тикетов" - }, - "no_categories": { - "description": "A server administrator must create at least one ticket category before a new ticket can be opened.", - "title": "❌ Не могу создать тикет" - }, - "select_category": { - "description": "Select the category most relevant to your ticket's topic:\n\n%s", - "title": "🔤 Пожалуйста, выберите категорию тикетов" - }, - "select_category_timeout": { - "description": "Вы слишком долго выбирали категорию тикетов.", - "title": "❌ Время реакции истекло" - } - } - }, - "panel": { - "description": "Create a new ticket panel", - "name": "panel", - "response": { - "invalid_category": { - "description": "One or more of the specified category IDs is invalid.", - "title": "❌ Недействительная категория" - } - } - }, - "remove": { - "description": "Remove a member from a ticket", - "name": "remove", - "response": { - "no_member": { - "description": "Please mention the member you want to remove.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't remove members from this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ Это не канал тикета" - }, - "removed": { - "description": "%s has been removed from %s.", - "title": "✅ Member removed" - } - } - }, - "settings": { - "description": "Configure Discord Tickets", - "name": "settings" - }, - "stats": { - "description": "Display ticket statistics", - "fields": { - "messages": "Messages", - "response_time": { - "minutes": "%s minutes", - "title": "Avg. response time" - }, - "tickets": "Tickets" - }, - "name": "stats", - "response": { - "global": { - "description": "Statistics about tickets across all guilds where this Discord Tickets instance is used.", - "title": "📊 Global stats" - }, - "guild": { - "description": "Statistics about tickets within this guild. This data is cached for an hour.", - "title": "📊 This server's stats" - } - } - }, - "survey": { - "description": "View survey responses", - "name": "survey", - "response": { - "list": { - "title": "📃 Surveys" - } - } - }, - "tag": { - "description": "Use a tag response", - "name": "tag", - "response": { - "error": "❌ Ошибка", - "list": { - "title": "📃 Tag list" - }, - "missing": "This tag requires the following arguments:\n%s", - "not_a_ticket": { - "description": "This tag can only be used within a ticket channel as it uses ticket references.", - "title": "❌ Это не канал тикета" - } - } - }, - "topic": { - "description": "Change the topic of the ticket", - "name": "топик", - "response": { - "changed": { - "description": "This ticket's topic has been changed.", - "title": "✅ Topic changed" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel you want to change the topic of.", - "title": "❌ Это не канал тикета" - } - } - } - }, - "message_will_be_deleted_in": "Это сообщение будет удалено в течение %d секунд", - "missing_permissions": { - "description": "You do not have the permissions required to use this command:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s принял этот тикет.", - "title": "✅ Тикет принят" - }, - "closed": { - "description": "This ticket has been closed.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Тикет закрыт" - }, - "closed_by_member": { - "description": "This ticket has been closed by %s.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Тикет закрыт" - }, - "closed_by_member_with_reason": { - "description": "This ticket has been closed by %s: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Тикет закрыт" - }, - "closed_with_reason": { - "description": "This ticket has been closed: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Тикет закрыт" - }, - "member_added": { - "description": "%s has been added by %s", - "title": "Member added" - }, - "member_removed": { - "description": "%s has been removed by %s", - "title": "Member removed" - }, - "opening_message": { - "fields": { - "topic": "Topic" - } - }, - "questions": "Please answer the following questions:\n\n%s", - "released": { - "description": "%s выпустил этот тикет.", - "title": "✅ Тикет выпущен" - }, - "survey": { - "complete": { - "description": "Thank you for your feedback.", - "title": "✅ Thank you" - }, - "start": { - "description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey? React with ✅ to start, or ignore this message.", - "title": "❔ Feedback" - } - } - } -} diff --git a/src/locales/th-TH.json b/src/locales/th-TH.json deleted file mode 100644 index 04cedde..0000000 --- a/src/locales/th-TH.json +++ /dev/null @@ -1,373 +0,0 @@ -{ - "bot": { - "missing_permissions": { - "description": "Discord Tickets ต้องการการสิทธิ์อนุญาตดังต่อไปนี้: %s", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s สร้างโดย [eartharoid](%s)" - }, - "collector_expires_in": "หมดอายุในอีก %d วินาที", - "command_execution_error": { - "description": "An unexpected error occurred during command execution.\nPlease ask an administrator to check the console output / logs for details.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Add a member to a ticket", - "name": "add", - "options": { - "member": { - "name": "สมาชิก" - } - }, - "response": { - "added": { - "description": "%s has been added to %s.", - "title": "✅ Member added" - }, - "no_member": { - "description": "Please mention the member you want to add.", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "You are not the creator of this ticket and you are not a staff member; you can't add members to this ticket.", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "blacklist": { - "description": "Blacklist/unblacklist a member from interacting with the bot", - "name": "blacklist", - "options": { - "add": { - "name": "เพิ่ม", - "options": { - "member_or_role": { - "name": "สมาชิกหรือยศ" - } - } - }, - "remove": { - "name": "ลบ", - "options": { - "member_or_role": { - "name": "สมาชิกหรือยศ" - } - } - }, - "show": { - "name": "แสดง" - } - }, - "response": { - "empty_list": { - "description": "There are no members or roles blacklisted. Type `%sblacklist ` to add a member or role to the blacklist.", - "title": "📃 Blacklisted members and roles" - }, - "illegal_action": { - "description": "%s is a staff member and cannot be blacklisted.", - "title": "❌ You can't blacklist this member" - }, - "invalid": { - "title": "❌ ผิดพลาด" - }, - "list": { - "fields": { - "members": "สมาชิก", - "roles": "ยศ" - }, - "title": "📃 Blacklisted members and roles" - }, - "member_added": { - "description": "<@%s> has been added to the blacklist. They will no longer be able to interact with the bot.", - "title": "✅ Added member to blacklist" - }, - "member_removed": { - "description": "<@%s> has been removed from the blacklist. They can now use the bot again.", - "title": "✅ Removed member from blacklist" - }, - "role_added": { - "description": "<@&%s> has been added to the blacklist. Members with this role will no longer be able to interact with the bot.", - "title": "✅ Added role to blacklist" - }, - "role_removed": { - "description": "<@&%s> has been removed from the blacklist. Members with this role can now use the bot again.", - "title": "✅ Removed role from blacklist" - } - } - }, - "close": { - "description": "Close a ticket channel", - "name": "close", - "options": { - "reason": { - "name": "สาเหตุ" - }, - "time": { - "name": "เวลา" - } - }, - "response": { - "canceled": { - "description": "คุณยกเลิกการดำเนินการ", - "title": "🚫 ยกเลิก" - }, - "closed": { - "description": "Ticket #%s has been closed.", - "title": "✅ Ticket closed" - }, - "closed_multiple": { - "description": [ - "%d ticket has been closed.", - "%d tickets have been closed." - ], - "title": [ - "✅ Ticket closed", - "✅ Tickets closed" - ] - }, - "confirm": { - "buttons": { - "cancel": "ยกเลิก", - "confirm": "ปิด" - }, - "description": "React with ✅ to close this ticket.", - "description_with_archive": "You will be able to view an archived version of it after.\nReact with ✅ to close this ticket.", - "title": "❔ Are you sure?" - }, - "confirm_multiple": { - "description": [ - "React with ✅ to close %d ticket.", - "React with ✅ to close %d tickets." - ], - "title": "❔ Are you sure?" - }, - "confirmation_timeout": { - "description": "You took too long to confirm.", - "title": "❌ Reaction time expired" - }, - "invalid_time": { - "description": "The time period provided could not be parsed.", - "title": "❌ Invalid input" - }, - "no_tickets": { - "description": "There are no tickets which have been inactive for this time period.", - "title": "❌ No tickets to close" - }, - "not_a_ticket": { - "description": "Please use this command in a ticket channel or use the ticket flag.\nType `%shelp close` for more information.", - "title": "❌ This isn't a ticket channel" - }, - "unresolvable": { - "description": "`%s` could not be resolved to a ticket. Please provide the ticket ID/mention or number.", - "title": "❌ Error" - } - } - }, - "help": { - "description": "List commands you have access to, or find out more about a command", - "name": "help", - "response": { - "list": { - "description": "The commands you have access to are listed below. For more information about a command, type `{prefix}help [command]`. To create a ticket, type `{prefix}new [topic]`.", - "fields": { - "commands": "Commands" - }, - "title": "❔ Help" - } - } - }, - "new": { - "description": "Create a new ticket", - "name": "new", - "request_topic": { - "description": "Please briefly state what this ticket is about in a few words.", - "title": "Ticket topic" - }, - "response": { - "created": { - "description": "Your ticket has been created: %s.", - "title": "✅ Ticket created" - }, - "error": { - "title": "❌ Error" - }, - "has_a_ticket": { - "description": "Please use your existing ticket (<#%s>) or close it before creating another.", - "title": "❌ You already have an open ticket" - }, - "max_tickets": { - "description": "Please use `%sclose` to close any unneeded tickets.\n\n%s", - "title": "❌ You already have %d open tickets" - }, - "no_categories": { - "description": "A server administrator must create at least one ticket category before a new ticket can be opened.", - "title": "❌ Can't create ticket" - }, - "select_category": { - "description": "Select the category most relevant to your ticket's topic:\n\n%s", - "title": "🔤 Please select the ticket category" - }, - "select_category_timeout": { - "description": "You took too long to select the ticket category.", - "title": "❌ Reaction time expired" - } - } - }, - "panel": { - "description": "สร้างแผงตั๋วสนับสนุนใหม่", - "name": "แผง", - "response": { - "invalid_category": { - "description": "รหัสหมวดหมู่ที่ระบุอย่างน้อยหนึ่งรายการไม่ถูกต้อง", - "title": "❌หมวดหมู่ไม่ถูกต้อง" - } - } - }, - "remove": { - "description": "ลบสมาชิกออกจากตั๋วสนับสนุน", - "name": "ลบ", - "response": { - "no_member": { - "description": "โปรดแท็กสมาชิกที่คุณต้องการลบ", - "title": "❌ Unknown member" - }, - "no_permission": { - "description": "คุณไม่ใช่ผู้สร้างตั๋วสนับสนุนใบนี้และคุณก็ไม่ได้เป็นหนุ่งในทีมงานของเซิร์ฟเวอร์ คุณจึงไม่สามารถลบสมาชิกออกจากตั๋วนี้ได้", - "title": "❌ Insufficient permission" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel, or mention the channel.", - "title": "❌ This isn't a ticket channel" - }, - "removed": { - "description": "%s ถูกลบออกจาก %s แล้ว", - "title": "✅สมาชิกถูกลบออกแล้ว" - } - } - }, - "settings": { - "description": "ตั้งค่าบอท Discord Tickets", - "name": "การตั้งค่า" - }, - "stats": { - "description": "Display ticket statistics", - "fields": { - "messages": "Messages", - "response_time": { - "minutes": "%s minutes", - "title": "Avg. response time" - }, - "tickets": "Tickets" - }, - "name": "stats", - "response": { - "global": { - "description": "Statistics about tickets across all guilds where this Discord Tickets instance is used.", - "title": "📊 Global stats" - }, - "guild": { - "description": "Statistics about tickets within this guild. This data is cached for an hour.", - "title": "📊 This server's stats" - } - } - }, - "survey": { - "description": "View survey responses", - "name": "survey", - "response": { - "list": { - "title": "📃 Surveys" - } - } - }, - "tag": { - "description": "Use a tag response", - "name": "tag", - "response": { - "error": "❌ Error", - "list": { - "title": "📃 Tag list" - }, - "missing": "This tag requires the following arguments:\n%s", - "not_a_ticket": { - "description": "This tag can only be used within a ticket channel as it uses ticket references.", - "title": "❌ This isn't a ticket channel" - } - } - }, - "topic": { - "description": "Change the topic of the ticket", - "name": "topic", - "response": { - "changed": { - "description": "This ticket's topic has been changed.", - "title": "✅ Topic changed" - }, - "not_a_ticket": { - "description": "Please use this command in the ticket channel you want to change the topic of.", - "title": "❌ This isn't a ticket channel" - } - } - } - }, - "message_will_be_deleted_in": "This message will be deleted in %d seconds", - "missing_permissions": { - "description": "You do not have the permissions required to use this command:\n%s", - "title": "❌" - }, - "ticket": { - "claimed": { - "description": "%s has claimed this ticket.", - "title": "✅ Ticket claimed" - }, - "closed": { - "description": "This ticket has been closed.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member": { - "description": "This ticket has been closed by %s.\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_by_member_with_reason": { - "description": "This ticket has been closed by %s: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "closed_with_reason": { - "description": "This ticket has been closed: `%s`\nThe channel will be deleted in 5 seconds.", - "title": "✅ Ticket closed" - }, - "member_added": { - "description": "%s has been added by %s", - "title": "Member added" - }, - "member_removed": { - "description": "%s has been removed by %s", - "title": "Member removed" - }, - "opening_message": { - "fields": { - "topic": "Topic" - } - }, - "questions": "Please answer the following questions:\n\n%s", - "released": { - "description": "%s has released this ticket.", - "title": "✅ Ticket released" - }, - "survey": { - "complete": { - "description": "Thank you for your feedback.", - "title": "✅ Thank you" - }, - "start": { - "description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey? React with ✅ to start, or ignore this message.", - "title": "❔ Feedback" - } - } - } -} diff --git a/src/locales/vi.json b/src/locales/vi.json deleted file mode 100644 index 0b0e32a..0000000 --- a/src/locales/vi.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "blacklisted": "❌ Bạn đã bị cấm", - "bot": { - "missing_permissions": { - "description": "Discord Tickets yêu cầu các quyền sau:\n%s ", - "title": "⚠️" - }, - "version": "[Discord Tickets](%s) v%s bởi [eartharoid](%s)" - }, - "collector_expires_in": "Hết hạn trong %d giây", - "command_execution_error": { - "description": "Một lỗi không mong muốn đã xảy ra trong quá trình thực hiện lệnh.\nVui lòng hỏi một quản trị viên để kiểm tra output từ console / logs để biết thêm chi tiết.", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "Thêm một thành viên vào ticket", - "name": "add", - "options": { - "member": { - "description": "Thành viên để thêm vào ticket", - "name": "thành viên" - }, - "ticket": { - "description": "Ticket để thêm thành viên vào", - "name": "ticket" - } - }, - "response": { - "added": { - "description": "%s đã được thêm vào %s.", - "title": "✅ Đã thêm thành viên" - }, - "no_member": { - "description": "Vui lòng mention thành viên bạn muốn thêm.", - "title": "❌ Thành viên không xác định" - }, - "no_permission": { - "description": "Bạn không phải người tạo ra ticket và bạn cũng không phải là staff; bạn không thể thêm thành viên vào ticket này.", - "title": "❌ Không đủ quyền" - }, - "not_a_ticket": { - "description": "Vui lòng sử dụng lệnh này trong một kênh ticket, hoặc mention tên kênh.", - "title": "❌ Đây không phải là một kênh ticket" - } - } - }, - "blacklist": { - "description": "Xem hoặc sửa đổi danh sách cấm", - "name": "danh sách cấm", - "options": { - "add": { - "description": "Thêm một thành viên hoặc role vào danh sách cấm", - "name": "thêm", - "options": { - "member_or_role": { - "description": "Thành viên hoặc role để thêm vào danh sách cấm", - "name": "member_or_role" - } - } - }, - "remove": { - "description": "Xóa thành viên hoặc role khỏi danh sách cấm", - "name": "xóa", - "options": { - "member_or_role": { - "description": "Thành viên hoặc role để xóa khỏi danh sách cấm", - "name": "member_or_role" - } - } - }, - "show": { - "description": "Hiển thị các thành viên và role trong danh sách cấm", - "name": "hiển thị" - } - }, - "response": { - "empty_list": { - "description": "Không tìm thấy thành viên hoặc role bị cấm. Sử dụng `/blacklist add` để thêm một thành viên hoặc role vào danh sách cấm.", - "title": "📃 Các thành viên và roles trong danh sách cấm" - }, - "illegal_action": { - "description": "%s là một staff và không thể bị cấm.", - "title": "❌ Bạn không thể cấm thành viên này" - }, - "invalid": { - "description": "Thành viên hoặc role này không thể bị xóa khỏi danh sách cấm bởi họ chưa bị cấm.", - "title": "❌ Lỗi" - }, - "list": { - "fields": { - "members": "Các thành viên", - "roles": "Roles" - }, - "title": "📃 Các thành viên và roles trong danh sách cấm" - }, - "member_added": { - "description": "<@%s> đã được thêm vào danh sách cấm. Họ sẽ không thể tương tác với bot nữa.", - "title": "✅ Đã thêm thành viên vào danh sách cấm" - }, - "member_removed": { - "description": "<@%s> đã được xóa khỏi danh sách cấm. Họ có thể sử lại bot.", - "title": "✅ Đã xóa thành viên khỏi danh sách cấm" - }, - "role_added": { - "description": "<@&%s> đã được thêm vào danh sách cấm. Các thành viên với role này sẽ không thể tương tác với bot.", - "title": "✅ Đã thêm role vào danh sách cấm" - }, - "role_removed": { - "description": "<@&%s> đã được xóa khỏi danh sách cấm. Các thành viên với role này có thể sử dụng lại bot.", - "title": "✅ Đã xóa role khỏi danh sách cấm" - } - } - }, - "close": { - "description": "Đóng một ticket", - "name": "đóng", - "options": { - "reason": { - "description": "Lý do để đóng ticket(s)", - "name": "lý do" - }, - "ticket": { - "description": "Ticket để đóng, có thể là số hoặc ID của kênh", - "name": "ticket" - }, - "time": { - "description": "Đóng tất cả tickets không hoạt động trong thời gian nhất định", - "name": "thời gian" - } - }, - "response": { - "canceled": { - "description": "Bạn đã hủy hành động.", - "title": "🚫 Đã hủy" - }, - "closed": { - "description": "Ticket #%s đã được đóng.", - "title": "✅ Ticket đã được đóng" - }, - "closed_multiple": { - "description": [ - "%d ticket đã được đóng", - "%d tickets đã được đóng." - ], - "title": [ - "✅ Ticket đã được đóng", - "✅ Tickets đã được đóng" - ] - }, - "confirm": { - "buttons": { - "cancel": "Hủy", - "confirm": "Đóng" - }, - "description": "Vui lòng xác nhận quyết định của bạn.", - "description_with_archive": "Ticket sẽ được lưu trữ cho tương lai.", - "title": "❔ Bạn có chắc chứ?" - }, - "confirm_multiple": { - "buttons": { - "cancel": "Hủy", - "confirm": [ - "Đóng %d ticket", - "Đóng %d tickets" - ] - }, - "description": [ - "Bạn sắp đóng %d ticket.", - "Bạn sắp đóng %d tickets." - ], - "title": "❔ Bạn có chắc chứ?" - }, - "confirmation_timeout": { - "description": "Bạn đã mất quá nhiều thời gian để xác nhận.", - "title": "❌ Thời gian tương tác đã kết thúc" - }, - "invalid_time": { - "description": "Không thể phân tích khoảng thời gian đã cung cấp.", - "title": "❌ Input không hợp lệ" - }, - "no_permission": { - "description": "Bạn không phải là staff hoặc người tạo ticket.", - "title": "❌ Không đủ quyền" - }, - "no_tickets": { - "description": "Không có tickets nào không hoạt động trong khoảng thời gian này.", - "title": "❌ Không có tickets nào để đóng" - }, - "not_a_ticket": { - "description": "Vui lòng sử dụng lệnh này trong một kênh ticket hoặc sử dụng flag ticket.\nNhập `/help close` để biết thêm thông tin.", - "title": "❌ Đây không phải là một kênh ticket" - }, - "unresolvable": { - "description": "`%s` không thể giải quyết tới ticket. Vui lòng cung cấp ticket ID/mention hoặc số.", - "title": "❌ Lỗi" - } - } - }, - "help": { - "description": "Danh sách các lệnh mà bạn có thể truy cập", - "name": "help", - "response": { - "list": { - "description": "Các lệnh mà bạn có thể truy cập được liệt kê ở dưới. Để tạo một ticket, nhập **`/new`**.", - "fields": { - "commands": "Các lệnh" - }, - "title": "❔ Help" - } - } - }, - "new": { - "description": "Tạo một ticket mới", - "name": "mới", - "options": { - "topic": { - "description": "Chủ đề của ticket", - "name": "chủ đề" - } - }, - "request_topic": { - "description": "Vui lòng nói ngắn gọn ticket này có nội dung gì trong một vài từ.", - "title": "⚠️ Chủ đề ticket" - }, - "response": { - "created": { - "description": "Ticket của bạn đã được tạo: %s.", - "title": "✅ Ticket đã được tạo" - }, - "error": { - "title": "❌ Lỗi" - }, - "has_a_ticket": { - "description": "Vui lòng sử dụng ticket hiện tại của bạn (<#%s>) hoặc đóng nó trước khi tạo một cái khác.", - "title": "❌ Bạn đang có một ticket khác" - }, - "max_tickets": { - "description": "Vui lòng sử dụng `/close` để đóng những tickets không cần thiết.\n\n%s", - "title": "❌ Bạn đã có %d tickets đang mở" - }, - "no_categories": { - "description": "Quản trị viên của server phải tạo ít nhất một ticket category trước khi một ticket có thể được mở.", - "title": "❌ Không thể tạo ticket" - }, - "select_category": { - "description": "Hãy chọn category phù hợp nhất cho chủ đề ticket của bạn.", - "title": "🔤 Vui lòng chọn ticket category" - }, - "select_category_timeout": { - "description": "Bạn đã mất quá nhiều thời gian để ticket category.", - "title": "❌ Thời gian tương tác đã kết thúc" - } - } - }, - "panel": { - "description": "Tạo một panel ticket mới", - "name": "panel", - "options": { - "categories": { - "description": "Danh sách IDs của category cần sử dụng dấu phẩy để ngăn cách", - "name": "categories" - }, - "description": { - "description": "Điền mô tả cho tin nhắn của panel", - "name": "mô tả" - }, - "image": { - "description": "URL của một hình ảnh cho tin nhắn của panel", - "name": "hình ảnh" - }, - "just_type": { - "description": "Tạo một panel \"chỉ để viết\"?", - "name": "chỉ_để_viết" - }, - "thumbnail": { - "description": "URL hình thumbnail nhỏ cho tin nhắn của panel", - "name": "hình thumbnail" - }, - "title": { - "description": "Tiêu đề cho tin nhắn của panel", - "name": "tiêu đề" - } - } - } - } -} diff --git a/src/locales/zh-Hans.json b/src/locales/zh-Hans.json deleted file mode 100644 index 837086e..0000000 --- a/src/locales/zh-Hans.json +++ /dev/null @@ -1,610 +0,0 @@ -{ - "blacklisted": "❌ 你被列入黑名单,无法开启客务单", - "bot": { - "missing_permissions": { - "description": "客服单需要以下权限:\n%s", - "title": "⚠️" - }, - "version": "[客服单](%s) v%s 由 [无风团队工作室](%s)" - }, - "collector_expires_in": "%d 秒后过期", - "command_execution_error": { - "description": "执行指令时发生错误,\n请让管理员查看控制台日志以获取详细信息", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "将用户加入到客服单", - "name": "add", - "options": { - "member": { - "description": "要加入到客服单的用户", - "name": "member" - }, - "ticket": { - "description": "将用户加入到的客服单", - "name": "ticket" - } - }, - "response": { - "added": { - "description": "%s 已加入到 %s", - "title": "✅ 已加入用户" - }, - "no_member": { - "description": "请标注您要加入的用户", - "title": "❌ 未知用户" - }, - "no_permission": { - "description": "您不是管理人员,无法向此客务单加入用户", - "title": "❌ 权限不足" - }, - "not_a_ticket": { - "description": "请在客服单频道中使用此指令,或标注该频道", - "title": "❌ 这不是客服单频道" - } - } - }, - "blacklist": { - "description": "查看或修改黑名单", - "name": "blacklist", - "options": { - "add": { - "description": "将用户或身分组列入到黑名单", - "name": "add", - "options": { - "member_or_role": { - "description": "要列入到黑名单的用户或身分组", - "name": "member_or_role" - } - } - }, - "remove": { - "description": "从黑名单中移除用户或身分组", - "name": "remove", - "options": { - "member_or_role": { - "description": "要从黑名单中移除的用户或身分组", - "name": "member_or_role" - } - } - }, - "show": { - "description": "显示黑名单中的用户和身分组", - "name": "show" - } - }, - "response": { - "empty_list": { - "description": "没有用户或身分组被列入黑名单,请输入 `/blacklist add` 将用户或身分组加入到黑名单", - "title": "📃 黑名单用户和身分组" - }, - "illegal_action": { - "description": "%s 是一位管理人员,不能被列入黑名单", - "title": "❌ 您不能将此用户列入黑名单" - }, - "invalid": { - "description": "此用户或身分组未列入黑名单,因此无法从黑名单中移除", - "title": "❌ 错误" - }, - "list": { - "fields": { - "members": "用户", - "roles": "身分组" - }, - "title": "📃 黑名单用户和身分组" - }, - "member_added": { - "description": "<@%s> 已加入到黑名单,他将不能再开启客服单", - "title": "✅ 已将用户列入到黑名单" - }, - "member_removed": { - "description": "<@%s> 已从黑名单中移除,他可以开启客服单了", - "title": "✅ 已将用户从黑名单中移除" - }, - "role_added": { - "description": "<@&%s> 已列入黑名单,有此身分组的用户将无法再开启客服单", - "title": "✅ 已将身分组列入到黑名单" - }, - "role_removed": { - "description": "<@&%s> 已从黑名单中移除。 有此身分组的用户现在可以开启客服单了", - "title": "✅ 已将身分组从黑名单中移除" - } - } - }, - "close": { - "description": "关闭客服单频道", - "name": "close", - "options": { - "reason": { - "description": "关闭客服单的原因", - "name": "reason" - }, - "ticket": { - "description": "要关闭客服单的频道 ID", - "name": "ticket" - }, - "time": { - "description": "关闭所有在指定时间内处于非活动状态的客服单", - "name": "time" - } - }, - "response": { - "canceled": { - "description": "您取消了这个操作", - "title": "🚫 取消" - }, - "closed": { - "description": "客服单 #%s 已关闭", - "title": "✅ 关闭客服单" - }, - "closed_multiple": { - "description": [ - "%d 个客服单已关闭", - "%d 个客服单已关闭" - ], - "title": [ - "✅ 关闭客服单", - "✅ 关闭客服单" - ] - }, - "confirm": { - "buttons": { - "cancel": "取消", - "confirm": "关闭" - }, - "description": "请确认您的操作!", - "description_with_archive": "此客服单将被保存备份", - "title": "❔ 你确定?" - }, - "confirm_multiple": { - "buttons": { - "cancel": "取消", - "confirm": [ - "关闭 %d 客服单", - "关闭 %d 客服单" - ] - }, - "description": [ - "您即将关闭 %d 个客服单", - "您即将关闭 %d 个客服单" - ], - "title": "❔ 你确定?" - }, - "confirmation_timeout": { - "description": "你确认的时间太长了", - "title": "❌ 确认时间已过" - }, - "invalid_time": { - "description": "无法解析提供的时间段", - "title": "❌ 输入无效" - }, - "no_permission": { - "description": "您不是管理人员", - "title": "❌ 权限不足" - }, - "no_tickets": { - "description": "没有在此时间段内处于非活动状态的客服单", - "title": "❌ 没有客服单可以关闭" - }, - "not_a_ticket": { - "description": "请在客服频道中使用此指令,\n输入`/help close`来获取更多资讯", - "title": "❌ 这不是客服单频道" - }, - "unresolvable": { - "description": "`%s` 无法解析为客服单,请提供客服单 ID或标注", - "title": "❌ 错误" - } - } - }, - "help": { - "description": "列出您有权使用的指令", - "name": "help", - "response": { - "list": { - "description": "下面列出了您可以使用的指令,要创建客服单,请键入 **`/new`**", - "fields": { - "commands": "指令" - }, - "title": "❔ 帮助" - } - } - }, - "new": { - "description": "创建客服单", - "name": "new", - "options": { - "topic": { - "description": "想要询问的问题或需要的帮助", - "name": "topic" - } - }, - "request_topic": { - "description": "有什么问题需要我们的帮助呢还是想询问服务呢", - "title": "⚠️ 客服询问" - }, - "response": { - "created": { - "description": "您的客服单已创建,它在这里 ➤ %s", - "title": "✅ 客服单已创建!" - }, - "error": { - "title": "❌ 错误" - }, - "has_a_ticket": { - "description": "请使用您现有的客服单 (<#%s>) 或在创建另一个客服单之前将其关闭", - "title": "❌ 您已经有一张客服单" - }, - "max_tickets": { - "description": "请通知管理人员来关闭您的客服单", - "title": "❌ 您已经有 %d 张尚未结束的客服单" - }, - "no_categories": { - "description": "管理人员必须先创建一个客服单分类才能打开新的客服单", - "title": "❌ 无法创建客服单" - }, - "select_category": { - "description": "选择与您的客服单问题最相关的分类", - "title": "🔤 请选择客服单分类" - }, - "select_category_timeout": { - "description": "您选择客服单分类的时间过长", - "title": "❌ 选择时间已过" - } - } - }, - "panel": { - "description": "创建新的客服单开启介面", - "name": "panel", - "options": { - "categories": { - "description": "使用逗号来增加分类 ID", - "name": "categories" - }, - "description": { - "description": "开启介面资讯", - "name": "description" - }, - "image": { - "description": "开启介面的图片网址", - "name": "image" - }, - "just_type": { - "description": "创建一个\"只需输入\"面板?", - "name": "just_type" - }, - "thumbnail": { - "description": "开启介面资讯的缩图网址", - "name": "thumbnail" - }, - "title": { - "description": "开启介面的标题", - "name": "title" - } - }, - "response": { - "invalid_category": { - "description": "一个或多个指定的分类 ID 无效", - "title": "❌ 分类无效" - }, - "too_many_categories": { - "description": "\"只需键入\"介面只能用于单个分类", - "title": "❌ 分类太多" - } - } - }, - "remove": { - "description": "从客服单中移除用户", - "name": "remove", - "options": { - "member": { - "description": "要从客服单中移除的用户", - "name": "member" - }, - "ticket": { - "description": "删除用户的客服单", - "name": "ticket" - } - }, - "response": { - "no_member": { - "description": "请标注您要移除的用户", - "title": "❌ 未知用户" - }, - "no_permission": { - "description": "您不是管理人员,您不能从此客服单中移除用户", - "title": "❌ 权限不足" - }, - "not_a_ticket": { - "description": "请在客服单频道中使用此指令,或标注该频道", - "title": "❌ 这不是客服单频道" - }, - "removed": { - "description": "%s 已从 %s 中移除", - "title": "✅ 已移除用户" - } - } - }, - "settings": { - "description": "设定客服单系统", - "name": "settings", - "options": { - "categories": { - "description": "管理您的客服单分类", - "name": "categories", - "options": { - "create": { - "description": "创建一个新分类", - "name": "create", - "options": { - "name": { - "description": "分类名称", - "name": "name" - }, - "roles": { - "description": "此分类的管理人员身分组 ID ,使用逗号做分隔", - "name": "roles" - } - } - }, - "delete": { - "description": "删除分类", - "name": "delete", - "options": { - "id": { - "description": "要删除分类的 ID", - "name": "id" - } - } - }, - "edit": { - "description": "更改分类的设定", - "name": "edit", - "options": { - "claiming": { - "description": "开启客服单?", - "name": "claiming" - }, - "id": { - "description": "要编辑的分类的 ID", - "name": "id" - }, - "image": { - "description": "图片网址", - "name": "image" - }, - "max_per_member": { - "description": "用户在此分类中可以开启的最大客服单数量", - "name": "max_per_member" - }, - "name": { - "description": "分类名称", - "name": "name" - }, - "name_format": { - "description": "客服单格式", - "name": "name_format" - }, - "opening_message": { - "description": "打开客服单时要发送的讯息", - "name": "opening_message" - }, - "opening_questions": { - "description": "开启客服单时要问的问题", - "name": "opening_questions" - }, - "ping": { - "description": "要标注的身分组 ID ,使用逗号做分隔", - "name": "ping" - }, - "require_topic": { - "description": "要求用户在开启客服单时询问问题?", - "name": "require_topic" - }, - "roles": { - "description": "以逗号做分隔,管理人员身分组 ID 列表", - "name": "roles" - }, - "survey": { - "description": "开启意见调查", - "name": "survey" - } - } - }, - "list": { - "description": "列出分类", - "name": "list" - } - } - }, - "set": { - "description": "设定选项", - "name": "set", - "options": { - "close_button": { - "description": "使用按钮开启和关闭?", - "name": "close_button" - }, - "colour": { - "description": "标准颜色", - "name": "colour" - }, - "error_colour": { - "description": "错误颜色", - "name": "error_colour" - }, - "footer": { - "description": "嵌入页脚文字", - "name": "footer" - }, - "locale": { - "description": "系统语言", - "name": "locale" - }, - "log_messages": { - "description": "储存客服单的讯息?", - "name": "log_messages" - }, - "success_colour": { - "description": "成功的颜色", - "name": "success_colour" - } - } - } - }, - "response": { - "category_created": "✅ `%s` 客服单分类已创建", - "category_deleted": "✅ `%s` 客服单分类已被删除", - "category_does_not_exist": "❌ 提供的 ID 不存在分类", - "category_list": "客服单分类", - "category_updated": "✅ `%s` 客服单分类已更新", - "settings_updated": "✅ 设定已更新" - } - }, - "stats": { - "description": "显示客服单统计资讯", - "fields": { - "messages": "讯息", - "response_time": { - "minutes": "%s 分钟", - "title": "平均回应时间" - }, - "tickets": "客服单" - }, - "name": "stats", - "response": { - "global": { - "description": "使用此客服单系统的所有群组的客服单统计资讯", - "title": "📊 所有群组客服单统计资讯" - }, - "guild": { - "description": "该群组内的客服单统计,此资料将储存1小时", - "title": "📊 此群组的统计资讯" - } - } - }, - "survey": { - "description": "查看意见调查回复", - "name": "survey", - "options": { - "survey": { - "description": "用于查看回复的意见调查的名称", - "name": "survey" - } - }, - "response": { - "list": { - "title": "📃 意见调查" - } - } - }, - "tag": { - "description": "使用标注来回应", - "name": "tag", - "options": { - "tag": { - "description": "要使用的标注名称", - "name": "tag" - } - }, - "response": { - "error": "❌ 错误", - "list": { - "title": "📃 标注列表" - }, - "missing": "此标注需要以下参数:\n%s", - "not_a_ticket": { - "description": "此标注只能在客服单频道中使用,因为它使用客服单参考", - "title": "❌ 这不是客服单频道" - } - } - }, - "topic": { - "description": "更改客服单问题", - "name": "topic", - "options": { - "new_topic": { - "description": "客服单新问题", - "name": "new_topic" - } - }, - "response": { - "changed": { - "description": "此客服单的问题已更改", - "title": "✅ 问题已更改" - }, - "not_a_ticket": { - "description": "请在您要更改问题的客服单频道中使用此指令", - "title": "❌ 这不是客服单频道" - } - } - } - }, - "message_will_be_deleted_in": "此讯息将在 %d 秒后删除", - "missing_permissions": { - "description": "您没有使用此指令所需的权限:\n%s", - "title": "❌ 错误" - }, - "panel": { - "create_ticket": "创建客服单" - }, - "ticket": { - "claim": "要求", - "claimed": { - "description": "%s 已领取这张客服单", - "title": "✅ 已领取客服单" - }, - "close": "关闭", - "closed": { - "description": "此客服单已关闭,\n此频道将在 5 秒后删除", - "title": "✅ 客服单已关闭" - }, - "closed_by_member": { - "description": "此客服单已被 %s 关闭,\n此频道将在 5 秒后删除", - "title": "✅ 客服单已关闭" - }, - "closed_by_member_with_reason": { - "description": "此客服单已被 %s 关闭:`%s`\n此频道将在 5 秒后被删除", - "title": "✅ 客服单已关闭" - }, - "closed_with_reason": { - "description": "此客服单已关闭,`%s`\n此频道将在 5 秒后删除", - "title": "✅ 客服单已关闭" - }, - "member_added": { - "description": "%s 已被 %s 加入", - "title": "已加入用户" - }, - "member_removed": { - "description": "%s 已被 %s 移除", - "title": "已移除用户" - }, - "opening_message": { - "content": "%s\n%s 已创建新客服单", - "fields": { - "topic": "问题" - } - }, - "questions": "请回答以下问题:\n\n%s", - "released": { - "description": "%s 已完成此客服单", - "title": "✅ 客服单完成" - }, - "survey": { - "complete": { - "description": "感谢您的意见调查", - "title": "✅ 感谢您" - }, - "start": { - "buttons": { - "ignore": "不要", - "start": "开始意见调查" - }, - "description": "您好,%s\n在删除此客服单之前,您介意完成一个快速的 %d 意见调查吗?", - "title": "❔ 意见调查" - } - }, - "unclaim": "传送" - }, - "updated_permissions": "✅ /指令权限已更新" -} diff --git a/src/locales/zh-Hant.json b/src/locales/zh-Hant.json deleted file mode 100644 index 37e5c6d..0000000 --- a/src/locales/zh-Hant.json +++ /dev/null @@ -1,610 +0,0 @@ -{ - "blacklisted": "❌ 你被列入黑名單,無法開啟客務單", - "bot": { - "missing_permissions": { - "description": "Discord 票證需要以下權限:\n%s ", - "title": "⚠️" - }, - "version": "[客服單](%s) v%s 由 [無風團隊工作室](%s)" - }, - "collector_expires_in": "%d 秒後過期", - "command_execution_error": { - "description": "執行指令時發生錯誤,\n請讓管理員查看控制台日誌以獲取詳細信息", - "title": "⚠️" - }, - "commands": { - "add": { - "description": "將用戶加入到客服單", - "name": "add", - "options": { - "member": { - "description": "要加入到客服單的用戶", - "name": "member" - }, - "ticket": { - "description": "將用戶加入到的客服單", - "name": "ticket" - } - }, - "response": { - "added": { - "description": "%s 已加入到 %s", - "title": "✅ 已加入用戶" - }, - "no_member": { - "description": "請標註您要加入的用戶", - "title": "❌ 未知用戶" - }, - "no_permission": { - "description": "您不是管理人員,無法向此客務單加入用戶", - "title": "❌ 權限不足" - }, - "not_a_ticket": { - "description": "請在客服單頻道中使用此指令,或標註該頻道", - "title": "❌ 這不是客服單頻道" - } - } - }, - "blacklist": { - "description": "查看或修改黑名單", - "name": "blacklist", - "options": { - "add": { - "description": "將用戶或身分組列入到黑名單", - "name": "add", - "options": { - "member_or_role": { - "description": "要列入到黑名單的用戶或身分組", - "name": "member_or_role" - } - } - }, - "remove": { - "description": "從黑名單中移除用戶或身分組", - "name": "remove", - "options": { - "member_or_role": { - "description": "要從黑名單中移除的用戶或身分組", - "name": "member_or_role" - } - } - }, - "show": { - "description": "顯示黑名單中的用戶和身分組", - "name": "show" - } - }, - "response": { - "empty_list": { - "description": "沒有用戶或身分組被列入黑名單,請輸入 `/blacklist add` 將用戶或身分組加入到黑名單", - "title": "📃 黑名單用戶和身分組" - }, - "illegal_action": { - "description": "%s 是一位管理人員,不能被列入黑名單", - "title": "❌ 您不能將此用戶列入黑名單" - }, - "invalid": { - "description": "此用戶或身分組未列入黑名單,因此無法從黑名單中移除", - "title": "❌ 錯誤" - }, - "list": { - "fields": { - "members": "用戶", - "roles": "身分組" - }, - "title": "📃 黑名單用戶和身分組" - }, - "member_added": { - "description": "<@%s> 已加入到黑名單,他將不能再開啟客服單", - "title": "✅ 已將用戶列入到黑名單" - }, - "member_removed": { - "description": "<@%s> 已從黑名單中移除,他可以開啟客服單了", - "title": "✅ 已將用戶從黑名單中移除" - }, - "role_added": { - "description": "<@&%s> 已列入黑名單,有此身分組的用戶將無法再開啟客服單", - "title": "✅ 已將身分組列入到黑名單" - }, - "role_removed": { - "description": "<@&%s> 已從黑名單中移除。 有此身分組的用戶現在可以開啟客服單了", - "title": "✅ 已將身分組從黑名單中移除" - } - } - }, - "close": { - "description": "關閉客服單頻道", - "name": "close", - "options": { - "reason": { - "description": "關閉客服單的原因", - "name": "reason" - }, - "ticket": { - "description": "要關閉客服單的頻道 ID", - "name": "ticket" - }, - "time": { - "description": "關閉所有在指定時間內處於非活動狀態的客服單", - "name": "time" - } - }, - "response": { - "canceled": { - "description": "您取消了這個操作", - "title": "🚫 取消" - }, - "closed": { - "description": "客服單 #%s 已關閉", - "title": "✅ 關閉客服單" - }, - "closed_multiple": { - "description": [ - "%d 個客服單已關閉", - "%d 個客服單已關閉" - ], - "title": [ - "✅ 關閉客服單", - "✅ 關閉客服單" - ] - }, - "confirm": { - "buttons": { - "cancel": "取消", - "confirm": "關閉" - }, - "description": "請確認您的操作!", - "description_with_archive": "此客服單將被保存備份", - "title": "❔ 你確定?" - }, - "confirm_multiple": { - "buttons": { - "cancel": "取消", - "confirm": [ - "關閉 %d 客服單", - "關閉 %d 客服單" - ] - }, - "description": [ - "您即將關閉 %d 個客服單", - "您即將關閉 %d 個客服單" - ], - "title": "❔ 你確定?" - }, - "confirmation_timeout": { - "description": "你確認的時間太長了", - "title": "❌ 確認時間已過" - }, - "invalid_time": { - "description": "無法解析提供的時間段", - "title": "❌ 輸入無效" - }, - "no_permission": { - "description": "您不是管理人員", - "title": "❌ 權限不足" - }, - "no_tickets": { - "description": "沒有在此時間段內處於非活動狀態的客服單", - "title": "❌ 沒有客服單可以關閉" - }, - "not_a_ticket": { - "description": "請在客服頻道中使用此指令,\n輸入`/help close`來獲取更多資訊", - "title": "❌ 這不是客服單頻道" - }, - "unresolvable": { - "description": "`%s` 無法解析為客服單,請提供客服單 ID或標註", - "title": "❌ 錯誤" - } - } - }, - "help": { - "description": "列出您有權使用的指令", - "name": "help", - "response": { - "list": { - "description": "下面列出了您可以使用的指令,要創建客服單,請鍵入 **`/new`**", - "fields": { - "commands": "指令" - }, - "title": "❔ 幫助" - } - } - }, - "new": { - "description": "創建客服單", - "name": "new", - "options": { - "topic": { - "description": "想要詢問的問題或需要的幫助", - "name": "topic" - } - }, - "request_topic": { - "description": "有甚麼問題需要我們的幫助呢還是想詢問服務呢", - "title": "⚠️ 客服詢問" - }, - "response": { - "created": { - "description": "您的客服單已創建,它在這裡 ➤ %s", - "title": "✅ 客服單已創建!" - }, - "error": { - "title": "❌ 錯誤" - }, - "has_a_ticket": { - "description": "請使用您現有的客服單 (<#%s>) 或在創建另一個客服單之前將其關閉", - "title": "❌ 您已經有一張客服單" - }, - "max_tickets": { - "description": "請通知管理人員來關閉您的客服單", - "title": "❌ 您已經有 %d 張尚未結束的客服單" - }, - "no_categories": { - "description": "管理人員必須先創建一個客服單分類才能打開新的客服單", - "title": "❌ 無法創建客服單" - }, - "select_category": { - "description": "選擇與您的客服單問題最相關的分類", - "title": "🔤 請選擇客服單分類" - }, - "select_category_timeout": { - "description": "您選擇客服單分類的時間過長", - "title": "❌ 選擇時間已過" - } - } - }, - "panel": { - "description": "創建新的客服單開啟介面", - "name": "panel", - "options": { - "categories": { - "description": "使用逗號來增加分類 ID", - "name": "categories" - }, - "description": { - "description": "開啟介面資訊", - "name": "description" - }, - "image": { - "description": "開啟介面的圖片網址", - "name": "image" - }, - "just_type": { - "description": "創建一個\"只需輸入\"面板?", - "name": "just_type" - }, - "thumbnail": { - "description": "開啟介面資訊的縮圖網址", - "name": "thumbnail" - }, - "title": { - "description": "開啟介面的標題", - "name": "title" - } - }, - "response": { - "invalid_category": { - "description": "一個或多個指定的分類 ID 無效", - "title": "❌ 分類無效" - }, - "too_many_categories": { - "description": "\"只需鍵入\"介面只能用於單個分類", - "title": "❌ 分類太多" - } - } - }, - "remove": { - "description": "從客服單中移除用戶", - "name": "remove", - "options": { - "member": { - "description": "要從客服單中移除的用戶", - "name": "member" - }, - "ticket": { - "description": "刪除用戶的客服單", - "name": "ticket" - } - }, - "response": { - "no_member": { - "description": "請標註您要移除的用戶", - "title": "❌ 未知用戶" - }, - "no_permission": { - "description": "您不是管理人員,您不能從此客服單中移除用戶", - "title": "❌ 權限不足" - }, - "not_a_ticket": { - "description": "請在客服單頻道中使用此指令,或標註該頻道", - "title": "❌ 這不是客服單頻道" - }, - "removed": { - "description": "%s 已從 %s 中移除", - "title": "✅ 已移除用戶" - } - } - }, - "settings": { - "description": "設定客服單系統", - "name": "settings", - "options": { - "categories": { - "description": "管理您的客服單分類", - "name": "categories", - "options": { - "create": { - "description": "創建一個新分類", - "name": "create", - "options": { - "name": { - "description": "分類名稱", - "name": "name" - }, - "roles": { - "description": "此分類的管理人員身分組 ID ,使用逗號做分隔", - "name": "roles" - } - } - }, - "delete": { - "description": "刪除分類", - "name": "delete", - "options": { - "id": { - "description": "要刪除分類的 ID", - "name": "id" - } - } - }, - "edit": { - "description": "更改分類的設定", - "name": "edit", - "options": { - "claiming": { - "description": "開啟客服單?", - "name": "claiming" - }, - "id": { - "description": "要編輯的分類的 ID", - "name": "id" - }, - "image": { - "description": "圖片網址", - "name": "image" - }, - "max_per_member": { - "description": "用戶在此分類中可以開啟的最大客服單數量", - "name": "max_per_member" - }, - "name": { - "description": "分類名稱", - "name": "name" - }, - "name_format": { - "description": "客服單格式", - "name": "name_format" - }, - "opening_message": { - "description": "打開客服單時要發送的訊息", - "name": "opening_message" - }, - "opening_questions": { - "description": "開啟客服單時要問的問題", - "name": "opening_questions" - }, - "ping": { - "description": "要標註的身分組 ID ,使用逗號做分隔", - "name": "ping" - }, - "require_topic": { - "description": "要求用戶在開啟客服單時詢問問題?", - "name": "require_topic" - }, - "roles": { - "description": "以逗號做分隔,管理人員身分組 ID 列表", - "name": "roles" - }, - "survey": { - "description": "開啟意見調查", - "name": "survey" - } - } - }, - "list": { - "description": "列出分類", - "name": "list" - } - } - }, - "set": { - "description": "設定選項", - "name": "set", - "options": { - "close_button": { - "description": "使用按鈕開啟和關閉?", - "name": "close_button" - }, - "colour": { - "description": "標準顏色", - "name": "colour" - }, - "error_colour": { - "description": "錯誤顏色", - "name": "error_colour" - }, - "footer": { - "description": "嵌入頁腳文字", - "name": "footer" - }, - "locale": { - "description": "系統語言", - "name": "locale" - }, - "log_messages": { - "description": "儲存客服單的訊息?", - "name": "log_messages" - }, - "success_colour": { - "description": "成功的顏色", - "name": "success_colour" - } - } - } - }, - "response": { - "category_created": "✅ `%s` 客服單分類已創建", - "category_deleted": "✅ `%s` 客服單分類已被刪除", - "category_does_not_exist": "❌ 提供的 ID 不存在分類", - "category_list": "客服單分類", - "category_updated": "✅ `%s` 客服單分類已更新", - "settings_updated": "✅ 設定已更新" - } - }, - "stats": { - "description": "顯示客服單統計資訊", - "fields": { - "messages": "訊息", - "response_time": { - "minutes": "%s 分鐘", - "title": "平均回應時間" - }, - "tickets": "客服單" - }, - "name": "stats", - "response": { - "global": { - "description": "使用此客服單系統的所有群組的客服單統計資訊", - "title": "📊 所有群組客服單統計資訊" - }, - "guild": { - "description": "該群組內的客服單統計,此資料將儲存1小時", - "title": "📊 此群組的統計資訊" - } - } - }, - "survey": { - "description": "查看意見調查回復", - "name": "survey", - "options": { - "survey": { - "description": "用於查看回復的意見調查的名稱", - "name": "survey" - } - }, - "response": { - "list": { - "title": "📃 意見調查" - } - } - }, - "tag": { - "description": "使用標註來回應", - "name": "tag", - "options": { - "tag": { - "description": "要使用的標註名稱", - "name": "tag" - } - }, - "response": { - "error": "❌ 錯誤", - "list": { - "title": "📃 標註列表" - }, - "missing": "此標註需要以下參數:\n%s", - "not_a_ticket": { - "description": "此標註只能在客服單頻道中使用,因為它使用客服單參考", - "title": "❌ 這不是客服單頻道" - } - } - }, - "topic": { - "description": "更改客服單問題", - "name": "topic", - "options": { - "new_topic": { - "description": "客服單新問題", - "name": "new_topic" - } - }, - "response": { - "changed": { - "description": "此客服單的問題已更改", - "title": "✅ 問題已更改" - }, - "not_a_ticket": { - "description": "請在您要更改問題的客服單頻道中使用此指令", - "title": "❌ 這不是客服單頻道" - } - } - } - }, - "message_will_be_deleted_in": "此訊息將在 %d 秒後刪除", - "missing_permissions": { - "description": "您沒有使用此指令所需的權限:\n%s", - "title": "❌ 錯誤" - }, - "panel": { - "create_ticket": "創建客服單" - }, - "ticket": { - "claim": "要求", - "claimed": { - "description": "%s 已領取這張客服單", - "title": "✅ 已領取客服單" - }, - "close": "關閉", - "closed": { - "description": "此客服單已關閉,\n此頻道將在 5 秒後刪除", - "title": "✅ 客服單已關閉" - }, - "closed_by_member": { - "description": "此客服單已被 %s 關閉,\n此頻道將在 5 秒後刪除", - "title": "✅ 客服單已關閉" - }, - "closed_by_member_with_reason": { - "description": "此客服單已被 %s 關閉:`%s`\n此頻道將在 5 秒後被刪除", - "title": "✅ 客服單已關閉" - }, - "closed_with_reason": { - "description": "此客服單已關閉,`%s`\n此頻道將在 5 秒後刪除", - "title": "✅ 客服單已關閉" - }, - "member_added": { - "description": "%s 已被 %s 加入", - "title": "已加入用戶" - }, - "member_removed": { - "description": "%s 已被 %s 移除", - "title": "已移除用戶" - }, - "opening_message": { - "content": "%s\n%s 已創建新客服單", - "fields": { - "topic": "問題" - } - }, - "questions": "請回答以下問題:\n\n%s", - "released": { - "description": "%s 已完成此客服單", - "title": "✅ 客服單完成" - }, - "survey": { - "complete": { - "description": "感謝您的意見調查", - "title": "✅ 感謝您" - }, - "start": { - "buttons": { - "ignore": "不要", - "start": "開始意見調查" - }, - "description": "您好,%s\n在刪除此客服單之前,您介意完成一個快速的 %d 意見調查嗎?", - "title": "❔ 意見調查" - } - }, - "unclaim": "傳送" - }, - "updated_permissions": "✅ /指令權限已更新" -} diff --git a/src/logger.js b/src/logger.js deleted file mode 100644 index f6e8dfc..0000000 --- a/src/logger.js +++ /dev/null @@ -1,39 +0,0 @@ -const { path } = require('./utils/fs'); -const config = require('../user/config'); -const Logger = require('leekslazylogger'); -module.exports = new Logger({ - debug: config.developer.debug, - directory: path('./logs/'), - keepFor: config.logs.keep_for, - levels: { - _logger: { format: '&f&!7{timestamp}&r [LOGGER] {text}' }, - basic: { format: '&f&!7{timestamp} {text}' }, - commands: { - format: '&f&!7{timestamp}&r &3[INFO] &d(COMMANDS)&r {text}', - type: 'info' - }, - console: { format: '&f&!7{timestamp} [INFO] {text}' }, - debug: { format: '&f&!7{timestamp}&r &1[DEBUG] &9{text}' }, - error: { format: '&f&!7{timestamp}&r &4[ERROR] &c{text}' }, - http: { - format: '&f&!7{timestamp}&r &3[INFO] &d(HTTP)&r {text}', - type: 'info' - }, - info: { format: '&f&!7{timestamp}&r &3[INFO] &b{text}' }, - notice: { format: '&f&!7{timestamp}&r &0&!6[NOTICE] {text}' }, - plugins: { - format: '&f&!7{timestamp}&r &3[INFO] &d(PLUGINS)&r {text}', - type: 'info' - }, - success: { format: '&f&!7{timestamp}&r &2[SUCCESS] &a{text}' }, - warn: { format: '&f&!7{timestamp}&r &6[WARN] &e{text}' }, - ws: { - format: '&f&!7{timestamp}&r &3[INFO] &d(WS)&r {text}', - type: 'info' - } - }, - logToFile: config.logs.enabled, - name: 'Discord Tickets by eartharoid', - splitFile: config.logs.split, - timestamp: 'YYYY-MM-DD HH:mm:ss' -}); \ No newline at end of file diff --git a/src/modules/commands/command.js b/src/modules/commands/command.js deleted file mode 100644 index 8a193a9..0000000 --- a/src/modules/commands/command.js +++ /dev/null @@ -1,125 +0,0 @@ -const { - Message, // eslint-disable-line no-unused-vars - Interaction // eslint-disable-line no-unused-vars -} = require('discord.js'); - -/** - * A command - */ -module.exports = class Command { - /** - * - * @typedef CommandOption - * @property {string} name - The option's name - * @property {number} type - The option's type (use `Command.option_types`) - * @property {string} description - The option's description - * @property {CommandOption[]} [options] - The option's options - * @property {(string|number)[]} [choices] - The option's choices - * @property {boolean} [required] - Is this arg required? Defaults to `false` - */ - /** - * Create a new Command - * @param {import('../../').Bot} client - The Discord Client - * @param {Object} data - Command data - * @param {string} data.name - The name of the command (3-32) - * @param {string} data.description - The description of the command (1-100) - * @param {boolean} [data.staff_only] - Only allow staff to use this command? - * @param {string[]} [data.permissions] - Array of permissions needed for a user to use this command - * @param {CommandOption[]} [data.options] - The command's options - */ - constructor(client, data) { - - /** The Discord Client */ - this.client = client; - - /** The CommandManager */ - this.manager = this.client.commands; - - if (typeof data !== 'object') { - throw new TypeError(`Expected type of command "data" to be an object, got "${typeof data}"`); - } - - /** - * The name of the command - * @type {string} - */ - this.name = data.name; - - /** - * The command description - * @type {string} - */ - this.description = data.description; - - /** - * Only allow staff to use this command? - * @type {boolean} - * @default false - */ - this.staff_only = data.staff_only === true; - - /** - * Array of permissions needed for a user to use this command - * @type {string[]} - */ - this.permissions = data.permissions ?? []; - - /** - * The command options - * @type {CommandOption[]} - */ - this.options = data.options ?? []; - - /** - * True if command is internal, false if it is from a plugin - * @type {boolean} - */ - this.internal = data.internal === true; - - if (!this.internal) { - /** - * The plugin this command belongs to, if any - * @type {(undefined|Plugin)} - */ - this.plugin = this.client.plugins.plugins.find(p => p.commands?.includes(this.name)); - } - - try { - this.manager.register(this); // register the command - } catch (error) { - return this.client.log.error(error); - } - } - - /** - * The code to be executed when a command is invoked - * @abstract - * @param {Interaction} interaction - The message that invoked this command - */ - async execute(interaction) { } // eslint-disable-line no-unused-vars - - async build(guild) { - return { - defaultPermission: !this.staff_only, - description: this.description, - name: this.name, - options: typeof this.options === 'function' ? await this.options(guild) : this.options - }; - } - - static get option_types() { - return { - SUB_COMMAND: 1, - SUB_COMMAND_GROUP: 2, - STRING: 3, // eslint-disable-line sort-keys - INTEGER: 4, // eslint-disable-line sort-keys - BOOLEAN: 5, // eslint-disable-line sort-keys - USER: 6, - CHANNEL: 7, // eslint-disable-line sort-keys - ROLE: 8, - MENTIONABLE: 9, // eslint-disable-line sort-keys - NUMBER: 10 - }; - } - -}; \ No newline at end of file diff --git a/src/modules/commands/manager.js b/src/modules/commands/manager.js deleted file mode 100644 index 9bb637f..0000000 --- a/src/modules/commands/manager.js +++ /dev/null @@ -1,213 +0,0 @@ - -const { - Client, // eslint-disable-line no-unused-vars - Collection, - Interaction, // eslint-disable-line no-unused-vars - MessageEmbed -} = require('discord.js'); - -const fs = require('fs'); -const { path } = require('../../utils/fs'); - -/** - * Manages the loading and execution of commands - */ -module.exports = class CommandManager { - /** - * Create a CommandManager instance - * @param {import('../..').Bot} client - */ - constructor(client) { - /** The Discord Client */ - this.client = client; - - /** - * A discord.js Collection (Map) of loaded commands - * @type {Collection} - */ - this.commands = new Collection(); - } - - /** Automatically load all internal commands */ - load() { - const files = fs.readdirSync(path('./src/commands')) - .filter(file => file.endsWith('.js')); - - for (let file of files) { - try { - file = require(`../../commands/${file}`); - new file(this.client); - } catch (e) { - this.client.log.warn('An error occurred whilst loading an internal command'); - this.client.log.error(e); - } - } - } - - /** Register a command */ - register(command) { - const exists = this.commands.has(command.name); - const is_internal = (exists && command.internal) || (exists && this.commands.get(command.name).internal); - - if (is_internal) { - const plugin = this.client.plugins.plugins.find(p => p.commands.includes(command.name)); - if (plugin) this.client.log.commands(`The "${plugin.name}" plugin has overridden the internal "${command.name}" command`); - else this.client.log.commands(`An unknown plugin has overridden the internal "${command.name}" command`); - if(command.internal) return; - } else if (exists) { - throw new Error(`A non-internal command with the name "${command.name}" already exists`); - } - - this.commands.set(command.name, command); - this.client.log.commands(`Loaded "${command.name}" command`); - } - - async publish(guild) { - if (!guild) { - return this.client.guilds.cache.forEach(guild => { - this.publish(guild); - }); - } - - try { - const commands = await Promise.all(this.client.commands.commands.map(async command => await command.build(guild))); - await this.client.application.commands.set(commands, guild.id); - await this.updatePermissions(guild); - this.client.log.success(`Published ${this.client.commands.commands.size} commands to "${guild.name}"`); - } catch (error) { - this.client.log.warn('An error occurred whilst publishing the commands'); - this.client.log.error(error); - } - } - - async updatePermissions(guild) { - guild.commands.fetch().then(async commands => { - const permissions = []; - const settings = await this.client.utils.getSettings(guild.id); - const blacklist = []; - settings.blacklist.users?.forEach(userId => { - blacklist.push({ - id: userId, - permission: false, - type: 'USER' - }); - }); - settings.blacklist.roles?.forEach(roleId => { - blacklist.push({ - id: roleId, - permission: false, - type: 'ROLE' - }); - }); - - const categories = await this.client.db.models.Category.findAll({ where: { guild: guild.id } }); - const staff_roles = new Set(categories.map(category => category.roles).flat()); - - commands.forEach(async g_cmd => { - const cmd_permissions = [...blacklist]; - const command = this.client.commands.commands.get(g_cmd.name); - - if (command.staff_only) { - cmd_permissions.push({ - id: guild.roles.everyone.id, - permission: false, - type: 'ROLE' - }); - staff_roles.forEach(roleId => { - cmd_permissions.push({ - id: roleId, - permission: true, - type: 'ROLE' - }); - }); - } - - permissions.push({ - id: g_cmd.id, - permissions: cmd_permissions - }); - }); - - this.client.log.debug(`Command permissions for "${guild.name}"`, require('util').inspect(permissions, { - colors: true, - depth: 10 - })); - - try { - await guild.commands.permissions.set({ fullPermissions: permissions }); - } catch (error) { - this.client.log.warn('An error occurred whilst updating command permissions'); - this.client.log.error(error); - } - }); - } - - /** - * Execute a command - * @param {Interaction} interaction - Command message - */ - async handle(interaction) { - if (!interaction.guild) return this.client.log.debug('Ignoring non-guild command interaction'); - const settings = await this.client.utils.getSettings(interaction.guild.id); - const i18n = this.client.i18n.getLocale(settings.locale); - - const command = this.commands.get(interaction.commandName); - if (!command) return; - - const bot_permissions = interaction.guild.me.permissionsIn(interaction.channel); - const required_bot_permissions = [ - 'ATTACH_FILES', - 'EMBED_LINKS', - 'MANAGE_CHANNELS', - 'MANAGE_MESSAGES' - ]; - - if (!bot_permissions.has(required_bot_permissions)) { - const perms = required_bot_permissions.map(p => `\`${p}\``).join(', '); - if (bot_permissions.has('EMBED_LINKS')) { - await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor('ORANGE') - .setTitle(i18n('bot.missing_permissions.title')) - .setDescription(i18n('bot.missing_permissions.description', perms)) - ] - }); - } else { - await interaction.reply({ content: i18n('bot.missing_permissions.description', perms) }); - } - return; - } - - const missing_permissions = command.permissions instanceof Array && !interaction.member.permissions.has(command.permissions); - if (missing_permissions) { - const perms = command.permissions.map(p => `\`${p}\``).join(', '); - return await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor(settings.error_colour) - .setTitle(i18n('missing_permissions.title')) - .setDescription(i18n('missing_permissions.description', perms)) - ], - ephemeral: true - }); - } - - try { - this.client.log.commands(`Executing "${command.name}" command (invoked by ${interaction.user.tag})`); - await command.execute(interaction); // execute the command - } catch (e) { - this.client.log.warn(`An error occurred whilst executing the ${command.name} command`); - this.client.log.error(e); - await interaction.reply({ - embeds: [ - new MessageEmbed() - .setColor('ORANGE') - .setTitle(i18n('command_execution_error.title')) - .setDescription(i18n('command_execution_error.description')) - ] - }); // hopefully no user will ever see this message - } - } - -}; diff --git a/src/modules/listeners/listener.js b/src/modules/listeners/listener.js deleted file mode 100644 index f7202d6..0000000 --- a/src/modules/listeners/listener.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = class EventListener { - - /** - * - * @param {import("../..").Bot} client - * @param {*} data - */ - constructor(client, data) { - this.client = client; - this.event = data.event; - this.raw = data.raw || false; - this.once = data.once || false; - } - -}; \ No newline at end of file diff --git a/src/modules/listeners/loader.js b/src/modules/listeners/loader.js deleted file mode 100644 index b0c601c..0000000 --- a/src/modules/listeners/loader.js +++ /dev/null @@ -1,36 +0,0 @@ -const fs = require('fs'); -const { path } = require('../../utils/fs'); - -/** - * Manages the loading of event listeners - */ -module.exports = class ListenerLoader { - /** - * Create a ListenerLoader instance - * @param {import('../..').Bot} client - */ - constructor(client) { - /** The Discord Client */ - this.client = client; - } - - load() { - const files = fs.readdirSync(path('./src/listeners')) - .filter(file => file.endsWith('.js')); - - for (let file of files) { - try { - file = require(`../../listeners/${file}`); - const listener = new file(this.client); - const on = listener.once ? 'once' : 'on'; - if (listener.raw) this.client.ws[on](listener.event, (...data) => listener.execute(...data)); - else this.client[on](listener.event, (...data) => listener.execute(...data)); - } catch (e) { - this.client.log.warn('An error occurred whilst loading a listener'); - this.client.log.error(e); - } - } - - } - -}; \ No newline at end of file diff --git a/src/modules/plugins/manager.js b/src/modules/plugins/manager.js deleted file mode 100644 index 1a6b89d..0000000 --- a/src/modules/plugins/manager.js +++ /dev/null @@ -1,94 +0,0 @@ -// eslint-disable-next-line no-unused-vars -const { Collection } = require('discord.js'); -// eslint-disable-next-line no-unused-vars -const Plugin = require('./plugin'); - -/** - * Manages the loading of plugins - */ -module.exports = class PluginManager { - /** - * Create a PluginManager instance - * @param {import('../..').Bot} client - */ - constructor(client) { - /** The Discord Client */ - this.client = client; - - /** - * A discord.js Collection (Map) of loaded plugins - * @type {Collection} - */ - this.plugins = new Collection(); - - /** Array of official plugins to be used to check if a plugin is official */ - this.official = [ - 'dsctickets.settings-server', - 'dsctickets.portal', - 'dsctickets.text-transcripts' - ]; - } - - handleError(id) { - if (!this.official.includes(id)) this.client.log.notice(`"${id}" is NOT an official plugin, please do not ask for help with it in the Discord Tickets support server, seek help from the plugin author instead.`); - } - - /** - * Register and load a plugin - * @param {Plugin} plugin - the Plugin class - * @param {Object} pkg - contents of package.json - */ - register(plugin, pkg) { - let { - name: id, - version, - author, - description - } = pkg; - - if (this.plugins.has(id)) { - this.client.log.warn(`(PLUGINS) A plugin with the ID "${id}" is already loaded, skipping`); - return; - } - - if (typeof author === 'object') { - author = author.name ?? 'unknown'; - } - - const about = { - author, - description, - id, - version - }; - - try { - plugin = new (plugin(Plugin))(this.client, about); - this.plugins.set(id, plugin); - this.client.log.plugins(`Loading "${plugin.name}" v${version} by ${author}`); - plugin.preload(); - } catch (e) { - this.handleError(id); - this.client.log.warn(`An error occurred whilst loading the "${id}" plugin`); - this.client.log.error(e); - process.exit(); - } - } - - /** Automatically register and load plugins */ - load() { - this.client.config.plugins.forEach(plugin => { - try { - const main = require(plugin); - const pkg = require(`${plugin}/package.json`); - this.register(main, pkg); - } catch (e) { - this.handleError(plugin); - this.client.log.warn(`An error occurred whilst loading ${plugin}; have you installed it?`); - this.client.log.error(e); - process.exit(); - } - }); - } - -}; diff --git a/src/modules/plugins/plugin.js b/src/modules/plugins/plugin.js deleted file mode 100644 index d8df69f..0000000 --- a/src/modules/plugins/plugin.js +++ /dev/null @@ -1,141 +0,0 @@ -/* eslint-disable no-unused-vars */ -const { Client } = require('discord.js'); -const Command = require('../commands/command'); -const fs = require('fs'); -const { join } = require('path'); -const { path } = require('../../utils/fs'); - -/** - * A plugin - */ -module.exports = class Plugin { - /** - * Create a new Plugin - * @param {import('../..').Bot} client The Discord Client - * @param {String} id The plugin ID - * @param {Object} options Plugin options - * @param {String} options.name A human-friendly name (can be different to the name in package.json) - * @param {String[]} options.commands An array of command names the plugin registers - */ - constructor(client, about, options = {}) { - /** The Discord Client */ - this.client = client; - - /** The PluginManager */ - this.manager = this.client.plugins; - - /** An official plugin? */ - this.official = this.manager.official.includes(this.id); - - const { - id, - version, - author, - description - } = about; - - /** - * The human-friendly name of the plugin - * @type {string} - */ - this.name = options.name ?? id; - - /** - * An array of commands from this plugin - * @type {string[]} - */ - this.commands = options.commands; - - /** - * The unique ID of the plugin (NPM package name) - * @type {string} - */ - this.id = id; - - /** - * The version of the plugin (NPM package version) - * @type {string} - */ - this.version = version; - - /** - * The plugin author's name (NPM package author) - * @type {(undefined|string)} - */ - this.author = author; - - /** - * The plugin description (NPM package description) - * @type {string} - */ - this.description = description; - - const clean = this.id.replace(/@[-_a-zA-Z0-9]+\//, ''); - - /** - * Information about the plugin directory - * @property {string} name - A cleaned version of the plugin's ID suitable for use in the directory name - * @property {string} path - The absolute path of the plugin directory - */ - this.directory = { - name: clean, - path: path(`./user/plugins/${clean}`) - }; - } - - /** - * Create the plugin directory if it doesn't already exist - * @returns {Boolean} True if created, false if it already existed - */ - createDirectory() { - if (!fs.existsSync(this.directory.path)) { - this.client.log.plugins(`Creating plugin directory for "${this.name}"`); - fs.mkdirSync(this.directory.path); - return true; - } else { - return false; - } - } - - /** - * Create the plugin config file if it doesn't already exist - * @param {Object} template The default config template - * @returns {Boolean} True if created, false if it already existed - */ - createConfig(template) { - this.createDirectory(); - const file = join(this.directory.path, 'config.json'); - if (!fs.existsSync(file)) { - this.client.log.plugins(`Creating plugin config file for "${this.name}"`); - fs.writeFileSync(file, JSON.stringify(template, null, 2)); - return true; - } else { - return false; - } - } - - /** - * Reset the plugin config file to the defaults - * @param {Object} template The default config template - */ - resetConfig(template) { - this.createDirectory(); - const file = join(this.directory.path, 'config.json'); - this.client.log.plugins(`Resetting plugin config file for "${this.name}"`); - fs.writeFileSync(file, JSON.stringify(template, null, 2)); - } - - /** - * The function where any code that needs to be executed before the client is ready should go. - * **This is executed _BEFORE_ the ready event** - * @abstract - */ - preload() { } - - /** - * The main function where your code should go. Create commands and event listeners here. - * **This is executed _after_ the ready event** - * @abstract - */ - load() {} -}; \ No newline at end of file diff --git a/src/modules/tickets/archives.js b/src/modules/tickets/archives.js deleted file mode 100644 index 8278c37..0000000 --- a/src/modules/tickets/archives.js +++ /dev/null @@ -1,202 +0,0 @@ -const { int2hex } = require('../../utils'); - -/** Manages ticket archiving */ -module.exports = class TicketArchives { - /** - * Create a TicketArchives instance - * @param {import('../..').Bot} client - */ - constructor(client) { - - /** The Discord Client */ - this.client = client; - - this.encrypt = this.client.cryptr.encrypt; - this.decrypt = this.client.cryptr.decrypt; - - } - - async addMessage(message) { - try { - // await this.client.db.transaction(async t => { - const t_row = await this.client.db.models.Ticket.findOne({ - where: { id: message.channel.id } - /* transaction: t */ - }); - - if (t_row) { - await this.client.db.models.Message.create({ - author: message.author.id, - createdAt: new Date(message.createdTimestamp), - data: this.encrypt(JSON.stringify({ - attachments: [...message.attachments.values()], - content: message.content, - embeds: message.embeds.map(embed => ({ embed })) - })), - id: message.id, - ticket: t_row.id - } /* { transaction: t } */); - - await this.updateEntities(message); - } - // }); - } catch (e) { - this.client.log.warn('Failed to add a message to the ticket archive'); - this.client.log.error(e); - } - } - - async updateMessage(message) { - try { - // await this.client.db.transaction(async t => { - const m_row = await this.client.db.models.Message.findOne({ - where: { id: message.id } - /* transaction: t */ - }); - - if (m_row) { - m_row.data = this.encrypt(JSON.stringify({ - attachments: [...message.attachments.values()], - content: message.content, - embeds: message.embeds.map(embed => ({ embed })) - })); - - if (message.editedTimestamp) { - m_row.edited = true; - await this.updateEntities(message); - } - - await m_row.save(/* { transaction: t } */); // save changes - } - // }); - } catch (e) { - this.client.log.warn('Failed to update message in the ticket archive'); - this.client.log.error(e); - } - } - - async deleteMessage(message) { - try { - // await this.client.db.transaction(async t => { - const msg = await this.client.db.models.Message.findOne({ - where: { id: message.id } - /* transaction: t */ - }); - - if (msg) { - msg.deleted = true; - await msg.save(/* { transaction: t } */); // save changes to message row - } - // }); - } catch (e) { - this.client.log.warn('Failed to delete message in ticket archive'); - this.client.log.error(e); - } - } - - async updateEntities(message) { - // message author - await this.updateMember(message.channel.id, message.member); - - // mentioned members - message.mentions.members.forEach(async member => { - await this.updateMember(message.channel.id, member); - }); - - // mentioned channels - message.mentions.channels.forEach(async channel => { - await this.updateChannel(message.channel.id, channel); - }); - - // mentioned roles - message.mentions.roles.forEach(async role => { - await this.updateRole(message.channel.id, role); - }); - } - - async updateMember(ticket_id, member) { - await this.updateRole(ticket_id, member.roles.highest); - - try { - // await this.client.db.transaction(async t => { - const u_model_data = { - ticket: ticket_id, - user: member.user.id - }; - - const [u_row] = await this.client.db.models.UserEntity.findOrCreate({ - defaults: { - ...u_model_data, - role: member.roles.highest.id - }, - where: u_model_data - /* transaction: t */ - }); - - await u_row.update({ - avatar: member.user.avatar, - bot: member.user.bot, - discriminator: member.user.discriminator, - display_name: this.encrypt(member.displayName), - role: member.roles.highest.id, - username: this.encrypt(member.user.username) - } /* { transaction: t } */); - - return u_row; - // }); - } catch (e) { - this.client.log.warn('Failed to update message author entity in ticket archive'); - this.client.log.error(e); - } - } - - async updateChannel(ticket_id, channel) { - try { - // await this.client.db.transaction(async t => { - const c_model_data = { - channel: channel.id, - ticket: ticket_id - }; - const [c_row] = await this.client.db.models.ChannelEntity.findOrCreate({ - defaults: c_model_data, - where: c_model_data - /* transaction: t */ - }); - - await c_row.update({ name: this.encrypt(channel.name) } /* { transaction: t } */); - - return c_row; - // }); - } catch (e) { - this.client.log.warn('Failed to update mentioned channels entities in ticket archive'); - this.client.log.error(e); - } - } - - async updateRole(ticket_id, role) { - try { - // await this.client.db.transaction(async t => { - const r_model_data = { - role: role.id, - ticket: ticket_id - }; - const [r_row] = await this.client.db.models.RoleEntity.findOrCreate({ - defaults: r_model_data, - where: r_model_data - /* transaction: t */ - }); - - await r_row.update({ - colour: role.color === 0 ? '7289DA' : int2hex(role.color), // 7289DA = 7506394 - name: this.encrypt(role.name) - } /* { transaction: t } */); - - return r_row; - // }); - } catch (e) { - this.client.log.warn('Failed to update mentioned roles entities in ticket archive'); - this.client.log.error(e); - } - } - -}; \ No newline at end of file diff --git a/src/modules/tickets/manager.js b/src/modules/tickets/manager.js deleted file mode 100644 index b19ab10..0000000 --- a/src/modules/tickets/manager.js +++ /dev/null @@ -1,440 +0,0 @@ -/* eslint-disable max-lines */ -const EventEmitter = require('events'); -const TicketArchives = require('./archives'); -const { - MessageActionRow, - MessageButton, - MessageEmbed -} = require('discord.js'); - -/** Manages tickets */ -module.exports = class TicketManager extends EventEmitter { - /** - * Create a TicketManager instance - * @param {import('../..').Bot} client - */ - constructor(client) { - super(); - - /** The Discord Client */ - this.client = client; - - this.setMaxListeners(this.client.config.max_listeners); - - this.archives = new TicketArchives(this.client); - } - - /** - * Create a new ticket - * @param {string} guild_id - ID of the guild to create the ticket in - * @param {string} creator_id - ID of the ticket creator (user) - * @param {string} category_id - ID of the ticket category - * @param {string} [topic] - The ticket topic - */ - async create(guild_id, creator_id, category_id, topic) { - if (!topic) topic = ''; - - const cat_row = await this.client.db.models.Category.findOne({ where: { id: category_id } }); - - if (!cat_row) throw new Error('Ticket category does not exist'); - - const cat_channel = await this.client.channels.fetch(category_id); - - if (cat_channel.children.size >= 50) throw new Error('Ticket category has reached child channel limit (50)'); - - const number = (await this.client.db.models.Ticket.count({ where: { guild: guild_id } })) + 1; - - const guild = this.client.guilds.cache.get(guild_id); - const creator = await guild.members.fetch(creator_id); - const name = cat_row.name_format - .replace(/{+\s?(user)?name\s?}+/gi, creator.displayName) - .replace(/{+\s?num(ber)?\s?}+/gi, number); - - const t_channel = await guild.channels.create(name, { - parent: category_id, - reason: `${creator.user.tag} requested a new ticket channel`, - topic: `${creator}${topic.length > 0 ? ` | ${topic}` : ''}`, - type: 'GUILD_TEXT' - }); - - t_channel.permissionOverwrites.edit(creator_id, { - ATTACH_FILES: true, - READ_MESSAGE_HISTORY: true, - SEND_MESSAGES: true, - VIEW_CHANNEL: true - }, `Ticket channel created by ${creator.user.tag}`); - - const t_row = await this.client.db.models.Ticket.create({ - category: category_id, - creator: creator_id, - guild: guild_id, - id: t_channel.id, - number, - topic: topic.length === 0 ? null : this.client.cryptr.encrypt(topic) - }); - - (async () => { - const settings = await this.client.utils.getSettings(guild.id); - const i18n = this.client.i18n.getLocale(settings.locale); - - topic = t_row.topic - ? this.client.cryptr.decrypt(t_row.topic) - : ''; - - if (cat_row.image) { - await t_channel.send({ content: cat_row.image }); - } - - const description = cat_row.opening_message - .replace(/{+\s?(user)?name\s?}+/gi, creator.displayName) - .replace(/{+\s?(tag|ping|mention)?\s?}+/gi, creator.user.toString()); - const embed = new MessageEmbed() - .setColor(settings.colour) - .setAuthor(creator.user.username, creator.user.displayAvatarURL()) - .setDescription(description) - .setFooter(settings.footer, guild.iconURL()); - - if (topic) embed.addField(i18n('ticket.opening_message.fields.topic'), topic); - - const components = new MessageActionRow(); - - if (cat_row.claiming) { - components.addComponents( - new MessageButton() - .setCustomId('ticket.claim') - .setLabel(i18n('ticket.claim')) - .setEmoji('🙌') - .setStyle('SECONDARY') - ); - } - - if (settings.close_button) { - components.addComponents( - new MessageButton() - .setCustomId('ticket.close') - .setLabel(i18n('ticket.close')) - .setEmoji('✖️') - .setStyle('DANGER') - ); - } - - const mentions = cat_row.ping instanceof Array && cat_row.ping.length > 0 - ? cat_row.ping.map(id => id === 'everyone' - ? '@everyone' - : id === 'here' - ? '@here' - : `<@&${id}>`) - .join(', ') - : ''; - const sent = await t_channel.send({ - components: cat_row.claiming || settings.close_button ? [components] : [], - content: i18n('ticket.opening_message.content', mentions, creator.user.toString()), - embeds: [embed] - }); - await sent.pin({ reason: 'Ticket opening message' }); - - await t_row.update({ opening_message: sent.id }); - - const pinned = t_channel.messages.cache.last(); - - if (pinned.system) { - pinned - .delete({ reason: 'Cleaning up system message' }) - .catch(() => this.client.log.warn('Failed to delete system pin message')); - } - - let questions; - if (cat_row.opening_questions) { - questions = cat_row.opening_questions - .map((q, index) => `**${index + 1}.** ${q}`) - .join('\n\n'); - } - - if (cat_row.require_topic && topic.length === 0) { - const collector_message = await t_channel.send({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('commands.new.request_topic.title')) - .setDescription(i18n('commands.new.request_topic.description')) - .setFooter(this.client.utils.footer(settings.footer, i18n('collector_expires_in', 120)), guild.iconURL()) - ] - }); - - const filter = message => message.author.id === t_row.creator; - const collector = t_channel.createMessageCollector({ - filter, - time: 120000 - }); - - collector.on('collect', async message => { - topic = message.content; - await t_row.update({ topic: this.client.cryptr.encrypt(topic) }); - await t_channel.setTopic(`${creator} | ${topic}`, { reason: 'User updated ticket topic' }); - await sent.edit( - new MessageEmbed() - .setColor(settings.colour) - .setAuthor(creator.user.username, creator.user.displayAvatarURL()) - .setDescription(description) - .addField(i18n('ticket.opening_message.fields.topic'), topic) - .setFooter(settings.footer, guild.iconURL()) - ); - await message.react('✅'); - collector.stop(); - }); - - collector.on('end', async () => { - collector_message - .delete() - .catch(() => this.client.log.warn('Failed to delete topic collector message')); - if (cat_row.opening_questions) { - await t_channel.send({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setDescription(i18n('ticket.questions', questions)) - .setFooter(settings.footer, guild.iconURL()) - ] - }); - } - }); - } else { - if (cat_row.opening_questions) { - await t_channel.send({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setDescription(i18n('ticket.questions', questions)) - .setFooter(settings.footer, guild.iconURL()) - ] - }); - } - } - })(); - - this.client.log.info(`${creator.user.tag} created a new ticket in "${guild.name}"`); - - this.emit('create', t_row.id, creator_id); - - return t_row; - } - - /** - * Close a ticket - * @param {(string|number)} ticket_id - The channel ID, or the ticket number - * @param {string?} closer_id - ID of the member who is closing the ticket, or null - * @param {string} [guild_id] - The ID of the ticket's guild (used if a ticket number is provided instead of ID) - * @param {string} [reason] - The reason for closing the ticket - */ - async close(ticket_id, closer_id, guild_id, reason) { - const t_row = await this.resolve(ticket_id, guild_id); - if (!t_row) throw new Error(`A ticket with the ID or number "${ticket_id}" could not be resolved`); - ticket_id = t_row.id; - - this.emit('beforeClose', ticket_id); - - const guild = this.client.guilds.cache.get(t_row.guild); - const settings = await this.client.utils.getSettings(guild.id); - const i18n = this.client.i18n.getLocale(settings.locale); - const channel = await this.client.channels.fetch(t_row.id); - - const close = async () => { - const pinned = await channel.messages.fetchPinned(); - await t_row.update({ - closed_by: closer_id || null, - closed_reason: reason ? this.client.cryptr.encrypt(reason) : null, - open: false, - pinned_messages: [...pinned.keys()] - }); - - if (closer_id) { - const closer = await guild.members.fetch(closer_id); - - await this.archives.updateMember(ticket_id, closer); - - const description = reason - ? i18n('ticket.closed_by_member_with_reason.description', closer.user.toString(), reason) - : i18n('ticket.closed_by_member.description', closer.user.toString()); - await channel.send({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setAuthor(closer.user.username, closer.user.displayAvatarURL()) - .setTitle(i18n('ticket.closed.title')) - .setDescription(description) - .setFooter(settings.footer, guild.iconURL()) - ] - }); - - setTimeout(async () => { - await channel.delete(`Ticket channel closed by ${closer.user.tag}${reason ? `: "${reason}"` : ''}`); - }, 5000); - - this.client.log.info(`${closer.user.tag} closed a ticket (${ticket_id})${reason ? `: "${reason}"` : ''}`); - } else { - const description = reason - ? i18n('ticket.closed_with_reason.description') - : i18n('ticket.closed.description'); - await channel.send({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setTitle(i18n('ticket.closed.title')) - .setDescription(description) - .setFooter(settings.footer, guild.iconURL()) - ] - }); - - setTimeout(async () => { - await channel.delete(`Ticket channel closed${reason ? `: "${reason}"` : ''}`); - }, 5000); - - this.client.log.info(`A ticket was closed (${ticket_id})${reason ? `: "${reason}"` : ''}`); - } - }; - - if (channel) { - guild.members.fetch(t_row.creator) - .then(async creator => { - const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } }); - if (creator && cat_row.survey) { - const survey = await this.client.db.models.Survey.findOne({ - where: { - guild: t_row.guild, - name: cat_row.survey - } - }); - - if (survey) { - await channel.send({ - components: [ - new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId(`start_survey:${channel.id}`) - .setLabel(i18n('ticket.survey.start.buttons.start')) - .setEmoji('✅') - .setStyle('SUCCESS') - ) - .addComponents( - new MessageButton() - .setCustomId(`ignore_survey:${channel.id}`) - .setLabel(i18n('ticket.survey.start.buttons.ignore')) - .setEmoji('❌') - .setStyle('SECONDARY') - ) - ], - content: creator.toString(), - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(i18n('ticket.survey.start.title')) - .setDescription(i18n('ticket.survey.start.description', creator.toString(), survey.questions.length)) - .setFooter(i18n('collector_expires_in', 60)) - ] - }); - - const filter = i => i.user.id === creator.id && i.customId.includes(channel.id); - const collector = channel.createMessageComponentCollector({ - filter, - time: 60000 - }); - - collector.on('collect', async i => { - await i.deferUpdate(); - - if (i.customId === `start_survey:${channel.id}`) { - const filter = message => message.author.id === creator.id; - let answers = []; - let number = 1; - for (const question of survey.questions) { - await channel.send({ - embeds: [ - new MessageEmbed() - .setColor(settings.colour) - .setTitle(`${number++}/${survey.questions.length}`) - .setDescription(question) - .setFooter(i18n('collector_expires_in', 60)) - ] - }); - - try { - const collected = await channel.awaitMessages({ - errors: ['time'], - filter, - max: 1, - time: 60000 - }); - answers.push(collected.first().content); - } catch (collected) { - await close(); - } - } - - await channel.send({ - embeds: [ - new MessageEmbed() - .setColor(settings.success_colour) - .setTitle(i18n('ticket.survey.complete.title')) - .setDescription(i18n('ticket.survey.complete.description')) - .setFooter(settings.footer, guild.iconURL()) - ] - }); - - answers = answers.map(a => this.client.cryptr.encrypt(a)); - await this.client.db.models.SurveyResponse.create({ - answers, - survey: survey.id, - ticket: t_row.id - }); - await close(); - } else { - await close(); - } - - collector.stop(); - }); - - collector.on('end', async collected => { - if (collected.size === 0) { - await close(); - } - }); - } - } else { - await close(); - } - }) - .catch(async error => { - this.client.log.debug(error); - await close(); - }); - } - - this.emit('close', ticket_id); - return t_row; - } - - /** - * - * @param {(string|number)} ticket_id - ID or number of the ticket - * @param {string} [guild_id] - The ID of the ticket's guild (used if a ticket number is provided instead of ID) - */ - async resolve(ticket_id, guild_id) { - let t_row; - - if (this.client.channels.resolve(ticket_id)) { - t_row = await this.client.db.models.Ticket.findOne({ where: { id: ticket_id } }); - } else { - t_row = await this.client.db.models.Ticket.findOne({ - where: { - guild: guild_id, - number: ticket_id - } - }); - } - - return t_row; - } - -}; \ No newline at end of file diff --git a/src/update/notifier.js b/src/update/notifier.js deleted file mode 100644 index 00e215f..0000000 --- a/src/update/notifier.js +++ /dev/null @@ -1,38 +0,0 @@ -/* eslint-disable no-console */ -const fetch = require('node-fetch'); -const boxen = require('boxen'); -const link = require('terminal-link'); -const semver = require('semver'); -const { format } = require('leekslazylogger'); - -const { version: current } = require('../../package.json'); - -module.exports = async client => { - if (!client.config.update_notice) return; - const json = await (await fetch('https://api.github.com/repos/discord-tickets/bot/releases')).json(); - const update = json[0]; - - const latest = semver.coerce(update.tag_name); - - if (!semver.valid(latest)) return; - - if (semver.lt(current, latest)) { - client.log.notice(`There is an update available for Discord Tickets (${current} -> ${update.tag_name})`); - - const lines = [ - `&k&6You are currently using &c${current}&6, the latest is &a${update.tag_name}&6.&r`, - `&k&6Download "&f${update.name}&6" from&r`, - link('&k&6the GitHub releases page.&r&6', 'https://github.com/discord-tickets/bot/releases/') - ]; - - console.log( - boxen(format(lines.join('\n')), { - align: 'center', - borderColor: 'yellow', - borderStyle: 'round', - margin: 1, - padding: 1 - }) - ); - } -}; \ No newline at end of file diff --git a/src/utils/discord.js b/src/utils/discord.js deleted file mode 100644 index 77d4800..0000000 --- a/src/utils/discord.js +++ /dev/null @@ -1,96 +0,0 @@ - -const { GuildMember } = require('discord.js'); // eslint-disable-line no-unused-vars -const { Model } = require('sequelize'); // eslint-disable-line no-unused-vars -const config = require('../../user/config'); -let current_presence = -1; - -module.exports = class DiscordUtils { - constructor(client) { - this.client = client; - } - - /** - * Generate embed footer text - * @param {string} text - * @param {string} [additional] - * @returns {string} - */ - footer(text, additional) { - if (text && additional) return `${text} | ${additional}`; - else return additional || text || ''; - } - - /** - * Check if a guild member is staff - * @param {GuildMember} member - the guild member - * @returns {boolean} - */ - async isStaff(member) { - const guild_categories = await this.client.db.models.Category.findAll({ where: { guild: member.guild.id } }); - return guild_categories.some(cat => cat.roles.some(r => member.roles.cache.has(r))); - } - - /** - * Fet a guild's settings - * @param {string} id - The guild's ID - * @returns {Promise} - */ - async getSettings(id) { - const data = { id }; - const [settings] = await this.client.db.models.Guild.findOrCreate({ - defaults: data, - where: data - }); - return settings; - } - - /** - * Delete a guild's settings - * @param {string} id - The guild ID - * @returns {Promise} - */ - async deleteSettings(id) { - const row = await this.getSettings(id); - return await row.destroy(); - } - - /** - * Select a presence from the config - * @returns {PresenceData} - */ - static selectPresence() { - const length = config.presence.presences.length; - if (length === 0) return {}; - - let num; - if (length === 1) { - num = 0; - } else if (config.presence.randomise) { - num = Math.floor(Math.random() * length); - } else { - current_presence = current_presence + 1; // ++ doesn't work on negative numbers - if (current_presence === length) { - current_presence = 0; - } - num = current_presence; - } - - const { - activity: name, - status, - type, - url - } = config.presence.presences[num]; - - return { - activities: [ - { - name, - type, - url - } - ], - status - }; - } -}; \ No newline at end of file diff --git a/src/utils/emoji.js b/src/utils/emoji.js deleted file mode 100644 index 8ee6000..0000000 --- a/src/utils/emoji.js +++ /dev/null @@ -1,43 +0,0 @@ -module.exports = { - letters: { - A: '🇦', - B: '🇧', - C: '🇨', - D: '🇩', - E: '🇪', - F: '🇫', - G: '🇬', - H: '🇭', - I: '🇮', - J: '🇯', - K: '🇰', - L: '🇱', - M: '🇲', - N: '🇳', - O: '🇴', - P: '🇵', - Q: '🇶', - R: '🇷', - S: '🇸', - T: '🇹', - U: '🇺', - V: '🇻', - W: '🇼', - X: '🇽', - Y: '🇾', - Z: '🇿' - }, - numbers: { - 0: '0️⃣', - 1: '1️⃣', - 2: '2️⃣', - 3: '3️⃣', - 4: '4️⃣', - 5: '5️⃣', - 6: '6️⃣', - 7: '7️⃣', - 8: '8️⃣', - 9: '9️⃣', - 10: '🔟' - } -}; \ No newline at end of file diff --git a/src/utils/fs.js b/src/utils/fs.js deleted file mode 100644 index 5f76ab3..0000000 --- a/src/utils/fs.js +++ /dev/null @@ -1,10 +0,0 @@ -const { join } = require('path'); - -module.exports = { - /** - * Make a relative path absolute - * @param {string} path - A path relative to the root of the project (like "./user/config.js") - * @returns {string} absolute path - */ - path: path => join(__dirname, '../../', path) -}; \ No newline at end of file diff --git a/src/utils/index.js b/src/utils/index.js deleted file mode 100644 index f702860..0000000 --- a/src/utils/index.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - int2hex: int => int.toString(16).toUpperCase(), - some: async (array, func) => { - for (const element of array) { - if (await func(element)) return true; - } - return false; - }, - wait: time => new Promise(res => setTimeout(res, time)) -}; \ No newline at end of file diff --git a/user/example.config.js b/user/example.config.js deleted file mode 100644 index 406af9e..0000000 --- a/user/example.config.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * ############################################################################################### - * ____ _ _____ _ _ - * | _ \ (_) ___ ___ ___ _ __ __| | |_ _| (_) ___ | | __ ___ | |_ ___ - * | | | | | | / __| / __| / _ \ | '__| / _` | | | | | / __| | |/ / / _ \ | __| / __| - * | |_| | | | \__ \ | (__ | (_) | | | | (_| | | | | | | (__ | < | __/ | |_ \__ \ - * |____/ |_| |___/ \___| \___/ |_| \__,_| |_| |_| \___| |_|\_\ \___| \__| |___/ - * - * --------------------- - * Support - * --------------------- - * - * > Documentation: https://discordtickets.app - * > Discord support server: https://go.eartharoid.me/discord - * - * ############################################################################################### - */ - - -module.exports = { - defaults: { - colour: '#009999', - log_messages: true, - name_format: 'ticket-{number}', - opening_message: 'Hello {name}, thank you for creating a ticket. A member of staff will soon be available to assist you.\n\n__All messages in this channel are stored for future reference.__' - }, - developer: { debug: false }, - locale: 'en-GB', - logs: { - enabled: true, - keep_for: 30, - split: true - }, - max_listeners: 10, - plugins: [], - presence: { - duration: 60, - presences: [ - { - activity: '/new', - type: 'PLAYING' - }, - { - activity: 'with tickets', - type: 'PLAYING' - }, - { - activity: 'tickets', - type: 'WATCHING' - } - ], - randomise: true - }, - super_secret_setting: true, - update_notice: true -}; \ No newline at end of file diff --git a/user/plugins/.gitkeep b/user/plugins/.gitkeep deleted file mode 100644 index e69de29..0000000 From 7ecf71f8f3524a81da7495ad3b8380232d2777ce Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 18 Mar 2022 12:54:33 +0000 Subject: [PATCH 002/409] Update files --- .eslintrc.js | 44 ++++++++++++++++++++++++++--------------- .github/SECURITY.md | 7 ++++--- .gitignore | 12 +++++------ user/example.config.yml | 22 +++++++++++++++++++++ 4 files changed, 60 insertions(+), 25 deletions(-) create mode 100644 user/example.config.yml diff --git a/.eslintrc.js b/.eslintrc.js index 9a258ff..5dd55cb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,12 +1,16 @@ module.exports = { 'env': { - 'commonjs': true, - 'es2021': true, - 'node': true + 'browser': false, + 'commonjs': false, + 'es6': true, + 'node': true, }, - 'extends': 'eslint:recommended', + 'extends': ['eslint:recommended'], + 'parser': '@typescript-eslint/parser', 'parserOptions': { 'ecmaVersion': 12 }, + 'root': true, 'rules': { + '@typescript-eslint/no-var-requires': ['off'], 'array-bracket-newline': [ 'error', 'consistent' @@ -37,13 +41,19 @@ module.exports = { ], 'comma-dangle': [ 'error', - 'never' + { + 'arrays': 'never', + 'exports': 'never', + 'functions': 'never', + 'imports': 'never', + 'objects': 'always-multiline', + } ], 'comma-spacing': [ 'error', { 'after': true, - 'before': false + 'before': false, } ], 'comma-style': [ @@ -96,12 +106,11 @@ module.exports = { 'ignoreStrings': true, 'ignoreTemplateLiterals': true, 'ignoreTrailingComments': true, - 'ignoreUrls': true + 'ignoreUrls': true, } ], 'max-lines': [ - 'warn', - 500 + 'warn' ], 'max-statements-per-line': [ 'error' @@ -110,7 +119,7 @@ module.exports = { 'warn' ], 'no-console': [ - 'warn' + 'off' ], 'no-return-assign': [ 'error' @@ -122,7 +131,10 @@ module.exports = { 'error' ], 'no-underscore-dangle': [ - 'error' + 'error', { + 'allowAfterThis': true, + 'allowFunctionParams': true, + } ], 'no-unneeded-ternary': [ 'error' @@ -137,7 +149,7 @@ module.exports = { 'error', { 'minProperties': 2, - 'multiline': true + 'multiline': true, } ], 'object-curly-spacing': [ @@ -157,7 +169,7 @@ module.exports = { 'error', { 'destructuring': 'all', - 'ignoreReadBeforeAssign': false + 'ignoreReadBeforeAssign': false, } ], 'quotes': [ @@ -184,6 +196,6 @@ module.exports = { 'spaced-comment': [ 'error', 'always' - ] - } -}; \ No newline at end of file + ], + }, +}; diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 1cd7051..94a7691 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -6,9 +6,10 @@ Release versions that will receive security updates. | Version | Supported | | ------- | --------- | -| 3.x | ✅ | -| 2.x | ❌ | -| 1.x | ❌ | +| 1.x | ❌ | +| 2.x | ❌ | +| 3.x | ❌ | +| 4.x | ✅ | ## Reporting a vulnerability diff --git a/.gitignore b/.gitignore index dcc5186..b4cc876 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,12 @@ # directories -.vscode/ node_modules/ -logs/ -site/ +dist/ # files -.env +.env* version -user/config.js +user/config.yml user/database.sqlite -user/plugins/*/ \ No newline at end of file +*.log +*.lock +*-lock.* \ No newline at end of file diff --git a/user/example.config.yml b/user/example.config.yml new file mode 100644 index 0000000..8e614f8 --- /dev/null +++ b/user/example.config.yml @@ -0,0 +1,22 @@ +##################################################### +## ____ _ ## +## | _ \ (_) ___ ___ ___ _ __ __| | ## +## | | | | | | / __| / __| / _ \ | '__| / _` | ## +## | |_| | | | \__ \ | (__ | (_) | | | | (_| | ## +## |____/ |_| |___/ \___| \___/ |_| \__,_| ## +## _____ _ _ ## +## |_ _| (_) ___ | | __ ___ | |_ ___ ## +## | | | | / __| | |/ / / _ \ | __| / __| ## +## | | | | | (__ | < | __/ | |_ \__ \ ## +## |_| |_| \___| |_|\_\ \___| \__| |___/ ## +## ## +## Documentation: https://discordtickets.app ## +## Support: https://go.eartharoid.me/discord ## +##################################################### + +logs: + files: + enabled: true + keepFor: 30 # days + split: true # split stdout (info) and stderr (warnings & errors) into separate files? + level: info \ No newline at end of file From 5ae4da227e0e34705f4d7cdd0605b45d231b5a3b Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 18 Mar 2022 16:27:32 +0000 Subject: [PATCH 003/409] Entrypoint, env and config etc --- .eslintrc.js | 201 -------------------------------------- .eslintrc.json | 209 ++++++++++++++++++++++++++++++++++++++++ jsconfig.json | 18 ++++ package.json | 49 ++++++++++ scripts/keygen.mjs | 8 ++ src/index.mjs | 75 ++++++++++++++ src/lib/banner.mjs | 22 +++++ src/lib/constants.js | 0 src/lib/logger.mjs | 44 +++++++++ user/example.config.yml | 2 +- 10 files changed, 426 insertions(+), 202 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 .eslintrc.json create mode 100644 jsconfig.json create mode 100644 package.json create mode 100644 scripts/keygen.mjs create mode 100644 src/index.mjs create mode 100644 src/lib/banner.mjs create mode 100644 src/lib/constants.js create mode 100644 src/lib/logger.mjs diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 5dd55cb..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,201 +0,0 @@ -module.exports = { - 'env': { - 'browser': false, - 'commonjs': false, - 'es6': true, - 'node': true, - }, - 'extends': ['eslint:recommended'], - 'parser': '@typescript-eslint/parser', - 'parserOptions': { 'ecmaVersion': 12 }, - 'root': true, - 'rules': { - '@typescript-eslint/no-var-requires': ['off'], - 'array-bracket-newline': [ - 'error', - 'consistent' - ], - 'array-bracket-spacing': [ - 'error', - 'never' - ], - 'array-element-newline': [ - 'error', - 'consistent' - ], - 'arrow-body-style': [ - 'error', - 'as-needed' - ], - 'arrow-parens': [ - 'error', - 'as-needed' - ], - 'block-spacing': [ - 'error', - 'always' - ], - 'brace-style': [ - 'error', - '1tbs' - ], - 'comma-dangle': [ - 'error', - { - 'arrays': 'never', - 'exports': 'never', - 'functions': 'never', - 'imports': 'never', - 'objects': 'always-multiline', - } - ], - 'comma-spacing': [ - 'error', - { - 'after': true, - 'before': false, - } - ], - 'comma-style': [ - 'error', - 'last' - ], - 'computed-property-spacing': [ - 'error', - 'never' - ], - 'curly': [ - 'error', - 'multi-line', // 'multi' - 'consistent' - ], - 'default-case-last': [ - 'error' - ], - 'dot-location': [ - 'error', - 'property' - ], - 'dot-notation': [ - 'error' - ], - 'eqeqeq': [ - 'error' - ], - 'func-call-spacing': [ - 'error', - 'never' - ], - 'indent': [ - 'error', - 'tab' - ], - 'linebreak-style': [ - 'off', - 'windows' - ], - 'max-depth': [ - 'warn', - { 'max': 5 } - ], - 'max-len': [ - 'warn', - { - 'code': 150, - 'ignoreRegExpLiterals': true, - 'ignoreStrings': true, - 'ignoreTemplateLiterals': true, - 'ignoreTrailingComments': true, - 'ignoreUrls': true, - } - ], - 'max-lines': [ - 'warn' - ], - 'max-statements-per-line': [ - 'error' - ], - 'multiline-comment-style': [ - 'warn' - ], - 'no-console': [ - 'off' - ], - 'no-return-assign': [ - 'error' - ], - 'no-template-curly-in-string': [ - 'warn' - ], - 'no-trailing-spaces': [ - 'error' - ], - 'no-underscore-dangle': [ - 'error', { - 'allowAfterThis': true, - 'allowFunctionParams': true, - } - ], - 'no-unneeded-ternary': [ - 'error' - ], - 'no-var': [ - 'error' - ], - 'no-whitespace-before-property': [ - 'error' - ], - 'object-curly-newline': [ - 'error', - { - 'minProperties': 2, - 'multiline': true, - } - ], - 'object-curly-spacing': [ - 'error', - 'always' - ], - 'object-property-newline': [ - 'error' - ], - 'operator-linebreak': [ - 'error' - ], - 'prefer-arrow-callback': [ - 'error' - ], - 'prefer-const': [ - 'error', - { - 'destructuring': 'all', - 'ignoreReadBeforeAssign': false, - } - ], - 'quotes': [ - 'error', - 'single' - ], - 'rest-spread-spacing': [ - 'error', - 'never' - ], - 'semi': [ - 'error', - 'always' - ], - 'sort-keys': [ - 'error', - 'asc', - { 'natural': true } - ], - 'space-in-parens': [ - 'error', - 'never' - ], - 'spaced-comment': [ - 'error', - 'always' - ], - }, -}; diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..6276302 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,209 @@ +{ + "env": { + "browser": false, + "commonjs": false, + "es6": true, + "node": true + }, + "extends": [ + "eslint:recommended" + ], + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "root": true, + "rules": { + "array-bracket-newline": [ + "error", + "consistent" + ], + "array-bracket-spacing": [ + "error", + "never" + ], + "array-element-newline": [ + "error", + "consistent" + ], + "arrow-body-style": [ + "error", + "as-needed" + ], + "arrow-parens": [ + "error", + "as-needed" + ], + "block-spacing": [ + "error", + "always" + ], + "brace-style": [ + "error", + "1tbs" + ], + "comma-dangle": [ + "error", + { + "arrays": "never", + "exports": "never", + "functions": "never", + "imports": "never", + "objects": "always-multiline" + } + ], + "comma-spacing": [ + "error", + { + "after": true, + "before": false + } + ], + "comma-style": [ + "error", + "last" + ], + "computed-property-spacing": [ + "error", + "never" + ], + "curly": [ + "error", + "multi-line", // "multi" + "consistent" + ], + "default-case-last": [ + "error" + ], + "dot-location": [ + "error", + "property" + ], + "dot-notation": [ + "error" + ], + "eqeqeq": [ + "error" + ], + "func-call-spacing": [ + "error", + "never" + ], + "indent": [ + "error", + "tab" + ], + "linebreak-style": [ + "off", + "windows" + ], + "max-depth": [ + "warn", + { + "max": 5 + } + ], + "max-len": [ + "warn", + { + "code": 150, + "ignoreRegExpLiterals": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true, + "ignoreTrailingComments": true, + "ignoreUrls": true + } + ], + "max-lines": [ + "warn" + ], + "max-statements-per-line": [ + "error" + ], + "multiline-comment-style": [ + "warn" + ], + "no-console": [ + "off" + ], + "no-return-assign": [ + "error" + ], + "no-template-curly-in-string": [ + "warn" + ], + "no-trailing-spaces": [ + "error" + ], + "no-underscore-dangle": [ + "error", + { + "allowAfterThis": true, + "allowFunctionParams": true + } + ], + "no-unneeded-ternary": [ + "error" + ], + "no-var": [ + "error" + ], + "no-whitespace-before-property": [ + "error" + ], + "object-curly-newline": [ + "error", + { + "minProperties": 2, + "multiline": true + } + ], + "object-curly-spacing": [ + "error", + "always" + ], + "object-property-newline": [ + "error" + ], + "operator-linebreak": [ + "error" + ], + "prefer-arrow-callback": [ + "error" + ], + "prefer-const": [ + "error", + { + "destructuring": "all", + "ignoreReadBeforeAssign": false + } + ], + "quotes": [ + "error", + "single" + ], + "rest-spread-spacing": [ + "error", + "never" + ], + "semi": [ + "error", + "always" + ], + "sort-keys": [ + "error", + "asc", + { + "natural": true + } + ], + "space-in-parens": [ + "error", + "never" + ], + "spaced-comment": [ + "error", + "always" + ] + } +} \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..40bf6bd --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "ESNext", + "target": "ESNext", + "moduleResolution": "Node", + "baseUrl": "src", + "resolveJsonModule": true, + "checkJs": true, + "paths": { + "@/*": ["*.mjs"], + "#/*": ["*.json"] + } + }, + "include": [ + "src/**/*.mjs", + "src/**/*.js" + ] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..49c37ba --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "discord-tickets", + "version": "4.0.0", + "private": "true", + "description": "An open-source Discord bot for ticket management", + "main": "src/index.mjs", + "module": "src/index.mjs", + "type": "module", + "scripts": { + "contributors:add": "all-contributors add", + "contributors:generate": "all-contributors generate", + "keygen": "node scripts/keygen.mjs", + "lint": "eslint src scripts --ext mjs --fix", + "test": "echo \"There's nothing to test\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/discord-tickets/bot.git" + }, + "keywords": [ + "discord", + "tickets", + "bot" + ], + "author": "eartharoid", + "license": "GPL-3.0", + "bugs": { + "url": "https://github.com/discord-tickets/bot/issues" + }, + "homepage": "https://discordtickets.app", + "engines": { + "node": ">=16.6" + }, + "dependencies": { + "@eartharoid/dtf": "^2.0.1", + "@sapphire/framework": "^2.4.1", + "discord.js": "^13.6.0", + "dotenv-cra": "^3.0.2", + "leeks.js": "^0.2.4", + "leekslazylogger": "^4.1.7", + "semver": "^7.3.5", + "terminal-link": "^3.0.0", + "yaml": "^1.10.2" + }, + "devDependencies": { + "all-contributors-cli": "^6.20.0", + "eslint": "^8.11.0" + } +} \ No newline at end of file diff --git a/scripts/keygen.mjs b/scripts/keygen.mjs new file mode 100644 index 0000000..614b775 --- /dev/null +++ b/scripts/keygen.mjs @@ -0,0 +1,8 @@ +import { randomBytes } from 'crypto'; +import { short } from 'leeks.js'; + +console.log(short( + 'Set the "DB_ENCRYPTION_KEY" environment variable to: \n&1&!f' + + randomBytes(24).toString('hex') + + '&r\n\n&0&!e WARNING &r &e&lDo not lose the encryption key or most of the data in the database will be unreadable, requiring a new key and a full reset.' +)); \ No newline at end of file diff --git a/src/index.mjs b/src/index.mjs new file mode 100644 index 0000000..ebb2379 --- /dev/null +++ b/src/index.mjs @@ -0,0 +1,75 @@ +/** + * Discord Tickets + * Copyright (C) 2022 Isaac Saunders + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * @name discord-tickets/bot + * @description An open-source Discord bot for ticket management + * @copyright 2022 Isaac Saunders + * @license GNU-GPLv3 + */ + +import dotenv from 'dotenv-cra'; +import fs from 'fs'; +import semver from 'semver'; +import { colours } from 'leeks.js'; +import logger from './lib/logger.mjs'; +import banner from './lib/banner.mjs'; +import YAML from 'yaml'; + +process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set +dotenv.config(); // load env file + +const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8')); + +// check node version +if (!semver.satisfies(process.versions.node, pkg.engines.node)) { + console.log('\x07' + colours.redBright(`Error: Discord Tickets requires Node.js version ${pkg.engines.node}; you are currently using ${process.versions.node}`)); + process.exit(1); +} + +if (process.env.DB_ENCRYPTION_KEY === undefined) { + console.log('\x07' + colours.redBright('Error: The "DB_ENCRYPTION_KEY" environment variable is not set.\nRun "npm run keygen" to generate a key, or set it to "false" to disable encryption (not recommended).')); + process.exit(1); +} + +console.log(banner(pkg.version)); // print big title + +process.env.CONFIG_PATH ??= './user/config.yml'; // set default config file path + +if (!fs.existsSync(process.env.CONFIG_PATH)) { + const examplePath = './user/example.config.yml'; + if (!fs.existsSync(examplePath)) { + console.log('\x07' + colours.redBright('The config file does not exist, and the example file is missing so cannot be copied from.')); + process.exit(1); + } else { + console.log('Creating config file...'); + fs.copyFileSync(examplePath, process.env.CONFIG_PATH); + console.log(`Copied config to ${process.env.CONFIG_PATH}`); + } +} + +const config = YAML.parse(fs.readFileSync(process.env.CONFIG_PATH, 'utf8')); + +const log = logger(config); + + +process.on('unhandledRejection', error => { + log.notice(`Discord Tickets v${pkg.version} on Node.js v${process.versions.node} (${process.platform})`); + log.notice('An error was not caught'); + if (error instanceof Error) log.warn(`Uncaught ${error.name}`); + log.error(error); +}); + diff --git a/src/lib/banner.mjs b/src/lib/banner.mjs new file mode 100644 index 0000000..811e9ed --- /dev/null +++ b/src/lib/banner.mjs @@ -0,0 +1,22 @@ +import { colours } from 'leeks.js'; +import link from 'terminal-link'; + +export default version => colours.cyan(` +######## #### ###### ###### ####### ######## ######## +## ## ## ## ## ## ## ## ## ## ## ## ## +## ## ## ## ## ## ## ## ## ## ## +## ## ## ###### ## ## ## ######## ## ## +## ## ## ## ## ## ## ## ## ## ## +## ## ## ## ## ## ## ## ## ## ## ## ## +######## #### ###### ###### ####### ## ## ######## +######## #### ###### ## ## ######## ######## ###### + ## ## ## ## ## ## ## ## ## ## + ## ## ## ## ## ## ## ## + ## ## ## ##### ###### ## ###### + ## ## ## ## ## ## ## ## + ## ## ## ## ## ## ## ## ## ## + ## #### ###### ## ## ######## ## ###### +`) + +colours.cyanBright(`\n${link('Discord Tickets', 'https://discordtickets.app')} bot v${version} by eartharoid`) + +colours.cyanBright('\n' + link('Sponsor this project', 'https://discordtickets.app/sponsor')) + +'\n\n'; \ No newline at end of file diff --git a/src/lib/constants.js b/src/lib/constants.js new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/logger.mjs b/src/lib/logger.mjs new file mode 100644 index 0000000..6372ff3 --- /dev/null +++ b/src/lib/logger.mjs @@ -0,0 +1,44 @@ +import Logger from 'leekslazylogger'; +import DTF from '@eartharoid/dtf'; +import { short } from 'leeks.js'; + +const dtf = new DTF(); +const colours = { + critical: ['&!4&f', '&!4&f'], + debug: ['&1', '&9'], + error: ['&4', '&c'], + info: ['&3', '&b'], + notice: ['&!6&0', '&!6&0'], + success: ['&2', '&a'], + warn: ['&6', '&e'], +}; + +export default config => { + const transports = [ + new Logger.transports.ConsoleTransport({ + format: log => { + const timestamp = dtf.fill('DD/MM/YY HH:mm:ss', log.timestamp); + const colour = colours[log.level.name]; + return short(`&f&!7${timestamp}&r ${colour[0]}[${log.level.name.toUpperCase()}]&r ${log.namespace ? `&d(${log.namespace.toUpperCase()})&r ` : ''}${colour[1]}${log.content}`); + }, + level: config.logs.level, + }) + ]; + + if (config.logs.files.enabled) { + transports.push( + new Logger.transports.FileTransport({ + clean_directory: config.logs.files.keepFor, + directory: config.logs.files.directory, + level: config.logs.level, + name: 'Discord Tickets by eartharoid', + }) + ); + } + + return new Logger({ + namespaces: ['commands', 'http', 'listeners'], + transports, + }); +}; + diff --git a/user/example.config.yml b/user/example.config.yml index 8e614f8..d443b6e 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -16,7 +16,7 @@ logs: files: + directory: ./logs enabled: true keepFor: 30 # days - split: true # split stdout (info) and stderr (warnings & errors) into separate files? level: info \ No newline at end of file From e5696422400ace34a6881108be3058e81084a6cb Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 21 Mar 2022 23:28:20 +0000 Subject: [PATCH 004/409] Start on database schema --- .eslintrc.json | 16 ++++++ README.md | 4 ++ jsconfig.json | 2 +- package.json | 8 ++- prisma/schema.prisma | 75 +++++++++++++++++++++++++ scripts/keygen.mjs | 2 +- src/client.mjs | 31 ++++++++++ src/index.mjs | 15 +++-- src/lib/banner.mjs | 25 ++------- src/lib/{constants.js => constants.mjs} | 0 10 files changed, 149 insertions(+), 29 deletions(-) create mode 100644 README.md create mode 100644 prisma/schema.prisma create mode 100644 src/client.mjs rename src/lib/{constants.js => constants.mjs} (100%) diff --git a/.eslintrc.json b/.eslintrc.json index 6276302..80ed293 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,6 +12,9 @@ "ecmaVersion": 12, "sourceType": "module" }, + "plugins": [ + "unused-imports" + ], "root": true, "rules": { "array-bracket-newline": [ @@ -145,6 +148,9 @@ "no-unneeded-ternary": [ "error" ], + "no-unused-expressions": [ + "error" + ], "no-var": [ "error" ], @@ -204,6 +210,16 @@ "spaced-comment": [ "error", "always" + ], + "unused-imports/no-unused-imports": "error", + "unused-imports/no-unused-vars": [ + "warn", + { + "vars": "all", + "varsIgnorePattern": "^_", + "args": "after-used", + "argsIgnorePattern": "^_" + } ] } } \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff369df --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +CONFIG_PATH=./user/config.yml +DISCORD_TOKEN= +DB_ENCRYPTION_KEY= +DB_CONNECTION_URL="" \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json index 40bf6bd..554aae6 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -5,7 +5,7 @@ "moduleResolution": "Node", "baseUrl": "src", "resolveJsonModule": true, - "checkJs": true, + "checkJs": false, "paths": { "@/*": ["*.mjs"], "#/*": ["*.json"] diff --git a/package.json b/package.json index 49c37ba..f024b27 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,11 @@ }, "dependencies": { "@eartharoid/dtf": "^2.0.1", + "@prisma/client": "^3.11.0", "@sapphire/framework": "^2.4.1", "discord.js": "^13.6.0", - "dotenv-cra": "^3.0.2", + "dotenv": "^16.0.0", + "figlet": "^1.5.2", "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", "semver": "^7.3.5", @@ -44,6 +46,8 @@ }, "devDependencies": { "all-contributors-cli": "^6.20.0", - "eslint": "^8.11.0" + "eslint": "^8.11.0", + "eslint-plugin-unused-imports": "^2.0.0", + "prisma": "^3.11.0" } } \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..faf775c --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,75 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mysql" + url = env("DB_CONNECTION_URL") +} + +model Category { + id String @id @unique @db.VarChar(19) + tickets Ticket[] + + @@map("categories") +} + +model Guild { + id String @id @unique @db.VarChar(19) + archive Boolean @default(true) + errorColour String @default("RED") + primaryColour String @default("#009999") + successColour String @default("GREEN") + tags Tag[] + tickets Ticket[] + + @@map("guilds") +} + +model Tag { + content String @db.Text + guild Guild @relation(fields: [guildId], references: [id]) + guildId String @db.VarChar(19) + name String + + @@id([guildId, name]) + @@unique([guildId, name]) + @@map("tags") +} + +model Ticket { + id String @id @unique @db.VarChar(19) + category Category @relation(fields: [categoryId], references: [id]) + categoryId String @db.VarChar(19) + claimedBy User? @relation(name: "ClaimedTickets", fields: [claimedById], references: [id]) + claimedById String @db.VarChar(19) + closedBy User? @relation(name: "ClosedTickets", fields: [closedById], references: [id]) + closedById String @db.VarChar(19) + closedReason String? + createdBy User? @relation(name: "CreatedTickets", fields: [createdById], references: [id]) + createdById String @db.VarChar(19) + firstResponse DateTime? + guild Guild @relation(fields: [guildId], references: [id]) + guildId String @db.VarChar(19) + lastMessage DateTime? + number Int + open Boolean @default(true) + openingMessage String @db.VarChar(19) + pinnedMessages Json @default("[]") + topic String? + + @@unique([guildId, number]) + @@map("tickets") +} + +model User { + id String @id @unique @db.VarChar(19) + ticketsCreated Ticket[] @relation("CreatedTickets") + ticketsClosed Ticket[] @relation("ClosedTickets") + ticketsClaimed Ticket[] @relation("ClaimedTickets") + + @@map("users") +} diff --git a/scripts/keygen.mjs b/scripts/keygen.mjs index 614b775..9dd0443 100644 --- a/scripts/keygen.mjs +++ b/scripts/keygen.mjs @@ -4,5 +4,5 @@ import { short } from 'leeks.js'; console.log(short( 'Set the "DB_ENCRYPTION_KEY" environment variable to: \n&1&!f' + randomBytes(24).toString('hex') + - '&r\n\n&0&!e WARNING &r &e&lDo not lose the encryption key or most of the data in the database will be unreadable, requiring a new key and a full reset.' + '&r\n\n&0&!e WARNING &r &e&lIf you lose the encryption key, most of the data in the database will become unreadable, requiring a new key and a full reset.' )); \ No newline at end of file diff --git a/src/client.mjs b/src/client.mjs new file mode 100644 index 0000000..64645a9 --- /dev/null +++ b/src/client.mjs @@ -0,0 +1,31 @@ +import { + container, + SapphireClient +} from '@sapphire/framework'; +import { Intents } from 'discord.js'; +import prisma from '@prisma/client'; + +export default class Client extends SapphireClient { + constructor() { + super({ + defaultPrefix: 'tickets/', + intents: [ + Intents.FLAGS.GUILDS, + Intents.FLAGS.GUILD_MEMBERS, + Intents.FLAGS.GUILD_MESSAGES, + Intents.FLAGS.GUILD_MESSAGE_REACTIONS + ], + }); + } + async login(token) { + container.prisma = new prisma.PrismaClient(); + return super.login(token); + } + + async destroy() { + await container.prisma.$disconnect(); + return super.destroy(); + } + + +} \ No newline at end of file diff --git a/src/index.mjs b/src/index.mjs index ebb2379..cb52eb3 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -21,13 +21,15 @@ * @license GNU-GPLv3 */ -import dotenv from 'dotenv-cra'; +import dotenv from 'dotenv'; import fs from 'fs'; import semver from 'semver'; import { colours } from 'leeks.js'; import logger from './lib/logger.mjs'; import banner from './lib/banner.mjs'; import YAML from 'yaml'; +import { container } from '@sapphire/framework'; +import Client from './client.mjs'; process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set dotenv.config(); // load env file @@ -36,7 +38,7 @@ const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8')); // check node version if (!semver.satisfies(process.versions.node, pkg.engines.node)) { - console.log('\x07' + colours.redBright(`Error: Discord Tickets requires Node.js version ${pkg.engines.node}; you are currently using ${process.versions.node}`)); + console.log('\x07' + colours.redBright(`Error: Your current Node.js version, ${process.versions.node}, does not meet the requirement "${pkg.engines.node}".`)); process.exit(1); } @@ -45,8 +47,6 @@ if (process.env.DB_ENCRYPTION_KEY === undefined) { process.exit(1); } -console.log(banner(pkg.version)); // print big title - process.env.CONFIG_PATH ??= './user/config.yml'; // set default config file path if (!fs.existsSync(process.env.CONFIG_PATH)) { @@ -61,10 +61,11 @@ if (!fs.existsSync(process.env.CONFIG_PATH)) { } } +console.log(banner(pkg.version)); // print big title + const config = YAML.parse(fs.readFileSync(process.env.CONFIG_PATH, 'utf8')); - const log = logger(config); - +container.log = log; process.on('unhandledRejection', error => { log.notice(`Discord Tickets v${pkg.version} on Node.js v${process.versions.node} (${process.platform})`); @@ -73,3 +74,5 @@ process.on('unhandledRejection', error => { log.error(error); }); +const client = new Client(); +client.login(); \ No newline at end of file diff --git a/src/lib/banner.mjs b/src/lib/banner.mjs index 811e9ed..2201c2f 100644 --- a/src/lib/banner.mjs +++ b/src/lib/banner.mjs @@ -1,22 +1,9 @@ import { colours } from 'leeks.js'; +import figlet from 'figlet'; import link from 'terminal-link'; -export default version => colours.cyan(` -######## #### ###### ###### ####### ######## ######## -## ## ## ## ## ## ## ## ## ## ## ## ## -## ## ## ## ## ## ## ## ## ## ## -## ## ## ###### ## ## ## ######## ## ## -## ## ## ## ## ## ## ## ## ## ## -## ## ## ## ## ## ## ## ## ## ## ## ## -######## #### ###### ###### ####### ## ## ######## -######## #### ###### ## ## ######## ######## ###### - ## ## ## ## ## ## ## ## ## ## - ## ## ## ## ## ## ## ## - ## ## ## ##### ###### ## ###### - ## ## ## ## ## ## ## ## - ## ## ## ## ## ## ## ## ## ## - ## #### ###### ## ## ######## ## ###### -`) + -colours.cyanBright(`\n${link('Discord Tickets', 'https://discordtickets.app')} bot v${version} by eartharoid`) + -colours.cyanBright('\n' + link('Sponsor this project', 'https://discordtickets.app/sponsor')) + -'\n\n'; \ No newline at end of file +export default version => colours.cyan(figlet.textSync('Discord', { font: 'Banner3' })) + + colours.cyan('\n\n' + figlet.textSync('Tickets', { font: 'Banner3' })) + + colours.cyanBright(`\n\n${link('Discord Tickets', 'https://discordtickets.app')} bot v${version} by eartharoid`) + + colours.cyanBright('\n' + link('Sponsor this project', 'https://discordtickets.app/sponsor')) + + '\n\n'; diff --git a/src/lib/constants.js b/src/lib/constants.mjs similarity index 100% rename from src/lib/constants.js rename to src/lib/constants.mjs From 88782ec1a5283b8a1177e754a7a4bc10cd90be3b Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 22 Mar 2022 22:05:22 +0000 Subject: [PATCH 005/409] Finish database schema --- prisma/schema.prisma | 152 +++++++++++++++++++++++++++++++++++------- src/lib/constants.mjs | 0 2 files changed, 128 insertions(+), 24 deletions(-) delete mode 100644 src/lib/constants.mjs diff --git a/prisma/schema.prisma b/prisma/schema.prisma index faf775c..5c4ea34 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,7 +10,64 @@ datasource db { url = env("DB_CONNECTION_URL") } +model ArchivedChannel { + channelId String @db.VarChar(19) + name String + ticket Ticket @relation(fields: [ticketId], references: [id]) + ticketId String @db.VarChar(19) + + @@id([ticketId, channelId]) + @@unique([ticketId, channelId]) + @@map("archived_channels") +} + +model ArchivedMessage { + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId]) + authorId String @db.VarChar(19) + data Json + deleted Boolean @default(false) + edited Boolean @default(false) + id String @id @unique @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id]) + ticketId String @db.VarChar(19) + + @@map("archived_messages") +} + +model ArchivedRole { + archivedUsers ArchivedUser[] + colour String @default("7289DA") @db.Char(6) + name String + roleId String @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id]) + ticketId String @db.VarChar(19) + + @@id([ticketId, roleId]) + @@unique([ticketId, roleId]) + @@map("archived_roles") +} + +model ArchivedUser { + archivedMessages ArchivedMessage[] + avatar String + bot Boolean @default(false) + discriminator String @db.Char(4) + displayName String + role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId]) + roleId String @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id]) + ticketId String @db.VarChar(19) + userId String @db.VarChar(19) + username String + + @@id([ticketId, userId]) + @@unique([ticketId, userId]) + @@map("archived_users") +} + model Category { + guild Guild @relation(fields: [guildId], references: [id]) + guildId String @db.VarChar(19) id String @id @unique @db.VarChar(19) tickets Ticket[] @@ -18,17 +75,60 @@ model Category { } model Guild { - id String @id @unique @db.VarChar(19) - archive Boolean @default(true) - errorColour String @default("RED") - primaryColour String @default("#009999") - successColour String @default("GREEN") + archive Boolean @default(true) + categories Category[] + errorColour String @default("RED") + id String @id @unique @db.VarChar(19) + primaryColour String @default("#009999") + successColour String @default("GREEN") + surveys Survey[] tags Tag[] tickets Ticket[] @@map("guilds") } +model Survey { + guild Guild @relation(fields: [guildId], references: [id]) + guildId String @db.VarChar(19) + name String + questions SurveyQuestion[] + responses SurveyResponse[] + + @@id([guildId, name]) + @@unique([guildId, name]) + @@map("surveys") +} + +model SurveyResponse { + answers SurveyQuestionAnswer[] + id Int @id @default(autoincrement()) + survey Survey @relation(fields: [surveyGuildId, surveyName], references: [guildId, name]) + surveyGuildId String @db.VarChar(19) + surveyName String + + @@map("survey_responses") +} + +model SurveyQuestion { + content String @db.Text + id Int @id @default(autoincrement()) + survey Survey @relation(fields: [surveyGuildId, surveyName], references: [guildId, name]) + surveyGuildId String @db.VarChar(19) + surveyName String + + @@map("survey_questions") +} + +model SurveyQuestionAnswer { + content String @db.Text + id Int @id @default(autoincrement()) + surveyResponse SurveyResponse @relation(fields: [surveyResponseId], references: [id]) + surveyResponseId Int + + @@map("survey_answers") +} + model Tag { content String @db.Text guild Guild @relation(fields: [guildId], references: [id]) @@ -41,25 +141,29 @@ model Tag { } model Ticket { - id String @id @unique @db.VarChar(19) - category Category @relation(fields: [categoryId], references: [id]) - categoryId String @db.VarChar(19) - claimedBy User? @relation(name: "ClaimedTickets", fields: [claimedById], references: [id]) - claimedById String @db.VarChar(19) - closedBy User? @relation(name: "ClosedTickets", fields: [closedById], references: [id]) - closedById String @db.VarChar(19) - closedReason String? - createdBy User? @relation(name: "CreatedTickets", fields: [createdById], references: [id]) - createdById String @db.VarChar(19) - firstResponse DateTime? - guild Guild @relation(fields: [guildId], references: [id]) - guildId String @db.VarChar(19) - lastMessage DateTime? - number Int - open Boolean @default(true) - openingMessage String @db.VarChar(19) - pinnedMessages Json @default("[]") - topic String? + archivedChannels ArchivedChannel[] + archivedMessages ArchivedMessage[] + archivedRoles ArchivedRole[] + archivedUsers ArchivedUser[] + category Category @relation(fields: [categoryId], references: [id]) + categoryId String @db.VarChar(19) + claimedBy User @relation(name: "ClaimedTickets", fields: [claimedById], references: [id]) + claimedById String @db.VarChar(19) + closedBy User @relation(name: "ClosedTickets", fields: [closedById], references: [id]) + closedById String @db.VarChar(19) + closedReason String? + createdBy User @relation(name: "CreatedTickets", fields: [createdById], references: [id]) + createdById String @db.VarChar(19) + firstResponse DateTime? + guild Guild @relation(fields: [guildId], references: [id]) + guildId String @db.VarChar(19) + id String @id @unique @db.VarChar(19) + lastMessage DateTime? + number Int + open Boolean @default(true) + openingMessage String @db.VarChar(19) + pinnedMessages Json @default("[]") + topic String? @@unique([guildId, number]) @@map("tickets") diff --git a/src/lib/constants.mjs b/src/lib/constants.mjs deleted file mode 100644 index e69de29..0000000 From fbabf25f07370c3e2536e05b23e1832b9c18d3b4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 22 Mar 2022 22:12:29 +0000 Subject: [PATCH 006/409] ;) --- prisma/schema.prisma | 3 --- 1 file changed, 3 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5c4ea34..c0d1f05 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,3 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - generator client { provider = "prisma-client-js" } From 73fb4e9ef17875383b96d60789aa745114be8d15 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 22 Mar 2022 23:38:35 +0000 Subject: [PATCH 007/409] Add category fields --- prisma/schema.prisma | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c0d1f05..e5142ba 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -63,10 +63,22 @@ model ArchivedUser { } model Category { - guild Guild @relation(fields: [guildId], references: [id]) - guildId String @db.VarChar(19) - id String @id @unique @db.VarChar(19) - tickets Ticket[] + channelName String @default("ticket-{num}") + claiming Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id]) + guildId String @db.VarChar(19) + id String @id @unique @db.VarChar(19) + image String? + memberLimit Int @default(1) + name String + openingMessage String @db.Text + ping Json @default("[]") + requireTopic Boolean @default(false) + roles Json + tickets Ticket[] + totalLimit Int @default(-1) + survey Survey @relation(fields: [guildId, surveyName], references: [guildId, name]) + surveyName String @@map("categories") } @@ -86,11 +98,12 @@ model Guild { } model Survey { - guild Guild @relation(fields: [guildId], references: [id]) - guildId String @db.VarChar(19) - name String - questions SurveyQuestion[] - responses SurveyResponse[] + categories Category[] + guild Guild @relation(fields: [guildId], references: [id]) + guildId String @db.VarChar(19) + name String + questions SurveyQuestion[] + responses SurveyResponse[] @@id([guildId, name]) @@unique([guildId, name]) From 5730e60663a842e4350634cf3bce31d1f0706b85 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 23 Mar 2022 16:06:22 +0000 Subject: [PATCH 008/409] Fix survey models --- prisma/schema.prisma | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e5142ba..34e6eb1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -116,6 +116,8 @@ model SurveyResponse { survey Survey @relation(fields: [surveyGuildId, surveyName], references: [guildId, name]) surveyGuildId String @db.VarChar(19) surveyName String + user User @relation(fields: [userId], references: [id]) + userId String @db.VarChar(19) @@map("survey_responses") } @@ -180,10 +182,11 @@ model Ticket { } model User { - id String @id @unique @db.VarChar(19) - ticketsCreated Ticket[] @relation("CreatedTickets") - ticketsClosed Ticket[] @relation("ClosedTickets") - ticketsClaimed Ticket[] @relation("ClaimedTickets") + id String @id @unique @db.VarChar(19) + surveyResponses SurveyResponse[] + ticketsCreated Ticket[] @relation("CreatedTickets") + ticketsClosed Ticket[] @relation("ClosedTickets") + ticketsClaimed Ticket[] @relation("ClaimedTickets") @@map("users") } From c80dbfc963d0f35988f45038b60f40c5c642b0c7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 16 Apr 2022 14:33:46 +0100 Subject: [PATCH 009/409] Fix & comment-out surveys --- prisma/schema.prisma | 81 +++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 34e6eb1..b1d9b10 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -77,7 +77,7 @@ model Category { roles Json tickets Ticket[] totalLimit Int @default(-1) - survey Survey @relation(fields: [guildId, surveyName], references: [guildId, name]) + // survey Survey @relation(fields: [guildId, surveyName], references: [guildId, name]) surveyName String @@map("categories") @@ -90,56 +90,59 @@ model Guild { id String @id @unique @db.VarChar(19) primaryColour String @default("#009999") successColour String @default("GREEN") - surveys Survey[] + // surveys Survey[] tags Tag[] tickets Ticket[] @@map("guilds") } -model Survey { - categories Category[] - guild Guild @relation(fields: [guildId], references: [id]) - guildId String @db.VarChar(19) - name String - questions SurveyQuestion[] - responses SurveyResponse[] +// model Survey { +// categories Category[] +// guild Guild @relation(fields: [guildId], references: [id]) +// guildId String @db.VarChar(19) +// name String +// questions SurveyQuestion[] +// responses SurveyResponse[] - @@id([guildId, name]) - @@unique([guildId, name]) - @@map("surveys") -} +// @@id([guildId, name]) +// @@unique([guildId, name]) +// @@map("surveys") +// } -model SurveyResponse { - answers SurveyQuestionAnswer[] - id Int @id @default(autoincrement()) - survey Survey @relation(fields: [surveyGuildId, surveyName], references: [guildId, name]) - surveyGuildId String @db.VarChar(19) - surveyName String - user User @relation(fields: [userId], references: [id]) - userId String @db.VarChar(19) +// model SurveyResponse { +// answers SurveyQuestionAnswer[] +// id Int @id @default(autoincrement()) +// survey Survey @relation(fields: [surveyGuildId, surveyName], references: [guildId, name]) +// surveyGuildId String @db.VarChar(19) +// surveyName String +// user User @relation(fields: [userId], references: [id]) +// userId String @db.VarChar(19) - @@map("survey_responses") -} +// @@map("survey_responses") +// } -model SurveyQuestion { - content String @db.Text - id Int @id @default(autoincrement()) - survey Survey @relation(fields: [surveyGuildId, surveyName], references: [guildId, name]) - surveyGuildId String @db.VarChar(19) - surveyName String +// model SurveyQuestion { +// answers SurveyQuestionAnswer[] +// content String @db.Text +// id Int @id @default(autoincrement()) +// survey Survey @relation(fields: [surveyGuildId, surveyName], references: [guildId, name]) +// surveyGuildId String @db.VarChar(19) +// surveyName String - @@map("survey_questions") -} +// @@map("survey_questions") +// } -model SurveyQuestionAnswer { - content String @db.Text - id Int @id @default(autoincrement()) - surveyResponse SurveyResponse @relation(fields: [surveyResponseId], references: [id]) - surveyResponseId Int +// model SurveyQuestionAnswer { +// content String @db.Text +// id Int @id @default(autoincrement()) +// surveyResponse SurveyResponse @relation(fields: [surveyResponseId], references: [id]) +// surveyResponseId Int +// question SurveyQuestion @relation(fields: [questionId], references: [id]) +// questionId Int - @@map("survey_answers") -} +// @@map("survey_answers") +// } model Tag { content String @db.Text @@ -183,7 +186,7 @@ model Ticket { model User { id String @id @unique @db.VarChar(19) - surveyResponses SurveyResponse[] + // surveyResponses SurveyResponse[] ticketsCreated Ticket[] @relation("CreatedTickets") ticketsClosed Ticket[] @relation("ClosedTickets") ticketsClaimed Ticket[] @relation("ClaimedTickets") From 93b6b2125cc93297d29eb955a5b463e6a34a4aa7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 16 Apr 2022 14:36:46 +0100 Subject: [PATCH 010/409] Change user avatar properties --- prisma/schema.prisma | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b1d9b10..3e11c82 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -45,11 +45,12 @@ model ArchivedRole { } model ArchivedUser { + archivedAvatarId String archivedMessages ArchivedMessage[] - avatar String bot Boolean @default(false) discriminator String @db.Char(4) displayName String + lastAvatarHash String role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId]) roleId String @db.VarChar(19) ticket Ticket @relation(fields: [ticketId], references: [id]) @@ -185,11 +186,11 @@ model Ticket { } model User { - id String @id @unique @db.VarChar(19) + id String @id @unique @db.VarChar(19) // surveyResponses SurveyResponse[] - ticketsCreated Ticket[] @relation("CreatedTickets") - ticketsClosed Ticket[] @relation("ClosedTickets") - ticketsClaimed Ticket[] @relation("ClaimedTickets") + ticketsCreated Ticket[] @relation("CreatedTickets") + ticketsClosed Ticket[] @relation("ClosedTickets") + ticketsClaimed Ticket[] @relation("ClaimedTickets") @@map("users") } From e3b6244729a89369fd4fa8f319a908791f88354c Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 16 Apr 2022 14:36:46 +0100 Subject: [PATCH 011/409] Revert "Change user avatar properties" This reverts commit 93b6b2125cc93297d29eb955a5b463e6a34a4aa7. --- prisma/schema.prisma | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3e11c82..b1d9b10 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -45,12 +45,11 @@ model ArchivedRole { } model ArchivedUser { - archivedAvatarId String archivedMessages ArchivedMessage[] + avatar String bot Boolean @default(false) discriminator String @db.Char(4) displayName String - lastAvatarHash String role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId]) roleId String @db.VarChar(19) ticket Ticket @relation(fields: [ticketId], references: [id]) @@ -186,11 +185,11 @@ model Ticket { } model User { - id String @id @unique @db.VarChar(19) + id String @id @unique @db.VarChar(19) // surveyResponses SurveyResponse[] - ticketsCreated Ticket[] @relation("CreatedTickets") - ticketsClosed Ticket[] @relation("ClosedTickets") - ticketsClaimed Ticket[] @relation("ClaimedTickets") + ticketsCreated Ticket[] @relation("CreatedTickets") + ticketsClosed Ticket[] @relation("ClosedTickets") + ticketsClaimed Ticket[] @relation("ClaimedTickets") @@map("users") } From fb076a0c96c1d68e32269ea00ea11143a9f94772 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 16 Apr 2022 16:43:22 +0100 Subject: [PATCH 012/409] Add ticket references --- prisma/schema.prisma | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b1d9b10..30b55c4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -162,12 +162,12 @@ model Ticket { archivedUsers ArchivedUser[] category Category @relation(fields: [categoryId], references: [id]) categoryId String @db.VarChar(19) - claimedBy User @relation(name: "ClaimedTickets", fields: [claimedById], references: [id]) + claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) claimedById String @db.VarChar(19) - closedBy User @relation(name: "ClosedTickets", fields: [closedById], references: [id]) + closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) closedById String @db.VarChar(19) closedReason String? - createdBy User @relation(name: "CreatedTickets", fields: [createdById], references: [id]) + createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String @db.VarChar(19) firstResponse DateTime? guild Guild @relation(fields: [guildId], references: [id]) @@ -178,6 +178,9 @@ model Ticket { open Boolean @default(true) openingMessage String @db.VarChar(19) pinnedMessages Json @default("[]") + referencedBy Ticket[] @relation("TicketsReferencedByTicket") + references Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesId], references: [id]) + referencesId String? @db.VarChar(19) topic String? @@unique([guildId, number]) @@ -187,9 +190,9 @@ model Ticket { model User { id String @id @unique @db.VarChar(19) // surveyResponses SurveyResponse[] - ticketsCreated Ticket[] @relation("CreatedTickets") - ticketsClosed Ticket[] @relation("ClosedTickets") - ticketsClaimed Ticket[] @relation("ClaimedTickets") + ticketsCreated Ticket[] @relation("TicketsCreatedByUser") + ticketsClosed Ticket[] @relation("TicketsClosedByUser") + ticketsClaimed Ticket[] @relation("TicketsClaimedByUser") @@map("users") } From bdbadd9f154ef62c5765b6b269037e8946205a31 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 16 Apr 2022 16:43:38 +0100 Subject: [PATCH 013/409] Update eslint rules --- .eslintrc.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 80ed293..c247600 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -48,10 +48,10 @@ "comma-dangle": [ "error", { - "arrays": "never", - "exports": "never", - "functions": "never", - "imports": "never", + "arrays": "always-multiline", + "exports": "always-multiline", + "functions": "always-multiline", + "imports": "always-multiline", "objects": "always-multiline" } ], @@ -197,7 +197,7 @@ "always" ], "sort-keys": [ - "error", + "warn", "asc", { "natural": true @@ -222,4 +222,4 @@ } ] } -} \ No newline at end of file +} From 202e9ceb0d516753e7b512f34302f5413bc13e3b Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 19 Apr 2022 15:50:56 +0100 Subject: [PATCH 014/409] Update schema.prisma --- prisma/schema.prisma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 30b55c4..0435179 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -78,7 +78,7 @@ model Category { tickets Ticket[] totalLimit Int @default(-1) // survey Survey @relation(fields: [guildId, surveyName], references: [guildId, name]) - surveyName String + // surveyName String @@map("categories") } From 366bf5eeb2e9d43359dd6a9440f50db028692b6b Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 20 Apr 2022 00:31:56 +0100 Subject: [PATCH 015/409] Add tag IDs --- prisma/schema.prisma | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 30b55c4..6d25dee 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -148,9 +148,9 @@ model Tag { content String @db.Text guild Guild @relation(fields: [guildId], references: [id]) guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) name String - @@id([guildId, name]) @@unique([guildId, name]) @@map("tags") } @@ -188,7 +188,7 @@ model Ticket { } model User { - id String @id @unique @db.VarChar(19) + id String @id @unique @db.VarChar(19) // surveyResponses SurveyResponse[] ticketsCreated Ticket[] @relation("TicketsCreatedByUser") ticketsClosed Ticket[] @relation("TicketsClosedByUser") From 7c3b15d6bd83ce3da5f706f5fdd8c1261e82d6e9 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 21 Apr 2022 21:08:08 +0100 Subject: [PATCH 016/409] Changed tag content to JSON and added survey links --- prisma/schema.prisma | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 96f42b6..686b9f2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -63,22 +63,25 @@ model ArchivedUser { } model Category { - channelName String @default("ticket-{num}") - claiming Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id]) - guildId String @db.VarChar(19) - id String @id @unique @db.VarChar(19) - image String? - memberLimit Int @default(1) - name String - openingMessage String @db.Text - ping Json @default("[]") - requireTopic Boolean @default(false) - roles Json - tickets Ticket[] - totalLimit Int @default(-1) + channelName String @default("ticket-{num}") + claiming Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id]) + guildId String @db.VarChar(19) + id String @id @unique @db.VarChar(19) + image String? + memberLimit Int @default(1) + name String + openingMessage String @db.Text + ping Json @default("[]") + requireTopic Boolean @default(false) + roles Json + tickets Ticket[] + totalLimit Int @default(-1) // survey Survey @relation(fields: [guildId, surveyName], references: [guildId, name]) // surveyName String + surveyDescription String + surveyLink String + surveyTitle String @@map("categories") } @@ -145,7 +148,7 @@ model Guild { // } model Tag { - content String @db.Text + content Json guild Guild @relation(fields: [guildId], references: [id]) guildId String @db.VarChar(19) id Int @id @default(autoincrement()) From 08e6841fd473fdcf7538eee8f70ec08a04bfc195 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 21 Apr 2022 23:17:57 +0100 Subject: [PATCH 017/409] Update Category model - Add description and emoji - Make survey properties nullable --- prisma/schema.prisma | 60 +++++--------------------------------------- 1 file changed, 6 insertions(+), 54 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 686b9f2..ba38f0e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -65,6 +65,8 @@ model ArchivedUser { model Category { channelName String @default("ticket-{num}") claiming Boolean @default(false) + description String + emoji String guild Guild @relation(fields: [guildId], references: [id]) guildId String @db.VarChar(19) id String @id @unique @db.VarChar(19) @@ -77,76 +79,27 @@ model Category { roles Json tickets Ticket[] totalLimit Int @default(-1) - // survey Survey @relation(fields: [guildId, surveyName], references: [guildId, name]) - // surveyName String - surveyDescription String - surveyLink String - surveyTitle String + surveyDescription String? + surveyLink String? + surveyTitle String? @@map("categories") } model Guild { archive Boolean @default(true) + blocklist Json categories Category[] errorColour String @default("RED") id String @id @unique @db.VarChar(19) primaryColour String @default("#009999") successColour String @default("GREEN") - // surveys Survey[] tags Tag[] tickets Ticket[] @@map("guilds") } -// model Survey { -// categories Category[] -// guild Guild @relation(fields: [guildId], references: [id]) -// guildId String @db.VarChar(19) -// name String -// questions SurveyQuestion[] -// responses SurveyResponse[] - -// @@id([guildId, name]) -// @@unique([guildId, name]) -// @@map("surveys") -// } - -// model SurveyResponse { -// answers SurveyQuestionAnswer[] -// id Int @id @default(autoincrement()) -// survey Survey @relation(fields: [surveyGuildId, surveyName], references: [guildId, name]) -// surveyGuildId String @db.VarChar(19) -// surveyName String -// user User @relation(fields: [userId], references: [id]) -// userId String @db.VarChar(19) - -// @@map("survey_responses") -// } - -// model SurveyQuestion { -// answers SurveyQuestionAnswer[] -// content String @db.Text -// id Int @id @default(autoincrement()) -// survey Survey @relation(fields: [surveyGuildId, surveyName], references: [guildId, name]) -// surveyGuildId String @db.VarChar(19) -// surveyName String - -// @@map("survey_questions") -// } - -// model SurveyQuestionAnswer { -// content String @db.Text -// id Int @id @default(autoincrement()) -// surveyResponse SurveyResponse @relation(fields: [surveyResponseId], references: [id]) -// surveyResponseId Int -// question SurveyQuestion @relation(fields: [questionId], references: [id]) -// questionId Int - -// @@map("survey_answers") -// } - model Tag { content Json guild Guild @relation(fields: [guildId], references: [id]) @@ -192,7 +145,6 @@ model Ticket { model User { id String @id @unique @db.VarChar(19) - // surveyResponses SurveyResponse[] ticketsCreated Ticket[] @relation("TicketsCreatedByUser") ticketsClosed Ticket[] @relation("TicketsClosedByUser") ticketsClaimed Ticket[] @relation("TicketsClaimedByUser") From 31a47614c7edad793d74eb27b4d665552b00c618 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 21 Apr 2022 23:50:43 +0100 Subject: [PATCH 018/409] Separate ticket and Discord categories and add required roles option --- prisma/schema.prisma | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ba38f0e..8a89dd6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -24,7 +24,7 @@ model ArchivedMessage { data Json deleted Boolean @default(false) edited Boolean @default(false) - id String @id @unique @db.VarChar(19) + id String @id @db.VarChar(19) ticket Ticket @relation(fields: [ticketId], references: [id]) ticketId String @db.VarChar(19) @@ -66,17 +66,19 @@ model Category { channelName String @default("ticket-{num}") claiming Boolean @default(false) description String + discordCategory String @db.VarChar(19) emoji String guild Guild @relation(fields: [guildId], references: [id]) guildId String @db.VarChar(19) - id String @id @unique @db.VarChar(19) + id Int @id @default(autoincrement()) image String? memberLimit Int @default(1) name String openingMessage String @db.Text ping Json @default("[]") + requiredRoles Json requireTopic Boolean @default(false) - roles Json + staffRoles Json tickets Ticket[] totalLimit Int @default(-1) surveyDescription String? @@ -91,7 +93,7 @@ model Guild { blocklist Json categories Category[] errorColour String @default("RED") - id String @id @unique @db.VarChar(19) + id String @id @db.VarChar(19) primaryColour String @default("#009999") successColour String @default("GREEN") tags Tag[] @@ -117,7 +119,7 @@ model Ticket { archivedRoles ArchivedRole[] archivedUsers ArchivedUser[] category Category @relation(fields: [categoryId], references: [id]) - categoryId String @db.VarChar(19) + categoryId Int claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) claimedById String @db.VarChar(19) closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) @@ -128,7 +130,7 @@ model Ticket { firstResponse DateTime? guild Guild @relation(fields: [guildId], references: [id]) guildId String @db.VarChar(19) - id String @id @unique @db.VarChar(19) + id String @id @db.VarChar(19) lastMessage DateTime? number Int open Boolean @default(true) @@ -144,7 +146,7 @@ model Ticket { } model User { - id String @id @unique @db.VarChar(19) + id String @id @db.VarChar(19) ticketsCreated Ticket[] @relation("TicketsCreatedByUser") ticketsClosed Ticket[] @relation("TicketsClosedByUser") ticketsClaimed Ticket[] @relation("TicketsClaimedByUser") From 3e10448a1efdf9b3e93105f6d565becb096d105e Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 22 Apr 2022 00:12:53 +0100 Subject: [PATCH 019/409] Handle deletions --- prisma/schema.prisma | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8a89dd6..2fb0035 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,7 +10,7 @@ datasource db { model ArchivedChannel { channelId String @db.VarChar(19) name String - ticket Ticket @relation(fields: [ticketId], references: [id]) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @db.VarChar(19) @@id([ticketId, channelId]) @@ -19,14 +19,14 @@ model ArchivedChannel { } model ArchivedMessage { - author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId]) - authorId String @db.VarChar(19) + author ArchivedUser? @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: SetNull) + authorId String @db.VarChar(19) data Json - deleted Boolean @default(false) - edited Boolean @default(false) - id String @id @db.VarChar(19) - ticket Ticket @relation(fields: [ticketId], references: [id]) - ticketId String @db.VarChar(19) + deleted Boolean @default(false) + edited Boolean @default(false) + id String @id @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) @@map("archived_messages") } @@ -36,7 +36,7 @@ model ArchivedRole { colour String @default("7289DA") @db.Char(6) name String roleId String @db.VarChar(19) - ticket Ticket @relation(fields: [ticketId], references: [id]) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @db.VarChar(19) @@id([ticketId, roleId]) @@ -50,9 +50,9 @@ model ArchivedUser { bot Boolean @default(false) discriminator String @db.Char(4) displayName String - role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId]) + role ArchivedRole? @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: SetNull) roleId String @db.VarChar(19) - ticket Ticket @relation(fields: [ticketId], references: [id]) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @db.VarChar(19) userId String @db.VarChar(19) username String @@ -118,7 +118,7 @@ model Ticket { archivedMessages ArchivedMessage[] archivedRoles ArchivedRole[] archivedUsers ArchivedUser[] - category Category @relation(fields: [categoryId], references: [id]) + category Category @relation(fields: [categoryId], references: [id], onDelete: SetNull) categoryId Int claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) claimedById String @db.VarChar(19) From 324a6f6c309f15dabee998f8195f31f5696d168a Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 22 Apr 2022 00:17:01 +0100 Subject: [PATCH 020/409] missed one --- prisma/schema.prisma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2fb0035..930099e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -137,7 +137,7 @@ model Ticket { openingMessage String @db.VarChar(19) pinnedMessages Json @default("[]") referencedBy Ticket[] @relation("TicketsReferencedByTicket") - references Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesId], references: [id]) + references Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesId], references: [id], onDelete: SetNull) referencesId String? @db.VarChar(19) topic String? From 07eb42f5abbbffaef4be321db78323043c4e7cc1 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 22 Apr 2022 00:18:30 +0100 Subject: [PATCH 021/409] Change message content from JSON to string to allow encryption --- prisma/schema.prisma | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 930099e..fee035e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -21,7 +21,7 @@ model ArchivedChannel { model ArchivedMessage { author ArchivedUser? @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: SetNull) authorId String @db.VarChar(19) - data Json + content String deleted Boolean @default(false) edited Boolean @default(false) id String @id @db.VarChar(19) @@ -103,7 +103,7 @@ model Guild { } model Tag { - content Json + content String guild Guild @relation(fields: [guildId], references: [id]) guildId String @db.VarChar(19) id Int @id @default(autoincrement()) From 6eb85417b923d782b5bedcda3f70c0440b10854f Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 22 Apr 2022 00:26:00 +0100 Subject: [PATCH 022/409] =?UTF-8?q?=F0=9F=94=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/schema.prisma | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fee035e..a1af018 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -79,11 +79,11 @@ model Category { requiredRoles Json requireTopic Boolean @default(false) staffRoles Json - tickets Ticket[] - totalLimit Int @default(-1) surveyDescription String? surveyLink String? surveyTitle String? + tickets Ticket[] + totalLimit Int @default(-1) @@map("categories") } From acf2c96d73a46f909b2d3025a2da7e34da738573 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 22 Apr 2022 00:28:07 +0100 Subject: [PATCH 023/409] ping -> pingRoles --- prisma/schema.prisma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a1af018..6557075 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -75,7 +75,7 @@ model Category { memberLimit Int @default(1) name String openingMessage String @db.Text - ping Json @default("[]") + pingRoles Json @default("[]") requiredRoles Json requireTopic Boolean @default(false) staffRoles Json From d3448a0e68378c817848ff181f6d71944149fafe Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 22 Apr 2022 00:35:07 +0100 Subject: [PATCH 024/409] fix --- prisma/schema.prisma | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6557075..74200d0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -15,20 +15,20 @@ model ArchivedChannel { @@id([ticketId, channelId]) @@unique([ticketId, channelId]) - @@map("archived_channels") + @@map("archivedChannels") } model ArchivedMessage { - author ArchivedUser? @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: SetNull) - authorId String @db.VarChar(19) + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId]) + authorId String @db.VarChar(19) content String - deleted Boolean @default(false) - edited Boolean @default(false) - id String @id @db.VarChar(19) - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) + deleted Boolean @default(false) + edited Boolean @default(false) + id String @id @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) - @@map("archived_messages") + @@map("archivedMessages") } model ArchivedRole { @@ -41,7 +41,7 @@ model ArchivedRole { @@id([ticketId, roleId]) @@unique([ticketId, roleId]) - @@map("archived_roles") + @@map("archivedRoles") } model ArchivedUser { @@ -50,7 +50,7 @@ model ArchivedUser { bot Boolean @default(false) discriminator String @db.Char(4) displayName String - role ArchivedRole? @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: SetNull) + role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId]) roleId String @db.VarChar(19) ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @db.VarChar(19) @@ -59,7 +59,7 @@ model ArchivedUser { @@id([ticketId, userId]) @@unique([ticketId, userId]) - @@map("archived_users") + @@map("archivedUsers") } model Category { @@ -118,8 +118,8 @@ model Ticket { archivedMessages ArchivedMessage[] archivedRoles ArchivedRole[] archivedUsers ArchivedUser[] - category Category @relation(fields: [categoryId], references: [id], onDelete: SetNull) - categoryId Int + category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) + categoryId Int? claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) claimedById String @db.VarChar(19) closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) From 2bf2fdb42904e68626f2d2468625244a38e9a843 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 22 Apr 2022 15:06:15 +0100 Subject: [PATCH 025/409] Soft-delete tickets --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 74200d0..2ff81d3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -128,6 +128,7 @@ model Ticket { createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String @db.VarChar(19) firstResponse DateTime? + deleted Boolean @default(false) guild Guild @relation(fields: [guildId], references: [id]) guildId String @db.VarChar(19) id String @id @db.VarChar(19) From 57f962846926c727ef3d74d38911caabc77b76b3 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 22 Apr 2022 15:08:15 +0100 Subject: [PATCH 026/409] Add Tcket.messageCount to maintain accurate stats after archived messages are deleted --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2ff81d3..c78b167 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -133,6 +133,7 @@ model Ticket { guildId String @db.VarChar(19) id String @id @db.VarChar(19) lastMessage DateTime? + messageCount Int? number Int open Boolean @default(true) openingMessage String @db.VarChar(19) From da7a2784eade727561de0e6c63c5f97fc2f44714 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 23 Apr 2022 21:24:30 +0100 Subject: [PATCH 027/409] Add user message count --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c78b167..47c7666 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -149,6 +149,7 @@ model Ticket { model User { id String @id @db.VarChar(19) + messageCount Int @default(0) ticketsCreated Ticket[] @relation("TicketsCreatedByUser") ticketsClosed Ticket[] @relation("TicketsClosedByUser") ticketsClaimed Ticket[] @relation("TicketsClaimedByUser") From aa0a265dbf8c6df71ca840bbed51142a254bfeb0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 27 Apr 2022 00:08:07 +0100 Subject: [PATCH 028/409] Update schema --- prisma/schema.prisma | 188 +++++++++++++++++++++++++++---------------- 1 file changed, 118 insertions(+), 70 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 47c7666..d649f5a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,10 +8,11 @@ datasource db { } model ArchivedChannel { - channelId String @db.VarChar(19) + channelId String @db.VarChar(19) + createdAt DateTime @default(now()) name String - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) @@id([ticketId, channelId]) @@unique([ticketId, channelId]) @@ -19,14 +20,15 @@ model ArchivedChannel { } model ArchivedMessage { - author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId]) - authorId String @db.VarChar(19) - content String - deleted Boolean @default(false) - edited Boolean @default(false) - id String @id @db.VarChar(19) - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId]) + authorId String @db.VarChar(19) + content String + createdAt DateTime @default(now()) + deleted Boolean @default(false) + edited Boolean @default(false) + id String @id @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) @@map("archivedMessages") } @@ -34,6 +36,7 @@ model ArchivedMessage { model ArchivedRole { archivedUsers ArchivedUser[] colour String @default("7289DA") @db.Char(6) + createdAt DateTime @default(now()) name String roleId String @db.VarChar(19) ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) @@ -48,6 +51,7 @@ model ArchivedUser { archivedMessages ArchivedMessage[] avatar String bot Boolean @default(false) + createdAt DateTime @default(now()) discriminator String @db.Char(4) displayName String role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId]) @@ -63,27 +67,26 @@ model ArchivedUser { } model Category { - channelName String @default("ticket-{num}") - claiming Boolean @default(false) - description String - discordCategory String @db.VarChar(19) - emoji String - guild Guild @relation(fields: [guildId], references: [id]) - guildId String @db.VarChar(19) - id Int @id @default(autoincrement()) - image String? - memberLimit Int @default(1) - name String - openingMessage String @db.Text - pingRoles Json @default("[]") - requiredRoles Json - requireTopic Boolean @default(false) - staffRoles Json - surveyDescription String? - surveyLink String? - surveyTitle String? - tickets Ticket[] - totalLimit Int @default(-1) + channelName String @default("ticket-{num}") + claiming Boolean @default(false) + createdAt DateTime @default(now()) + description String + discordCategory String @db.VarChar(19) + emoji String + guild Guild @relation(fields: [guildId], references: [id]) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + image String? + memberLimit Int @default(1) + name String + openingMessage String @db.Text + pingRoles Json @default("[]") + questions Question[] + requiredRoles Json + requireTopic Boolean @default(false) + staffRoles Json + tickets Ticket[] + totalLimit Int @default(-1) @@map("categories") } @@ -92,8 +95,10 @@ model Guild { archive Boolean @default(true) blocklist Json categories Category[] + createdAt DateTime @default(now()) errorColour String @default("RED") id String @id @db.VarChar(19) + logChannel String? @db.VarChar(19) primaryColour String @default("#009999") successColour String @default("GREEN") tags Tag[] @@ -102,57 +107,100 @@ model Guild { @@map("guilds") } +model Question { + answers QuestionAnswer[] + id Int @id @default(autoincrement()) + category Category? @relation(fields: [categoryId], references: [id]) + categoryId Int? + maxLength Int @default(4000) + minLength Int @default(0) + order Int? + placeholder String? @db.VarChar(100) + required Boolean @default(true) + style Int @default(2) + value String? @db.VarChar(4000) + + @@map("questions") +} + +model QuestionAnswer { + id Int @id @default(autoincrement()) + ticket Ticket @relation(fields: [ticketId], references: [id]) + ticketId String @db.VarChar(19) + question Question @relation(fields: [questionId], references: [id]) + questionId Int + user User @relation(fields: [userId], references: [id]) + userId String @db.VarChar(19) + value String? @db.VarChar(4000) + + @@map("questionAnswers") +} + model Tag { - content String - guild Guild @relation(fields: [guildId], references: [id]) - guildId String @db.VarChar(19) - id Int @id @default(autoincrement()) - name String + content String + createdAt DateTime @default(now()) + guild Guild @relation(fields: [guildId], references: [id]) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + name String + regex String? @@unique([guildId, name]) @@map("tags") } model Ticket { - archivedChannels ArchivedChannel[] - archivedMessages ArchivedMessage[] - archivedRoles ArchivedRole[] - archivedUsers ArchivedUser[] - category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) - categoryId Int? - claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) - claimedById String @db.VarChar(19) - closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) - closedById String @db.VarChar(19) - closedReason String? - createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) - createdById String @db.VarChar(19) - firstResponse DateTime? - deleted Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id]) - guildId String @db.VarChar(19) - id String @id @db.VarChar(19) - lastMessage DateTime? - messageCount Int? - number Int - open Boolean @default(true) - openingMessage String @db.VarChar(19) - pinnedMessages Json @default("[]") - referencedBy Ticket[] @relation("TicketsReferencedByTicket") - references Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesId], references: [id], onDelete: SetNull) - referencesId String? @db.VarChar(19) - topic String? + archivedChannels ArchivedChannel[] + archivedMessages ArchivedMessage[] + archivedRoles ArchivedRole[] + archivedUsers ArchivedUser[] + category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) + categoryId Int? + claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) + claimedById String @db.VarChar(19) + closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) + closedById String @db.VarChar(19) + closedReason String? + createdAt DateTime @default(now()) + createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) + createdById String @db.VarChar(19) + firstResponseAt DateTime? + deleted Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id]) + guildId String @db.VarChar(19) + id String @id @db.VarChar(19) + lastMessageAt DateTime? + messageCount Int? + number Int + open Boolean @default(true) + openingMessage String @db.VarChar(19) + pinnedMessages Json @default("[]") + priority Priority? + referencedBy Ticket[] @relation("TicketsReferencedByTicket") + referencesMessageId String @db.VarChar(19) + referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) + referencesTicketId String? @db.VarChar(19) + topic String? + questionAnswers QuestionAnswer[] @@unique([guildId, number]) @@map("tickets") } model User { - id String @id @db.VarChar(19) - messageCount Int @default(0) - ticketsCreated Ticket[] @relation("TicketsCreatedByUser") - ticketsClosed Ticket[] @relation("TicketsClosedByUser") - ticketsClaimed Ticket[] @relation("TicketsClaimedByUser") + createdAt DateTime @default(now()) + id String @id @db.VarChar(19) + messageCount Int @default(0) + ticketsCreated Ticket[] @relation("TicketsCreatedByUser") + ticketsClosed Ticket[] @relation("TicketsClosedByUser") + ticketsClaimed Ticket[] @relation("TicketsClaimedByUser") + questionAnswers QuestionAnswer[] @@map("users") } + +enum Priority { + LOW + MEDIUM + HIGH +} From d1bab9799d7a2f68ba8c87a2b81d7a60d01d2b57 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 27 Apr 2022 00:15:12 +0100 Subject: [PATCH 029/409] Add feedback --- prisma/schema.prisma | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d649f5a..8f6b78f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -73,6 +73,8 @@ model Category { description String discordCategory String @db.VarChar(19) emoji String + enableFeedback Boolean @default(false) + feedback Feedback[] guild Guild @relation(fields: [guildId], references: [id]) guildId String @db.VarChar(19) id Int @id @default(autoincrement()) @@ -91,6 +93,19 @@ model Category { @@map("categories") } +model Feedback { + category Category? @relation(fields: [categoryId], references: [id]) + categoryId Int? + comment String? @db.VarChar(4000) + id Int @id @default(autoincrement()) + rating Int + ticket Ticket @relation(fields: [ticketId], references: [id]) + ticketId String @unique @db.VarChar(19) + user User? @relation(fields: [userId], references: [id]) + userId String? @db.VarChar(19) + +} + model Guild { archive Boolean @default(true) blocklist Json @@ -164,6 +179,8 @@ model Ticket { createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String @db.VarChar(19) + feedback Feedback? + feedbackId Int? firstResponseAt DateTime? deleted Boolean @default(false) guild Guild @relation(fields: [guildId], references: [id]) @@ -189,6 +206,7 @@ model Ticket { model User { createdAt DateTime @default(now()) + feedback Feedback[] id String @id @db.VarChar(19) messageCount Int @default(0) ticketsCreated Ticket[] @relation("TicketsCreatedByUser") From 4e51032a0b058250844a1417e156df83a7f45ca1 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 27 Apr 2022 15:19:16 +0100 Subject: [PATCH 030/409] Finish & fix schema --- prisma/schema.prisma | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8f6b78f..36cc6d5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,7 +22,7 @@ model ArchivedChannel { model ArchivedMessage { author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId]) authorId String @db.VarChar(19) - content String + content String @db.Text createdAt DateTime @default(now()) deleted Boolean @default(false) edited Boolean @default(false) @@ -74,7 +74,6 @@ model Category { discordCategory String @db.VarChar(19) emoji String enableFeedback Boolean @default(false) - feedback Feedback[] guild Guild @relation(fields: [guildId], references: [id]) guildId String @db.VarChar(19) id Int @id @default(autoincrement()) @@ -94,24 +93,28 @@ model Category { } model Feedback { - category Category? @relation(fields: [categoryId], references: [id]) - categoryId Int? - comment String? @db.VarChar(4000) - id Int @id @default(autoincrement()) - rating Int - ticket Ticket @relation(fields: [ticketId], references: [id]) - ticketId String @unique @db.VarChar(19) - user User? @relation(fields: [userId], references: [id]) - userId String? @db.VarChar(19) + comment String? + createdAt DateTime @default(now()) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + rating Int + ticket Ticket @relation(fields: [ticketId], references: [id]) + ticketId String @unique @db.VarChar(19) + user User? @relation(fields: [userId], references: [id]) + userId String? @db.VarChar(19) + @@map("feedback") } model Guild { + autoTag Json? archive Boolean @default(true) blocklist Json categories Category[] createdAt DateTime @default(now()) errorColour String @default("RED") + feedback Feedback[] id String @id @db.VarChar(19) logChannel String? @db.VarChar(19) primaryColour String @default("#009999") @@ -124,21 +127,24 @@ model Guild { model Question { answers QuestionAnswer[] + createdAt DateTime @default(now()) id Int @id @default(autoincrement()) category Category? @relation(fields: [categoryId], references: [id]) categoryId Int? - maxLength Int @default(4000) - minLength Int @default(0) - order Int? + label String + maxLength Int? @default(4000) + minLength Int? @default(0) + order Int placeholder String? @db.VarChar(100) required Boolean @default(true) style Int @default(2) - value String? @db.VarChar(4000) + value String? @db.Text @@map("questions") } model QuestionAnswer { + createdAt DateTime @default(now()) id Int @id @default(autoincrement()) ticket Ticket @relation(fields: [ticketId], references: [id]) ticketId String @db.VarChar(19) @@ -146,7 +152,7 @@ model QuestionAnswer { questionId Int user User @relation(fields: [userId], references: [id]) userId String @db.VarChar(19) - value String? @db.VarChar(4000) + value String? @db.Text @@map("questionAnswers") } From 94fef25df0c13b32e96e8a8de3c97bc422a41fed Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 27 Apr 2022 15:26:18 +0100 Subject: [PATCH 031/409] Update archive models --- prisma/schema.prisma | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 36cc6d5..1994504 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,7 +20,7 @@ model ArchivedChannel { } model ArchivedMessage { - author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId]) + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) authorId String @db.VarChar(19) content String @db.Text createdAt DateTime @default(now()) @@ -54,7 +54,7 @@ model ArchivedUser { createdAt DateTime @default(now()) discriminator String @db.Char(4) displayName String - role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId]) + role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) roleId String @db.VarChar(19) ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @db.VarChar(19) From 4f332829618587959e3e3ac2f5941527c9d34212 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 27 Apr 2022 15:31:47 +0100 Subject: [PATCH 032/409] ;) --- prisma/schema.prisma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1994504..81e4719 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -93,7 +93,7 @@ model Category { } model Feedback { - comment String? + comment String? @db.Text createdAt DateTime @default(now()) guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) From 1c92966064903144a47e41521c5482cd5d8099c7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 27 Apr 2022 17:22:13 +0100 Subject: [PATCH 033/409] Add Ticket.closedAt --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 81e4719..daa0a5e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -179,6 +179,7 @@ model Ticket { categoryId Int? claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) claimedById String @db.VarChar(19) + closedAt DateTime? closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) closedById String @db.VarChar(19) closedReason String? From 9cdd3f92e52fcaf72b70303c7b40b0797d9c69d5 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 4 May 2022 18:41:57 +0100 Subject: [PATCH 034/409] Add slowmode option --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index daa0a5e..aa7c14e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -83,6 +83,7 @@ model Category { openingMessage String @db.Text pingRoles Json @default("[]") questions Question[] + ratelimit Int? requiredRoles Json requireTopic Boolean @default(false) staffRoles Json From cbfbf05e8cd61b8250d5d023f75bcc0290be4f12 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 5 May 2022 21:29:28 +0100 Subject: [PATCH 035/409] mjs -> cjs --- jsconfig.json | 5 ----- package.json | 18 ++++++++---------- scripts/{keygen.mjs => keygen.js} | 6 +++--- src/client.js | 24 ++++++++++++++++++++++++ src/client.mjs | 31 ------------------------------- src/{index.mjs => index.js} | 22 +++++++++++----------- src/lib/{banner.mjs => banner.js} | 8 ++++---- src/lib/{logger.mjs => logger.js} | 12 ++++++------ src/listeners/client/ready.js | 16 ++++++++++++++++ 9 files changed, 72 insertions(+), 70 deletions(-) rename scripts/{keygen.mjs => keygen.js} (68%) create mode 100644 src/client.js delete mode 100644 src/client.mjs rename src/{index.mjs => index.js} (87%) rename src/lib/{banner.mjs => banner.js} (59%) rename src/lib/{logger.mjs => logger.js} (85%) create mode 100644 src/listeners/client/ready.js diff --git a/jsconfig.json b/jsconfig.json index 554aae6..120fd76 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -6,13 +6,8 @@ "baseUrl": "src", "resolveJsonModule": true, "checkJs": false, - "paths": { - "@/*": ["*.mjs"], - "#/*": ["*.json"] - } }, "include": [ - "src/**/*.mjs", "src/**/*.js" ] } \ No newline at end of file diff --git a/package.json b/package.json index f024b27..33d8694 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,11 @@ "version": "4.0.0", "private": "true", "description": "An open-source Discord bot for ticket management", - "main": "src/index.mjs", - "module": "src/index.mjs", - "type": "module", + "main": "src/", "scripts": { "contributors:add": "all-contributors add", "contributors:generate": "all-contributors generate", - "keygen": "node scripts/keygen.mjs", + "keygen": "node scripts/keygen", "lint": "eslint src scripts --ext mjs --fix", "test": "echo \"There's nothing to test\" && exit 1" }, @@ -32,22 +30,22 @@ "node": ">=16.6" }, "dependencies": { + "@eartharoid/dbf": "^0.0.1", "@eartharoid/dtf": "^2.0.1", - "@prisma/client": "^3.11.0", - "@sapphire/framework": "^2.4.1", + "@prisma/client": "^3.13.0", "discord.js": "^13.6.0", "dotenv": "^16.0.0", "figlet": "^1.5.2", "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", - "semver": "^7.3.5", - "terminal-link": "^3.0.0", + "semver": "^7.3.7", + "terminal-link": "^2.1.1", "yaml": "^1.10.2" }, "devDependencies": { "all-contributors-cli": "^6.20.0", - "eslint": "^8.11.0", + "eslint": "^8.14.0", "eslint-plugin-unused-imports": "^2.0.0", - "prisma": "^3.11.0" + "prisma": "^3.13.0" } } \ No newline at end of file diff --git a/scripts/keygen.mjs b/scripts/keygen.js similarity index 68% rename from scripts/keygen.mjs rename to scripts/keygen.js index 9dd0443..1750a61 100644 --- a/scripts/keygen.mjs +++ b/scripts/keygen.js @@ -1,8 +1,8 @@ -import { randomBytes } from 'crypto'; -import { short } from 'leeks.js'; +const { randomBytes } = require('crypto'); +const { short } = require('leeks.js'); console.log(short( 'Set the "DB_ENCRYPTION_KEY" environment variable to: \n&1&!f' + randomBytes(24).toString('hex') + - '&r\n\n&0&!e WARNING &r &e&lIf you lose the encryption key, most of the data in the database will become unreadable, requiring a new key and a full reset.' + '&r\n\n&0&!e WARNING &r &e&lIf you lose the encryption key, most of the data in the database will become unreadable, requiring a new key and a full reset.', )); \ No newline at end of file diff --git a/src/client.js b/src/client.js new file mode 100644 index 0000000..f24a6b8 --- /dev/null +++ b/src/client.js @@ -0,0 +1,24 @@ +const { Client: FrameworkClient }= require('@eartharoid/dbf'); +const { Intents } = require('discord.js'); +const prisma = require('@prisma/client'); + +module.exports = class Client extends FrameworkClient { + constructor() { + super({ + intents: [ + Intents.FLAGS.GUILDS, + Intents.FLAGS.GUILD_MEMBERS, + Intents.FLAGS.GUILD_MESSAGES, + ], + }); + } + async login(token) { + this.prisma = new prisma.PrismaClient(); + return super.login(token); + } + + async destroy() { + await this.prisma.$disconnect(); + return super.destroy(); + } +}; \ No newline at end of file diff --git a/src/client.mjs b/src/client.mjs deleted file mode 100644 index 64645a9..0000000 --- a/src/client.mjs +++ /dev/null @@ -1,31 +0,0 @@ -import { - container, - SapphireClient -} from '@sapphire/framework'; -import { Intents } from 'discord.js'; -import prisma from '@prisma/client'; - -export default class Client extends SapphireClient { - constructor() { - super({ - defaultPrefix: 'tickets/', - intents: [ - Intents.FLAGS.GUILDS, - Intents.FLAGS.GUILD_MEMBERS, - Intents.FLAGS.GUILD_MESSAGES, - Intents.FLAGS.GUILD_MESSAGE_REACTIONS - ], - }); - } - async login(token) { - container.prisma = new prisma.PrismaClient(); - return super.login(token); - } - - async destroy() { - await container.prisma.$disconnect(); - return super.destroy(); - } - - -} \ No newline at end of file diff --git a/src/index.mjs b/src/index.js similarity index 87% rename from src/index.mjs rename to src/index.js index cb52eb3..e98cd17 100644 --- a/src/index.mjs +++ b/src/index.js @@ -21,18 +21,17 @@ * @license GNU-GPLv3 */ -import dotenv from 'dotenv'; -import fs from 'fs'; -import semver from 'semver'; -import { colours } from 'leeks.js'; -import logger from './lib/logger.mjs'; -import banner from './lib/banner.mjs'; -import YAML from 'yaml'; -import { container } from '@sapphire/framework'; -import Client from './client.mjs'; + +const fs = require('fs'); +const semver = require('semver'); +const { colours } = require('leeks.js'); +const logger = require('./lib/logger'); +const banner = require('./lib/banner'); +const YAML = require('yaml'); +const Client = require('./client'); process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set -dotenv.config(); // load env file +require('dotenv').config(); // load env file const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8')); @@ -65,7 +64,6 @@ console.log(banner(pkg.version)); // print big title const config = YAML.parse(fs.readFileSync(process.env.CONFIG_PATH, 'utf8')); const log = logger(config); -container.log = log; process.on('unhandledRejection', error => { log.notice(`Discord Tickets v${pkg.version} on Node.js v${process.versions.node} (${process.platform})`); @@ -75,4 +73,6 @@ process.on('unhandledRejection', error => { }); const client = new Client(); +client.config = config; +client.log = log; client.login(); \ No newline at end of file diff --git a/src/lib/banner.mjs b/src/lib/banner.js similarity index 59% rename from src/lib/banner.mjs rename to src/lib/banner.js index 2201c2f..2ade4cb 100644 --- a/src/lib/banner.mjs +++ b/src/lib/banner.js @@ -1,8 +1,8 @@ -import { colours } from 'leeks.js'; -import figlet from 'figlet'; -import link from 'terminal-link'; +const { colours } = require('leeks.js'); +const figlet = require('figlet'); +const link = require('terminal-link'); -export default version => colours.cyan(figlet.textSync('Discord', { font: 'Banner3' })) + +module.exports = version => colours.cyan(figlet.textSync('Discord', { font: 'Banner3' })) + colours.cyan('\n\n' + figlet.textSync('Tickets', { font: 'Banner3' })) + colours.cyanBright(`\n\n${link('Discord Tickets', 'https://discordtickets.app')} bot v${version} by eartharoid`) + colours.cyanBright('\n' + link('Sponsor this project', 'https://discordtickets.app/sponsor')) + diff --git a/src/lib/logger.mjs b/src/lib/logger.js similarity index 85% rename from src/lib/logger.mjs rename to src/lib/logger.js index 6372ff3..4a330ae 100644 --- a/src/lib/logger.mjs +++ b/src/lib/logger.js @@ -1,6 +1,6 @@ -import Logger from 'leekslazylogger'; -import DTF from '@eartharoid/dtf'; -import { short } from 'leeks.js'; +const Logger = require('leekslazylogger'); +const DTF = require('@eartharoid/dtf'); +const { short } = require('leeks.js'); const dtf = new DTF(); const colours = { @@ -13,7 +13,7 @@ const colours = { warn: ['&6', '&e'], }; -export default config => { +module.exports = config => { const transports = [ new Logger.transports.ConsoleTransport({ format: log => { @@ -22,7 +22,7 @@ export default config => { return short(`&f&!7${timestamp}&r ${colour[0]}[${log.level.name.toUpperCase()}]&r ${log.namespace ? `&d(${log.namespace.toUpperCase()})&r ` : ''}${colour[1]}${log.content}`); }, level: config.logs.level, - }) + }), ]; if (config.logs.files.enabled) { @@ -32,7 +32,7 @@ export default config => { directory: config.logs.files.directory, level: config.logs.level, name: 'Discord Tickets by eartharoid', - }) + }), ); } diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js new file mode 100644 index 0000000..5ef5e5b --- /dev/null +++ b/src/listeners/client/ready.js @@ -0,0 +1,16 @@ +const dbf = require('@eartharoid/dbf'); + +module.exports = class extends dbf.Listener { + constructor(client) { + super(client, { + emitter: client, + event: 'ready', + id: 'clientReady', + once: true, + }); + } + + run() { + this.client.log.success('Connected to Discord as "%s"', this.client.user.tag); + } +}; \ No newline at end of file From a37d5e7613cfe5caf97a5ba66acb92f898e0d2f4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 5 May 2022 21:47:08 +0100 Subject: [PATCH 036/409] First command --- src/commands/stdin/eval.js | 20 ++++++++++++++++++++ src/index.js | 3 +-- src/listeners/client/ready.js | 4 ++-- 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 src/commands/stdin/eval.js diff --git a/src/commands/stdin/eval.js b/src/commands/stdin/eval.js new file mode 100644 index 0000000..ef394d0 --- /dev/null +++ b/src/commands/stdin/eval.js @@ -0,0 +1,20 @@ +const { StdinCommand } = require('@eartharoid/dbf'); + +module.exports = class extends StdinCommand { + constructor(client) { + super(client, { + id: 'stdinEval', + name: 'eval', + }); + } + + async run(input) { + const toEval = input.join(' '); + try { + const res = await eval(toEval); + console.log(res); + } catch (error) { + this.client.log.error(error); + } + } +}; \ No newline at end of file diff --git a/src/index.js b/src/index.js index e98cd17..4571a7c 100644 --- a/src/index.js +++ b/src/index.js @@ -22,6 +22,7 @@ */ +const pkg = require('../package.json'); const fs = require('fs'); const semver = require('semver'); const { colours } = require('leeks.js'); @@ -33,8 +34,6 @@ const Client = require('./client'); process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set require('dotenv').config(); // load env file -const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8')); - // check node version if (!semver.satisfies(process.versions.node, pkg.engines.node)) { console.log('\x07' + colours.redBright(`Error: Your current Node.js version, ${process.versions.node}, does not meet the requirement "${pkg.engines.node}".`)); diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 5ef5e5b..b6965b4 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -1,6 +1,6 @@ -const dbf = require('@eartharoid/dbf'); +const { Listener } = require('@eartharoid/dbf'); -module.exports = class extends dbf.Listener { +module.exports = class extends Listener { constructor(client) { super(client, { emitter: client, From 11ec090ab677ac8df8918159d77e76584bf24cbc Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 6 May 2022 00:01:19 +0100 Subject: [PATCH 037/409] Start on API --- .eslintrc.json | 2 +- README.md | 3 +- package.json | 2 + src/http.js | 37 +++++++++++++++++++ src/index.js | 5 ++- .../guilds/[guild]/categories/[category].js | 0 .../admin/guilds/[guild]/categories/index.js | 0 src/routes/api/admin/guilds/[guild]/index.js | 0 src/routes/api/admin/guilds/index.js | 0 src/routes/index.js | 6 +++ 10 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 src/http.js create mode 100644 src/routes/api/admin/guilds/[guild]/categories/[category].js create mode 100644 src/routes/api/admin/guilds/[guild]/categories/index.js create mode 100644 src/routes/api/admin/guilds/[guild]/index.js create mode 100644 src/routes/api/admin/guilds/index.js create mode 100644 src/routes/index.js diff --git a/.eslintrc.json b/.eslintrc.json index c247600..e472619 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -124,7 +124,7 @@ "error" ], "multiline-comment-style": [ - "warn" + "off" ], "no-console": [ "off" diff --git a/README.md b/README.md index ff369df..1cb051d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ CONFIG_PATH=./user/config.yml DISCORD_TOKEN= DB_ENCRYPTION_KEY= -DB_CONNECTION_URL="" \ No newline at end of file +DB_CONNECTION_URL="" +HTTP_BIND=3000 \ No newline at end of file diff --git a/package.json b/package.json index 33d8694..c9013cb 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,11 @@ "@prisma/client": "^3.13.0", "discord.js": "^13.6.0", "dotenv": "^16.0.0", + "fastify": "^3.29.0", "figlet": "^1.5.2", "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", + "node-dir": "^0.1.17", "semver": "^7.3.7", "terminal-link": "^2.1.1", "yaml": "^1.10.2" diff --git a/src/http.js b/src/http.js new file mode 100644 index 0000000..387d5f3 --- /dev/null +++ b/src/http.js @@ -0,0 +1,37 @@ +const fastify = require('fastify')(); +const { readFiles } = require('node-dir'); +const { join } = require('path'); + +module.exports = client => { + const dir = join(__dirname, '/routes'); + + readFiles(dir, + { + exclude: /^\./, + match: /.js$/, + }, + (err, content, next) => next(), + (err, files) => { + if (err) throw err; + + for (const file of files) { + const path = file + .substring(0, file.length - 3) // remove `.js` + .substring(dir.length) // remove higher directories + .replace(/\[(\w+)\]/gi, ':$1') // convert [] to : + .replace('/index', '') || '/'; // remove index + const route = require(file); + + Object.keys(route).forEach(method => fastify[method](path, { + config: { client }, + ...route[method], + })); // register route + } + + fastify.listen(process.env.HTTP_BIND, (err, addr) => { + if (err) client.log.error.http(err); + else client.log.success.http(`Listening at ${addr}`); + }); + }, + ); +}; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 4571a7c..a5bd741 100644 --- a/src/index.js +++ b/src/index.js @@ -30,6 +30,7 @@ const logger = require('./lib/logger'); const banner = require('./lib/banner'); const YAML = require('yaml'); const Client = require('./client'); +const http = require('./http'); process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set require('dotenv').config(); // load env file @@ -74,4 +75,6 @@ process.on('unhandledRejection', error => { const client = new Client(); client.config = config; client.log = log; -client.login(); \ No newline at end of file +client.login().then(() => { + http(client); +}); \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category].js b/src/routes/api/admin/guilds/[guild]/categories/[category].js new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/api/admin/guilds/[guild]/index.js b/src/routes/api/admin/guilds/[guild]/index.js new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/api/admin/guilds/index.js b/src/routes/api/admin/guilds/index.js new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 0000000..64cdf38 --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,6 @@ +module.exports.get = { + handler: (req, res) => { + const { client } = res.context.config; + return `Hello, I am ${client.user.username}!`; + }, +}; \ No newline at end of file From e65405c183e767d3919889d064790aa565a033f9 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 6 May 2022 00:12:38 +0100 Subject: [PATCH 038/409] Add logging --- src/http.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/http.js b/src/http.js index 387d5f3..4102191 100644 --- a/src/http.js +++ b/src/http.js @@ -1,8 +1,29 @@ const fastify = require('fastify')(); -const { readFiles } = require('node-dir'); +const { short } = require('leeks.js'); const { join } = require('path'); +const { readFiles } = require('node-dir'); module.exports = client => { + fastify.addHook('onResponse', (req, res, done) => { + done(); + const status = (res.statusCode >= 500 + ? '&4' + : res.statusCode >= 400 + ? '&6' + : res.statusCode >= 300 + ? '&3' + : res.statusCode >= 200 + ? '&2' + : '&f') + res.statusCode; + let response_time = res.getResponseTime().toFixed(2); + response_time = (response_time >= 20 + ? '&c' + : response_time >= 5 + ? '&e' + : '&a') + response_time + 'ms'; + client.log.info.http(short(`${req.ip} ${req.method} ${req.routerPath ?? '*'} &m-+>&r ${status}&b in ${response_time}`)); + }); + const dir = join(__dirname, '/routes'); readFiles(dir, From b89328125a3abfb82b3202ae6f1b34875777c0b3 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 6 May 2022 00:27:27 +0100 Subject: [PATCH 039/409] prisma --- src/client.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client.js b/src/client.js index f24a6b8..3632196 100644 --- a/src/client.js +++ b/src/client.js @@ -1,6 +1,6 @@ const { Client: FrameworkClient }= require('@eartharoid/dbf'); const { Intents } = require('discord.js'); -const prisma = require('@prisma/client'); +const { PrismaClient } = require('@prisma/client'); module.exports = class Client extends FrameworkClient { constructor() { @@ -12,8 +12,10 @@ module.exports = class Client extends FrameworkClient { ], }); } + async login(token) { - this.prisma = new prisma.PrismaClient(); + this.prisma = new PrismaClient(); + // this.prisma.$use((params, next) => {}) return super.login(token); } From 425e7ab1518cdded1d8e43653b78428c6fc17392 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 6 May 2022 11:09:56 +0100 Subject: [PATCH 040/409] en-GB --- src/lib/logger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/logger.js b/src/lib/logger.js index 4a330ae..054e9ff 100644 --- a/src/lib/logger.js +++ b/src/lib/logger.js @@ -2,7 +2,7 @@ const Logger = require('leekslazylogger'); const DTF = require('@eartharoid/dtf'); const { short } = require('leeks.js'); -const dtf = new DTF(); +const dtf = new DTF('en-GB'); const colours = { critical: ['&!4&f', '&!4&f'], debug: ['&1', '&9'], From a1823a750e157a13a71dc7d0144578177ce9db2a Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 6 May 2022 21:17:53 +0100 Subject: [PATCH 041/409] Add authentication --- README.md | 4 +- package.json | 4 ++ src/http.js | 52 +++++++++++++++++++- src/lib/http.js | 1 + src/routes/api/admin/guilds/[guild]/index.js | 7 +++ src/routes/api/admin/guilds/index.js | 9 ++++ src/routes/auth/callback.js | 29 +++++++++++ src/routes/index.js | 4 +- 8 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 src/lib/http.js create mode 100644 src/routes/auth/callback.js diff --git a/README.md b/README.md index 1cb051d..5d6f6e5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ CONFIG_PATH=./user/config.yml +DISCORD_SECRET= DISCORD_TOKEN= DB_ENCRYPTION_KEY= DB_CONNECTION_URL="" -HTTP_BIND=3000 \ No newline at end of file +HTTP_BIND=3000 +HTTP_EXTERNAL= \ No newline at end of file diff --git a/package.json b/package.json index c9013cb..faf7d7a 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,9 @@ "dependencies": { "@eartharoid/dbf": "^0.0.1", "@eartharoid/dtf": "^2.0.1", + "@fastify/cookie": "^6.0.0", + "@fastify/jwt": "^5.0.1", + "@fastify/oauth2": "^5.0.0", "@prisma/client": "^3.13.0", "discord.js": "^13.6.0", "dotenv": "^16.0.0", @@ -40,6 +43,7 @@ "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", "node-dir": "^0.1.17", + "node-fetch": "2", "semver": "^7.3.7", "terminal-link": "^2.1.1", "yaml": "^1.10.2" diff --git a/src/http.js b/src/http.js index 4102191..5e7ccbd 100644 --- a/src/http.js +++ b/src/http.js @@ -1,9 +1,50 @@ const fastify = require('fastify')(); +const oauth = require('@fastify/oauth2'); +const { randomBytes } = require('crypto'); const { short } = require('leeks.js'); const { join } = require('path'); const { readFiles } = require('node-dir'); module.exports = client => { + + // oauth2 plugin + fastify.register(oauth, { + callbackUri: `${process.env.HTTP_EXTERNAL}/auth/callback`, + credentials: { + auth: oauth.DISCORD_CONFIGURATION, + client: { + id: client.user.id, + secret: process.env.DISCORD_SECRET, + }, + }, + name: 'discord', + scope: ['identify'], + startRedirectPath: '/auth/login', + }); + + // cookies plugin + fastify.register(require('@fastify/cookie')); + + // jwt plugin + fastify.register(require('@fastify/jwt'), { + cookie: { + cookieName: 'token', + signed: false, + }, + secret: randomBytes(16).toString('hex'), + }); + + // auth + fastify.decorate('authenticate', async (req, res) => { + try { + const data = await req.jwtVerify(); + if (data.payload.expiresAt < Date.now()) res.redirect('/auth/login'); + } catch (err) { + res.send(err); + } + }); + + // logging fastify.addHook('onResponse', (req, res, done) => { done(); const status = (res.statusCode >= 500 @@ -22,8 +63,12 @@ module.exports = client => { ? '&e' : '&a') + response_time + 'ms'; client.log.info.http(short(`${req.ip} ${req.method} ${req.routerPath ?? '*'} &m-+>&r ${status}&b in ${response_time}`)); + done(); }); + fastify.addHook('onError', async (req, res, err) => client.log.error.http(err)); + + // route loading const dir = join(__dirname, '/routes'); readFiles(dir, @@ -43,12 +88,15 @@ module.exports = client => { .replace('/index', '') || '/'; // remove index const route = require(file); - Object.keys(route).forEach(method => fastify[method](path, { + Object.keys(route).forEach(method => fastify.route({ config: { client }, - ...route[method], + method: method.toUpperCase(), + path, + ...route[method](fastify), })); // register route } + // start server fastify.listen(process.env.HTTP_BIND, (err, addr) => { if (err) client.log.error.http(err); else client.log.success.http(`Listening at ${addr}`); diff --git a/src/lib/http.js b/src/lib/http.js new file mode 100644 index 0000000..9dbcdf2 --- /dev/null +++ b/src/lib/http.js @@ -0,0 +1 @@ +module.exports.domain = process.env.HTTP_EXTERNAL.match(/http(s?):\/\/(?[a-zA-Z0-9\-_.]+)(:\d+)?/).groups.domain; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/index.js b/src/routes/api/admin/guilds/[guild]/index.js index e69de29..6dff4d6 100644 --- a/src/routes/api/admin/guilds/[guild]/index.js +++ b/src/routes/api/admin/guilds/[guild]/index.js @@ -0,0 +1,7 @@ +module.exports.get = fastify => ({ + handler: (req, res) => { + const { client } = res.context.config; + return client.guilds.cache.get(req.params.guild); + }, + onRequest: [fastify.authenticate], +}); \ No newline at end of file diff --git a/src/routes/api/admin/guilds/index.js b/src/routes/api/admin/guilds/index.js index e69de29..c3e1d0a 100644 --- a/src/routes/api/admin/guilds/index.js +++ b/src/routes/api/admin/guilds/index.js @@ -0,0 +1,9 @@ +module.exports.get = fastify => ({ + handler: async (req, res) => { + const { client } = res.context.config; + const user = await client.users.fetch(req.user.payload.id); + console.log(req.user.payload.username, user?.tag); + res.send(client.guilds.cache); + }, + onRequest: [fastify.authenticate], +}); \ No newline at end of file diff --git a/src/routes/auth/callback.js b/src/routes/auth/callback.js new file mode 100644 index 0000000..5afa6b1 --- /dev/null +++ b/src/routes/auth/callback.js @@ -0,0 +1,29 @@ +const fetch = require('node-fetch'); +const { domain } = require('../../lib/http'); + +module.exports.get = () => ({ + handler: async function (req, res) { // must NOT use arrow function syntax + const { + access_token, expires_in, + } = await this.discord.getAccessTokenFromAuthorizationCodeFlow(req); + const user = await (await fetch('https://discordapp.com/api/users/@me', { headers: { 'Authorization': `Bearer ${access_token}` } })).json(); + const payload = { + avatar: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`, + discriminator: user.discriminator, + expiresAt: Date.now() + (expires_in * 1000), + id: user.id, + username: user.username, + + }; + const token = this.jwt.sign({ payload }); + res + .setCookie('token', token, { + domain: domain, + httpOnly: true, + path: '/', + sameSite: true, + secure: false, + }) + .redirect('/'); + }, +}); \ No newline at end of file diff --git a/src/routes/index.js b/src/routes/index.js index 64cdf38..0a10ed6 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,6 +1,6 @@ -module.exports.get = { +module.exports.get = () => ({ handler: (req, res) => { const { client } = res.context.config; return `Hello, I am ${client.user.username}!`; }, -}; \ No newline at end of file +}); \ No newline at end of file From ad9bd9893372f4ba365d4c8a701605a870a7cd21 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 7 May 2022 18:28:22 +0100 Subject: [PATCH 042/409] Add user route --- src/routes/api/users/@me.js | 4 ++++ src/routes/auth/callback.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 src/routes/api/users/@me.js diff --git a/src/routes/api/users/@me.js b/src/routes/api/users/@me.js new file mode 100644 index 0000000..f8fcf24 --- /dev/null +++ b/src/routes/api/users/@me.js @@ -0,0 +1,4 @@ +module.exports.get = fastify => ({ + handler: req => req.user.payload, + onRequest: [fastify.authenticate], +}); \ No newline at end of file diff --git a/src/routes/auth/callback.js b/src/routes/auth/callback.js index 5afa6b1..43cf2cc 100644 --- a/src/routes/auth/callback.js +++ b/src/routes/auth/callback.js @@ -8,7 +8,7 @@ module.exports.get = () => ({ } = await this.discord.getAccessTokenFromAuthorizationCodeFlow(req); const user = await (await fetch('https://discordapp.com/api/users/@me', { headers: { 'Authorization': `Bearer ${access_token}` } })).json(); const payload = { - avatar: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`, + avatar: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.webp`, discriminator: user.discriminator, expiresAt: Date.now() + (expires_in * 1000), id: user.id, From c082552fae2542c8474af33c155dbccf7b2ad7ea Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 7 May 2022 18:28:38 +0100 Subject: [PATCH 043/409] Update guilds route --- src/http.js | 26 ++++++++++++++++++++++++-- src/routes/api/admin/guilds/index.js | 14 +++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/http.js b/src/http.js index 5e7ccbd..5fbc234 100644 --- a/src/http.js +++ b/src/http.js @@ -1,6 +1,6 @@ const fastify = require('fastify')(); const oauth = require('@fastify/oauth2'); -const { randomBytes } = require('crypto'); +// const { randomBytes } = require('crypto'); const { short } = require('leeks.js'); const { join } = require('path'); const { readFiles } = require('node-dir'); @@ -31,7 +31,8 @@ module.exports = client => { cookieName: 'token', signed: false, }, - secret: randomBytes(16).toString('hex'), + // secret: randomBytes(16).toString('hex'), + secret: process.env.DB_ENCRYPTION_KEY, }); // auth @@ -44,6 +45,27 @@ module.exports = client => { } }); + fastify.decorate('isAdmin', async (req, res) => { + try { + const userId = req.user.payload.id; + const guildId = req.params.guild; + const guild = client.guilds.cache.get(guildId); + const guildMember = await guild.members.fetch(userId); + const isAdmin = guildMember.permissions.has('MANAGE_GUILD'); + + if (!isAdmin) { + return res.code(401).send({ + error: 'Unauthorised', + message: 'User is not authorised for this action', + statusCode: 401, + + }); + } + } catch (err) { + res.send(err); + } + }); + // logging fastify.addHook('onResponse', (req, res, done) => { done(); diff --git a/src/routes/api/admin/guilds/index.js b/src/routes/api/admin/guilds/index.js index c3e1d0a..54c5dc9 100644 --- a/src/routes/api/admin/guilds/index.js +++ b/src/routes/api/admin/guilds/index.js @@ -1,9 +1,17 @@ module.exports.get = fastify => ({ handler: async (req, res) => { const { client } = res.context.config; - const user = await client.users.fetch(req.user.payload.id); - console.log(req.user.payload.username, user?.tag); - res.send(client.guilds.cache); + const guilds = client.guilds.cache + .filter(async guild => { + const member = await guild.members.fetch(req.user.payload.id); + return member.permissions.has('MANAGE_GUILD'); + }) + .map(guild => ({ + id: guild.id, + logo: guild.iconURL(), + name: guild.name, + })); + res.send(guilds); }, onRequest: [fastify.authenticate], }); \ No newline at end of file From cb4013337f087c0d363deebce04e443fb8351e4e Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 7 May 2022 18:41:39 +0100 Subject: [PATCH 044/409] Add GET guild settings route --- prisma/schema.prisma | 2 +- src/routes/api/admin/guilds/[guild]/index.js | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index aa7c14e..ecf4641 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -111,7 +111,7 @@ model Feedback { model Guild { autoTag Json? archive Boolean @default(true) - blocklist Json + blocklist Json @default("[]") categories Category[] createdAt DateTime @default(now()) errorColour String @default("RED") diff --git a/src/routes/api/admin/guilds/[guild]/index.js b/src/routes/api/admin/guilds/[guild]/index.js index 6dff4d6..df98bcd 100644 --- a/src/routes/api/admin/guilds/[guild]/index.js +++ b/src/routes/api/admin/guilds/[guild]/index.js @@ -1,7 +1,11 @@ module.exports.get = fastify => ({ - handler: (req, res) => { - const { client } = res.context.config; - return client.guilds.cache.get(req.params.guild); + handler: async (req, res) => { + /** @type {import('../../../../../client')} */ + const client = res.context.config.client; + + const settings = await client.prisma.guild.findUnique({ where: { id: req.params.guild } }) ?? + await client.prisma.guild.create({ data: { id: req.params.guild } }); + res.send(settings); }, - onRequest: [fastify.authenticate], + onRequest: [fastify.authenticate, fastify.isAdmin], }); \ No newline at end of file From 66b46cf3e27428a7f253d1daee948af79a49fbe8 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 7 May 2022 18:46:19 +0100 Subject: [PATCH 045/409] Add PATCH guild settings route --- src/routes/api/admin/guilds/[guild]/index.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/routes/api/admin/guilds/[guild]/index.js b/src/routes/api/admin/guilds/[guild]/index.js index df98bcd..21f8331 100644 --- a/src/routes/api/admin/guilds/[guild]/index.js +++ b/src/routes/api/admin/guilds/[guild]/index.js @@ -5,6 +5,22 @@ module.exports.get = fastify => ({ const settings = await client.prisma.guild.findUnique({ where: { id: req.params.guild } }) ?? await client.prisma.guild.create({ data: { id: req.params.guild } }); + + res.send(settings); + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); + +module.exports.patch = fastify => ({ + handler: async (req, res) => { + /** @type {import('../../../../../client')} */ + const client = res.context.config.client; + + const settings = await client.prisma.guild.update({ + data: req.body, + where: { id: req.params.guild }, + }); + res.send(settings); }, onRequest: [fastify.authenticate, fastify.isAdmin], From 41ccd778c646848896ada34df7d54daa211637c5 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 7 May 2022 21:16:18 +0100 Subject: [PATCH 046/409] Add GET & POST guild categories routes --- prisma/schema.prisma | 2 +- .../admin/guilds/[guild]/categories/index.js | 58 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ecf4641..e6abcc5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -84,7 +84,7 @@ model Category { pingRoles Json @default("[]") questions Question[] ratelimit Int? - requiredRoles Json + requiredRoles Json @default("[]") requireTopic Boolean @default(false) staffRoles Json tickets Ticket[] diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index e69de29..b702ba4 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -0,0 +1,58 @@ +module.exports.get = fastify => ({ + handler: async (req, res) => { + /** @type {import('../../../../../../client')} */ + const client = res.context.config.client; + + const categories = await client.prisma.guild.findUnique({ where: { id: req.params.guild } }).categories(); + + res.send(categories); + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); + +module.exports.post = fastify => ({ + handler: async (req, res) => { + /** @type {import('../../../../../../client')} */ + const client = res.context.config.client; + + const user = await client.users.fetch(req.user.payload.id); + const guild = client.guilds.cache.get(req.params.guild); + const data = req.body; + const allow = ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES']; + + if (!data.discordCategory) { + const channel = await guild.channels.create(data.name, { + permissionOverwrites: [ + ...[ + { + deny: ['VIEW_CHANNEL'], + id: guild.roles.everyone, + }, + { + allow: allow, + id: client.user.id, + }, + ], + ...data.staffRoles.map(id => ({ + allow: allow, + id, + })), + ], + position: 1, + reason: `Tickets category created by ${user.tag}`, + type: 'GUILD_CATEGORY', + }); + data.discordCategory = channel.id; + } + + const category = await client.prisma.category.create({ + data: { + guild: { connect: { id: guild.id } }, + ...data, + }, + }); + + res.send(category); + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); From 145514a86b4cbec435e0c5a5de0bc7ba57b1080f Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 7 May 2022 21:28:33 +0100 Subject: [PATCH 047/409] Add DELETE guild settings route For completely resetting a guild --- prisma/schema.prisma | 6 +++--- src/routes/api/admin/guilds/[guild]/index.js | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e6abcc5..cbd42cd 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -74,7 +74,7 @@ model Category { discordCategory String @db.VarChar(19) emoji String enableFeedback Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id]) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) id Int @id @default(autoincrement()) image String? @@ -161,7 +161,7 @@ model QuestionAnswer { model Tag { content String createdAt DateTime @default(now()) - guild Guild @relation(fields: [guildId], references: [id]) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) id Int @id @default(autoincrement()) name String @@ -191,7 +191,7 @@ model Ticket { feedbackId Int? firstResponseAt DateTime? deleted Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id]) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) id String @id @db.VarChar(19) lastMessageAt DateTime? diff --git a/src/routes/api/admin/guilds/[guild]/index.js b/src/routes/api/admin/guilds/[guild]/index.js index 21f8331..fe54f10 100644 --- a/src/routes/api/admin/guilds/[guild]/index.js +++ b/src/routes/api/admin/guilds/[guild]/index.js @@ -1,3 +1,16 @@ +module.exports.delete = fastify => ({ + handler: async (req, res) => { + /** @type {import('../../../../../client')} */ + const client = res.context.config.client; + + await client.prisma.guild.delete({ where: { id: req.params.guild } }); + const settings = await client.prisma.guild.create({ data: { id: req.params.guild } }); + + res.send(settings); + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); + module.exports.get = fastify => ({ handler: async (req, res) => { /** @type {import('../../../../../client')} */ From 97623f3203cc026ce081456c047b6a0b5f450a2f Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 15 Jul 2022 23:19:42 +0100 Subject: [PATCH 048/409] Changes --- .eslintrc.json | 2 +- README.md | 2 +- package.json | 18 +++-- prisma/schema.prisma | 1 + scripts/keygen.js | 2 +- src/client.js | 2 + src/http.js | 44 ++++++++--- src/index.js | 4 +- src/listeners/client/ready.js | 3 +- .../admin/guilds/[guild]/categories/index.js | 4 +- src/routes/api/admin/guilds/[guild]/data.js | 13 ++++ src/routes/api/admin/guilds/[guild]/index.js | 75 +++++++++++-------- .../api/admin/guilds/[guild]/settings.js | 40 ++++++++++ src/routes/api/admin/guilds/index.js | 1 + src/routes/api/client.js | 39 ++++++++++ 15 files changed, 193 insertions(+), 57 deletions(-) create mode 100644 src/routes/api/admin/guilds/[guild]/data.js create mode 100644 src/routes/api/admin/guilds/[guild]/settings.js create mode 100644 src/routes/api/client.js diff --git a/.eslintrc.json b/.eslintrc.json index e472619..c0a3d52 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -109,7 +109,7 @@ "max-len": [ "warn", { - "code": 150, + "code": 180, "ignoreRegExpLiterals": true, "ignoreStrings": true, "ignoreTemplateLiterals": true, diff --git a/README.md b/README.md index 5d6f6e5..b3f23b4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ CONFIG_PATH=./user/config.yml DISCORD_SECRET= DISCORD_TOKEN= -DB_ENCRYPTION_KEY= +ENCRYPTION_KEY= DB_CONNECTION_URL="" HTTP_BIND=3000 HTTP_EXTERNAL= \ No newline at end of file diff --git a/package.json b/package.json index faf7d7a..c331d89 100644 --- a/package.json +++ b/package.json @@ -33,25 +33,29 @@ "@eartharoid/dbf": "^0.0.1", "@eartharoid/dtf": "^2.0.1", "@fastify/cookie": "^6.0.0", + "@fastify/cors": "^8.0.0", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.0.0", - "@prisma/client": "^3.13.0", - "discord.js": "^13.6.0", - "dotenv": "^16.0.0", - "fastify": "^3.29.0", + "@prisma/client": "^3.15.2", + "discord.js": "^13.8.1", + "dotenv": "^16.0.1", + "fastify": "^4.2.1", "figlet": "^1.5.2", + "keyv": "^4.3.2", "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", + "ms": "^2.1.3", "node-dir": "^0.1.17", - "node-fetch": "2", + "node-fetch": "^2.6.7", "semver": "^7.3.7", "terminal-link": "^2.1.1", "yaml": "^1.10.2" }, "devDependencies": { "all-contributors-cli": "^6.20.0", - "eslint": "^8.14.0", + "eslint": "^8.19.0", "eslint-plugin-unused-imports": "^2.0.0", - "prisma": "^3.13.0" + "nodemon": "^2.0.19", + "prisma": "^3.15.2" } } \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index cbd42cd..fdda180 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -116,6 +116,7 @@ model Guild { createdAt DateTime @default(now()) errorColour String @default("RED") feedback Feedback[] + footer String? @default("Discord Tickets by eartharoid") id String @id @db.VarChar(19) logChannel String? @db.VarChar(19) primaryColour String @default("#009999") diff --git a/scripts/keygen.js b/scripts/keygen.js index 1750a61..bb91e25 100644 --- a/scripts/keygen.js +++ b/scripts/keygen.js @@ -2,7 +2,7 @@ const { randomBytes } = require('crypto'); const { short } = require('leeks.js'); console.log(short( - 'Set the "DB_ENCRYPTION_KEY" environment variable to: \n&1&!f' + + 'Set the "ENCRYPTION_KEY" environment variable to: \n&1&!f' + randomBytes(24).toString('hex') + '&r\n\n&0&!e WARNING &r &e&lIf you lose the encryption key, most of the data in the database will become unreadable, requiring a new key and a full reset.', )); \ No newline at end of file diff --git a/src/client.js b/src/client.js index 3632196..ee713ee 100644 --- a/src/client.js +++ b/src/client.js @@ -1,6 +1,7 @@ const { Client: FrameworkClient }= require('@eartharoid/dbf'); const { Intents } = require('discord.js'); const { PrismaClient } = require('@prisma/client'); +const Keyv = require('keyv'); module.exports = class Client extends FrameworkClient { constructor() { @@ -16,6 +17,7 @@ module.exports = class Client extends FrameworkClient { async login(token) { this.prisma = new PrismaClient(); // this.prisma.$use((params, next) => {}) + this.keyv = new Keyv(); return super.login(token); } diff --git a/src/http.js b/src/http.js index 5fbc234..4988973 100644 --- a/src/http.js +++ b/src/http.js @@ -3,10 +3,17 @@ const oauth = require('@fastify/oauth2'); // const { randomBytes } = require('crypto'); const { short } = require('leeks.js'); const { join } = require('path'); -const { readFiles } = require('node-dir'); +const { readFiles } = require('node-dir'); module.exports = client => { + // cors plugins + fastify.register(require('@fastify/cors'), { + credentials: true, + methods: ['DELETE', 'GET', 'PATCH', 'PUT', 'POST'], + origin: true, + }); + // oauth2 plugin fastify.register(oauth, { callbackUri: `${process.env.HTTP_EXTERNAL}/auth/callback`, @@ -32,14 +39,22 @@ module.exports = client => { signed: false, }, // secret: randomBytes(16).toString('hex'), - secret: process.env.DB_ENCRYPTION_KEY, + secret: process.env.ENCRYPTION_KEY, }); // auth fastify.decorate('authenticate', async (req, res) => { try { const data = await req.jwtVerify(); - if (data.payload.expiresAt < Date.now()) res.redirect('/auth/login'); + // if (data.payload.expiresAt < Date.now()) res.redirect('/auth/login'); + if (data.payload.expiresAt < Date.now()) { + return res.code(401).send({ + error: 'Unauthorised', + message: 'You are not authenticated.', + statusCode: 401, + + }); + } } catch (err) { res.send(err); } @@ -50,14 +65,21 @@ module.exports = client => { const userId = req.user.payload.id; const guildId = req.params.guild; const guild = client.guilds.cache.get(guildId); - const guildMember = await guild.members.fetch(userId); - const isAdmin = guildMember.permissions.has('MANAGE_GUILD'); + if (!guild) { + return res.code(404).send({ + error: 'Not Found', + message: 'The requested resource could not be found.', + statusCode: 404, - if (!isAdmin) { - return res.code(401).send({ - error: 'Unauthorised', - message: 'User is not authorised for this action', - statusCode: 401, + }); + } + const guildMember = await guild.members.fetch(userId); + const isAdmin = guildMember?.permissions.has('MANAGE_GUILD'); + if (!guildMember || !isAdmin) { + return res.code(403).send({ + error: 'Forbidden', + message: 'You are not permitted for this action.', + statusCode: 403, }); } @@ -119,7 +141,7 @@ module.exports = client => { } // start server - fastify.listen(process.env.HTTP_BIND, (err, addr) => { + fastify.listen({ port: process.env.HTTP_BIND }, (err, addr) => { if (err) client.log.error.http(err); else client.log.success.http(`Listening at ${addr}`); }); diff --git a/src/index.js b/src/index.js index a5bd741..3b00fac 100644 --- a/src/index.js +++ b/src/index.js @@ -41,8 +41,8 @@ if (!semver.satisfies(process.versions.node, pkg.engines.node)) { process.exit(1); } -if (process.env.DB_ENCRYPTION_KEY === undefined) { - console.log('\x07' + colours.redBright('Error: The "DB_ENCRYPTION_KEY" environment variable is not set.\nRun "npm run keygen" to generate a key, or set it to "false" to disable encryption (not recommended).')); +if (process.env.ENCRYPTION_KEY === undefined) { + console.log('\x07' + colours.redBright('Error: The "ENCRYPTION_KEY" environment variable is not set.\nRun "npm run keygen" to generate a key, or set it to "false" to disable encryption (not recommended).')); process.exit(1); } diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index b6965b4..5ec8ed9 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -11,6 +11,7 @@ module.exports = class extends Listener { } run() { + process.title = this.client.user.tag + ' [Discord Tickets]'; this.client.log.success('Connected to Discord as "%s"', this.client.user.tag); } -}; \ No newline at end of file +}; diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index b702ba4..ed0776e 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -1,6 +1,6 @@ module.exports.get = fastify => ({ handler: async (req, res) => { - /** @type {import('../../../../../../client')} */ + /** @type {import('client')} */ const client = res.context.config.client; const categories = await client.prisma.guild.findUnique({ where: { id: req.params.guild } }).categories(); @@ -12,7 +12,7 @@ module.exports.get = fastify => ({ module.exports.post = fastify => ({ handler: async (req, res) => { - /** @type {import('../../../../../../client')} */ + /** @type {import('client')} */ const client = res.context.config.client; const user = await client.users.fetch(req.user.payload.id); diff --git a/src/routes/api/admin/guilds/[guild]/data.js b/src/routes/api/admin/guilds/[guild]/data.js new file mode 100644 index 0000000..0a3e8c8 --- /dev/null +++ b/src/routes/api/admin/guilds/[guild]/data.js @@ -0,0 +1,13 @@ +module.exports.get = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const id = req.params.guild; + const guild = client.guilds.cache.get(id) ?? {}; + const { query } = req.query; + if (!query) return {}; + const data = query.split(/\./g).reduce((acc, part) => acc && acc[part], guild); + return data; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); diff --git a/src/routes/api/admin/guilds/[guild]/index.js b/src/routes/api/admin/guilds/[guild]/index.js index fe54f10..4c51f79 100644 --- a/src/routes/api/admin/guilds/[guild]/index.js +++ b/src/routes/api/admin/guilds/[guild]/index.js @@ -1,40 +1,53 @@ -module.exports.delete = fastify => ({ - handler: async (req, res) => { - /** @type {import('../../../../../client')} */ - const client = res.context.config.client; - - await client.prisma.guild.delete({ where: { id: req.params.guild } }); - const settings = await client.prisma.guild.create({ data: { id: req.params.guild } }); - - res.send(settings); - }, - onRequest: [fastify.authenticate, fastify.isAdmin], -}); +/* eslint-disable no-underscore-dangle */ +const ms = require('ms'); module.exports.get = fastify => ({ handler: async (req, res) => { - /** @type {import('../../../../../client')} */ + /** @type {import("client")} */ const client = res.context.config.client; + const id = req.params.guild; + const cacheKey = `cache/stats/guild:${id}`; + let cached = await client.keyv.get(cacheKey); - const settings = await client.prisma.guild.findUnique({ where: { id: req.params.guild } }) ?? - await client.prisma.guild.create({ data: { id: req.params.guild } }); + if (!cached) { + const guild = client.guilds.cache.get(id); + const settings = await client.prisma.guild.findUnique({ where: { id } }) ?? + await client.prisma.guild.create({ data: { id } }); + const categories = await client.prisma.category.findMany({ + select: { + _count: { select: { tickets: true } }, + id: true, + name: true, + }, + where: { guildId: id }, + }); + const tickets = await client.prisma.ticket.findMany({ + select: { + createdAt: true, + firstResponseAt: true, + }, + where: { guildId: id }, + }); + cached = { + createdAt: settings.createdAt, + id: guild.id, + logo: guild.iconURL(), + name: guild.name, + stats: { + avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length), + categories: categories.map(c => ({ + id: c.id, + name: c.name, + tickets: c._count.tickets, + })), + tags: await client.prisma.tag.count({ where: { guildId: id } }), + tickets: tickets.length, + }, + }; + await client.keyv.set(cacheKey, cached, ms('5m')); + } - res.send(settings); - }, - onRequest: [fastify.authenticate, fastify.isAdmin], -}); - -module.exports.patch = fastify => ({ - handler: async (req, res) => { - /** @type {import('../../../../../client')} */ - const client = res.context.config.client; - - const settings = await client.prisma.guild.update({ - data: req.body, - where: { id: req.params.guild }, - }); - - res.send(settings); + return cached; }, onRequest: [fastify.authenticate, fastify.isAdmin], }); \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/settings.js b/src/routes/api/admin/guilds/[guild]/settings.js new file mode 100644 index 0000000..6004ad5 --- /dev/null +++ b/src/routes/api/admin/guilds/[guild]/settings.js @@ -0,0 +1,40 @@ +module.exports.delete = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const id = req.params.guild; + await client.prisma.guild.delete({ where: { id } }); + const settings = await client.prisma.guild.create({ data: { id } }); + + return settings; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); + +module.exports.get = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const id = req.params.guild; + const settings = await client.prisma.guild.findUnique({ where: { id } }) ?? + await client.prisma.guild.create({ data: { id } }); + + return settings; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); + +module.exports.patch = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const id = req.params.guild; + const settings = await client.prisma.guild.update({ + data: req.body, + where: { id }, + }); + + return settings; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); \ No newline at end of file diff --git a/src/routes/api/admin/guilds/index.js b/src/routes/api/admin/guilds/index.js index 54c5dc9..11ecb7e 100644 --- a/src/routes/api/admin/guilds/index.js +++ b/src/routes/api/admin/guilds/index.js @@ -4,6 +4,7 @@ module.exports.get = fastify => ({ const guilds = client.guilds.cache .filter(async guild => { const member = await guild.members.fetch(req.user.payload.id); + if (!member) return false; return member.permissions.has('MANAGE_GUILD'); }) .map(guild => ({ diff --git a/src/routes/api/client.js b/src/routes/api/client.js new file mode 100644 index 0000000..160ef5e --- /dev/null +++ b/src/routes/api/client.js @@ -0,0 +1,39 @@ +const ms = require('ms'); + +module.exports.get = () => ({ + handler: async (req, res) => { + /** @type {import("client")} */ + const client = res.context.config.client; + const cacheKey = 'cache/stats/client'; + let cached = await client.keyv.get(cacheKey); + + if (!cached) { + const tickets = await client.prisma.ticket.findMany({ + select: { + createdAt: true, + firstResponseAt: true, + }, + }); + const users = await client.prisma.user.findMany({ select: { messageCount: true } }); + cached = { + avatar: client.user.avatarURL(), + discriminator: client.user.discriminator, + id: client.user.id, + stats: { + activatedUsers: users.length, + archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they get deleted + avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length), + categories: await client.prisma.category.count(), + guilds: client.guilds.cache.size, + members: client.guilds.cache.reduce((t, g) => t + g.memberCount, 0), + tags: await client.prisma.tag.count(), + tickets: tickets.length, + }, + username: client.user.username, + }; + await client.keyv.set(cacheKey, cached, ms('5m')); + } + + return cached; + }, +}); \ No newline at end of file From 79462e83e62f042ad9cf5f5a2bf30169eafd03fa Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 16 Jul 2022 22:18:50 +0100 Subject: [PATCH 049/409] Settings, encryption, logging --- package.json | 7 +- prisma/schema.prisma | 6 +- src/client.js | 24 ++++- src/i18n/en-GB.yml | 19 ++++ src/index.js | 4 +- src/lib/logging.js | 97 +++++++++++++++++++ src/lib/prisma.js | 41 ++++++++ src/lib/strings.js | 1 + .../api/admin/guilds/[guild]/settings.js | 25 ++++- src/routes/api/locales.js | 7 ++ 10 files changed, 221 insertions(+), 10 deletions(-) create mode 100644 src/i18n/en-GB.yml create mode 100644 src/lib/logging.js create mode 100644 src/lib/prisma.js create mode 100644 src/lib/strings.js create mode 100644 src/routes/api/locales.js diff --git a/package.json b/package.json index c331d89..d7d6a6c 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,14 @@ "dependencies": { "@eartharoid/dbf": "^0.0.1", "@eartharoid/dtf": "^2.0.1", + "@eartharoid/i18n": "^1.0.4", "@fastify/cookie": "^6.0.0", "@fastify/cors": "^8.0.0", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.0.0", - "@prisma/client": "^3.15.2", + "@prisma/client": "^4.0.0", + "cryptr": "^6.0.3", + "deep-object-diff": "^1.1.7", "discord.js": "^13.8.1", "dotenv": "^16.0.1", "fastify": "^4.2.1", @@ -56,6 +59,6 @@ "eslint": "^8.19.0", "eslint-plugin-unused-imports": "^2.0.0", "nodemon": "^2.0.19", - "prisma": "^3.15.2" + "prisma": "^4.0.0" } } \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fdda180..06f40cc 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -109,7 +109,8 @@ model Feedback { } model Guild { - autoTag Json? + autoClose Int? + autoTag Json @default("[]") archive Boolean @default(true) blocklist Json @default("[]") categories Category[] @@ -118,11 +119,14 @@ model Guild { feedback Feedback[] footer String? @default("Discord Tickets by eartharoid") id String @id @db.VarChar(19) + locale String @default("en-GB") logChannel String? @db.VarChar(19) primaryColour String @default("#009999") + staleAfter Int? successColour String @default("GREEN") tags Tag[] tickets Ticket[] + workingHours Json @default("[null, null, null, null, null, null, null]") @@map("guilds") } diff --git a/src/client.js b/src/client.js index ee713ee..b2683aa 100644 --- a/src/client.js +++ b/src/client.js @@ -2,9 +2,14 @@ const { Client: FrameworkClient }= require('@eartharoid/dbf'); const { Intents } = require('discord.js'); const { PrismaClient } = require('@prisma/client'); const Keyv = require('keyv'); +const I18n = require('@eartharoid/i18n'); +const fs = require('fs'); +const { join } = require('path'); +const YAML = require('yaml'); +const middleware = require('./lib/prisma'); module.exports = class Client extends FrameworkClient { - constructor() { + constructor(config, log) { super({ intents: [ Intents.FLAGS.GUILDS, @@ -12,11 +17,26 @@ module.exports = class Client extends FrameworkClient { Intents.FLAGS.GUILD_MESSAGES, ], }); + + const locales = {}; + fs.readdirSync(join(__dirname, 'i18n')) + .filter(file => file.endsWith('.yml')) + .forEach(file => { + const data = fs.readFileSync(join(__dirname, 'i18n/' + file), { encoding: 'utf8' }); + const name = file.slice(0, file.length - 4); + locales[name] = YAML.parse(data); + }); + + /** @type {I18n} */ + this.i18n = new I18n('en-GB', locales); + this.config = config; + this.log = log; } async login(token) { + /** @type {PrismaClient} */ this.prisma = new PrismaClient(); - // this.prisma.$use((params, next) => {}) + this.prisma.$use(middleware); this.keyv = new Keyv(); return super.login(token); } diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml new file mode 100644 index 0000000..8809366 --- /dev/null +++ b/src/i18n/en-GB.yml @@ -0,0 +1,19 @@ +command: +log: + admin: + description: + joined: '{user} {verb} {targetType}' + target: + category: 'a category' + settings: 'the settings' + differences: 'Differences' + title: + joined: '{targetType} {verb}' + target: + category: 'Category' + settings: 'Settings' + verb: + create: 'created' + delete: 'deleted' + update: 'updated' + ticket: \ No newline at end of file diff --git a/src/index.js b/src/index.js index 3b00fac..9411cb0 100644 --- a/src/index.js +++ b/src/index.js @@ -72,9 +72,7 @@ process.on('unhandledRejection', error => { log.error(error); }); -const client = new Client(); -client.config = config; -client.log = log; +const client = new Client(config, log); client.login().then(() => { http(client); }); \ No newline at end of file diff --git a/src/lib/logging.js b/src/lib/logging.js new file mode 100644 index 0000000..a586614 --- /dev/null +++ b/src/lib/logging.js @@ -0,0 +1,97 @@ +const { MessageEmbed } = require('discord.js'); + +/** + * @param {import("client")} client + * @param {string} guildId + * @returns {import("discord.js").TextChannel?} +*/ +async function getLogChannel(client, guildId) { + const { logChannel: channelId } = await client.prisma.guild.findUnique({ + select: { logChannel: true }, + where: { id: guildId }, + }); + return channelId && client.channels.cache.get(channelId); +} + +/** + * @param {import("client")} client + * @param {*} target + * @returns {string} target.type + * @returns {string} target.id +*/ +async function getTargetName(client, target) { + if (target.type === 'settings') { + return client.guilds.cache.get(target.id).name; + } else { + const row = await client.prisma[target.type].findUnique({ where: { id: target.id } }); + return row.name ?? target.id; + } +} + +/** + * @param {import("client")} client + * @param {object} details + * @param {string} details.guildId + * @param {string} details.userId + * @param {string} details.action +*/ +async function logAdminEvent(client, { + guildId, userId, action, target, diff, +}) { + const user = await client.users.fetch(userId); + client.log.info(`${user.tag} ${action}d ${target.type} ${target.id}`); + const settings = await client.prisma.guild.findUnique({ + select: { + footer: true, + locale: true, + logChannel: true, + }, + where: { id: guildId }, + }); + if (!settings.logChannel) return; + const getMessage = client.i18n.getLocale(settings.locale); + const i18nOptions = { + user: `<@${user.id}>`, + verb: getMessage(`log.admin.verb.${action}`), + }; + const channel = client.channels.cache.get(settings.logChannel); + if (!channel) return; + const targetName = await getTargetName(client, target); + return await channel.send({ + embeds: [ + new MessageEmbed() + .setColor('ORANGE') + .setAuthor({ + iconURL: user.avatarURL(), + name: user.username, + }) + .setTitle(getMessage('log.admin.title.joined', { + ...i18nOptions, + targetType: getMessage(`log.admin.title.target.${target.type}`), + verb: getMessage(`log.admin.verb.${action}`), + })) + .setDescription(getMessage('log.admin.description.joined', { + ...i18nOptions, + targetType: getMessage(`log.admin.description.target.${target.type}`), + verb: getMessage(`log.admin.verb.${action}`), + })) + .addField(getMessage(`log.admin.title.target.${target.type}`), targetName), + // .setFooter({ + // iconURL: client.guilds.cache.get(guildId).iconURL(), + // text: settings.footer, + // }), + ...[ + action === 'update' && diff && + new MessageEmbed() + .setColor('ORANGE') + .setTitle(getMessage('log.admin.differences')) + .setDescription(`\`\`\`json\n${JSON.stringify(diff)}\n\`\`\``), + ], + ], + }); +} + +module.exports = { + getLogChannel, + logAdminEvent, +}; \ No newline at end of file diff --git a/src/lib/prisma.js b/src/lib/prisma.js new file mode 100644 index 0000000..81c1e42 --- /dev/null +++ b/src/lib/prisma.js @@ -0,0 +1,41 @@ +const Cryptr = require('cryptr'); +const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); +const fields = [ + 'name', + 'content', + 'username', + 'displayName', + 'channelName', + 'openingMessage', + 'description', + 'value', + 'placeholder', + 'closedReason', + 'topic', + 'comment', + 'label', + 'regex', +]; +const shouldEncrypt = ['create', 'createMany', 'update', 'updateMany', 'upsert']; +const shouldDecrypt = ['findUnique', 'findFirst', 'findMany']; + +module.exports = async (params, next) => { + if (params.args.data && shouldEncrypt.includes(params.action)) { + for (const field of fields) { + if (field in params.args.data) { + params.args.data[field] = cryptr.encrypt(params.args.data[field]); + } + } + } + + const result = await next(params); + + if (result && shouldDecrypt.includes(params.action)) { + for (const field of fields) { + if (field in result) { + result[field] = cryptr.decrypt(params.result[field]); + } + } + } + return result; +}; \ No newline at end of file diff --git a/src/lib/strings.js b/src/lib/strings.js new file mode 100644 index 0000000..12c79f1 --- /dev/null +++ b/src/lib/strings.js @@ -0,0 +1 @@ +module.exports.capitalise = string => string.charAt(0).toUpperCase() + string.slice(1); \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/settings.js b/src/routes/api/admin/guilds/[guild]/settings.js index 6004ad5..32887a7 100644 --- a/src/routes/api/admin/guilds/[guild]/settings.js +++ b/src/routes/api/admin/guilds/[guild]/settings.js @@ -1,3 +1,6 @@ +const { logAdminEvent } = require('../../../../../lib/logging.js'); +const { updatedDiff } = require('deep-object-diff'); + module.exports.delete = fastify => ({ handler: async (req, res) => { /** @type {import('client')} */ @@ -5,7 +8,15 @@ module.exports.delete = fastify => ({ const id = req.params.guild; await client.prisma.guild.delete({ where: { id } }); const settings = await client.prisma.guild.create({ data: { id } }); - + logAdminEvent(client, { + action: 'delete', + guildId: id, + target: { + id, + type: 'settings', + }, + userId: req.user.payload.id, + }); return settings; }, onRequest: [fastify.authenticate, fastify.isAdmin], @@ -29,11 +40,21 @@ module.exports.patch = fastify => ({ /** @type {import('client')} */ const client = res.context.config.client; const id = req.params.guild; + const original = await client.prisma.guild.findUnique({ where: { id } }); const settings = await client.prisma.guild.update({ data: req.body, where: { id }, }); - + logAdminEvent(client, { + action: 'update', + diff: updatedDiff(original, settings), + guildId: id, + target: { + id, + type: 'settings', + }, + userId: req.user.payload.id, + }); return settings; }, onRequest: [fastify.authenticate, fastify.isAdmin], diff --git a/src/routes/api/locales.js b/src/routes/api/locales.js new file mode 100644 index 0000000..2da51c1 --- /dev/null +++ b/src/routes/api/locales.js @@ -0,0 +1,7 @@ +module.exports.get = () => ({ + handler: async (req, res) => { + /** @type {import("client")} */ + const client = res.context.config.client; + return client.i18n.locales; + }, +}); \ No newline at end of file From 574d101637515cdf38d27b370f691fa5d88590ab Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 16 Jul 2022 23:07:18 +0100 Subject: [PATCH 050/409] Update schema --- prisma/schema.prisma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 06f40cc..01fccb5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -126,7 +126,7 @@ model Guild { successColour String @default("GREEN") tags Tag[] tickets Ticket[] - workingHours Json @default("[null, null, null, null, null, null, null]") + workingHours Json @default("[\"UTC\", null, null, null, null, null, null, null]") @@map("guilds") } From 5aac998bee3faaaad0e2b2fa01af04b957a8730e Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 17 Jul 2022 14:13:44 +0100 Subject: [PATCH 051/409] new log diff --- .eslintrc.json | 3 +++ package.json | 2 +- prisma/schema.prisma | 2 +- src/lib/logging.js | 21 +++++++++++++++++-- src/listeners/client/ready.js | 3 ++- .../api/admin/guilds/[guild]/settings.js | 9 ++++++-- 6 files changed, 33 insertions(+), 7 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index c0a3d52..dc66be5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -129,6 +129,9 @@ "no-console": [ "off" ], + "no-prototype-builtins": [ + "off" + ], "no-return-assign": [ "error" ], diff --git a/package.json b/package.json index d7d6a6c..f2bbf8c 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "@fastify/oauth2": "^5.0.0", "@prisma/client": "^4.0.0", "cryptr": "^6.0.3", - "deep-object-diff": "^1.1.7", "discord.js": "^13.8.1", "dotenv": "^16.0.1", "fastify": "^4.2.1", @@ -50,6 +49,7 @@ "ms": "^2.1.3", "node-dir": "^0.1.17", "node-fetch": "^2.6.7", + "object-diffy": "^1.0.4", "semver": "^7.3.7", "terminal-link": "^2.1.1", "yaml": "^1.10.2" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 01fccb5..eb31fd9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -126,7 +126,7 @@ model Guild { successColour String @default("GREEN") tags Tag[] tickets Ticket[] - workingHours Json @default("[\"UTC\", null, null, null, null, null, null, null]") + workingHours Json @default("[\"UTC\", [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"]]") @@map("guilds") } diff --git a/src/lib/logging.js b/src/lib/logging.js index a586614..535f041 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -1,4 +1,20 @@ const { MessageEmbed } = require('discord.js'); +const { diff: getDiff } = require('object-diffy'); + +function makeDiff({ + original, updated, +}) { + const diff = getDiff(original, updated); + const fields = []; + for (const key in diff) { + if (key === 'createdAt') continue; // object-diffy doesn't like dates + fields.push({ + name: key, + value: `\`\`\`diff\n${diff[key].from && `- ${diff[key].from}\n`}\n${diff[key].to && `+ ${diff[key].to}\n`}\n\`\`\``, + }); + } + return fields; +} /** * @param {import("client")} client @@ -57,6 +73,7 @@ async function logAdminEvent(client, { const channel = client.channels.cache.get(settings.logChannel); if (!channel) return; const targetName = await getTargetName(client, target); + return await channel.send({ embeds: [ new MessageEmbed() @@ -81,11 +98,11 @@ async function logAdminEvent(client, { // text: settings.footer, // }), ...[ - action === 'update' && diff && + diff && new MessageEmbed() .setColor('ORANGE') .setTitle(getMessage('log.admin.differences')) - .setDescription(`\`\`\`json\n${JSON.stringify(diff)}\n\`\`\``), + .setFields(makeDiff(diff)), ], ], }); diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 5ec8ed9..4315f0a 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -11,7 +11,8 @@ module.exports = class extends Listener { } run() { - process.title = this.client.user.tag + ' [Discord Tickets]'; + // process.title = `"[Discord Tickets] ${this.client.user.tag}"`; // too long and gets cut off + process.title = 'tickets'; this.client.log.success('Connected to Discord as "%s"', this.client.user.tag); } }; diff --git a/src/routes/api/admin/guilds/[guild]/settings.js b/src/routes/api/admin/guilds/[guild]/settings.js index 32887a7..ee3757a 100644 --- a/src/routes/api/admin/guilds/[guild]/settings.js +++ b/src/routes/api/admin/guilds/[guild]/settings.js @@ -1,5 +1,4 @@ const { logAdminEvent } = require('../../../../../lib/logging.js'); -const { updatedDiff } = require('deep-object-diff'); module.exports.delete = fastify => ({ handler: async (req, res) => { @@ -37,6 +36,8 @@ module.exports.get = fastify => ({ module.exports.patch = fastify => ({ handler: async (req, res) => { + if (req.body.hasOwnProperty('id')) delete req.body.id; + if (req.body.hasOwnProperty('createdAt')) delete req.body.createdAt; /** @type {import('client')} */ const client = res.context.config.client; const id = req.params.guild; @@ -47,8 +48,12 @@ module.exports.patch = fastify => ({ }); logAdminEvent(client, { action: 'update', - diff: updatedDiff(original, settings), + diff: { + original, + updated: settings, + }, guildId: id, + original, target: { id, type: 'settings', From 3e3ac50f08346efdd04e60835383b38282314e42 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 17 Jul 2022 18:59:58 +0100 Subject: [PATCH 052/409] Fix diff --- src/lib/logging.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/logging.js b/src/lib/logging.js index 535f041..1c57dd7 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -8,9 +8,12 @@ function makeDiff({ const fields = []; for (const key in diff) { if (key === 'createdAt') continue; // object-diffy doesn't like dates + const from = diff[key].from === null ? '' : `- ${diff[key].from}\n`; + const to = diff[key].to === null ? '' : `+ ${diff[key].to}\n`; fields.push({ + inline: true, name: key, - value: `\`\`\`diff\n${diff[key].from && `- ${diff[key].from}\n`}\n${diff[key].to && `+ ${diff[key].to}\n`}\n\`\`\``, + value: `\`\`\`diff\n${from + to}\n\`\`\``, }); } return fields; @@ -98,10 +101,10 @@ async function logAdminEvent(client, { // text: settings.footer, // }), ...[ - diff && + diff?.original && new MessageEmbed() .setColor('ORANGE') - .setTitle(getMessage('log.admin.differences')) + .setTitle(getMessage('log.admin.changes')) .setFields(makeDiff(diff)), ], ], From 3aa9a9b2c607f2fad7350f815fc1499d8bfd626a Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 17 Jul 2022 19:00:12 +0100 Subject: [PATCH 053/409] Encryption/schema fixes --- prisma/schema.prisma | 20 ++++++++++---------- src/i18n/en-GB.yml | 2 +- src/lib/prisma.js | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index eb31fd9..5a06197 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -53,13 +53,13 @@ model ArchivedUser { bot Boolean @default(false) createdAt DateTime @default(now()) discriminator String @db.Char(4) - displayName String + displayName String @db.VarChar(512) role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) roleId String @db.VarChar(19) ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @db.VarChar(19) userId String @db.VarChar(19) - username String + username String @db.VarChar(512) @@id([ticketId, userId]) @@unique([ticketId, userId]) @@ -70,7 +70,7 @@ model Category { channelName String @default("ticket-{num}") claiming Boolean @default(false) createdAt DateTime @default(now()) - description String + description String @db.VarChar(512) discordCategory String @db.VarChar(19) emoji String enableFeedback Boolean @default(false) @@ -79,7 +79,7 @@ model Category { id Int @id @default(autoincrement()) image String? memberLimit Int @default(1) - name String + name String @db.VarChar(512) openingMessage String @db.Text pingRoles Json @default("[]") questions Question[] @@ -137,7 +137,7 @@ model Question { id Int @id @default(autoincrement()) category Category? @relation(fields: [categoryId], references: [id]) categoryId Int? - label String + label String @db.VarChar(512) maxLength Int? @default(4000) minLength Int? @default(0) order Int @@ -164,13 +164,13 @@ model QuestionAnswer { } model Tag { - content String + content String @db.Text() createdAt DateTime @default(now()) guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) id Int @id @default(autoincrement()) - name String - regex String? + name String @db.VarChar(512) + regex String? @db.VarChar(512) @@unique([guildId, name]) @@map("tags") @@ -188,7 +188,7 @@ model Ticket { closedAt DateTime? closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) closedById String @db.VarChar(19) - closedReason String? + closedReason String? @db.VarChar(512) createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String @db.VarChar(19) @@ -210,7 +210,7 @@ model Ticket { referencesMessageId String @db.VarChar(19) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? @db.VarChar(19) - topic String? + topic String? @db.VarChar(512) questionAnswers QuestionAnswer[] @@unique([guildId, number]) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 8809366..2657f6a 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -1,12 +1,12 @@ command: log: admin: + changes: 'Changes' description: joined: '{user} {verb} {targetType}' target: category: 'a category' settings: 'the settings' - differences: 'Differences' title: joined: '{targetType} {verb}' target: diff --git a/src/lib/prisma.js b/src/lib/prisma.js index 81c1e42..ad8112b 100644 --- a/src/lib/prisma.js +++ b/src/lib/prisma.js @@ -22,7 +22,7 @@ const shouldDecrypt = ['findUnique', 'findFirst', 'findMany']; module.exports = async (params, next) => { if (params.args.data && shouldEncrypt.includes(params.action)) { for (const field of fields) { - if (field in params.args.data) { + if (field in params.args.data && params.args.data[field] !== null && params.args.data[field] !== undefined) { params.args.data[field] = cryptr.encrypt(params.args.data[field]); } } @@ -32,7 +32,7 @@ module.exports = async (params, next) => { if (result && shouldDecrypt.includes(params.action)) { for (const field of fields) { - if (field in result) { + if (field in result && result[field] !== null && result[field] !== undefined) { result[field] = cryptr.decrypt(params.result[field]); } } From 24d5e6d99d839d9e9824643def13ca3c2fb66e92 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 17 Jul 2022 22:33:42 +0100 Subject: [PATCH 054/409] Fixes and stuff --- package.json | 2 +- src/client.js | 2 +- src/i18n/en-GB.yml | 4 +- src/lib/logger.js | 2 +- src/lib/logging.js | 23 +--- src/lib/prisma.js | 57 +++++++--- .../guilds/[guild]/categories/[category].js | 102 ++++++++++++++++++ .../admin/guilds/[guild]/categories/index.js | 46 +++++++- .../api/admin/guilds/[guild]/settings.js | 3 +- 9 files changed, 198 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index f2bbf8c..11934a0 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@fastify/oauth2": "^5.0.0", "@prisma/client": "^4.0.0", "cryptr": "^6.0.3", - "discord.js": "^13.8.1", + "discord.js": "^13.9.0", "dotenv": "^16.0.1", "fastify": "^4.2.1", "figlet": "^1.5.2", diff --git a/src/client.js b/src/client.js index b2683aa..1b14547 100644 --- a/src/client.js +++ b/src/client.js @@ -36,7 +36,7 @@ module.exports = class Client extends FrameworkClient { async login(token) { /** @type {PrismaClient} */ this.prisma = new PrismaClient(); - this.prisma.$use(middleware); + this.prisma.$use(middleware(this.log)); this.keyv = new Keyv(); return super.login(token); } diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 2657f6a..9998e1e 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -1,7 +1,9 @@ command: log: admin: - changes: 'Changes' + changes: + title: 'Changes' + description: 'These were the changes made:' description: joined: '{user} {verb} {targetType}' target: diff --git a/src/lib/logger.js b/src/lib/logger.js index 054e9ff..9ba59e7 100644 --- a/src/lib/logger.js +++ b/src/lib/logger.js @@ -37,7 +37,7 @@ module.exports = config => { } return new Logger({ - namespaces: ['commands', 'http', 'listeners'], + namespaces: ['commands', 'http', 'listeners', 'settings', 'tickets'], transports, }); }; diff --git a/src/lib/logging.js b/src/lib/logging.js index 1c57dd7..7bab016 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -32,21 +32,6 @@ async function getLogChannel(client, guildId) { return channelId && client.channels.cache.get(channelId); } -/** - * @param {import("client")} client - * @param {*} target - * @returns {string} target.type - * @returns {string} target.id -*/ -async function getTargetName(client, target) { - if (target.type === 'settings') { - return client.guilds.cache.get(target.id).name; - } else { - const row = await client.prisma[target.type].findUnique({ where: { id: target.id } }); - return row.name ?? target.id; - } -} - /** * @param {import("client")} client * @param {object} details @@ -58,7 +43,7 @@ async function logAdminEvent(client, { guildId, userId, action, target, diff, }) { const user = await client.users.fetch(userId); - client.log.info(`${user.tag} ${action}d ${target.type} ${target.id}`); + client.log.info.settings(`${user.tag} ${action}d ${target.type} ${target.id}`); const settings = await client.prisma.guild.findUnique({ select: { footer: true, @@ -75,7 +60,6 @@ async function logAdminEvent(client, { }; const channel = client.channels.cache.get(settings.logChannel); if (!channel) return; - const targetName = await getTargetName(client, target); return await channel.send({ embeds: [ @@ -95,7 +79,7 @@ async function logAdminEvent(client, { targetType: getMessage(`log.admin.description.target.${target.type}`), verb: getMessage(`log.admin.verb.${action}`), })) - .addField(getMessage(`log.admin.title.target.${target.type}`), targetName), + .addField(getMessage(`log.admin.title.target.${target.type}`), target.name ?? target.id), // .setFooter({ // iconURL: client.guilds.cache.get(guildId).iconURL(), // text: settings.footer, @@ -104,7 +88,8 @@ async function logAdminEvent(client, { diff?.original && new MessageEmbed() .setColor('ORANGE') - .setTitle(getMessage('log.admin.changes')) + .setTitle(getMessage('log.admin.changes.title')) + .setDescription(getMessage('log.admin.changes.description')) .setFields(makeDiff(diff)), ], ], diff --git a/src/lib/prisma.js b/src/lib/prisma.js index ad8112b..204b327 100644 --- a/src/lib/prisma.js +++ b/src/lib/prisma.js @@ -5,7 +5,7 @@ const fields = [ 'content', 'username', 'displayName', - 'channelName', + // 'channelName', 'openingMessage', 'description', 'value', @@ -19,23 +19,50 @@ const fields = [ const shouldEncrypt = ['create', 'createMany', 'update', 'updateMany', 'upsert']; const shouldDecrypt = ['findUnique', 'findFirst', 'findMany']; -module.exports = async (params, next) => { - if (params.args.data && shouldEncrypt.includes(params.action)) { - for (const field of fields) { - if (field in params.args.data && params.args.data[field] !== null && params.args.data[field] !== undefined) { - params.args.data[field] = cryptr.encrypt(params.args.data[field]); + + + +module.exports = log => { + const encrypt = obj => { + for (const prop in obj) { + if (obj.hasOwnProperty(prop)) { + if (typeof obj[prop] === 'object') { + obj[prop] = encrypt(obj[prop]); + } else if (typeof obj[prop] === 'string' && obj[prop].length !== 0 && fields.includes(prop)) { + try { + obj[prop] = cryptr.encrypt(obj[prop]); + } catch (error) { + log.warn(`Failed to encrypt ${prop}`); + log.debug(error); + } + } } } - } + return obj; + }; - const result = await next(params); - - if (result && shouldDecrypt.includes(params.action)) { - for (const field of fields) { - if (field in result && result[field] !== null && result[field] !== undefined) { - result[field] = cryptr.decrypt(params.result[field]); + const decrypt = obj => { + for (const prop in obj) { + if (obj.hasOwnProperty(prop)) { + if (typeof obj[prop] === 'object') { + obj[prop] = decrypt(obj[prop]); + } else if (typeof obj[prop] === 'string' && obj[prop].length !== 0 && fields.includes(prop)) { + try { + obj[prop] = cryptr.decrypt(obj[prop]); + } catch (error) { + log.warn(`Failed to decrypt ${prop}`); + log.debug(error); + } + } } } - } - return result; + return obj; + }; + + return async (params, next) => { + if (params.args.data && shouldEncrypt.includes(params.action)) params.args = encrypt(params.args); + let result = await next(params); + if (result && shouldDecrypt.includes(params.action)) result = decrypt(result); + return result; + }; }; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category].js b/src/routes/api/admin/guilds/[guild]/categories/[category].js index e69de29..36854e7 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category].js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category].js @@ -0,0 +1,102 @@ +const { logAdminEvent } = require('../../../../../../lib/logging'); + +module.exports.get = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + + const category = await client.prisma.category.findUnique({ + include: { + questions: { + select: { + createdAt: true, + id: true, + label: true, + maxLength: true, + minLength: true, + order: true, + placeholder: true, + required: true, + style: true, + value: true, + }, + }, + }, + where: { id: Number(req.params.category) }, + }); + + return category; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); + +module.exports.patch = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + + const user = await client.users.fetch(req.user.payload.id); + const guild = client.guilds.cache.get(req.params.guild); + const data = req.body; + const allow = ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES']; + const original = req.params.category && await client.prisma.category.findUnique({ where: { id: req.params.category } }); + if (!original) return res.status(404); + + if (!data.discordCategory) { + const channel = await guild.channels.create(data.name, { + permissionOverwrites: [ + ...[ + { + deny: ['VIEW_CHANNEL'], + id: guild.roles.everyone, + }, + { + allow: allow, + id: client.user.id, + }, + ], + ...data.staffRoles.map(id => ({ + allow: allow, + id, + })), + ], + position: 1, + reason: `Tickets category created by ${user.tag}`, + type: 'GUILD_CATEGORY', + }); + data.discordCategory = channel.id; + } + + const category = await client.prisma.category.update({ + data: { + guild: { connect: { id: guild.id } }, + ...data, + questions: { + upsert: data.questions?.map(q => ({ + create: q, + update: q, + where: { id: q.id }, + })), + }, + }, + }); + + logAdminEvent(client, { + action: 'update', + diff: { + original, + updated: category, + }, + guildId: guild.id, + target: { + id: category.id, + name: category.name, + type: 'category', + }, + userId: req.user.payload.id, + }); + + return category; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index ed0776e..e20f34b 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -1,11 +1,35 @@ +const { logAdminEvent } = require('../../../../../../lib/logging'); + module.exports.get = fastify => ({ handler: async (req, res) => { /** @type {import('client')} */ const client = res.context.config.client; - const categories = await client.prisma.guild.findUnique({ where: { id: req.params.guild } }).categories(); + const { categories } = await client.prisma.guild.findUnique({ + select: { + categories: { + include: { + questions: { + select: { + createdAt: true, + id: true, + label: true, + maxLength: true, + minLength: true, + order: true, + placeholder: true, + required: true, + style: true, + value: true, + }, + }, + }, + }, + }, + where: { id: req.params.guild }, + }); - res.send(categories); + return categories; }, onRequest: [fastify.authenticate, fastify.isAdmin], }); @@ -45,14 +69,28 @@ module.exports.post = fastify => ({ data.discordCategory = channel.id; } + if (data.channelName === null) data.channelName = undefined; + const category = await client.prisma.category.create({ data: { guild: { connect: { id: guild.id } }, ...data, + questions: { createMany: { data: data.questions ?? [] } }, }, }); - res.send(category); + logAdminEvent(client, { + action: 'create', + guildId: guild.id, + target: { + id: category.id, + name: category.name, + type: 'category', + }, + userId: req.user.payload.id, + }); + + return category; }, onRequest: [fastify.authenticate, fastify.isAdmin], -}); +}); \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/settings.js b/src/routes/api/admin/guilds/[guild]/settings.js index ee3757a..e422142 100644 --- a/src/routes/api/admin/guilds/[guild]/settings.js +++ b/src/routes/api/admin/guilds/[guild]/settings.js @@ -12,6 +12,7 @@ module.exports.delete = fastify => ({ guildId: id, target: { id, + name: client.guilds.cache.get(id), type: 'settings', }, userId: req.user.payload.id, @@ -53,9 +54,9 @@ module.exports.patch = fastify => ({ updated: settings, }, guildId: id, - original, target: { id, + name: client.guilds.cache.get(id), type: 'settings', }, userId: req.user.payload.id, From 4a48ba635a8722c74f1ea7d5232f69933e986b5f Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 17 Jul 2022 23:01:00 +0100 Subject: [PATCH 055/409] Fix everything (db/encryption, logging etc) --- prisma/schema.prisma | 2 +- src/i18n/en-GB.yml | 4 +- src/lib/logging.js | 58 +++++++++---------- src/lib/prisma.js | 5 +- .../guilds/[guild]/categories/[category].js | 33 +++++++++-- .../admin/guilds/[guild]/categories/index.js | 2 +- 6 files changed, 61 insertions(+), 43 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5a06197..11909de 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -67,7 +67,7 @@ model ArchivedUser { } model Category { - channelName String @default("ticket-{num}") + channelName String claiming Boolean @default(false) createdAt DateTime @default(now()) description String @db.VarChar(512) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 9998e1e..2657f6a 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -1,9 +1,7 @@ command: log: admin: - changes: - title: 'Changes' - description: 'These were the changes made:' + changes: 'Changes' description: joined: '{user} {verb} {targetType}' target: diff --git a/src/lib/logging.js b/src/lib/logging.js index 7bab016..7bae537 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -60,40 +60,36 @@ async function logAdminEvent(client, { }; const channel = client.channels.cache.get(settings.logChannel); if (!channel) return; + const embeds = [ + new MessageEmbed() + .setColor('ORANGE') + .setAuthor({ + iconURL: user.avatarURL(), + name: user.username, + }) + .setTitle(getMessage('log.admin.title.joined', { + ...i18nOptions, + targetType: getMessage(`log.admin.title.target.${target.type}`), + verb: getMessage(`log.admin.verb.${action}`), + })) + .setDescription(getMessage('log.admin.description.joined', { + ...i18nOptions, + targetType: getMessage(`log.admin.description.target.${target.type}`), + verb: getMessage(`log.admin.verb.${action}`), + })) + .addField(getMessage(`log.admin.title.target.${target.type}`), target.name ?? target.id), + ]; - return await channel.send({ - embeds: [ + if (diff && diff.original) { + embeds.push( new MessageEmbed() .setColor('ORANGE') - .setAuthor({ - iconURL: user.avatarURL(), - name: user.username, - }) - .setTitle(getMessage('log.admin.title.joined', { - ...i18nOptions, - targetType: getMessage(`log.admin.title.target.${target.type}`), - verb: getMessage(`log.admin.verb.${action}`), - })) - .setDescription(getMessage('log.admin.description.joined', { - ...i18nOptions, - targetType: getMessage(`log.admin.description.target.${target.type}`), - verb: getMessage(`log.admin.verb.${action}`), - })) - .addField(getMessage(`log.admin.title.target.${target.type}`), target.name ?? target.id), - // .setFooter({ - // iconURL: client.guilds.cache.get(guildId).iconURL(), - // text: settings.footer, - // }), - ...[ - diff?.original && - new MessageEmbed() - .setColor('ORANGE') - .setTitle(getMessage('log.admin.changes.title')) - .setDescription(getMessage('log.admin.changes.description')) - .setFields(makeDiff(diff)), - ], - ], - }); + .setTitle(getMessage('log.admin.changes')) + .setFields(makeDiff(diff)), + ); + } + + return await channel.send({ embeds }); } module.exports = { diff --git a/src/lib/prisma.js b/src/lib/prisma.js index 204b327..d0818c9 100644 --- a/src/lib/prisma.js +++ b/src/lib/prisma.js @@ -17,7 +17,7 @@ const fields = [ 'regex', ]; const shouldEncrypt = ['create', 'createMany', 'update', 'updateMany', 'upsert']; -const shouldDecrypt = ['findUnique', 'findFirst', 'findMany']; +// const shouldDecrypt = ['findUnique', 'findFirst', 'findMany']; @@ -62,7 +62,8 @@ module.exports = log => { return async (params, next) => { if (params.args.data && shouldEncrypt.includes(params.action)) params.args = encrypt(params.args); let result = await next(params); - if (result && shouldDecrypt.includes(params.action)) result = decrypt(result); + // if (result && shouldDecrypt.includes(params.action)) result = decrypt(result); + if (result) result = decrypt(result); return result; }; }; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category].js b/src/routes/api/admin/guilds/[guild]/categories/[category].js index 36854e7..eb7228a 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category].js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category].js @@ -1,10 +1,33 @@ const { logAdminEvent } = require('../../../../../../lib/logging'); +module.exports.delete = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const categoryId = Number(req.params.category); + const category = await client.prisma.category.delete({ where: { id: categoryId } }); + + logAdminEvent(client, { + action: 'delete', + guildId: req.params.guild, + target: { + id: category.id, + name: category.name, + type: 'category', + }, + userId: req.user.payload.id, + }); + + return category; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); + module.exports.get = fastify => ({ handler: async (req, res) => { /** @type {import('client')} */ const client = res.context.config.client; - + const categoryId = Number(req.params.category); const category = await client.prisma.category.findUnique({ include: { questions: { @@ -22,7 +45,7 @@ module.exports.get = fastify => ({ }, }, }, - where: { id: Number(req.params.category) }, + where: { id: categoryId }, }); return category; @@ -34,12 +57,12 @@ module.exports.patch = fastify => ({ handler: async (req, res) => { /** @type {import('client')} */ const client = res.context.config.client; - + const categoryId = Number(req.params.category); const user = await client.users.fetch(req.user.payload.id); const guild = client.guilds.cache.get(req.params.guild); const data = req.body; const allow = ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES']; - const original = req.params.category && await client.prisma.category.findUnique({ where: { id: req.params.category } }); + const original = req.params.category && await client.prisma.category.findUnique({ where: { id: categoryId } }); if (!original) return res.status(404); if (!data.discordCategory) { @@ -69,7 +92,6 @@ module.exports.patch = fastify => ({ const category = await client.prisma.category.update({ data: { - guild: { connect: { id: guild.id } }, ...data, questions: { upsert: data.questions?.map(q => ({ @@ -79,6 +101,7 @@ module.exports.patch = fastify => ({ })), }, }, + where: { id: categoryId }, }); logAdminEvent(client, { diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index e20f34b..2d1d9bd 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -69,7 +69,7 @@ module.exports.post = fastify => ({ data.discordCategory = channel.id; } - if (data.channelName === null) data.channelName = undefined; + if (!data.channelName) data.channelName = 'ticket-{num}'; const category = await client.prisma.category.create({ data: { From c04787bb80da7e9131e0c1f68e83611d0783941a Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 17 Jul 2022 23:05:12 +0100 Subject: [PATCH 056/409] don't create a new category again --- .../guilds/[guild]/categories/[category].js | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category].js b/src/routes/api/admin/guilds/[guild]/categories/[category].js index eb7228a..143605d 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category].js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category].js @@ -58,38 +58,11 @@ module.exports.patch = fastify => ({ /** @type {import('client')} */ const client = res.context.config.client; const categoryId = Number(req.params.category); - const user = await client.users.fetch(req.user.payload.id); const guild = client.guilds.cache.get(req.params.guild); const data = req.body; - const allow = ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES']; const original = req.params.category && await client.prisma.category.findUnique({ where: { id: categoryId } }); if (!original) return res.status(404); - if (!data.discordCategory) { - const channel = await guild.channels.create(data.name, { - permissionOverwrites: [ - ...[ - { - deny: ['VIEW_CHANNEL'], - id: guild.roles.everyone, - }, - { - allow: allow, - id: client.user.id, - }, - ], - ...data.staffRoles.map(id => ({ - allow: allow, - id, - })), - ], - position: 1, - reason: `Tickets category created by ${user.tag}`, - type: 'GUILD_CATEGORY', - }); - data.discordCategory = channel.id; - } - const category = await client.prisma.category.update({ data: { ...data, From 3a1c0f54bd4aaf8a9f9d2a5cf8474fd9c81db140 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 18 Jul 2022 13:34:29 +0100 Subject: [PATCH 057/409] Node v18, discord.js v14 --- package.json | 15 +++-- src/client.js | 10 ++-- src/http.js | 55 ++++++++----------- .../admin/guilds/[guild]/categories/index.js | 2 +- src/routes/auth/callback.js | 1 - 5 files changed, 37 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 11934a0..3b2029b 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,10 @@ }, "homepage": "https://discordtickets.app", "engines": { - "node": ">=16.6" + "node": ">=18.0" }, "dependencies": { - "@eartharoid/dbf": "^0.0.1", + "@eartharoid/dbf": "^0.2.0", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.0.4", "@fastify/cookie": "^6.0.0", @@ -39,26 +39,25 @@ "@fastify/oauth2": "^5.0.0", "@prisma/client": "^4.0.0", "cryptr": "^6.0.3", - "discord.js": "^13.9.0", + "discord.js": "^14.0.2", "dotenv": "^16.0.1", "fastify": "^4.2.1", "figlet": "^1.5.2", - "keyv": "^4.3.2", + "keyv": "^4.3.3", "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", "ms": "^2.1.3", "node-dir": "^0.1.17", - "node-fetch": "^2.6.7", "object-diffy": "^1.0.4", + "prisma": "^4.0.0", "semver": "^7.3.7", "terminal-link": "^2.1.1", "yaml": "^1.10.2" }, "devDependencies": { "all-contributors-cli": "^6.20.0", - "eslint": "^8.19.0", + "eslint": "^8.20.0", "eslint-plugin-unused-imports": "^2.0.0", - "nodemon": "^2.0.19", - "prisma": "^4.0.0" + "nodemon": "^2.0.19" } } \ No newline at end of file diff --git a/src/client.js b/src/client.js index 1b14547..a1e78ea 100644 --- a/src/client.js +++ b/src/client.js @@ -1,5 +1,5 @@ -const { Client: FrameworkClient }= require('@eartharoid/dbf'); -const { Intents } = require('discord.js'); +const { Client: FrameworkClient } = require('@eartharoid/dbf'); +const { GatewayIntentBits } = require('discord.js'); const { PrismaClient } = require('@prisma/client'); const Keyv = require('keyv'); const I18n = require('@eartharoid/i18n'); @@ -12,9 +12,9 @@ module.exports = class Client extends FrameworkClient { constructor(config, log) { super({ intents: [ - Intents.FLAGS.GUILDS, - Intents.FLAGS.GUILD_MEMBERS, - Intents.FLAGS.GUILD_MESSAGES, + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildMessages, ], }); diff --git a/src/http.js b/src/http.js index 4988973..21d578a 100644 --- a/src/http.js +++ b/src/http.js @@ -3,7 +3,7 @@ const oauth = require('@fastify/oauth2'); // const { randomBytes } = require('crypto'); const { short } = require('leeks.js'); const { join } = require('path'); -const { readFiles } = require('node-dir'); +const { files } = require('node-dir'); module.exports = client => { @@ -115,36 +115,29 @@ module.exports = client => { // route loading const dir = join(__dirname, '/routes'); - readFiles(dir, - { - exclude: /^\./, - match: /.js$/, - }, - (err, content, next) => next(), - (err, files) => { - if (err) throw err; + files(dir, { + exclude: /^\./, + match: /.js$/, + sync: true, + }).forEach(file => { + const path = file + .substring(0, file.length - 3) // remove `.js` + .substring(dir.length) // remove higher directories + .replace(/\[(\w+)\]/gi, ':$1') // convert [] to : + .replace('/index', '') || '/'; // remove index + const route = require(file); - for (const file of files) { - const path = file - .substring(0, file.length - 3) // remove `.js` - .substring(dir.length) // remove higher directories - .replace(/\[(\w+)\]/gi, ':$1') // convert [] to : - .replace('/index', '') || '/'; // remove index - const route = require(file); + Object.keys(route).forEach(method => fastify.route({ + config: { client }, + method: method.toUpperCase(), + path, + ...route[method](fastify), + })); // register route + }); - Object.keys(route).forEach(method => fastify.route({ - config: { client }, - method: method.toUpperCase(), - path, - ...route[method](fastify), - })); // register route - } - - // start server - fastify.listen({ port: process.env.HTTP_BIND }, (err, addr) => { - if (err) client.log.error.http(err); - else client.log.success.http(`Listening at ${addr}`); - }); - }, - ); + // start server + fastify.listen({ port: process.env.HTTP_BIND }, (err, addr) => { + if (err) client.log.error.http(err); + else client.log.success.http(`Listening at ${addr}`); + }); }; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index 2d1d9bd..1a8a9df 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -69,7 +69,7 @@ module.exports.post = fastify => ({ data.discordCategory = channel.id; } - if (!data.channelName) data.channelName = 'ticket-{num}'; + data.channelName ??= 'ticket-{num}'; const category = await client.prisma.category.create({ data: { diff --git a/src/routes/auth/callback.js b/src/routes/auth/callback.js index 43cf2cc..400fcb3 100644 --- a/src/routes/auth/callback.js +++ b/src/routes/auth/callback.js @@ -1,4 +1,3 @@ -const fetch = require('node-fetch'); const { domain } = require('../../lib/http'); module.exports.get = () => ({ From 28f1e85759c3b8daae18ec59c2537cb906075e44 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 18 Jul 2022 21:53:17 +0100 Subject: [PATCH 058/409] More fixes --- README.md | 7 ++++--- package.json | 3 ++- prisma/schema.prisma | 6 +++--- src/client.js | 1 + src/http.js | 16 ++++++++++++++-- src/lib/logging.js | 17 +++++++++++------ .../guilds/[guild]/categories/[category].js | 3 +++ .../admin/guilds/[guild]/categories/index.js | 15 ++++++++++----- .../api/admin/guilds/[guild]/settings.js | 18 ++++++++++++++---- 9 files changed, 62 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b3f23b4..0017bbb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ CONFIG_PATH=./user/config.yml DISCORD_SECRET= DISCORD_TOKEN= +DB_CONNECTION_URL="mysql://test:password@localhost/tickets0" ENCRYPTION_KEY= -DB_CONNECTION_URL="" -HTTP_BIND=3000 -HTTP_EXTERNAL= \ No newline at end of file +HTTP_BIND=8080 +HTTP_EXTERNAL=http://localhost:8080 +SUPER= \ No newline at end of file diff --git a/package.json b/package.json index 3b2029b..d92d681 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "node": ">=18.0" }, "dependencies": { - "@eartharoid/dbf": "^0.2.0", + "@eartharoid/dbf": "^0.2.2", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.0.4", "@fastify/cookie": "^6.0.0", @@ -48,6 +48,7 @@ "leekslazylogger": "^4.1.7", "ms": "^2.1.3", "node-dir": "^0.1.17", + "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", "prisma": "^4.0.0", "semver": "^7.3.7", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 11909de..757dd7f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -115,7 +115,7 @@ model Guild { blocklist Json @default("[]") categories Category[] createdAt DateTime @default(now()) - errorColour String @default("RED") + errorColour String @default("Red") feedback Feedback[] footer String? @default("Discord Tickets by eartharoid") id String @id @db.VarChar(19) @@ -123,7 +123,7 @@ model Guild { logChannel String? @db.VarChar(19) primaryColour String @default("#009999") staleAfter Int? - successColour String @default("GREEN") + successColour String @default("Green") tags Tag[] tickets Ticket[] workingHours Json @default("[\"UTC\", [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"]]") @@ -141,7 +141,7 @@ model Question { maxLength Int? @default(4000) minLength Int? @default(0) order Int - placeholder String? @db.VarChar(100) + placeholder String? @db.VarChar(512) required Boolean @default(true) style Int @default(2) value String? @db.Text diff --git a/src/client.js b/src/client.js index a1e78ea..3c8b02a 100644 --- a/src/client.js +++ b/src/client.js @@ -31,6 +31,7 @@ module.exports = class Client extends FrameworkClient { this.i18n = new I18n('en-GB', locales); this.config = config; this.log = log; + this.supers = (process.env.SUPER ?? '').split(','); } async login(token) { diff --git a/src/http.js b/src/http.js index 21d578a..f695fad 100644 --- a/src/http.js +++ b/src/http.js @@ -74,8 +74,8 @@ module.exports = client => { }); } const guildMember = await guild.members.fetch(userId); - const isAdmin = guildMember?.permissions.has('MANAGE_GUILD'); - if (!guildMember || !isAdmin) { + const isAdmin = guildMember?.permissions.has('MANAGE_GUILD') || client.supers.includes(userId); + if (!isAdmin) { return res.code(403).send({ error: 'Forbidden', message: 'You are not permitted for this action.', @@ -88,6 +88,18 @@ module.exports = client => { } }); + // body processing + fastify.addHook('preHandler', (req, res, done) => { + if (req.body && typeof req.body === 'object') { + for (const prop in req.body) { + if (typeof req.body[prop] === 'string') { + req.body[prop] = req.body[prop].trim(); + } + } + } + done(); + }); + // logging fastify.addHook('onResponse', (req, res, done) => { done(); diff --git a/src/lib/logging.js b/src/lib/logging.js index 7bae537..a88572a 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -1,4 +1,4 @@ -const { MessageEmbed } = require('discord.js'); +const { EmbedBuilder } = require('discord.js'); const { diff: getDiff } = require('object-diffy'); function makeDiff({ @@ -61,8 +61,8 @@ async function logAdminEvent(client, { const channel = client.channels.cache.get(settings.logChannel); if (!channel) return; const embeds = [ - new MessageEmbed() - .setColor('ORANGE') + new EmbedBuilder() + .setColor('Orange') .setAuthor({ iconURL: user.avatarURL(), name: user.username, @@ -77,13 +77,18 @@ async function logAdminEvent(client, { targetType: getMessage(`log.admin.description.target.${target.type}`), verb: getMessage(`log.admin.verb.${action}`), })) - .addField(getMessage(`log.admin.title.target.${target.type}`), target.name ?? target.id), + .addFields([ + { + name: getMessage(`log.admin.title.target.${target.type}`), + value: target.name ?? target.id, + }, + ]), ]; if (diff && diff.original) { embeds.push( - new MessageEmbed() - .setColor('ORANGE') + new EmbedBuilder() + .setColor('Orange') .setTitle(getMessage('log.admin.changes')) .setFields(makeDiff(diff)), ); diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category].js b/src/routes/api/admin/guilds/[guild]/categories/[category].js index 143605d..20b0d52 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category].js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category].js @@ -63,6 +63,9 @@ module.exports.patch = fastify => ({ const original = req.params.category && await client.prisma.category.findUnique({ where: { id: categoryId } }); if (!original) return res.status(404); + if (data.hasOwnProperty('id')) delete data.id; + if (data.hasOwnProperty('createdAt')) delete data.createdAt; + const category = await client.prisma.category.update({ data: { ...data, diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index 1a8a9df..f7219a1 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -1,4 +1,6 @@ const { logAdminEvent } = require('../../../../../../lib/logging'); +const emoji = require('node-emoji'); +const { ChannelType: { GuildCategory } } = require('discord.js'); module.exports.get = fastify => ({ handler: async (req, res) => { @@ -42,14 +44,17 @@ module.exports.post = fastify => ({ const user = await client.users.fetch(req.user.payload.id); const guild = client.guilds.cache.get(req.params.guild); const data = req.body; - const allow = ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES']; + const allow = ['ViewChannel', 'ReadMessageHistory', 'SendMessages', 'EmbedLinks', 'AttachFiles']; if (!data.discordCategory) { - const channel = await guild.channels.create(data.name, { + let name = data.name; + if (emoji.hasEmoji(data.emoji)) name = `${emoji.get(data.emoji)} ${name}`; + const channel = await guild.channels.create({ + name, permissionOverwrites: [ ...[ { - deny: ['VIEW_CHANNEL'], + deny: ['ViewChannel'], id: guild.roles.everyone, }, { @@ -64,12 +69,12 @@ module.exports.post = fastify => ({ ], position: 1, reason: `Tickets category created by ${user.tag}`, - type: 'GUILD_CATEGORY', + type: GuildCategory, }); data.discordCategory = channel.id; } - data.channelName ??= 'ticket-{num}'; + data.channelName ||= 'ticket-{num}'; // not ??=, expect empty string const category = await client.prisma.category.create({ data: { diff --git a/src/routes/api/admin/guilds/[guild]/settings.js b/src/routes/api/admin/guilds/[guild]/settings.js index e422142..0c6e929 100644 --- a/src/routes/api/admin/guilds/[guild]/settings.js +++ b/src/routes/api/admin/guilds/[guild]/settings.js @@ -1,4 +1,5 @@ const { logAdminEvent } = require('../../../../../lib/logging.js'); +const { Colors } = require('discord.js'); module.exports.delete = fastify => ({ handler: async (req, res) => { @@ -37,16 +38,25 @@ module.exports.get = fastify => ({ module.exports.patch = fastify => ({ handler: async (req, res) => { - if (req.body.hasOwnProperty('id')) delete req.body.id; - if (req.body.hasOwnProperty('createdAt')) delete req.body.createdAt; + const data = req.body; + if (data.hasOwnProperty('id')) delete data.id; + if (data.hasOwnProperty('createdAt')) delete data.createdAt; + const colours = ['errorColour', 'primaryColour', 'successColour']; + for (const c of colours) { + if (data[c] && !data[c].startsWith('#') && !(data[c] in Colors)) { // if not null/empty and not hex + throw new Error(`${data[c]} is not a valid colour. Valid colours are HEX and: ${Object.keys(Colors).join(', ')}`); + } + } + /** @type {import('client')} */ const client = res.context.config.client; const id = req.params.guild; const original = await client.prisma.guild.findUnique({ where: { id } }); const settings = await client.prisma.guild.update({ - data: req.body, + data: data, where: { id }, }); + logAdminEvent(client, { action: 'update', diff: { @@ -56,7 +66,7 @@ module.exports.patch = fastify => ({ guildId: id, target: { id, - name: client.guilds.cache.get(id), + name: client.guilds.cache.get(id).name, type: 'settings', }, userId: req.user.payload.id, From 5786c3598d56747279f3e3afdf3cd7038113f6e4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 19 Jul 2022 15:57:19 +0100 Subject: [PATCH 059/409] Giving up on encryption and life --- prisma/schema.prisma | 36 +++++----- src/client.js | 2 - src/lib/logging.js | 11 +-- src/lib/prisma.js | 69 ------------------- .../guilds/[guild]/categories/[category].js | 28 +++++--- .../admin/guilds/[guild]/categories/index.js | 38 +++++----- .../api/admin/guilds/[guild]/problems.js | 32 +++++++++ 7 files changed, 95 insertions(+), 121 deletions(-) delete mode 100644 src/lib/prisma.js create mode 100644 src/routes/api/admin/guilds/[guild]/problems.js diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 757dd7f..a9d2717 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -53,13 +53,13 @@ model ArchivedUser { bot Boolean @default(false) createdAt DateTime @default(now()) discriminator String @db.Char(4) - displayName String @db.VarChar(512) + displayName String @db.Text role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) roleId String @db.VarChar(19) ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @db.VarChar(19) userId String @db.VarChar(19) - username String @db.VarChar(512) + username String @db.Text @@id([ticketId, userId]) @@unique([ticketId, userId]) @@ -70,7 +70,7 @@ model Category { channelName String claiming Boolean @default(false) createdAt DateTime @default(now()) - description String @db.VarChar(512) + description String discordCategory String @db.VarChar(19) emoji String enableFeedback Boolean @default(false) @@ -79,7 +79,7 @@ model Category { id Int @id @default(autoincrement()) image String? memberLimit Int @default(1) - name String @db.VarChar(512) + name String openingMessage String @db.Text pingRoles Json @default("[]") questions Question[] @@ -134,14 +134,14 @@ model Guild { model Question { answers QuestionAnswer[] createdAt DateTime @default(now()) - id Int @id @default(autoincrement()) - category Category? @relation(fields: [categoryId], references: [id]) - categoryId Int? - label String @db.VarChar(512) + id String @id @default(uuid()) + category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) + categoryId Int + label String maxLength Int? @default(4000) minLength Int? @default(0) order Int - placeholder String? @db.VarChar(512) + placeholder String? required Boolean @default(true) style Int @default(2) value String? @db.Text @@ -152,11 +152,11 @@ model Question { model QuestionAnswer { createdAt DateTime @default(now()) id Int @id @default(autoincrement()) - ticket Ticket @relation(fields: [ticketId], references: [id]) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @db.VarChar(19) - question Question @relation(fields: [questionId], references: [id]) - questionId Int - user User @relation(fields: [userId], references: [id]) + question Question @relation(fields: [questionId], references: [id], onDelete: Cascade) + questionId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId String @db.VarChar(19) value String? @db.Text @@ -164,13 +164,13 @@ model QuestionAnswer { } model Tag { - content String @db.Text() + content String @db.Text createdAt DateTime @default(now()) guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) id Int @id @default(autoincrement()) - name String @db.VarChar(512) - regex String? @db.VarChar(512) + name String + regex String? @@unique([guildId, name]) @@map("tags") @@ -188,7 +188,7 @@ model Ticket { closedAt DateTime? closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) closedById String @db.VarChar(19) - closedReason String? @db.VarChar(512) + closedReason String? createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String @db.VarChar(19) @@ -210,7 +210,7 @@ model Ticket { referencesMessageId String @db.VarChar(19) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? @db.VarChar(19) - topic String? @db.VarChar(512) + topic String? questionAnswers QuestionAnswer[] @@unique([guildId, number]) diff --git a/src/client.js b/src/client.js index 3c8b02a..4467174 100644 --- a/src/client.js +++ b/src/client.js @@ -6,7 +6,6 @@ const I18n = require('@eartharoid/i18n'); const fs = require('fs'); const { join } = require('path'); const YAML = require('yaml'); -const middleware = require('./lib/prisma'); module.exports = class Client extends FrameworkClient { constructor(config, log) { @@ -37,7 +36,6 @@ module.exports = class Client extends FrameworkClient { async login(token) { /** @type {PrismaClient} */ this.prisma = new PrismaClient(); - this.prisma.$use(middleware(this.log)); this.keyv = new Keyv(); return super.login(token); } diff --git a/src/lib/logging.js b/src/lib/logging.js index a88572a..19e7edf 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -1,4 +1,7 @@ -const { EmbedBuilder } = require('discord.js'); +const { + cleanCodeBlockContent, + EmbedBuilder, +} = require('discord.js'); const { diff: getDiff } = require('object-diffy'); function makeDiff({ @@ -8,12 +11,12 @@ function makeDiff({ const fields = []; for (const key in diff) { if (key === 'createdAt') continue; // object-diffy doesn't like dates - const from = diff[key].from === null ? '' : `- ${diff[key].from}\n`; - const to = diff[key].to === null ? '' : `+ ${diff[key].to}\n`; + const from = diff[key].from === null ? '' : `- ${diff[key].from.toString().replace(/\n/g, '\\n')}\n`; + const to = diff[key].to === null ? '' : `+ ${diff[key].to.toString().replace(/\n/g, '\\n')}\n`; fields.push({ inline: true, name: key, - value: `\`\`\`diff\n${from + to}\n\`\`\``, + value: `\`\`\`diff\n${cleanCodeBlockContent(from + to)}\n\`\`\``, }); } return fields; diff --git a/src/lib/prisma.js b/src/lib/prisma.js deleted file mode 100644 index d0818c9..0000000 --- a/src/lib/prisma.js +++ /dev/null @@ -1,69 +0,0 @@ -const Cryptr = require('cryptr'); -const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); -const fields = [ - 'name', - 'content', - 'username', - 'displayName', - // 'channelName', - 'openingMessage', - 'description', - 'value', - 'placeholder', - 'closedReason', - 'topic', - 'comment', - 'label', - 'regex', -]; -const shouldEncrypt = ['create', 'createMany', 'update', 'updateMany', 'upsert']; -// const shouldDecrypt = ['findUnique', 'findFirst', 'findMany']; - - - - -module.exports = log => { - const encrypt = obj => { - for (const prop in obj) { - if (obj.hasOwnProperty(prop)) { - if (typeof obj[prop] === 'object') { - obj[prop] = encrypt(obj[prop]); - } else if (typeof obj[prop] === 'string' && obj[prop].length !== 0 && fields.includes(prop)) { - try { - obj[prop] = cryptr.encrypt(obj[prop]); - } catch (error) { - log.warn(`Failed to encrypt ${prop}`); - log.debug(error); - } - } - } - } - return obj; - }; - - const decrypt = obj => { - for (const prop in obj) { - if (obj.hasOwnProperty(prop)) { - if (typeof obj[prop] === 'object') { - obj[prop] = decrypt(obj[prop]); - } else if (typeof obj[prop] === 'string' && obj[prop].length !== 0 && fields.includes(prop)) { - try { - obj[prop] = cryptr.decrypt(obj[prop]); - } catch (error) { - log.warn(`Failed to decrypt ${prop}`); - log.debug(error); - } - } - } - } - return obj; - }; - - return async (params, next) => { - if (params.args.data && shouldEncrypt.includes(params.action)) params.args = encrypt(params.args); - let result = await next(params); - // if (result && shouldDecrypt.includes(params.action)) result = decrypt(result); - if (result) result = decrypt(result); - return result; - }; -}; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category].js b/src/routes/api/admin/guilds/[guild]/categories/[category].js index 20b0d52..6f9aa16 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category].js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category].js @@ -1,10 +1,14 @@ const { logAdminEvent } = require('../../../../../../lib/logging'); +const { randomUUID } = require('crypto'); module.exports.delete = fastify => ({ handler: async (req, res) => { /** @type {import('client')} */ const client = res.context.config.client; + const guildId = req.params.guild; const categoryId = Number(req.params.category); + const original = req.params.category && await client.prisma.category.findUnique({ where: { id: categoryId } }); + if (!original || original.guildId !== guildId) return res.status(404).send(new Error('Not Found')); const category = await client.prisma.category.delete({ where: { id: categoryId } }); logAdminEvent(client, { @@ -27,12 +31,13 @@ module.exports.get = fastify => ({ handler: async (req, res) => { /** @type {import('client')} */ const client = res.context.config.client; + const guildId = req.params.guild; const categoryId = Number(req.params.category); const category = await client.prisma.category.findUnique({ include: { questions: { select: { - createdAt: true, + // createdAt: true, id: true, label: true, maxLength: true, @@ -45,9 +50,11 @@ module.exports.get = fastify => ({ }, }, }, - where: { id: categoryId }, + where: { id: categoryId }, }); + if (!category || category.guildId !== guildId) return res.status(404).send(new Error('Not Found')); + return category; }, onRequest: [fastify.authenticate, fastify.isAdmin], @@ -57,11 +64,13 @@ module.exports.patch = fastify => ({ handler: async (req, res) => { /** @type {import('client')} */ const client = res.context.config.client; + const guildId = req.params.guild; const categoryId = Number(req.params.category); const guild = client.guilds.cache.get(req.params.guild); const data = req.body; const original = req.params.category && await client.prisma.category.findUnique({ where: { id: categoryId } }); - if (!original) return res.status(404); + if (!original || original.guildId !== guildId) return res.status(404).send(new Error('Not Found')); + if (data.hasOwnProperty('id')) delete data.id; if (data.hasOwnProperty('createdAt')) delete data.createdAt; @@ -70,11 +79,14 @@ module.exports.patch = fastify => ({ data: { ...data, questions: { - upsert: data.questions?.map(q => ({ - create: q, - update: q, - where: { id: q.id }, - })), + upsert: data.questions?.map(q => { + if (!q.id) q.id = randomUUID(); + return { + create: q, + update: q, + where: { id: q.id }, + }; + }), }, }, where: { id: categoryId }, diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index f7219a1..ec9a5a0 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -8,29 +8,27 @@ module.exports.get = fastify => ({ const client = res.context.config.client; const { categories } = await client.prisma.guild.findUnique({ - select: { - categories: { - include: { - questions: { - select: { - createdAt: true, - id: true, - label: true, - maxLength: true, - minLength: true, - order: true, - placeholder: true, - required: true, - style: true, - value: true, - }, - }, - }, - }, - }, + select: { categories: true }, where: { id: req.params.guild }, }); + // include: { + // questions: { + // select: { + // createdAt: true, + // id: true, + // label: true, + // maxLength: true, + // minLength: true, + // order: true, + // placeholder: true, + // required: true, + // style: true, + // value: true, + // }, + // }, + // }, + return categories; }, onRequest: [fastify.authenticate, fastify.isAdmin], diff --git a/src/routes/api/admin/guilds/[guild]/problems.js b/src/routes/api/admin/guilds/[guild]/problems.js new file mode 100644 index 0000000..d4c5b2b --- /dev/null +++ b/src/routes/api/admin/guilds/[guild]/problems.js @@ -0,0 +1,32 @@ +module.exports.get = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const id = req.params.guild; + const guild = client.guilds.cache.get(id); + const settings = await client.prisma.guild.findUnique({ where: { id } }) ?? + await client.prisma.guild.create({ data: { id } }); + const problems = []; + + if (settings.logChannel) { + const permissions = guild.members.me.permissionsIn(settings.logChannel); + + if (!permissions.has('SendMessages')) { + problems.push({ + id: 'logChannelMissingPermission', + permission: 'SendMessages', + }); + } + + if (!permissions.has('EmbedLinks')) { + problems.push({ + id: 'logChannelMissingPermission', + permission: 'EmbedLinks', + }); + } + } + + return problems; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); From 98b35e3a81e9b11e3c45d59ec46a0affec5b58b4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 19 Jul 2022 22:34:45 +0100 Subject: [PATCH 060/409] Finish questions --- prisma/schema.prisma | 4 +- src/i18n/en-GB.yml | 2 + src/lib/logging.js | 20 ++++++-- .../{[category].js => [category]/index.js} | 49 +++++++++++++++++-- .../[category]/questions/[question].js | 29 +++++++++++ 5 files changed, 95 insertions(+), 9 deletions(-) rename src/routes/api/admin/guilds/[guild]/categories/{[category].js => [category]/index.js} (75%) create mode 100644 src/routes/api/admin/guilds/[guild]/categories/[category]/questions/[question].js diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a9d2717..bb98b2f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -137,11 +137,11 @@ model Question { id String @id @default(uuid()) category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) categoryId Int - label String + label String @db.VarChar(45) maxLength Int? @default(4000) minLength Int? @default(0) order Int - placeholder String? + placeholder String? @db.VarChar(100) required Boolean @default(true) style Int @default(2) value String? @db.Text diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 2657f6a..8366a9d 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -6,11 +6,13 @@ log: joined: '{user} {verb} {targetType}' target: category: 'a category' + question: 'a question' settings: 'the settings' title: joined: '{targetType} {verb}' target: category: 'Category' + question: 'Question' settings: 'Settings' verb: create: 'created' diff --git a/src/lib/logging.js b/src/lib/logging.js index 19e7edf..5165780 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -4,15 +4,29 @@ const { } = require('discord.js'); const { diff: getDiff } = require('object-diffy'); + +const exists = thing => (typeof thing === 'string' && thing.length > 0) && thing !== null && thing !== undefined; + +const arrToObj = obj => { + for (const key in obj) { + if (obj[key] instanceof Array && obj[key][0]?.id) { + const temp = {}; + obj[key].forEach(v => (temp[v.id] = v)); + obj[key] = temp; + } + } + return obj; +}; + function makeDiff({ original, updated, }) { - const diff = getDiff(original, updated); + const diff = getDiff(arrToObj(original), arrToObj(updated)); const fields = []; for (const key in diff) { if (key === 'createdAt') continue; // object-diffy doesn't like dates - const from = diff[key].from === null ? '' : `- ${diff[key].from.toString().replace(/\n/g, '\\n')}\n`; - const to = diff[key].to === null ? '' : `+ ${diff[key].to.toString().replace(/\n/g, '\\n')}\n`; + const from = exists(diff[key].from) ? `- ${String(diff[key].from).replace(/\n/g, '\\n')}\n` : ''; + const to = exists(diff[key].to) ? `+ ${String(diff[key].to).replace(/\n/g, '\\n')}\n` : ''; fields.push({ inline: true, name: key, diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category].js b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js similarity index 75% rename from src/routes/api/admin/guilds/[guild]/categories/[category].js rename to src/routes/api/admin/guilds/[guild]/categories/[category]/index.js index 6f9aa16..d4a09a5 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category].js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js @@ -1,4 +1,4 @@ -const { logAdminEvent } = require('../../../../../../lib/logging'); +const { logAdminEvent } = require('../../../../../../../lib/logging'); const { randomUUID } = require('crypto'); module.exports.delete = fastify => ({ @@ -7,7 +7,7 @@ module.exports.delete = fastify => ({ const client = res.context.config.client; const guildId = req.params.guild; const categoryId = Number(req.params.category); - const original = req.params.category && await client.prisma.category.findUnique({ where: { id: categoryId } }); + const original = categoryId && await client.prisma.category.findUnique({ where: { id: categoryId } }); if (!original || original.guildId !== guildId) return res.status(404).send(new Error('Not Found')); const category = await client.prisma.category.delete({ where: { id: categoryId } }); @@ -68,9 +68,49 @@ module.exports.patch = fastify => ({ const categoryId = Number(req.params.category); const guild = client.guilds.cache.get(req.params.guild); const data = req.body; - const original = req.params.category && await client.prisma.category.findUnique({ where: { id: categoryId } }); - if (!original || original.guildId !== guildId) return res.status(404).send(new Error('Not Found')); + const select = { + channelName: true, + claiming: true, + // createdAt: true, + description: true, + discordCategory: true, + emoji: true, + enableFeedback: true, + guildId: true, + id: true, + image: true, + memberLimit: true, + name: true, + openingMessage: true, + pingRoles: true, + questions: { + select: { + // createdAt: true, + id: true, + label: true, + maxLength: true, + minLength: true, + order: true, + placeholder: true, + required: true, + style: true, + value: true, + }, + }, + ratelimit: true, + requireTopic: true, + requiredRoles: true, + staffRoles: true, + totalLimit: true, + }; + + const original = req.params.category && await client.prisma.category.findUnique({ + select, + where: { id: categoryId }, + }); + + if (!original || original.guildId !== guildId) return res.status(404).send(new Error('Not Found')); if (data.hasOwnProperty('id')) delete data.id; if (data.hasOwnProperty('createdAt')) delete data.createdAt; @@ -89,6 +129,7 @@ module.exports.patch = fastify => ({ }), }, }, + select, where: { id: categoryId }, }); diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category]/questions/[question].js b/src/routes/api/admin/guilds/[guild]/categories/[category]/questions/[question].js new file mode 100644 index 0000000..213e102 --- /dev/null +++ b/src/routes/api/admin/guilds/[guild]/categories/[category]/questions/[question].js @@ -0,0 +1,29 @@ +const { logAdminEvent } = require('../../../../../../../../lib/logging'); + +module.exports.delete = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const guildId = req.params.guild; + const categoryId = Number(req.params.category); + const questionId = req.params.question; + const original = questionId && await client.prisma.question.findUnique({ where: { id: questionId } }); + const category = categoryId && await client.prisma.category.findUnique({ where: { id: categoryId } }); + if (original?.categoryId !== categoryId || category.guildId !== guildId) return res.status(404).send(new Error('Not Found')); + const question = await client.prisma.question.delete({ where: { id: questionId } }); + + logAdminEvent(client, { + action: 'delete', + guildId: req.params.guild, + target: { + id: question.id, + name: question.label, + type: 'question', + }, + userId: req.user.payload.id, + }); + + return question; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); \ No newline at end of file From f93d058f55648252633445ed61a59cac7b4e0b82 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 21 Jul 2022 01:25:37 +0100 Subject: [PATCH 061/409] Panels! --- package.json | 6 +- prisma/schema.prisma | 2 + src/i18n/en-GB.yml | 13 +- src/lib/logging.js | 12 +- src/routes/api/admin/guilds/[guild]/panels.js | 141 ++++++++++++++++++ 5 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 src/routes/api/admin/guilds/[guild]/panels.js diff --git a/package.json b/package.json index d92d681..7be000b 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "contributors:generate": "all-contributors generate", "keygen": "node scripts/keygen", "lint": "eslint src scripts --ext mjs --fix", + "start": "node .", + "studio": "npx prisma studio", "test": "echo \"There's nothing to test\" && exit 1" }, "repository": { @@ -37,7 +39,7 @@ "@fastify/cors": "^8.0.0", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.0.0", - "@prisma/client": "^4.0.0", + "@prisma/client": "^4.1.0", "cryptr": "^6.0.3", "discord.js": "^14.0.2", "dotenv": "^16.0.1", @@ -50,7 +52,7 @@ "node-dir": "^0.1.17", "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", - "prisma": "^4.0.0", + "prisma": "^4.1.0", "semver": "^7.3.7", "terminal-link": "^2.1.1", "yaml": "^1.10.2" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index bb98b2f..12ffb19 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -114,6 +114,8 @@ model Guild { archive Boolean @default(true) blocklist Json @default("[]") categories Category[] + claimButton Boolean @default(false) + closeButton Boolean @default(false) createdAt DateTime @default(now()) errorColour String @default("Red") feedback Feedback[] diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 8366a9d..dc5993a 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -1,4 +1,8 @@ -command: +buttons: + create: + emoji: '🎫' + text: 'Create a ticket' +commands: log: admin: changes: 'Changes' @@ -6,16 +10,21 @@ log: joined: '{user} {verb} {targetType}' target: category: 'a category' + panel: 'a panel' question: 'a question' settings: 'the settings' title: joined: '{targetType} {verb}' target: category: 'Category' + panel: 'Panel' question: 'Question' settings: 'Settings' verb: create: 'created' delete: 'deleted' update: 'updated' - ticket: \ No newline at end of file + tickets: +menus: + panel: + placeholder: 'Select a ticket category' \ No newline at end of file diff --git a/src/lib/logging.js b/src/lib/logging.js index 5165780..2464618 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -5,7 +5,7 @@ const { const { diff: getDiff } = require('object-diffy'); -const exists = thing => (typeof thing === 'string' && thing.length > 0) && thing !== null && thing !== undefined; +const exists = thing => (typeof thing === 'string' && thing.length > 0) && (thing !== null && thing !== undefined); const arrToObj = obj => { for (const key in obj) { @@ -26,7 +26,7 @@ function makeDiff({ for (const key in diff) { if (key === 'createdAt') continue; // object-diffy doesn't like dates const from = exists(diff[key].from) ? `- ${String(diff[key].from).replace(/\n/g, '\\n')}\n` : ''; - const to = exists(diff[key].to) ? `+ ${String(diff[key].to).replace(/\n/g, '\\n')}\n` : ''; + const to = exists(diff[key].to) ? `+ ${String(diff[key].to).replace(/\n/g, '\\n')}\n` : ''; fields.push({ inline: true, name: key, @@ -70,6 +70,10 @@ async function logAdminEvent(client, { where: { id: guildId }, }); if (!settings.logChannel) return; + const colour = action === 'create' + ? 'Green' : action === 'update' + ? 'Orange' : action === 'delete' + ? 'Red' : 'Default'; const getMessage = client.i18n.getLocale(settings.locale); const i18nOptions = { user: `<@${user.id}>`, @@ -79,7 +83,7 @@ async function logAdminEvent(client, { if (!channel) return; const embeds = [ new EmbedBuilder() - .setColor('Orange') + .setColor(colour) .setAuthor({ iconURL: user.avatarURL(), name: user.username, @@ -105,7 +109,7 @@ async function logAdminEvent(client, { if (diff && diff.original) { embeds.push( new EmbedBuilder() - .setColor('Orange') + .setColor(colour) .setTitle(getMessage('log.admin.changes')) .setFields(makeDiff(diff)), ); diff --git a/src/routes/api/admin/guilds/[guild]/panels.js b/src/routes/api/admin/guilds/[guild]/panels.js new file mode 100644 index 0000000..3e0a211 --- /dev/null +++ b/src/routes/api/admin/guilds/[guild]/panels.js @@ -0,0 +1,141 @@ +const { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle: { + Primary, + Secondary, + }, + ChannelType: { GuildText }, + EmbedBuilder, + SelectMenuBuilder, + SelectMenuOptionBuilder, +} = require('discord.js'); +const emoji = require('node-emoji'); +const { logAdminEvent } = require('../../../../../lib/logging'); + +module.exports.post = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const guild = client.guilds.cache.get(req.params.guild); + const data = req.body; + + const settings = await client.prisma.guild.findUnique({ + select: { + categories: true, + footer: true, + locale: true, + primaryColour: true, + }, + where: { id: guild.id }, + }); + const getMessage = client.i18n.getLocale(settings.locale); + const categories = settings.categories.filter(c => data.categories.includes(c.id)); + if (categories.length === 0) throw new Error('No categories'); + if (categories.length !== 1 && data.type === 'MESSAGE') throw new Error('Invalid number of categories for panel type'); + + let channel; + if (data.channel) { + channel = await client.channels.fetch(data.channel); + } else { + const allow = ['ViewChannel', 'ReadMessageHistory']; + if (data.type === 'MESSAGE') allow.push('SendMessages'); + channel = await guild.channels.create({ + name: 'create-a-ticket', + permissionOverwrites: [ + { + allow, + deny: ['AddReactions', 'AttachFiles'], + id: guild.roles.everyone, + }, + ], + position: 1, + rateLimitPerUser: 15, + reason: 'New ticket panel', + type: GuildText, + }); + } + + const embed = new EmbedBuilder() + .setColor(settings.primaryColour) + .setTitle(data.title) + .setFooter({ + iconURL: guild.iconURL(), + text: settings.footer, + }); + + if (data.description) embed.setDescription(data.description); + if (data.image) embed.setImage(data.image); + if (data.thumbnail) embed.setThumbnail(data.thumbnail); + + if (data.type === 'MESSAGE') { + await channel.send({ embeds: [embed] }); + } else { + const components = []; + + if (categories.length === 1) { + components.push( + new ButtonBuilder() + .setCustomId(JSON.stringify({ + action: 'createTicket', + target: categories[0].id, + })) + .setStyle(Primary) + .setLabel(getMessage('buttons.create.text')) + .setEmoji(getMessage('buttons.create.emoji')), + ); + } else { + if (data.type === 'BUTTON') { + components.push( + ...categories.map(category => + new ButtonBuilder() + .setCustomId(JSON.stringify({ + action: 'createTicket', + target: category.id, + })) + .setStyle(Secondary) + .setLabel(category.name) + .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), + ), + ); + } else { + components.push( + new SelectMenuBuilder() + .setCustomId('createTicket') + .setPlaceholder(getMessage('menus.panel.placeholder')) + .setOptions( + categories.map(category => + new SelectMenuOptionBuilder() + .setValue(String(category.id)) + .setLabel(category.name) + .setDescription(category.description) + .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), + ), + ), + ); + } + } + + await channel.send({ + components: [ + new ActionRowBuilder() + .setComponents(components), + ], + embeds: [embed], + }); + } + + logAdminEvent(client, { + action: 'create', + guildId: guild.id, + target: { + id: channel.toString(), + type: 'panel', + }, + userId: req.user.payload.id, + }); + + return true; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); \ No newline at end of file From 3a9483dbfba1b5aa0f87d3512d9b02c95f4ba70b Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 21 Jul 2022 14:30:03 +0100 Subject: [PATCH 062/409] Remove CONFIG_PATH env var --- README.md | 2 +- src/index.js | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0017bbb..3a0707a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -CONFIG_PATH=./user/config.yml DISCORD_SECRET= DISCORD_TOKEN= DB_CONNECTION_URL="mysql://test:password@localhost/tickets0" ENCRYPTION_KEY= HTTP_BIND=8080 HTTP_EXTERNAL=http://localhost:8080 +PORTAL=http://localhost:3000 SUPER= \ No newline at end of file diff --git a/src/index.js b/src/index.js index 9411cb0..8019b3b 100644 --- a/src/index.js +++ b/src/index.js @@ -46,23 +46,21 @@ if (process.env.ENCRYPTION_KEY === undefined) { process.exit(1); } -process.env.CONFIG_PATH ??= './user/config.yml'; // set default config file path - -if (!fs.existsSync(process.env.CONFIG_PATH)) { +if (!fs.existsSync('./user/config.yml')) { const examplePath = './user/example.config.yml'; if (!fs.existsSync(examplePath)) { console.log('\x07' + colours.redBright('The config file does not exist, and the example file is missing so cannot be copied from.')); process.exit(1); } else { console.log('Creating config file...'); - fs.copyFileSync(examplePath, process.env.CONFIG_PATH); - console.log(`Copied config to ${process.env.CONFIG_PATH}`); + fs.copyFileSync(examplePath, './user/config.yml'); + console.log(`Copied config to ${'./user/config.yml'}`); } } console.log(banner(pkg.version)); // print big title -const config = YAML.parse(fs.readFileSync(process.env.CONFIG_PATH, 'utf8')); +const config = YAML.parse(fs.readFileSync('./user/config.yml', 'utf8')); const log = logger(config); process.on('unhandledRejection', error => { From cc97a58165bc045248de1deb7b9e8bbf6db8cecc Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 22 Jul 2022 00:17:07 +0100 Subject: [PATCH 063/409] Select menu question type and finally fix logging diff --- prisma/schema.prisma | 13 +++++++++++-- src/lib/logging.js | 5 +++-- .../guilds/[guild]/categories/[category]/index.js | 4 ++++ src/routes/api/client.js | 1 + 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 12ffb19..8e2b303 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -70,6 +70,8 @@ model Category { channelName String claiming Boolean @default(false) createdAt DateTime @default(now()) + cooldown Int? + customTopic String? description String discordCategory String @db.VarChar(19) emoji String @@ -142,10 +144,12 @@ model Question { label String @db.VarChar(45) maxLength Int? @default(4000) minLength Int? @default(0) + options Json @default("[]") order Int placeholder String? @db.VarChar(100) required Boolean @default(true) style Int @default(2) + type QuestionType @default(TEXT) value String? @db.Text @@map("questions") @@ -207,7 +211,7 @@ model Ticket { open Boolean @default(true) openingMessage String @db.VarChar(19) pinnedMessages Json @default("[]") - priority Priority? + priority TicketPriority? referencedBy Ticket[] @relation("TicketsReferencedByTicket") referencesMessageId String @db.VarChar(19) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) @@ -232,8 +236,13 @@ model User { @@map("users") } -enum Priority { +enum TicketPriority { LOW MEDIUM HIGH } + +enum QuestionType { + MENU + TEXT +} diff --git a/src/lib/logging.js b/src/lib/logging.js index 2464618..8289cf9 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -4,8 +4,9 @@ const { } = require('discord.js'); const { diff: getDiff } = require('object-diffy'); +const uuidRegex = /[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/g; -const exists = thing => (typeof thing === 'string' && thing.length > 0) && (thing !== null && thing !== undefined); +const exists = thing => typeof thing === 'string' ? thing.length > 0 : thing !== null && thing !== undefined; const arrToObj = obj => { for (const key in obj) { @@ -29,7 +30,7 @@ function makeDiff({ const to = exists(diff[key].to) ? `+ ${String(diff[key].to).replace(/\n/g, '\\n')}\n` : ''; fields.push({ inline: true, - name: key, + name: key.replace(uuidRegex, $1 => $1.split('-')[0]), value: `\`\`\`diff\n${cleanCodeBlockContent(from + to)}\n\`\`\``, }); } diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js index d4a09a5..29fe1a7 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js @@ -42,10 +42,12 @@ module.exports.get = fastify => ({ label: true, maxLength: true, minLength: true, + options: true, order: true, placeholder: true, required: true, style: true, + type: true, value: true, }, }, @@ -91,10 +93,12 @@ module.exports.patch = fastify => ({ label: true, maxLength: true, minLength: true, + options: true, order: true, placeholder: true, required: true, style: true, + type: true, value: true, }, }, diff --git a/src/routes/api/client.js b/src/routes/api/client.js index 160ef5e..b9821f9 100644 --- a/src/routes/api/client.js +++ b/src/routes/api/client.js @@ -19,6 +19,7 @@ module.exports.get = () => ({ avatar: client.user.avatarURL(), discriminator: client.user.discriminator, id: client.user.id, + portal: process.env.PORTAL || null, stats: { activatedUsers: users.length, archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they get deleted From 08e757febf543acfb41ad315bb617cae81334195 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 23 Jul 2022 01:13:25 +0100 Subject: [PATCH 064/409] Add logout & tags endpoints --- src/i18n/en-GB.yml | 2 + .../api/admin/guilds/[guild]/tags/[tag].js | 42 +++++++++++++++++ .../api/admin/guilds/[guild]/tags/index.js | 46 +++++++++++++++++++ src/routes/auth/logout.js | 7 +++ user/example.config.yml | 4 +- 5 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/routes/api/admin/guilds/[guild]/tags/[tag].js create mode 100644 src/routes/api/admin/guilds/[guild]/tags/index.js create mode 100644 src/routes/auth/logout.js diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index dc5993a..5df10cb 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -13,6 +13,7 @@ log: panel: 'a panel' question: 'a question' settings: 'the settings' + tag: 'a tag' title: joined: '{targetType} {verb}' target: @@ -20,6 +21,7 @@ log: panel: 'Panel' question: 'Question' settings: 'Settings' + tag: 'Tag' verb: create: 'created' delete: 'deleted' diff --git a/src/routes/api/admin/guilds/[guild]/tags/[tag].js b/src/routes/api/admin/guilds/[guild]/tags/[tag].js new file mode 100644 index 0000000..6edea35 --- /dev/null +++ b/src/routes/api/admin/guilds/[guild]/tags/[tag].js @@ -0,0 +1,42 @@ +const { logAdminEvent } = require('../../../../../../lib/logging'); + +module.exports.delete = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const guildId = req.params.guild; + const tagId = req.params.tag; + const original = tagId && await client.prisma.tag.findUnique({ where: { id: tagId } }); + if (original.guildId !== guildId) return res.status(404).send(new Error('Not Found')); + const tag = await client.prisma.tag.delete({ where: { id: tagId } }); + + logAdminEvent(client, { + action: 'delete', + guildId: req.params.guild, + target: { + id: tag.id, + name: tag.name, + type: 'tag', + }, + userId: req.user.payload.id, + }); + + return tag; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); + +module.exports.get = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const guildId = req.params.guild; + const tagId = Number(req.params.tag); + const tag = await client.prisma.tag.findUnique({ where: { id: tagId } }); + + if (!tag || tag.guildId !== guildId) return res.status(404).send(new Error('Not Found')); + + return tag; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/tags/index.js b/src/routes/api/admin/guilds/[guild]/tags/index.js new file mode 100644 index 0000000..73f31d4 --- /dev/null +++ b/src/routes/api/admin/guilds/[guild]/tags/index.js @@ -0,0 +1,46 @@ +const { logAdminEvent } = require('../../../../../../lib/logging'); + +module.exports.get = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + + const { tags } = await client.prisma.guild.findUnique({ + select: { tags: true }, + where: { id: req.params.guild }, + }); + + return tags; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); + + +module.exports.post = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const guild = client.guilds.cache.get(req.params.guild); + const data = req.body; + const tag = await client.prisma.tag.create({ + data: { + guild: { connect: { id: guild.id } }, + ...data, + }, + }); + + logAdminEvent(client, { + action: 'create', + guildId: guild.id, + target: { + id: tag.id, + name: tag.name, + type: 'tag', + }, + userId: req.user.payload.id, + }); + + return tag; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], +}); \ No newline at end of file diff --git a/src/routes/auth/logout.js b/src/routes/auth/logout.js new file mode 100644 index 0000000..9a0a6c9 --- /dev/null +++ b/src/routes/auth/logout.js @@ -0,0 +1,7 @@ +module.exports.get = () => ({ + handler: async function (req, res) { // must NOT use arrow function syntax + res + .clearCookie('token', '/') + .send('Logged out.'); + }, +}); \ No newline at end of file diff --git a/user/example.config.yml b/user/example.config.yml index d443b6e..3519d93 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -19,4 +19,6 @@ logs: directory: ./logs enabled: true keepFor: 30 # days - level: info \ No newline at end of file + level: info +overrides: + disableArchives: false \ No newline at end of file From 3de9cd8c3a56b7659cc86f11fd4fa693014622e2 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 23 Jul 2022 01:44:03 +0100 Subject: [PATCH 065/409] Add support for SQLite and PostgreSQL. Also add/fix encryption... ...for a smaller number of fields --- .gitignore | 12 +- README.md | 6 +- db/mysql/schema.prisma | 248 ++++++++++++++++++ db/postgresql/schema.prisma | 248 ++++++++++++++++++ db/sqlite/schema.prisma | 237 +++++++++++++++++ package.json | 4 +- scripts/prisma.js | 22 ++ src/client.js | 4 + src/lib/middleware/prisma-encryption.js | 49 ++++ src/lib/middleware/prisma-types.js | 35 +++ .../admin/guilds/[guild]/categories/index.js | 3 +- 11 files changed, 859 insertions(+), 9 deletions(-) create mode 100644 db/mysql/schema.prisma create mode 100644 db/postgresql/schema.prisma create mode 100644 db/sqlite/schema.prisma create mode 100644 scripts/prisma.js create mode 100644 src/lib/middleware/prisma-encryption.js create mode 100644 src/lib/middleware/prisma-types.js diff --git a/.gitignore b/.gitignore index b4cc876..50a83bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,12 @@ # directories -node_modules/ dist/ +node_modules/ +prisma/ # files -.env* -version -user/config.yml -user/database.sqlite +.env +*.db *.log *.lock -*-lock.* \ No newline at end of file +*-lock.* +user/config.yml \ No newline at end of file diff --git a/README.md b/README.md index 3a0707a..7f13b31 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ DISCORD_SECRET= DISCORD_TOKEN= DB_CONNECTION_URL="mysql://test:password@localhost/tickets0" +DB_PROVIDER=mysql ENCRYPTION_KEY= HTTP_BIND=8080 HTTP_EXTERNAL=http://localhost:8080 PORTAL=http://localhost:3000 -SUPER= \ No newline at end of file +SUPER= + + +https://www.prisma.io/docs/reference/database-reference/supported-databases \ No newline at end of file diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma new file mode 100644 index 0000000..8e2b303 --- /dev/null +++ b/db/mysql/schema.prisma @@ -0,0 +1,248 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mysql" + url = env("DB_CONNECTION_URL") +} + +model ArchivedChannel { + channelId String @db.VarChar(19) + createdAt DateTime @default(now()) + name String + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) + + @@id([ticketId, channelId]) + @@unique([ticketId, channelId]) + @@map("archivedChannels") +} + +model ArchivedMessage { + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) + authorId String @db.VarChar(19) + content String @db.Text + createdAt DateTime @default(now()) + deleted Boolean @default(false) + edited Boolean @default(false) + id String @id @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) + + @@map("archivedMessages") +} + +model ArchivedRole { + archivedUsers ArchivedUser[] + colour String @default("7289DA") @db.Char(6) + createdAt DateTime @default(now()) + name String + roleId String @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) + + @@id([ticketId, roleId]) + @@unique([ticketId, roleId]) + @@map("archivedRoles") +} + +model ArchivedUser { + archivedMessages ArchivedMessage[] + avatar String + bot Boolean @default(false) + createdAt DateTime @default(now()) + discriminator String @db.Char(4) + displayName String @db.Text + role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) + roleId String @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) + userId String @db.VarChar(19) + username String @db.Text + + @@id([ticketId, userId]) + @@unique([ticketId, userId]) + @@map("archivedUsers") +} + +model Category { + channelName String + claiming Boolean @default(false) + createdAt DateTime @default(now()) + cooldown Int? + customTopic String? + description String + discordCategory String @db.VarChar(19) + emoji String + enableFeedback Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + image String? + memberLimit Int @default(1) + name String + openingMessage String @db.Text + pingRoles Json @default("[]") + questions Question[] + ratelimit Int? + requiredRoles Json @default("[]") + requireTopic Boolean @default(false) + staffRoles Json + tickets Ticket[] + totalLimit Int @default(-1) + + @@map("categories") +} + +model Feedback { + comment String? @db.Text + createdAt DateTime @default(now()) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + rating Int + ticket Ticket @relation(fields: [ticketId], references: [id]) + ticketId String @unique @db.VarChar(19) + user User? @relation(fields: [userId], references: [id]) + userId String? @db.VarChar(19) + + @@map("feedback") +} + +model Guild { + autoClose Int? + autoTag Json @default("[]") + archive Boolean @default(true) + blocklist Json @default("[]") + categories Category[] + claimButton Boolean @default(false) + closeButton Boolean @default(false) + createdAt DateTime @default(now()) + errorColour String @default("Red") + feedback Feedback[] + footer String? @default("Discord Tickets by eartharoid") + id String @id @db.VarChar(19) + locale String @default("en-GB") + logChannel String? @db.VarChar(19) + primaryColour String @default("#009999") + staleAfter Int? + successColour String @default("Green") + tags Tag[] + tickets Ticket[] + workingHours Json @default("[\"UTC\", [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"]]") + + @@map("guilds") +} + +model Question { + answers QuestionAnswer[] + createdAt DateTime @default(now()) + id String @id @default(uuid()) + category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) + categoryId Int + label String @db.VarChar(45) + maxLength Int? @default(4000) + minLength Int? @default(0) + options Json @default("[]") + order Int + placeholder String? @db.VarChar(100) + required Boolean @default(true) + style Int @default(2) + type QuestionType @default(TEXT) + value String? @db.Text + + @@map("questions") +} + +model QuestionAnswer { + createdAt DateTime @default(now()) + id Int @id @default(autoincrement()) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) + question Question @relation(fields: [questionId], references: [id], onDelete: Cascade) + questionId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String @db.VarChar(19) + value String? @db.Text + + @@map("questionAnswers") +} + +model Tag { + content String @db.Text + createdAt DateTime @default(now()) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + name String + regex String? + + @@unique([guildId, name]) + @@map("tags") +} + +model Ticket { + archivedChannels ArchivedChannel[] + archivedMessages ArchivedMessage[] + archivedRoles ArchivedRole[] + archivedUsers ArchivedUser[] + category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) + categoryId Int? + claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) + claimedById String @db.VarChar(19) + closedAt DateTime? + closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) + closedById String @db.VarChar(19) + closedReason String? + createdAt DateTime @default(now()) + createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) + createdById String @db.VarChar(19) + feedback Feedback? + feedbackId Int? + firstResponseAt DateTime? + deleted Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id String @id @db.VarChar(19) + lastMessageAt DateTime? + messageCount Int? + number Int + open Boolean @default(true) + openingMessage String @db.VarChar(19) + pinnedMessages Json @default("[]") + priority TicketPriority? + referencedBy Ticket[] @relation("TicketsReferencedByTicket") + referencesMessageId String @db.VarChar(19) + referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) + referencesTicketId String? @db.VarChar(19) + topic String? + questionAnswers QuestionAnswer[] + + @@unique([guildId, number]) + @@map("tickets") +} + +model User { + createdAt DateTime @default(now()) + feedback Feedback[] + id String @id @db.VarChar(19) + messageCount Int @default(0) + ticketsCreated Ticket[] @relation("TicketsCreatedByUser") + ticketsClosed Ticket[] @relation("TicketsClosedByUser") + ticketsClaimed Ticket[] @relation("TicketsClaimedByUser") + questionAnswers QuestionAnswer[] + + @@map("users") +} + +enum TicketPriority { + LOW + MEDIUM + HIGH +} + +enum QuestionType { + MENU + TEXT +} diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma new file mode 100644 index 0000000..9b47b9a --- /dev/null +++ b/db/postgresql/schema.prisma @@ -0,0 +1,248 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DB_CONNECTION_URL") +} + +model ArchivedChannel { + channelId String @db.VarChar(19) + createdAt DateTime @default(now()) + name String + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) + + @@id([ticketId, channelId]) + @@unique([ticketId, channelId]) + @@map("archivedChannels") +} + +model ArchivedMessage { + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) + authorId String @db.VarChar(19) + content String @db.Text + createdAt DateTime @default(now()) + deleted Boolean @default(false) + edited Boolean @default(false) + id String @id @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) + + @@map("archivedMessages") +} + +model ArchivedRole { + archivedUsers ArchivedUser[] + colour String @default("7289DA") @db.Char(6) + createdAt DateTime @default(now()) + name String + roleId String @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) + + @@id([ticketId, roleId]) + @@unique([ticketId, roleId]) + @@map("archivedRoles") +} + +model ArchivedUser { + archivedMessages ArchivedMessage[] + avatar String + bot Boolean @default(false) + createdAt DateTime @default(now()) + discriminator String @db.Char(4) + displayName String @db.Text + role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) + roleId String @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) + userId String @db.VarChar(19) + username String @db.Text + + @@id([ticketId, userId]) + @@unique([ticketId, userId]) + @@map("archivedUsers") +} + +model Category { + channelName String + claiming Boolean @default(false) + createdAt DateTime @default(now()) + cooldown Int? + customTopic String? + description String + discordCategory String @db.VarChar(19) + emoji String + enableFeedback Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + image String? + memberLimit Int @default(1) + name String + openingMessage String @db.Text + pingRoles Json @default("[]") + questions Question[] + ratelimit Int? + requiredRoles Json @default("[]") + requireTopic Boolean @default(false) + staffRoles Json + tickets Ticket[] + totalLimit Int @default(-1) + + @@map("categories") +} + +model Feedback { + comment String? @db.Text + createdAt DateTime @default(now()) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + rating Int + ticket Ticket @relation(fields: [ticketId], references: [id]) + ticketId String @unique @db.VarChar(19) + user User? @relation(fields: [userId], references: [id]) + userId String? @db.VarChar(19) + + @@map("feedback") +} + +model Guild { + autoClose Int? + autoTag Json @default("[]") + archive Boolean @default(true) + blocklist Json @default("[]") + categories Category[] + claimButton Boolean @default(false) + closeButton Boolean @default(false) + createdAt DateTime @default(now()) + errorColour String @default("Red") + feedback Feedback[] + footer String? @default("Discord Tickets by eartharoid") + id String @id @db.VarChar(19) + locale String @default("en-GB") + logChannel String? @db.VarChar(19) + primaryColour String @default("#009999") + staleAfter Int? + successColour String @default("Green") + tags Tag[] + tickets Ticket[] + workingHours Json @default("[\"UTC\", [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"]]") + + @@map("guilds") +} + +model Question { + answers QuestionAnswer[] + createdAt DateTime @default(now()) + id String @id @default(uuid()) + category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) + categoryId Int + label String @db.VarChar(45) + maxLength Int? @default(4000) + minLength Int? @default(0) + options Json @default("[]") + order Int + placeholder String? @db.VarChar(100) + required Boolean @default(true) + style Int @default(2) + type QuestionType @default(TEXT) + value String? @db.Text + + @@map("questions") +} + +model QuestionAnswer { + createdAt DateTime @default(now()) + id Int @id @default(autoincrement()) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) + question Question @relation(fields: [questionId], references: [id], onDelete: Cascade) + questionId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String @db.VarChar(19) + value String? @db.Text + + @@map("questionAnswers") +} + +model Tag { + content String @db.Text + createdAt DateTime @default(now()) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + name String + regex String? + + @@unique([guildId, name]) + @@map("tags") +} + +model Ticket { + archivedChannels ArchivedChannel[] + archivedMessages ArchivedMessage[] + archivedRoles ArchivedRole[] + archivedUsers ArchivedUser[] + category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) + categoryId Int? + claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) + claimedById String @db.VarChar(19) + closedAt DateTime? + closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) + closedById String @db.VarChar(19) + closedReason String? + createdAt DateTime @default(now()) + createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) + createdById String @db.VarChar(19) + feedback Feedback? + feedbackId Int? + firstResponseAt DateTime? + deleted Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id String @id @db.VarChar(19) + lastMessageAt DateTime? + messageCount Int? + number Int + open Boolean @default(true) + openingMessage String @db.VarChar(19) + pinnedMessages Json @default("[]") + priority TicketPriority? + referencedBy Ticket[] @relation("TicketsReferencedByTicket") + referencesMessageId String @db.VarChar(19) + referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) + referencesTicketId String? @db.VarChar(19) + topic String? + questionAnswers QuestionAnswer[] + + @@unique([guildId, number]) + @@map("tickets") +} + +model User { + createdAt DateTime @default(now()) + feedback Feedback[] + id String @id @db.VarChar(19) + messageCount Int @default(0) + ticketsCreated Ticket[] @relation("TicketsCreatedByUser") + ticketsClosed Ticket[] @relation("TicketsClosedByUser") + ticketsClaimed Ticket[] @relation("TicketsClaimedByUser") + questionAnswers QuestionAnswer[] + + @@map("users") +} + +enum TicketPriority { + LOW + MEDIUM + HIGH +} + +enum QuestionType { + MENU + TEXT +} diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma new file mode 100644 index 0000000..86a1222 --- /dev/null +++ b/db/sqlite/schema.prisma @@ -0,0 +1,237 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + url = env("DB_CONNECTION_URL") +} + +model ArchivedChannel { + channelId String + createdAt DateTime @default(now()) + name String + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String + + @@id([ticketId, channelId]) + @@unique([ticketId, channelId]) + @@map("archivedChannels") +} + +model ArchivedMessage { + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) + authorId String + content String + createdAt DateTime @default(now()) + deleted Boolean @default(false) + edited Boolean @default(false) + id String @id + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String + + @@map("archivedMessages") +} + +model ArchivedRole { + archivedUsers ArchivedUser[] + colour String @default("7289DA") + createdAt DateTime @default(now()) + name String + roleId String + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String + + @@id([ticketId, roleId]) + @@unique([ticketId, roleId]) + @@map("archivedRoles") +} + +model ArchivedUser { + archivedMessages ArchivedMessage[] + avatar String + bot Boolean @default(false) + createdAt DateTime @default(now()) + discriminator String + displayName String + role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) + roleId String + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String + userId String + username String + + @@id([ticketId, userId]) + @@unique([ticketId, userId]) + @@map("archivedUsers") +} + +model Category { + channelName String + claiming Boolean @default(false) + createdAt DateTime @default(now()) + cooldown Int? + customTopic String? + description String + discordCategory String + emoji String + enableFeedback Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String + id Int @id @default(autoincrement()) + image String? + memberLimit Int @default(1) + name String + openingMessage String + pingRoles String @default("[]") + questions Question[] + ratelimit Int? + requiredRoles String @default("[]") + requireTopic Boolean @default(false) + staffRoles String + tickets Ticket[] + totalLimit Int @default(-1) + + @@map("categories") +} + +model Feedback { + comment String? + createdAt DateTime @default(now()) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String + id Int @id @default(autoincrement()) + rating Int + ticket Ticket @relation(fields: [ticketId], references: [id]) + ticketId String @unique + user User? @relation(fields: [userId], references: [id]) + userId String? + + @@map("feedback") +} + +model Guild { + autoClose Int? + autoTag String @default("[]") + archive Boolean @default(true) + blocklist String @default("[]") + categories Category[] + claimButton Boolean @default(false) + closeButton Boolean @default(false) + createdAt DateTime @default(now()) + errorColour String @default("Red") + feedback Feedback[] + footer String? @default("Discord Tickets by eartharoid") + id String @id + locale String @default("en-GB") + logChannel String? + primaryColour String @default("#009999") + staleAfter Int? + successColour String @default("Green") + tags Tag[] + tickets Ticket[] + workingHours String @default("[\"UTC\", [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"]]") + + @@map("guilds") +} + +model Question { + answers QuestionAnswer[] + createdAt DateTime @default(now()) + id String @id @default(uuid()) + category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) + categoryId Int + label String + maxLength Int? @default(4000) + minLength Int? @default(0) + options String @default("[]") + order Int + placeholder String? + required Boolean @default(true) + style Int @default(2) + type String @default("TEXT") + value String? + + @@map("questions") +} + +model QuestionAnswer { + createdAt DateTime @default(now()) + id Int @id @default(autoincrement()) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String + question Question @relation(fields: [questionId], references: [id], onDelete: Cascade) + questionId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + userId String + value String? + + @@map("questionAnswers") +} + +model Tag { + content String + createdAt DateTime @default(now()) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String + id Int @id @default(autoincrement()) + name String + regex String? + + @@unique([guildId, name]) + @@map("tags") +} + +model Ticket { + archivedChannels ArchivedChannel[] + archivedMessages ArchivedMessage[] + archivedRoles ArchivedRole[] + archivedUsers ArchivedUser[] + category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) + categoryId Int? + claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) + claimedById String + closedAt DateTime? + closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) + closedById String + closedReason String? + createdAt DateTime @default(now()) + createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) + createdById String + feedback Feedback? + feedbackId Int? + firstResponseAt DateTime? + deleted Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String + id String @id + lastMessageAt DateTime? + messageCount Int? + number Int + open Boolean @default(true) + openingMessage String + pinnedMessages String @default("[]") + priority String? + referencedBy Ticket[] @relation("TicketsReferencedByTicket") + referencesMessageId String + referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) + referencesTicketId String? + topic String? + questionAnswers QuestionAnswer[] + + @@unique([guildId, number]) + @@map("tickets") +} + +model User { + createdAt DateTime @default(now()) + feedback Feedback[] + id String @id + messageCount Int @default(0) + ticketsCreated Ticket[] @relation("TicketsCreatedByUser") + ticketsClosed Ticket[] @relation("TicketsClosedByUser") + ticketsClaimed Ticket[] @relation("TicketsClaimedByUser") + questionAnswers QuestionAnswer[] + + @@map("users") +} diff --git a/package.json b/package.json index 7be000b..8103942 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "contributors:generate": "all-contributors generate", "keygen": "node scripts/keygen", "lint": "eslint src scripts --ext mjs --fix", - "start": "node .", + "prisma": "node scripts/prisma", + "start": "npm run prisma && node .", "studio": "npx prisma studio", "test": "echo \"There's nothing to test\" && exit 1" }, @@ -45,6 +46,7 @@ "dotenv": "^16.0.1", "fastify": "^4.2.1", "figlet": "^1.5.2", + "fs-extra": "^10.1.0", "keyv": "^4.3.3", "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", diff --git a/scripts/prisma.js b/scripts/prisma.js new file mode 100644 index 0000000..21db72c --- /dev/null +++ b/scripts/prisma.js @@ -0,0 +1,22 @@ +require('dotenv').config(); +const fs = require('fs-extra'); +// const { promisify } = require('util'); +// const exec = promisify(require('child_process').exec); +const { spawnSync } = require('child_process'); + +const providers = ['mysql', 'postgresql', 'sqlite']; +const provider = process.env.DB_PROVIDER; +if (!providers.includes(provider)) throw new Error(`DB_PROVIDER must be one of: ${providers}`); + +if (!fs.existsSync('./prisma')) fs.mkdirSync('./prisma'); +fs.copySync(`./db/${provider}`, './prisma'); // copy schema & migrations + +npx('prisma generate'); +npx('prisma migrate deploy'); + +function npx(cmd) { + const child = spawnSync('npx', cmd.split(/\s/)); + if (child.stdout) console.log(child.stdout.toString()); + if (child.stderr) console.log(child.stderr.toString()); + if (child.status) process.exit(child.status); +} \ No newline at end of file diff --git a/src/client.js b/src/client.js index 4467174..6cb7249 100644 --- a/src/client.js +++ b/src/client.js @@ -6,6 +6,8 @@ const I18n = require('@eartharoid/i18n'); const fs = require('fs'); const { join } = require('path'); const YAML = require('yaml'); +const encryptionMiddleware = require('./lib/middleware/prisma-encryption'); +const typesMiddleware = require('./lib/middleware/prisma-types'); module.exports = class Client extends FrameworkClient { constructor(config, log) { @@ -36,6 +38,8 @@ module.exports = class Client extends FrameworkClient { async login(token) { /** @type {PrismaClient} */ this.prisma = new PrismaClient(); + this.prisma.$use(encryptionMiddleware); + this.prisma.$use(typesMiddleware); this.keyv = new Keyv(); return super.login(token); } diff --git a/src/lib/middleware/prisma-encryption.js b/src/lib/middleware/prisma-encryption.js new file mode 100644 index 0000000..138b793 --- /dev/null +++ b/src/lib/middleware/prisma-encryption.js @@ -0,0 +1,49 @@ +const Cryptr = require('cryptr'); +const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); +const encryptedFields = [ + // 'name', + 'content', + 'username', + 'displayName', + // 'channelName', + // 'openingMessage', + // 'description', + 'value', + // 'placeholder', + 'closedReason', + 'topic', + 'comment', + // 'label', + // 'regex', +]; + +const encrypt = obj => { + for (const prop in obj) { + if (typeof obj[prop] === 'string' && obj[prop].length !== 0 && encryptedFields.includes(prop)) { + obj[prop] = cryptr.encrypt(obj[prop]); + } else if (typeof obj[prop] === 'object') { + obj[prop] = encrypt(obj[prop]); + } + } + return obj; +}; + +const decrypt = obj => { + for (const prop in obj) { + if (typeof obj[prop] === 'string' && obj[prop].length !== 0 && encryptedFields.includes(prop)) { + obj[prop] = cryptr.decrypt(obj[prop]); + } else if (typeof obj[prop] === 'object') { + obj[prop] = decrypt(obj[prop]); + } + } + return obj; +}; + +module.exports = async (params, next) => { + if (params.args.create) params.args.create = encrypt(params.args.create); + if (params.args.data) params.args.data = encrypt(params.args.data); + if (params.args.update) params.args.update = encrypt(params.args.update); + let result = await next(params); + if (result) result = decrypt(result); + return result; +}; \ No newline at end of file diff --git a/src/lib/middleware/prisma-types.js b/src/lib/middleware/prisma-types.js new file mode 100644 index 0000000..0718f14 --- /dev/null +++ b/src/lib/middleware/prisma-types.js @@ -0,0 +1,35 @@ +const jsonFields = [ + 'pingRoles', + 'requiredRoles', + 'staffRoles', + 'autoTag', + 'blocklist', + 'workingHours', + 'options', + 'pinnedMessages', +]; + +const traverse = (obj, func) => { + for (const prop in obj) { + console.log(prop, typeof obj[prop], obj[prop]); + if (jsonFields.includes(prop) && obj[prop] !== null && obj[prop] !== undefined) { + obj[prop] = func(obj[prop]); + } else if (typeof obj[prop] === 'object') { + obj[prop] = traverse(obj[prop], func); + } + } + return obj; +}; + +module.exports = async (params, next) => { + if (process.env.DB_PROVIDER === 'sqlite') { + if (params.args.create) params.args.create = traverse(params.args.create, val => JSON.stringify(val)); + if (params.args.data) params.args.data = traverse(params.args.data, val => JSON.stringify(val)); + if (params.args.update) params.args.update = traverse(params.args.update, val => JSON.stringify(val)); + let result = await next(params); + if (result) result = traverse(result, val => JSON.parse(val)); + return result; + } else { + return await next(params); + } +}; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index ec9a5a0..b95dee7 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -78,7 +78,8 @@ module.exports.post = fastify => ({ data: { guild: { connect: { id: guild.id } }, ...data, - questions: { createMany: { data: data.questions ?? [] } }, + // questions: { createMany: { data: data.questions ?? [] } }, + questions: { create: { data: data.questions ?? [] } }, // sqlite doesn't support createMany? }, }); From d147fa178b7b9bef8ab27d192a3d10112729f53c Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 23 Jul 2022 13:27:32 +0100 Subject: [PATCH 066/409] Improve prisma scripts --- db/mysql/schema.prisma | 2 +- db/postgresql/schema.prisma | 2 +- db/sqlite/schema.prisma | 2 +- package.json | 3 ++- prisma/schema.prisma | 2 +- scripts/{prisma.js => postinstall.js} | 10 ++++++++-- scripts/preinstall.js | 23 +++++++++++++++++++++++ 7 files changed, 37 insertions(+), 7 deletions(-) rename scripts/{prisma.js => postinstall.js} (83%) create mode 100644 scripts/preinstall.js diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index 8e2b303..6834ce1 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -90,7 +90,7 @@ model Category { requireTopic Boolean @default(false) staffRoles Json tickets Ticket[] - totalLimit Int @default(-1) + totalLimit Int @default(50) @@map("categories") } diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index 9b47b9a..5511dbe 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -90,7 +90,7 @@ model Category { requireTopic Boolean @default(false) staffRoles Json tickets Ticket[] - totalLimit Int @default(-1) + totalLimit Int @default(50) @@map("categories") } diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma index 86a1222..6f7542c 100644 --- a/db/sqlite/schema.prisma +++ b/db/sqlite/schema.prisma @@ -90,7 +90,7 @@ model Category { requireTopic Boolean @default(false) staffRoles String tickets Ticket[] - totalLimit Int @default(-1) + totalLimit Int @default(50) @@map("categories") } diff --git a/package.json b/package.json index 8103942..e7fcecd 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "contributors:generate": "all-contributors generate", "keygen": "node scripts/keygen", "lint": "eslint src scripts --ext mjs --fix", - "prisma": "node scripts/prisma", + "preinstall": "node scripts/preinstall", + "postinstall": "node scripts/postinstall", "start": "npm run prisma && node .", "studio": "npx prisma studio", "test": "echo \"There's nothing to test\" && exit 1" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8e2b303..6834ce1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -90,7 +90,7 @@ model Category { requireTopic Boolean @default(false) staffRoles Json tickets Ticket[] - totalLimit Int @default(-1) + totalLimit Int @default(50) @@map("categories") } diff --git a/scripts/prisma.js b/scripts/postinstall.js similarity index 83% rename from scripts/prisma.js rename to scripts/postinstall.js index 21db72c..bd0cdc5 100644 --- a/scripts/prisma.js +++ b/scripts/postinstall.js @@ -1,11 +1,16 @@ require('dotenv').config(); const fs = require('fs-extra'); -// const { promisify } = require('util'); -// const exec = promisify(require('child_process').exec); const { spawnSync } = require('child_process'); + const providers = ['mysql', 'postgresql', 'sqlite']; const provider = process.env.DB_PROVIDER; + +if (!provider) { + console.log('[postinstall] environment not set, exiting.'); + process.exit(0); +} + if (!providers.includes(provider)) throw new Error(`DB_PROVIDER must be one of: ${providers}`); if (!fs.existsSync('./prisma')) fs.mkdirSync('./prisma'); @@ -15,6 +20,7 @@ npx('prisma generate'); npx('prisma migrate deploy'); function npx(cmd) { + console.log(`[postinstall] > ${cmd}`); const child = spawnSync('npx', cmd.split(/\s/)); if (child.stdout) console.log(child.stdout.toString()); if (child.stderr) console.log(child.stderr.toString()); diff --git a/scripts/preinstall.js b/scripts/preinstall.js new file mode 100644 index 0000000..2f00282 --- /dev/null +++ b/scripts/preinstall.js @@ -0,0 +1,23 @@ +const { randomBytes } = require('crypto'); +const fs = require('fs'); + +const env = { + DB_CONNECTION_URL: '', + DB_PROVIDER: '', // don't default to sqlite, postinstall checks if empty + DISCORD_SECRET: '', + DISCORD_TOKEN: '', + ENCRYPTION_KEY: randomBytes(24).toString('hex'), + HTTP_BIND: 8080, + HTTP_EXTERNAL: 'http://localhost:8080', + PORTAL: '', + SUPER: '319467558166069248', +}; + +// check DISCORD_TOKEN because we don't want to force use of the .env file +if (!process.env.DISCORD_TOKEN && !fs.existsSync('./.env')) { + console.log('[preinstall] Generating ENCRYPTION_KEY'); + fs.writeFileSync('./.env', Object.entries(env).map(([k, v]) => `${k}=${v}`).join('\n')); + console.log('[preinstall] Created .env file'); +} else { + console.log('[preinstall] Nothing to do'); +} \ No newline at end of file From e00ff4e831268c213e545846568841b49203f6e6 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 23 Jul 2022 13:34:18 +0100 Subject: [PATCH 067/409] Stop tracking prisma/schema.prisma --- prisma/schema.prisma | 248 ------------------------------------------- 1 file changed, 248 deletions(-) delete mode 100644 prisma/schema.prisma diff --git a/prisma/schema.prisma b/prisma/schema.prisma deleted file mode 100644 index 6834ce1..0000000 --- a/prisma/schema.prisma +++ /dev/null @@ -1,248 +0,0 @@ -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "mysql" - url = env("DB_CONNECTION_URL") -} - -model ArchivedChannel { - channelId String @db.VarChar(19) - createdAt DateTime @default(now()) - name String - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) - - @@id([ticketId, channelId]) - @@unique([ticketId, channelId]) - @@map("archivedChannels") -} - -model ArchivedMessage { - author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) - authorId String @db.VarChar(19) - content String @db.Text - createdAt DateTime @default(now()) - deleted Boolean @default(false) - edited Boolean @default(false) - id String @id @db.VarChar(19) - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) - - @@map("archivedMessages") -} - -model ArchivedRole { - archivedUsers ArchivedUser[] - colour String @default("7289DA") @db.Char(6) - createdAt DateTime @default(now()) - name String - roleId String @db.VarChar(19) - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) - - @@id([ticketId, roleId]) - @@unique([ticketId, roleId]) - @@map("archivedRoles") -} - -model ArchivedUser { - archivedMessages ArchivedMessage[] - avatar String - bot Boolean @default(false) - createdAt DateTime @default(now()) - discriminator String @db.Char(4) - displayName String @db.Text - role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) - roleId String @db.VarChar(19) - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) - userId String @db.VarChar(19) - username String @db.Text - - @@id([ticketId, userId]) - @@unique([ticketId, userId]) - @@map("archivedUsers") -} - -model Category { - channelName String - claiming Boolean @default(false) - createdAt DateTime @default(now()) - cooldown Int? - customTopic String? - description String - discordCategory String @db.VarChar(19) - emoji String - enableFeedback Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) - guildId String @db.VarChar(19) - id Int @id @default(autoincrement()) - image String? - memberLimit Int @default(1) - name String - openingMessage String @db.Text - pingRoles Json @default("[]") - questions Question[] - ratelimit Int? - requiredRoles Json @default("[]") - requireTopic Boolean @default(false) - staffRoles Json - tickets Ticket[] - totalLimit Int @default(50) - - @@map("categories") -} - -model Feedback { - comment String? @db.Text - createdAt DateTime @default(now()) - guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) - guildId String @db.VarChar(19) - id Int @id @default(autoincrement()) - rating Int - ticket Ticket @relation(fields: [ticketId], references: [id]) - ticketId String @unique @db.VarChar(19) - user User? @relation(fields: [userId], references: [id]) - userId String? @db.VarChar(19) - - @@map("feedback") -} - -model Guild { - autoClose Int? - autoTag Json @default("[]") - archive Boolean @default(true) - blocklist Json @default("[]") - categories Category[] - claimButton Boolean @default(false) - closeButton Boolean @default(false) - createdAt DateTime @default(now()) - errorColour String @default("Red") - feedback Feedback[] - footer String? @default("Discord Tickets by eartharoid") - id String @id @db.VarChar(19) - locale String @default("en-GB") - logChannel String? @db.VarChar(19) - primaryColour String @default("#009999") - staleAfter Int? - successColour String @default("Green") - tags Tag[] - tickets Ticket[] - workingHours Json @default("[\"UTC\", [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"], [\"00:00\",\"23:59\"]]") - - @@map("guilds") -} - -model Question { - answers QuestionAnswer[] - createdAt DateTime @default(now()) - id String @id @default(uuid()) - category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade) - categoryId Int - label String @db.VarChar(45) - maxLength Int? @default(4000) - minLength Int? @default(0) - options Json @default("[]") - order Int - placeholder String? @db.VarChar(100) - required Boolean @default(true) - style Int @default(2) - type QuestionType @default(TEXT) - value String? @db.Text - - @@map("questions") -} - -model QuestionAnswer { - createdAt DateTime @default(now()) - id Int @id @default(autoincrement()) - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) - question Question @relation(fields: [questionId], references: [id], onDelete: Cascade) - questionId String - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - userId String @db.VarChar(19) - value String? @db.Text - - @@map("questionAnswers") -} - -model Tag { - content String @db.Text - createdAt DateTime @default(now()) - guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) - guildId String @db.VarChar(19) - id Int @id @default(autoincrement()) - name String - regex String? - - @@unique([guildId, name]) - @@map("tags") -} - -model Ticket { - archivedChannels ArchivedChannel[] - archivedMessages ArchivedMessage[] - archivedRoles ArchivedRole[] - archivedUsers ArchivedUser[] - category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) - categoryId Int? - claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) - claimedById String @db.VarChar(19) - closedAt DateTime? - closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) - closedById String @db.VarChar(19) - closedReason String? - createdAt DateTime @default(now()) - createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) - createdById String @db.VarChar(19) - feedback Feedback? - feedbackId Int? - firstResponseAt DateTime? - deleted Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) - guildId String @db.VarChar(19) - id String @id @db.VarChar(19) - lastMessageAt DateTime? - messageCount Int? - number Int - open Boolean @default(true) - openingMessage String @db.VarChar(19) - pinnedMessages Json @default("[]") - priority TicketPriority? - referencedBy Ticket[] @relation("TicketsReferencedByTicket") - referencesMessageId String @db.VarChar(19) - referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) - referencesTicketId String? @db.VarChar(19) - topic String? - questionAnswers QuestionAnswer[] - - @@unique([guildId, number]) - @@map("tickets") -} - -model User { - createdAt DateTime @default(now()) - feedback Feedback[] - id String @id @db.VarChar(19) - messageCount Int @default(0) - ticketsCreated Ticket[] @relation("TicketsCreatedByUser") - ticketsClosed Ticket[] @relation("TicketsClosedByUser") - ticketsClaimed Ticket[] @relation("TicketsClaimedByUser") - questionAnswers QuestionAnswer[] - - @@map("users") -} - -enum TicketPriority { - LOW - MEDIUM - HIGH -} - -enum QuestionType { - MENU - TEXT -} From 5f5ffca74c1eda293b0859d5f0f9f942eb5efecf Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 23 Jul 2022 20:28:48 +0100 Subject: [PATCH 068/409] Finally "fix" (hack) Prisma middleware bug Middleware runs twice on question upsert? --- .gitignore | 2 +- db/mysql/schema.prisma | 4 +- db/postgresql/schema.prisma | 4 +- db/sqlite/schema.prisma | 2 +- src/client.js | 4 +- src/lib/middleware/prisma-encryption.js | 34 +++++++--------- src/lib/middleware/prisma-sqlite.js | 39 +++++++++++++++++++ src/lib/middleware/prisma-types.js | 35 ----------------- .../admin/guilds/[guild]/categories/index.js | 3 +- 9 files changed, 63 insertions(+), 64 deletions(-) create mode 100644 src/lib/middleware/prisma-sqlite.js delete mode 100644 src/lib/middleware/prisma-types.js diff --git a/.gitignore b/.gitignore index 50a83bf..cba93c1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ prisma/ # files .env *.db +*.db-journal *.log -*.lock *-lock.* user/config.yml \ No newline at end of file diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index 6834ce1..46dde9c 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -194,7 +194,7 @@ model Ticket { closedAt DateTime? closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) closedById String @db.VarChar(19) - closedReason String? + closedReason String? @db.Text createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String @db.VarChar(19) @@ -216,7 +216,7 @@ model Ticket { referencesMessageId String @db.VarChar(19) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? @db.VarChar(19) - topic String? + topic String? @db.Text questionAnswers QuestionAnswer[] @@unique([guildId, number]) diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index 5511dbe..d093f69 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -194,7 +194,7 @@ model Ticket { closedAt DateTime? closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) closedById String @db.VarChar(19) - closedReason String? + closedReason String? @db.Text createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String @db.VarChar(19) @@ -216,7 +216,7 @@ model Ticket { referencesMessageId String @db.VarChar(19) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? @db.VarChar(19) - topic String? + topic String? @db.Text questionAnswers QuestionAnswer[] @@unique([guildId, number]) diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma index 6f7542c..cf3337b 100644 --- a/db/sqlite/schema.prisma +++ b/db/sqlite/schema.prisma @@ -4,7 +4,7 @@ generator client { datasource db { provider = "sqlite" - url = env("DB_CONNECTION_URL") + url = "file:../user/database.db" } model ArchivedChannel { diff --git a/src/client.js b/src/client.js index 6cb7249..dc5a47d 100644 --- a/src/client.js +++ b/src/client.js @@ -7,7 +7,7 @@ const fs = require('fs'); const { join } = require('path'); const YAML = require('yaml'); const encryptionMiddleware = require('./lib/middleware/prisma-encryption'); -const typesMiddleware = require('./lib/middleware/prisma-types'); +const sqliteMiddleware = require('./lib/middleware/prisma-sqlite'); module.exports = class Client extends FrameworkClient { constructor(config, log) { @@ -39,7 +39,7 @@ module.exports = class Client extends FrameworkClient { /** @type {PrismaClient} */ this.prisma = new PrismaClient(); this.prisma.$use(encryptionMiddleware); - this.prisma.$use(typesMiddleware); + if (process.env.DB_PROVIDER === 'sqlite') this.prisma.$use(sqliteMiddleware); this.keyv = new Keyv(); return super.login(token); } diff --git a/src/lib/middleware/prisma-encryption.js b/src/lib/middleware/prisma-encryption.js index 138b793..87836e4 100644 --- a/src/lib/middleware/prisma-encryption.js +++ b/src/lib/middleware/prisma-encryption.js @@ -17,33 +17,29 @@ const encryptedFields = [ // 'regex', ]; -const encrypt = obj => { +const traverse = (obj, action) => { for (const prop in obj) { - if (typeof obj[prop] === 'string' && obj[prop].length !== 0 && encryptedFields.includes(prop)) { - obj[prop] = cryptr.encrypt(obj[prop]); + if (encryptedFields.includes(prop) && typeof obj[prop] === 'string' && obj[prop].length !== 0) { + try { + // prevent double encryption bug (from nested writes - notably upserting questions in category update). + // not sure why it happens + if (action === 'ENCRYPT' && cryptr.decrypt(obj[prop])) continue; // don't encrypt if it already encrypted + else obj[prop] = cryptr[action.toLowerCase()](obj[prop]); + } catch { + // do nothing + } } else if (typeof obj[prop] === 'object') { - obj[prop] = encrypt(obj[prop]); - } - } - return obj; -}; - -const decrypt = obj => { - for (const prop in obj) { - if (typeof obj[prop] === 'string' && obj[prop].length !== 0 && encryptedFields.includes(prop)) { - obj[prop] = cryptr.decrypt(obj[prop]); - } else if (typeof obj[prop] === 'object') { - obj[prop] = decrypt(obj[prop]); + obj[prop] = traverse(obj[prop], action); } } return obj; }; module.exports = async (params, next) => { - if (params.args.create) params.args.create = encrypt(params.args.create); - if (params.args.data) params.args.data = encrypt(params.args.data); - if (params.args.update) params.args.update = encrypt(params.args.update); + if (params.args.create) params.args.create = traverse(params.args.create, 'ENCRYPT'); + if (params.args.data) params.args.data = traverse(params.args.data, 'ENCRYPT'); + if (params.args.update) params.args.update = traverse(params.args.update, 'ENCRYPT'); let result = await next(params); - if (result) result = decrypt(result); + if (result) result = traverse(result, 'DECRYPT'); return result; }; \ No newline at end of file diff --git a/src/lib/middleware/prisma-sqlite.js b/src/lib/middleware/prisma-sqlite.js new file mode 100644 index 0000000..8af1ca1 --- /dev/null +++ b/src/lib/middleware/prisma-sqlite.js @@ -0,0 +1,39 @@ +const jsonFields = [ + 'pingRoles', + 'requiredRoles', + 'staffRoles', + 'autoTag', + 'blocklist', + 'workingHours', + 'options', + 'pinnedMessages', +]; + +const traverse = (obj, action) => { + for (let prop in obj) { + if (prop === 'createMany') { + obj.create = obj[prop].data; + delete obj[prop]; + prop = 'create'; + traverse(obj[prop], action); + } else if (jsonFields.includes(prop) && obj[prop] !== null && obj[prop] !== undefined) { + if (action === 'SERIALISE' && typeof obj[prop] !== 'string') { + obj[prop] = JSON.stringify(obj[prop]); + } else if (action === 'PARSE' && typeof obj[prop] === 'string') { + obj[prop] = JSON.parse(obj[prop]); + } + } else if (typeof obj[prop] === 'object' && obj[prop] !== null && obj[prop] !== undefined) { + traverse(obj[prop], action); + } + } + return obj; +}; + +module.exports = async (params, next) => { + if (params.args.create) params.args.create = traverse(params.args.create, 'SERIALISE'); + if (params.args.data) params.args.data = traverse(params.args.data, 'SERIALISE'); + if (params.args.update) params.args.update = traverse(params.args.update, 'SERIALISE'); + let result = await next(params); + if (result) result = traverse(result, 'PARSE'); + return result; +}; \ No newline at end of file diff --git a/src/lib/middleware/prisma-types.js b/src/lib/middleware/prisma-types.js deleted file mode 100644 index 0718f14..0000000 --- a/src/lib/middleware/prisma-types.js +++ /dev/null @@ -1,35 +0,0 @@ -const jsonFields = [ - 'pingRoles', - 'requiredRoles', - 'staffRoles', - 'autoTag', - 'blocklist', - 'workingHours', - 'options', - 'pinnedMessages', -]; - -const traverse = (obj, func) => { - for (const prop in obj) { - console.log(prop, typeof obj[prop], obj[prop]); - if (jsonFields.includes(prop) && obj[prop] !== null && obj[prop] !== undefined) { - obj[prop] = func(obj[prop]); - } else if (typeof obj[prop] === 'object') { - obj[prop] = traverse(obj[prop], func); - } - } - return obj; -}; - -module.exports = async (params, next) => { - if (process.env.DB_PROVIDER === 'sqlite') { - if (params.args.create) params.args.create = traverse(params.args.create, val => JSON.stringify(val)); - if (params.args.data) params.args.data = traverse(params.args.data, val => JSON.stringify(val)); - if (params.args.update) params.args.update = traverse(params.args.update, val => JSON.stringify(val)); - let result = await next(params); - if (result) result = traverse(result, val => JSON.parse(val)); - return result; - } else { - return await next(params); - } -}; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index b95dee7..ec9a5a0 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -78,8 +78,7 @@ module.exports.post = fastify => ({ data: { guild: { connect: { id: guild.id } }, ...data, - // questions: { createMany: { data: data.questions ?? [] } }, - questions: { create: { data: data.questions ?? [] } }, // sqlite doesn't support createMany? + questions: { createMany: { data: data.questions ?? [] } }, }, }); From 31635cc7cb3cc84157f46b494ac33effd2d51288 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 25 Jul 2022 00:25:26 +0100 Subject: [PATCH 069/409] Fix tags --- .../api/admin/guilds/[guild]/tags/[tag].js | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/routes/api/admin/guilds/[guild]/tags/[tag].js b/src/routes/api/admin/guilds/[guild]/tags/[tag].js index 6edea35..4a954dd 100644 --- a/src/routes/api/admin/guilds/[guild]/tags/[tag].js +++ b/src/routes/api/admin/guilds/[guild]/tags/[tag].js @@ -5,7 +5,7 @@ module.exports.delete = fastify => ({ /** @type {import('client')} */ const client = res.context.config.client; const guildId = req.params.guild; - const tagId = req.params.tag; + const tagId = Number(req.params.tag); const original = tagId && await client.prisma.tag.findUnique({ where: { id: tagId } }); if (original.guildId !== guildId) return res.status(404).send(new Error('Not Found')); const tag = await client.prisma.tag.delete({ where: { id: tagId } }); @@ -39,4 +39,45 @@ module.exports.get = fastify => ({ return tag; }, onRequest: [fastify.authenticate, fastify.isAdmin], +}); + +module.exports.patch = fastify => ({ + handler: async (req, res) => { + /** @type {import('client')} */ + const client = res.context.config.client; + const guildId = req.params.guild; + const tagId = Number(req.params.tag); + const guild = client.guilds.cache.get(req.params.guild); + const data = req.body; + + const original = req.params.tag && await client.prisma.tag.findUnique({ where: { id: tagId } }); + + if (!original || original.guildId !== guildId) return res.status(404).send(new Error('Not Found')); + + if (data.hasOwnProperty('id')) delete data.id; + if (data.hasOwnProperty('createdAt')) delete data.createdAt; + + const tag = await client.prisma.tag.update({ + data, + where: { id: tagId }, + }); + + logAdminEvent(client, { + action: 'update', + diff: { + original, + updated: tag, + }, + guildId: guild.id, + target: { + id: tag.id, + name: tag.name, + type: 'tag', + }, + userId: req.user.payload.id, + }); + + return tag; + }, + onRequest: [fastify.authenticate, fastify.isAdmin], }); \ No newline at end of file From aa51cd05540809d1e1f2bc28eac6eca014d43a77 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 2 Aug 2022 17:25:53 +0100 Subject: [PATCH 070/409] Update to @eartharoid/dbf@0.3 --- package.json | 2 +- src/client.js | 2 +- src/listeners/client/ready.js | 4 ++-- src/{commands => }/stdin/eval.js | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) rename src/{commands => }/stdin/eval.js (82%) diff --git a/package.json b/package.json index e7fcecd..2baa031 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@eartharoid/dbf": "^0.2.2", + "@eartharoid/dbf": "^0.3.0", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.0.4", "@fastify/cookie": "^6.0.0", diff --git a/src/client.js b/src/client.js index dc5a47d..bec0b96 100644 --- a/src/client.js +++ b/src/client.js @@ -1,4 +1,4 @@ -const { Client: FrameworkClient } = require('@eartharoid/dbf'); +const { FrameworkClient } = require('@eartharoid/dbf'); const { GatewayIntentBits } = require('discord.js'); const { PrismaClient } = require('@prisma/client'); const Keyv = require('keyv'); diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 4315f0a..26a436f 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -1,11 +1,11 @@ const { Listener } = require('@eartharoid/dbf'); module.exports = class extends Listener { - constructor(client) { + constructor(client, options) { super(client, { + ...options, emitter: client, event: 'ready', - id: 'clientReady', once: true, }); } diff --git a/src/commands/stdin/eval.js b/src/stdin/eval.js similarity index 82% rename from src/commands/stdin/eval.js rename to src/stdin/eval.js index ef394d0..c081510 100644 --- a/src/commands/stdin/eval.js +++ b/src/stdin/eval.js @@ -1,10 +1,10 @@ const { StdinCommand } = require('@eartharoid/dbf'); module.exports = class extends StdinCommand { - constructor(client) { + constructor(client, options) { super(client, { - id: 'stdinEval', - name: 'eval', + ...options, + id: 'eval', }); } From 327573a38a0793a16eba6dcc9c241065f005dfe0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 2 Aug 2022 17:32:55 +0100 Subject: [PATCH 071/409] Add application commands publishing --- src/listeners/stdin/unknown.js | 15 +++++++++++++++ src/stdin/commands.js | 24 ++++++++++++++++++++++++ src/stdin/eval.js | 2 ++ 3 files changed, 41 insertions(+) create mode 100644 src/listeners/stdin/unknown.js create mode 100644 src/stdin/commands.js diff --git a/src/listeners/stdin/unknown.js b/src/listeners/stdin/unknown.js new file mode 100644 index 0000000..d68e27a --- /dev/null +++ b/src/listeners/stdin/unknown.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.stdin, + event: 'unknown', + }); + } + + run(commandName) { + console.log('Unknown command:', commandName); + } +}; diff --git a/src/stdin/commands.js b/src/stdin/commands.js new file mode 100644 index 0000000..6b1242b --- /dev/null +++ b/src/stdin/commands.js @@ -0,0 +1,24 @@ +const { StdinCommand } = require('@eartharoid/dbf'); + +module.exports = class Commands extends StdinCommand { + constructor(client, options) { + super(client, { + ...options, + id: 'commands', + }); + } + + async run(args) { + switch (args[0]) { + case 'publish': { + this.client.commands.publish() + .then(commands => { + if (!commands) return console.log('None published'); + console.log('Published %d commands', commands.size); + }) + .catch(console.error); + break; + } + } + } +}; \ No newline at end of file diff --git a/src/stdin/eval.js b/src/stdin/eval.js index c081510..e9787b6 100644 --- a/src/stdin/eval.js +++ b/src/stdin/eval.js @@ -13,8 +13,10 @@ module.exports = class extends StdinCommand { try { const res = await eval(toEval); console.log(res); + return true; } catch (error) { this.client.log.error(error); + return false; } } }; \ No newline at end of file From e28392352c0f12a2f1b87f4e4f3db162d80127af Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 2 Aug 2022 18:03:55 +0100 Subject: [PATCH 072/409] Add autocomplete, button, menu, and modal files --- package.json | 2 +- src/autocomplete/tag.js | 12 ++++++++++++ src/buttons/claim.js | 12 ++++++++++++ src/buttons/close.js | 12 ++++++++++++ src/buttons/create.js | 12 ++++++++++++ src/buttons/unclaim.js | 12 ++++++++++++ src/i18n/en-GB.yml | 2 +- src/menus/create.js | 12 ++++++++++++ src/modals/feedback.js | 12 ++++++++++++ src/modals/questions.js | 12 ++++++++++++ src/routes/api/admin/guilds/[guild]/panels.js | 8 ++++---- 11 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 src/autocomplete/tag.js create mode 100644 src/buttons/claim.js create mode 100644 src/buttons/close.js create mode 100644 src/buttons/create.js create mode 100644 src/buttons/unclaim.js create mode 100644 src/menus/create.js create mode 100644 src/modals/feedback.js create mode 100644 src/modals/questions.js diff --git a/package.json b/package.json index 2baa031..612aa58 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@eartharoid/dbf": "^0.3.0", + "@eartharoid/dbf": "^0.3.1", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.0.4", "@fastify/cookie": "^6.0.0", diff --git a/src/autocomplete/tag.js b/src/autocomplete/tag.js new file mode 100644 index 0000000..126bd78 --- /dev/null +++ b/src/autocomplete/tag.js @@ -0,0 +1,12 @@ +const { Autocompleter } = require('@eartharoid/dbf'); + +module.exports = class TagCompleter extends Autocompleter { + constructor(client, options) { + super(client, { + ...options, + id: 'tag', + }); + } + + async run(value, comamnd, interaction) { } +}; \ No newline at end of file diff --git a/src/buttons/claim.js b/src/buttons/claim.js new file mode 100644 index 0000000..2461e4b --- /dev/null +++ b/src/buttons/claim.js @@ -0,0 +1,12 @@ +const { Button } = require('@eartharoid/dbf'); + +module.exports = class ClaimButton extends Button { + constructor(client, options) { + super(client, { + ...options, + id: 'claim', + }); + } + + async run (id, interaction) {} +}; \ No newline at end of file diff --git a/src/buttons/close.js b/src/buttons/close.js new file mode 100644 index 0000000..bd7b151 --- /dev/null +++ b/src/buttons/close.js @@ -0,0 +1,12 @@ +const { Button } = require('@eartharoid/dbf'); + +module.exports = class CloseButton extends Button { + constructor(client, options) { + super(client, { + ...options, + id: 'close', + }); + } + + async run(id, interaction) { } +}; \ No newline at end of file diff --git a/src/buttons/create.js b/src/buttons/create.js new file mode 100644 index 0000000..be7a97f --- /dev/null +++ b/src/buttons/create.js @@ -0,0 +1,12 @@ +const { Button } = require('@eartharoid/dbf'); + +module.exports = class CreateButton extends Button { + constructor(client, options) { + super(client, { + ...options, + id: 'create', + }); + } + + async run(id, interaction) { } +}; \ No newline at end of file diff --git a/src/buttons/unclaim.js b/src/buttons/unclaim.js new file mode 100644 index 0000000..50ed4b5 --- /dev/null +++ b/src/buttons/unclaim.js @@ -0,0 +1,12 @@ +const { Button } = require('@eartharoid/dbf'); + +module.exports = class UnclaimButton extends Button { + constructor(client, options) { + super(client, { + ...options, + id: 'unclaim', + }); + } + + async run(id, interaction) { } +}; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 5df10cb..9dedf91 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -28,5 +28,5 @@ log: update: 'updated' tickets: menus: - panel: + create: placeholder: 'Select a ticket category' \ No newline at end of file diff --git a/src/menus/create.js b/src/menus/create.js new file mode 100644 index 0000000..a0a0dfa --- /dev/null +++ b/src/menus/create.js @@ -0,0 +1,12 @@ +const { Menu } = require('@eartharoid/dbf'); + +module.exports = class CreateMenu extends Menu { + constructor(client, options) { + super(client, { + ...options, + id: 'create', + }); + } + + async run(id, interaction) { } +}; \ No newline at end of file diff --git a/src/modals/feedback.js b/src/modals/feedback.js new file mode 100644 index 0000000..74c19ac --- /dev/null +++ b/src/modals/feedback.js @@ -0,0 +1,12 @@ +const { Modal } = require('@eartharoid/dbf'); + +module.exports = class FeedbackModal extends Modal { + constructor(client, options) { + super(client, { + ...options, + id: 'feedback', + }); + } + + async run(id, interaction) { } +}; \ No newline at end of file diff --git a/src/modals/questions.js b/src/modals/questions.js new file mode 100644 index 0000000..5554551 --- /dev/null +++ b/src/modals/questions.js @@ -0,0 +1,12 @@ +const { Modal } = require('@eartharoid/dbf'); + +module.exports = class QuestionsModal extends Modal { + constructor(client, options) { + super(client, { + ...options, + id: 'questions', + }); + } + + async run(id, interaction) { } +}; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/panels.js b/src/routes/api/admin/guilds/[guild]/panels.js index 3e0a211..7b39f1a 100644 --- a/src/routes/api/admin/guilds/[guild]/panels.js +++ b/src/routes/api/admin/guilds/[guild]/panels.js @@ -77,7 +77,7 @@ module.exports.post = fastify => ({ components.push( new ButtonBuilder() .setCustomId(JSON.stringify({ - action: 'createTicket', + action: 'create', target: categories[0].id, })) .setStyle(Primary) @@ -90,7 +90,7 @@ module.exports.post = fastify => ({ ...categories.map(category => new ButtonBuilder() .setCustomId(JSON.stringify({ - action: 'createTicket', + action: 'create', target: category.id, })) .setStyle(Secondary) @@ -101,8 +101,8 @@ module.exports.post = fastify => ({ } else { components.push( new SelectMenuBuilder() - .setCustomId('createTicket') - .setPlaceholder(getMessage('menus.panel.placeholder')) + .setCustomId('create') + .setPlaceholder(getMessage('menus.create.placeholder')) .setOptions( categories.map(category => new SelectMenuOptionBuilder() From b4618aac1b9c2c58305606125bc8cf3cd557916f Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 2 Aug 2022 21:13:32 +0100 Subject: [PATCH 073/409] Create commands --- README.md | 4 +- package.json | 2 +- src/autocomplete/references.js | 12 ++ src/autocomplete/ticket.js | 12 ++ src/commands/message/create.js | 17 +++ src/commands/message/pin.js | 17 +++ src/commands/slash/add.js | 51 +++++++++ src/commands/slash/close.js | 61 ++++++++++ src/commands/slash/force-close.js | 61 ++++++++++ src/commands/slash/new.js | 47 ++++++++ src/commands/slash/priority.js | 59 ++++++++++ src/commands/slash/remove.js | 51 +++++++++ src/commands/slash/tag.js | 47 ++++++++ src/commands/slash/tickets.js | 46 ++++++++ src/commands/slash/topic.js | 46 ++++++++ src/commands/slash/transcript.js | 47 ++++++++ src/commands/user/create.js | 17 +++ src/i18n/en-GB.yml | 144 +++++++++++++++++++++--- src/listeners/commands/componentLoad.js | 21 ++++ 19 files changed, 743 insertions(+), 19 deletions(-) create mode 100644 src/autocomplete/references.js create mode 100644 src/autocomplete/ticket.js create mode 100644 src/commands/message/create.js create mode 100644 src/commands/message/pin.js create mode 100644 src/commands/slash/add.js create mode 100644 src/commands/slash/close.js create mode 100644 src/commands/slash/force-close.js create mode 100644 src/commands/slash/new.js create mode 100644 src/commands/slash/priority.js create mode 100644 src/commands/slash/remove.js create mode 100644 src/commands/slash/tag.js create mode 100644 src/commands/slash/tickets.js create mode 100644 src/commands/slash/topic.js create mode 100644 src/commands/slash/transcript.js create mode 100644 src/commands/user/create.js create mode 100644 src/listeners/commands/componentLoad.js diff --git a/README.md b/README.md index 7f13b31..cc63107 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,6 @@ PORTAL=http://localhost:3000 SUPER= -https://www.prisma.io/docs/reference/database-reference/supported-databases \ No newline at end of file +https://www.prisma.io/docs/reference/database-reference/supported-databases + +![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create and slash/force-close \ No newline at end of file diff --git a/package.json b/package.json index 612aa58..b0e911b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@eartharoid/dbf": "^0.3.1", + "@eartharoid/dbf": "^0.3.2", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.0.4", "@fastify/cookie": "^6.0.0", diff --git a/src/autocomplete/references.js b/src/autocomplete/references.js new file mode 100644 index 0000000..5d3c128 --- /dev/null +++ b/src/autocomplete/references.js @@ -0,0 +1,12 @@ +const { Autocompleter } = require('@eartharoid/dbf'); + +module.exports = class ReferencesCompleter extends Autocompleter { + constructor(client, options) { + super(client, { + ...options, + id: 'references', + }); + } + + async run(value, comamnd, interaction) { } +}; \ No newline at end of file diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js new file mode 100644 index 0000000..6c7214b --- /dev/null +++ b/src/autocomplete/ticket.js @@ -0,0 +1,12 @@ +const { Autocompleter } = require('@eartharoid/dbf'); + +module.exports = class TicketCompleter extends Autocompleter { + constructor(client, options) { + super(client, { + ...options, + id: 'ticket', + }); + } + + async run(value, comamnd, interaction) { } +}; \ No newline at end of file diff --git a/src/commands/message/create.js b/src/commands/message/create.js new file mode 100644 index 0000000..774e8fd --- /dev/null +++ b/src/commands/message/create.js @@ -0,0 +1,17 @@ +const { MessageCommand } = require('@eartharoid/dbf'); + +module.exports = class CreateMessageCommand extends MessageCommand { + constructor(client, options) { + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.message.create.name'))); + + super(client, { + ...options, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/message/pin.js b/src/commands/message/pin.js new file mode 100644 index 0000000..fbcfb05 --- /dev/null +++ b/src/commands/message/pin.js @@ -0,0 +1,17 @@ +const { MessageCommand } = require('@eartharoid/dbf'); + +module.exports = class PinMessageCommand extends MessageCommand { + constructor(client, options) { + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.message.pin.name'))); + + super(client, { + ...options, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/add.js b/src/commands/slash/add.js new file mode 100644 index 0000000..c126a41 --- /dev/null +++ b/src/commands/slash/add.js @@ -0,0 +1,51 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class AddSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.add.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.add.name'))); + + let opts = [ + { + name: 'member', + required: true, + type: ApplicationCommandOptionType.User, + }, + { + name: 'ticket', + required: false, + type: ApplicationCommandOptionType.Channel, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.add.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.add.options.${o.name}.name`))); + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/close.js b/src/commands/slash/close.js new file mode 100644 index 0000000..74bad52 --- /dev/null +++ b/src/commands/slash/close.js @@ -0,0 +1,61 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class CloseSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.close.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.close.name'))); + + let opts = [ + { + name: 'channel', + required: false, + type: ApplicationCommandOptionType.Channel, + }, + { + name: 'number', + required: false, + type: ApplicationCommandOptionType.Integer, + }, + { + name: 'reason', + required: false, + type: ApplicationCommandOptionType.String, + }, + { + name: 'time', + required: false, + type: ApplicationCommandOptionType.String, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.close.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.close.options.${o.name}.name`))); + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/force-close.js b/src/commands/slash/force-close.js new file mode 100644 index 0000000..a122652 --- /dev/null +++ b/src/commands/slash/force-close.js @@ -0,0 +1,61 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class ForceCloseSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.force-close.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.force-close.name'))); + + let opts = [ + { + name: 'channel', + required: false, + type: ApplicationCommandOptionType.Channel, + }, + { + name: 'number', + required: false, + type: ApplicationCommandOptionType.Integer, + }, + { + name: 'reason', + required: false, + type: ApplicationCommandOptionType.String, + }, + { + name: 'time', + required: false, + type: ApplicationCommandOptionType.String, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.force-close.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.force-close.options.${o.name}.name`))); + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/new.js b/src/commands/slash/new.js new file mode 100644 index 0000000..2fd5da2 --- /dev/null +++ b/src/commands/slash/new.js @@ -0,0 +1,47 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class NewSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.new.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.new.name'))); + + let opts = [ + { + autocomplete: true, + name: 'references', + required: false, + type: ApplicationCommandOptionType.Integer, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.new.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.new.options.${o.name}.name`))); + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/priority.js b/src/commands/slash/priority.js new file mode 100644 index 0000000..d13aac9 --- /dev/null +++ b/src/commands/slash/priority.js @@ -0,0 +1,59 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class PrioritySlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.priority.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.priority.name'))); + + let opts = [ + { + choices: ['HIGH', 'MEDIUM', 'LOW'], + name: 'priority', + required: true, + type: ApplicationCommandOptionType.String, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.priority.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.priority.options.${o.name}.name`))); + + if (o.choices) { + o.choices = o.choices.map(c => { + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.priority.options.${o.name}.choices.${c}`))); + return { + name: nameLocalizations['en-GB'], + nameLocalizations: nameLocalizations, + value: c, + }; + }); + } + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/remove.js b/src/commands/slash/remove.js new file mode 100644 index 0000000..aded8e2 --- /dev/null +++ b/src/commands/slash/remove.js @@ -0,0 +1,51 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class RemoveSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.remove.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.remove.name'))); + + let opts = [ + { + name: 'member', + required: true, + type: ApplicationCommandOptionType.User, + }, + { + name: 'ticket', + required: false, + type: ApplicationCommandOptionType.Channel, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.remove.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.remove.options.${o.name}.name`))); + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/tag.js b/src/commands/slash/tag.js new file mode 100644 index 0000000..832be57 --- /dev/null +++ b/src/commands/slash/tag.js @@ -0,0 +1,47 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class TagSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.tag.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.tag.name'))); + + let opts = [ + { + autocomplete: true, + name: 'tag', + required: true, + type: ApplicationCommandOptionType.String, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.tag.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.tag.options.${o.name}.name`))); + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/tickets.js b/src/commands/slash/tickets.js new file mode 100644 index 0000000..94a7cce --- /dev/null +++ b/src/commands/slash/tickets.js @@ -0,0 +1,46 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class TicketsSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.tickets.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.tickets.name'))); + + let opts = [ + { + name: 'member', + required: false, + type: ApplicationCommandOptionType.User, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.tickets.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.tickets.options.${o.name}.name`))); + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/topic.js b/src/commands/slash/topic.js new file mode 100644 index 0000000..1774dc1 --- /dev/null +++ b/src/commands/slash/topic.js @@ -0,0 +1,46 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class TopicSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.topic.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.topic.name'))); + + let opts = [ + { + name: 'new-topic', + required: true, + type: ApplicationCommandOptionType.String, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.topic.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.topic.options.${o.name}.name`))); + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/transcript.js b/src/commands/slash/transcript.js new file mode 100644 index 0000000..db9ad4b --- /dev/null +++ b/src/commands/slash/transcript.js @@ -0,0 +1,47 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class TranscriptSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transcript.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transcript.name'))); + + let opts = [ + { + autocomplete: true, + name: 'ticket', + required: true, + type: ApplicationCommandOptionType.Integer, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transcript.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transcript.options.${o.name}.name`))); + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/user/create.js b/src/commands/user/create.js new file mode 100644 index 0000000..8a9579b --- /dev/null +++ b/src/commands/user/create.js @@ -0,0 +1,17 @@ +const { UserCommand } = require('@eartharoid/dbf'); + +module.exports = class CreateUserCommand extends UserCommand { + constructor(client, options) { + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.user.create.name'))); + + super(client, { + ...options, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 9dedf91..b53b472 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -1,32 +1,142 @@ +test: | + line 1 + line 2 buttons: create: - emoji: '🎫' - text: 'Create a ticket' + emoji: 🎫 + text: Create a ticket commands: + message: + create: + name: Create a ticket from message + pin: + name: Pin message + slash: + add: + description: Add a member to a ticket + name: add + options: + member: + description: The member to add to the ticket + name: member + ticket: + description: The ticket to add the member to + name: ticket + close: + description: Close a ticket + name: close + options: + channel: + description: The ticket channel to close + name: channel + number: + description: The number of the ticket to close + name: number + reason: + description: The reason for closing the ticket(s) + name: reason + time: + description: Close all tickets that have been inactive for the specific time + name: time + force-close: + description: Forcibly close a ticket + name: force-close + options: + channel: + description: The ticket channel to close + name: channel + number: + description: The number of the ticket to close + name: number + reason: + description: The reason for closing the ticket(s) + name: reason + time: + description: Close all tickets that have been inactive for the specific time + name: time + new: + description: Create a new ticket + name: new + options: + references: + description: The number of a related ticket + name: references + priority: + description: Set the priority of a ticket + name: priority + options: + priority: + choices: + HIGH: 🔴 High + MEDIUM: 🟠 Medium + LOW: 🟢 Low + description: The priority of the ticket + name: priority + remove: + description: Remove a member from a ticket + name: remove + options: + member: + description: The member to remove from the ticket + name: member + ticket: + description: The ticket to remove the member from + name: ticket + tag: + description: Use a tag + name: tag + options: + tag: + description: The name of the tag to use + name: tag + topic: + description: Change the topic of a ticket + name: topic + options: + new-topic: + description: The new topic of the ticket + name: new-topic + tickets: + description: List your own or someone else's tickets + name: tickets + options: + member: + description: The member to list the tickets of + name: member + transcript: + description: Get the transcript of a ticket + name: transcript + options: + ticket: + description: The number of the ticket to get the transcript of + name: ticket + user: + create: + name: Create a ticket for user log: admin: - changes: 'Changes' + changes: Changes description: joined: '{user} {verb} {targetType}' target: - category: 'a category' - panel: 'a panel' - question: 'a question' - settings: 'the settings' - tag: 'a tag' + category: a category + panel: a panel + question: a question + settings: the settings + tag: a tag title: joined: '{targetType} {verb}' target: - category: 'Category' - panel: 'Panel' - question: 'Question' - settings: 'Settings' - tag: 'Tag' + category: Category + panel: Panel + question: Question + settings: Settings + tag: Tag verb: - create: 'created' - delete: 'deleted' - update: 'updated' + create: created + delete: deleted + update: updated tickets: menus: create: - placeholder: 'Select a ticket category' \ No newline at end of file + placeholder: Select a ticket category \ No newline at end of file diff --git a/src/listeners/commands/componentLoad.js b/src/listeners/commands/componentLoad.js new file mode 100644 index 0000000..019f604 --- /dev/null +++ b/src/listeners/commands/componentLoad.js @@ -0,0 +1,21 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.commands, + event: 'componentLoad', + }); + } + + run(command) { + const types = { + 1: 'slash', + 2: 'user', + 3: 'message', + }; + this.client.log.info.commands(`Loaded "${command.name}" ${types[command.type]} command`); + return true; + } +}; From 954681685a8b7db4e2c3031090d9c813d45a5f1f Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 2 Aug 2022 21:29:44 +0100 Subject: [PATCH 074/409] Add `move` command --- README.md | 2 +- src/autocomplete/category.js | 12 +++++++++ src/commands/slash/move.js | 47 ++++++++++++++++++++++++++++++++++++ src/i18n/en-GB.yml | 7 ++++++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/autocomplete/category.js create mode 100644 src/commands/slash/move.js diff --git a/README.md b/README.md index cc63107..dd49730 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,4 @@ SUPER= https://www.prisma.io/docs/reference/database-reference/supported-databases -![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create and slash/force-close \ No newline at end of file +![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create, slash/force-close and slash/move \ No newline at end of file diff --git a/src/autocomplete/category.js b/src/autocomplete/category.js new file mode 100644 index 0000000..e8c25a1 --- /dev/null +++ b/src/autocomplete/category.js @@ -0,0 +1,12 @@ +const { Autocompleter } = require('@eartharoid/dbf'); + +module.exports = class CategoryCompleter extends Autocompleter { + constructor(client, options) { + super(client, { + ...options, + id: 'category', + }); + } + + async run(value, comamnd, interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/move.js b/src/commands/slash/move.js new file mode 100644 index 0000000..6867570 --- /dev/null +++ b/src/commands/slash/move.js @@ -0,0 +1,47 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class MoveSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.move.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.move.name'))); + + let opts = [ + { + autocomplete: true, + name: 'category', + required: true, + type: ApplicationCommandOptionType.String, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.move.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.move.options.${o.name}.name`))); + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index b53b472..a25acfd 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -54,6 +54,13 @@ commands: time: description: Close all tickets that have been inactive for the specific time name: time + move: + description: Move a ticket to another category + name: move + options: + category: + description: The category to move the ticket to + name: category new: description: Create a new ticket name: new From cdb8fa04c4b30809ae8bd80c595cded400e1a718 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 5 Aug 2022 14:01:19 +0100 Subject: [PATCH 075/409] Closes #321 --- .../admin/guilds/[guild]/categories/index.js | 47 ++++++++++++------- src/routes/api/admin/guilds/[guild]/index.js | 1 + src/routes/api/client.js | 3 +- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index ec9a5a0..ac30fce 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -1,33 +1,44 @@ const { logAdminEvent } = require('../../../../../../lib/logging'); const emoji = require('node-emoji'); const { ChannelType: { GuildCategory } } = require('discord.js'); +const ms = require('ms'); module.exports.get = fastify => ({ handler: async (req, res) => { /** @type {import('client')} */ const client = res.context.config.client; - const { categories } = await client.prisma.guild.findUnique({ - select: { categories: true }, + let { categories } = await client.prisma.guild.findUnique({ + select: { + categories: { + select: { + createdAt: true, + description: true, + discordCategory: true, + emoji: true, + id: true, + image: true, + name: true, + requiredRoles: true, + staffRoles: true, + tickets: true, + }, + }, + }, where: { id: req.params.guild }, }); - // include: { - // questions: { - // select: { - // createdAt: true, - // id: true, - // label: true, - // maxLength: true, - // minLength: true, - // order: true, - // placeholder: true, - // required: true, - // style: true, - // value: true, - // }, - // }, - // }, + categories = categories.map(c => { + c = { + ...c, + stats: { + avgResolutionTime: ms(c.tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / c.tickets.length), + avgResponseTime: ms(c.tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / c.tickets.length), + }, + }; + delete c.tickets; + return c; + }); return categories; }, diff --git a/src/routes/api/admin/guilds/[guild]/index.js b/src/routes/api/admin/guilds/[guild]/index.js index 4c51f79..2c10f98 100644 --- a/src/routes/api/admin/guilds/[guild]/index.js +++ b/src/routes/api/admin/guilds/[guild]/index.js @@ -34,6 +34,7 @@ module.exports.get = fastify => ({ logo: guild.iconURL(), name: guild.name, stats: { + avgResolutionTime: ms(tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / tickets.length), avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length), categories: categories.map(c => ({ id: c.id, diff --git a/src/routes/api/client.js b/src/routes/api/client.js index b9821f9..b3321a6 100644 --- a/src/routes/api/client.js +++ b/src/routes/api/client.js @@ -23,6 +23,7 @@ module.exports.get = () => ({ stats: { activatedUsers: users.length, archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they get deleted + avgResolutionTime: ms(tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / tickets.length), avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length), categories: await client.prisma.category.count(), guilds: client.guilds.cache.size, @@ -32,7 +33,7 @@ module.exports.get = () => ({ }, username: client.user.username, }; - await client.keyv.set(cacheKey, cached, ms('5m')); + await client.keyv.set(cacheKey, cached, ms('15m')); } return cached; From 052c1591572a43ff94c2a7e4e2abc6ca761cf11e Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 5 Aug 2022 22:21:55 +0100 Subject: [PATCH 076/409] start making things --- README.md | 20 +- package.json | 16 +- scripts/keygen.js | 2 +- src/buttons/create.js | 12 +- src/client.js | 16 +- src/i18n/en-GB.yml | 20 +- src/lib/strings.js | 1 - src/lib/tickets/manager.js | 146 ++++++++++++++ src/lib/users.js | 29 +++ src/listeners/client/error.js | 15 ++ src/listeners/client/guildCreate.js | 15 ++ src/listeners/client/guildDelete.js | 15 ++ src/listeners/client/guildMemberRemove.js | 15 ++ src/listeners/client/messageCreate.js | 184 ++++++++++++++++++ src/listeners/client/messageDelete.js | 15 ++ src/listeners/client/messageUpdate.js | 15 ++ src/listeners/client/warn.js | 15 ++ src/listeners/commands/error.js | 19 ++ src/listeners/commands/run.js | 24 +++ src/menus/create.js | 12 +- src/modals/questions.js | 10 +- src/modals/topic.js | 18 ++ src/routes/api/admin/guilds/[guild]/panels.js | 55 +++--- src/schemas/settings.js | 24 +++ user/example.config.yml | 2 +- 25 files changed, 669 insertions(+), 46 deletions(-) delete mode 100644 src/lib/strings.js create mode 100644 src/lib/tickets/manager.js create mode 100644 src/lib/users.js create mode 100644 src/listeners/client/error.js create mode 100644 src/listeners/client/guildCreate.js create mode 100644 src/listeners/client/guildDelete.js create mode 100644 src/listeners/client/guildMemberRemove.js create mode 100644 src/listeners/client/messageCreate.js create mode 100644 src/listeners/client/messageDelete.js create mode 100644 src/listeners/client/messageUpdate.js create mode 100644 src/listeners/client/warn.js create mode 100644 src/listeners/commands/error.js create mode 100644 src/listeners/commands/run.js create mode 100644 src/modals/topic.js create mode 100644 src/schemas/settings.js diff --git a/README.md b/README.md index dd49730..ef2dc3b 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,22 @@ SUPER= https://www.prisma.io/docs/reference/database-reference/supported-databases -![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create, slash/force-close and slash/move \ No newline at end of file +![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create, slash/force-close and slash/move + +menu question max length cannot be higher than question options + +- TODO: post stats +- TODO: settings bundle download +- TODO: update notifications +- TODO: check inline to-dos + + +creation requires an interaction: +- /new -> category? -> topic or questions -> create +- user:create(self) -> category? -> topic or questions -> create +- user:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create +- message:create(self) -> category? -> topic or questions -> create +- message:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create +- DM -> guild? -> category? -> topic or questions -> create +- panel(interaction) -> topic or questions -> create +- panel(message) -> DM (channel fallback) button -> topic or questions -> create \ No newline at end of file diff --git a/package.json b/package.json index b0e911b..ad5ee6f 100644 --- a/package.json +++ b/package.json @@ -34,18 +34,18 @@ "node": ">=18.0" }, "dependencies": { - "@eartharoid/dbf": "^0.3.2", + "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.0.4", "@fastify/cookie": "^6.0.0", - "@fastify/cors": "^8.0.0", + "@fastify/cors": "^8.1.0", "@fastify/jwt": "^5.0.1", - "@fastify/oauth2": "^5.0.0", - "@prisma/client": "^4.1.0", + "@fastify/oauth2": "^5.1.0", + "@prisma/client": "^4.1.1", "cryptr": "^6.0.3", - "discord.js": "^14.0.2", + "discord.js": "^14.1.2", "dotenv": "^16.0.1", - "fastify": "^4.2.1", + "fastify": "^4.3.0", "figlet": "^1.5.2", "fs-extra": "^10.1.0", "keyv": "^4.3.3", @@ -55,14 +55,14 @@ "node-dir": "^0.1.17", "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", - "prisma": "^4.1.0", + "prisma": "^4.1.1", "semver": "^7.3.7", "terminal-link": "^2.1.1", "yaml": "^1.10.2" }, "devDependencies": { "all-contributors-cli": "^6.20.0", - "eslint": "^8.20.0", + "eslint": "^8.21.0", "eslint-plugin-unused-imports": "^2.0.0", "nodemon": "^2.0.19" } diff --git a/scripts/keygen.js b/scripts/keygen.js index bb91e25..4a6964a 100644 --- a/scripts/keygen.js +++ b/scripts/keygen.js @@ -2,7 +2,7 @@ const { randomBytes } = require('crypto'); const { short } = require('leeks.js'); console.log(short( - 'Set the "ENCRYPTION_KEY" environment variable to: \n&1&!f' + + 'Set the "ENCRYPTION_KEY" environment variable to: \n&!b' + randomBytes(24).toString('hex') + '&r\n\n&0&!e WARNING &r &e&lIf you lose the encryption key, most of the data in the database will become unreadable, requiring a new key and a full reset.', )); \ No newline at end of file diff --git a/src/buttons/create.js b/src/buttons/create.js index be7a97f..2e7d02c 100644 --- a/src/buttons/create.js +++ b/src/buttons/create.js @@ -8,5 +8,15 @@ module.exports = class CreateButton extends Button { }); } - async run(id, interaction) { } + /** + * @param {*} id + * @param {import("discord.js").ButtonInteraction} interaction + */ + async run(id, interaction) { + await this.client.tickets.create({ + categoryId: id.target, + interaction, + topic: id.topic, + }); + } }; \ No newline at end of file diff --git a/src/client.js b/src/client.js index bec0b96..f058fbb 100644 --- a/src/client.js +++ b/src/client.js @@ -1,11 +1,14 @@ const { FrameworkClient } = require('@eartharoid/dbf'); -const { GatewayIntentBits } = require('discord.js'); +const { + GatewayIntentBits, Partials, +} = require('discord.js'); const { PrismaClient } = require('@prisma/client'); const Keyv = require('keyv'); const I18n = require('@eartharoid/i18n'); const fs = require('fs'); const { join } = require('path'); const YAML = require('yaml'); +const TicketManager = require('./lib/tickets/manager'); const encryptionMiddleware = require('./lib/middleware/prisma-encryption'); const sqliteMiddleware = require('./lib/middleware/prisma-sqlite'); @@ -13,10 +16,19 @@ module.exports = class Client extends FrameworkClient { constructor(config, log) { super({ intents: [ + GatewayIntentBits.DirectMessages, + GatewayIntentBits.DirectMessageReactions, + GatewayIntentBits.DirectMessageTyping, + GatewayIntentBits.MessageContent, GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, ], + partials: [ + Partials.Message, + Partials.Channel, + Partials.Reaction, + ], }); const locales = {}; @@ -30,6 +42,8 @@ module.exports = class Client extends FrameworkClient { /** @type {I18n} */ this.i18n = new I18n('en-GB', locales); + /** @type {TicketManager} */ + this.tickets = new TicketManager(this); this.config = config; this.log = log; this.supers = (process.env.SUPER ?? '').split(','); diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index a25acfd..5eb9901 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -2,6 +2,9 @@ test: | line 1 line 2 buttons: + confirm_open: + emoji: ✅ + text: Create ticket create: emoji: 🎫 text: Create a ticket @@ -120,6 +123,9 @@ commands: user: create: name: Create a ticket for user +dm: + confirm_open: + title: 'Do you want to open a ticket with the following topic?' log: admin: changes: Changes @@ -145,5 +151,15 @@ log: update: updated tickets: menus: - create: - placeholder: Select a ticket category \ No newline at end of file + category: + placeholder: Select a ticket category + guild: + placeholder: Select a server +modals: + feedback: + title: 'Feedback' + topic: 'Topic' +misc: + no_categories: + description: No ticket categories have been configured. + title: ❌ There are no ticket categories \ No newline at end of file diff --git a/src/lib/strings.js b/src/lib/strings.js deleted file mode 100644 index 12c79f1..0000000 --- a/src/lib/strings.js +++ /dev/null @@ -1 +0,0 @@ -module.exports.capitalise = string => string.charAt(0).toUpperCase() + string.slice(1); \ No newline at end of file diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js new file mode 100644 index 0000000..6554bfe --- /dev/null +++ b/src/lib/tickets/manager.js @@ -0,0 +1,146 @@ +const { + ActionRowBuilder, + ModalBuilder, + SelectMenuBuilder, + SelectMenuOptionBuilder, + TextInputBuilder, + TextInputStyle, +} = require('discord.js'); +const emoji = require('node-emoji'); + +module.exports = class TicketManager { + constructor(client) { + /** @type {import("client")} */ + this.client = client; + } + + /** + * @param {object} data + * @param {string} data.category + * @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction} data.interaction + * @param {string?} [data.topic] + */ + async create({ + categoryId, interaction, topic, reference, + }) { + const category = await this.client.prisma.category.findUnique({ + include: { + guild: true, + questions: true, + }, + where: { id: Number(categoryId) }, + }); + + // TODO: if member !required roles -> stop + + // TODO: if discordCategory has 50 channels -> stop + + // TODO: if category has max channels -> stop + + // TODO: if member has max -> stop + + // TODO: if cooldown -> stop + + const getMessage = this.client.i18n.getLocale(category.guild.locale); + + if (category.questions.length >= 1) { + await interaction.showModal( + new ModalBuilder() + .setCustomId(JSON.stringify({ + action: 'questions', + categoryId, + reference, + })) + .setTitle(category.name) + .setComponents( + category.questions + .sort((a, b) => a.order - b.order) + .map(q => { + if (q.type === 'TEXT') { + return new ActionRowBuilder() + .setComponents( + new TextInputBuilder() + .setCustomId(q.id) + .setLabel(q.label) + .setStyle(q.style) + .setMaxLength(q.maxLength) + .setMinLength(q.minLength) + .setPlaceholder(q.placeholder) + .setRequired(q.required) + .setValue(q.value), + ); + } else if (q.type === 'MENU') { + return new ActionRowBuilder() + .setComponents( + new SelectMenuBuilder() + .setCustomId(q.id) + .setPlaceholder(q.placeholder || q.label) + .setMaxValues(q.maxLength) + .setMinValues(q.minLength) + .setOptions( + q.options.map((o, i) => { + const builder = new SelectMenuOptionBuilder() + .setValue(String(i)) + .setLabel(o.label); + if (o.description) builder.setDescription(o.description); + if (o.emoji) builder.setEmoji(emoji.hasEmoji(o.emoji) ? emoji.get(o.emoji) : { id: o.emoji }); + return builder; + }), + ), + ); + } + }), + ), + ); + } else if (category.requireTopic && !topic) { + await interaction.showModal( + new ModalBuilder() + .setCustomId(JSON.stringify({ + action: 'topic', + categoryId, + reference, + })) + .setTitle(category.name) + .setComponents( + new ActionRowBuilder() + .setComponents( + new TextInputBuilder() + .setCustomId('topic') + .setLabel(getMessage('modals.topic')) + .setStyle(TextInputStyle.Long), + ), + ), + ); + } else { + await this.postQuestions({ + categoryId, + interaction, + topic, + }); + } + } + + /** + * @param {object} data + * @param {string} data.category + * @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction|import("discord.js").ModalSubmitInteraction} data.interaction + * @param {string?} [data.topic] + */ + async postQuestions({ + categoryId, interaction, topic, reference, + }) { + await interaction.deferReply({ ephemeral: true }); + console.log(require('util').inspect(interaction, { + colors: true, + depth: 10, + })); + if (interaction.isModalSubmit()) { + + } + + interaction.editReply({ + components: [], + embeds: [], + }); + } +}; \ No newline at end of file diff --git a/src/lib/users.js b/src/lib/users.js new file mode 100644 index 0000000..f116db3 --- /dev/null +++ b/src/lib/users.js @@ -0,0 +1,29 @@ +/** + * + * @param {import("client")} client + * @param {string} userId + * @returns {Promise} + */ +module.exports.getCommonGuilds = async (client, userId) => await client.guilds.cache.filter(async guild => { + const member = await guild.members.fetch(userId); + return !!member; +}); + +/** + * + * @param {import("discord.js").Guild} guild + * @param {string} userId + * @returns {Promise} + */ +module.exports.isStaff = async (guild, userId) => { + /** @type {import("client")} */ + const client = guild.client; + if (guild.client.supers.includes(userId)) return true; + const guildMember = await guild.members.fetch(userId); + if (guildMember?.permissions.has('MANAGE_GUILD')) return true; + const { categories } = await client.prisma.guild.findUnique({ + select: { categories: true }, + where: { id: guild.id }, + }); + return categories.some(cat => cat.roles.some(r => guildMember.roles.cache.has(r))); +}; \ No newline at end of file diff --git a/src/listeners/client/error.js b/src/listeners/client/error.js new file mode 100644 index 0000000..d101a43 --- /dev/null +++ b/src/listeners/client/error.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'error', + }); + } + + run(error) { + this.client.log.error(error); + } +}; diff --git a/src/listeners/client/guildCreate.js b/src/listeners/client/guildCreate.js new file mode 100644 index 0000000..5cfbb4c --- /dev/null +++ b/src/listeners/client/guildCreate.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'guildCreate', + }); + } + + run(guild) { + this.client.log.success(`Added to guild "${guild.name}"`); + } +}; diff --git a/src/listeners/client/guildDelete.js b/src/listeners/client/guildDelete.js new file mode 100644 index 0000000..01eb59c --- /dev/null +++ b/src/listeners/client/guildDelete.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'guildDelete', + }); + } + + run(guild) { + this.client.log.info(`Removed from guild "${guild.name}"`); + } +}; diff --git a/src/listeners/client/guildMemberRemove.js b/src/listeners/client/guildMemberRemove.js new file mode 100644 index 0000000..8e40061 --- /dev/null +++ b/src/listeners/client/guildMemberRemove.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'guildMemberRemove', + }); + } + + run(member) { + // TODO: close tickets + } +}; diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js new file mode 100644 index 0000000..64bcd37 --- /dev/null +++ b/src/listeners/client/messageCreate.js @@ -0,0 +1,184 @@ +const { Listener } = require('@eartharoid/dbf'); +const { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle: { Success }, + ChannelType, + ComponentType, + EmbedBuilder, + SelectMenuBuilder, + SelectMenuOptionBuilder, +} = require('discord.js'); +const { getCommonGuilds } = require('../../lib/users'); +const ms = require('ms'); +const emoji = require('node-emoji'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'messageCreate', + }); + } + + /** + * @param {string} guildId + * @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction} interaction + */ + async useGuild(settings, interaction, topic) { + const getMessage = this.client.i18n.getLocale(settings.locale); + if (settings.categories.length === 0) { + interaction.editReply({ + components: [], + embeds: [ + new EmbedBuilder() + .setColor(settings.errorColour) + .setTitle(getMessage('misc.no_categories.title')) + .setDescription(getMessage('misc.no_categories.description')), + ], + }); + } else if (settings.categories.length === 1) { + await this.client.tickets.create({ + categoryId: settings.categories[0].id, + interaction, + topic, + }); + } else { + const sent = await interaction.editReply({ + components: [ + new ActionRowBuilder() + .setComponents( + new SelectMenuBuilder() + .setCustomId(JSON.stringify({ + action: 'create', + topic, + })) + .setPlaceholder(getMessage('menus.category.placeholder')) + .setOptions( + settings.categories.map(category => + new SelectMenuOptionBuilder() + .setValue(String(category.id)) + .setLabel(category.name) + .setDescription(category.description) + .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), + ), + ), + ), + ], + }); + sent.awaitMessageComponent({ + componentType: ComponentType.SelectMenu, + filter: () => true, + time: ms('30s'), + }) + .then(async () => { + await sent.delete(); + }) + .catch(error => { + if (error) this.client.log.error(error); + sent.delete(); + }); + } + + } + + /** + * @param {import("discord.js").Message} message + */ + async run(message) { + /** @type {import("client")} */ + const client = this.client; + + if (message.channel.type === ChannelType.DM) { + if (message.author.bot) return false; + const commonGuilds = await getCommonGuilds(this.client, message.author.id); + if (commonGuilds.size === 0) { + return false; + } else if (commonGuilds.size === 1) { + const settings = await client.prisma.guild.findUnique({ + select: { + categories: true, + errorColour: true, + locale: true, + primaryColour: true, + }, + where: { id: commonGuilds.at(0).id }, + }); + const getMessage = this.client.i18n.getLocale(settings.locale); + const sent = await message.reply({ + components: [ + new ActionRowBuilder() + .setComponents( + new ButtonBuilder() + .setCustomId(message.id) + .setStyle(Success) + .setLabel(getMessage('buttons.confirm_open.text')) + .setEmoji(getMessage('buttons.confirm_open.emoji')), + ), + ], + embeds: [ + new EmbedBuilder() + .setColor(settings.primaryColour) + .setTitle(getMessage('dm.confirm_open.title')) + .setDescription(message.content), + ], + }); + sent.awaitMessageComponent({ + componentType: ComponentType.Button, + filter: interaction => interaction.deferUpdate(), + time: ms('30s'), + }) + .then(async interaction => await this.useGuild(settings, interaction, message.content)) + .catch(error => { + if (error) this.client.log.error(error); + sent.delete(); + }); + } else { + const getMessage = this.client.i18n.getLocale(); + const sent = await message.reply({ + components: [ + new ActionRowBuilder() + .setComponents( + new SelectMenuBuilder() + .setCustomId(message.id) + .setPlaceholder(getMessage('menus.guild.placeholder')) + .setOptions( + commonGuilds.map(g => + new SelectMenuOptionBuilder() + .setValue(String(g.id)) + .setLabel(g.name), + ), + ), + ), + + ], + }); + sent.awaitMessageComponent({ + componentType: ComponentType.SelectMenu, + filter: interaction => interaction.deferUpdate(), + time: ms('30s'), + }) + .then(async interaction => { + const settings = await client.prisma.guild.findUnique({ + select: { + categories: true, + errorColour: true, + locale: true, + primaryColour: true, + }, + where: { id: interaction.values[0] }, + }); + await this.useGuild(settings, interaction, message.content); + }) + .catch(error => { + if (error) this.client.log.error(error); + sent.delete(); + }); + } + } else { + // TODO: archive messages in tickets + // TODO: auto tag + } + } +}; diff --git a/src/listeners/client/messageDelete.js b/src/listeners/client/messageDelete.js new file mode 100644 index 0000000..11504a0 --- /dev/null +++ b/src/listeners/client/messageDelete.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'messageDelete', + }); + } + + run(message) { + // TODO: archive messages in tickets + } +}; diff --git a/src/listeners/client/messageUpdate.js b/src/listeners/client/messageUpdate.js new file mode 100644 index 0000000..59a2a63 --- /dev/null +++ b/src/listeners/client/messageUpdate.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'messageUpdate', + }); + } + + run(oldMessage, newMessage) { + // TODO: archive messages in tickets + } +}; diff --git a/src/listeners/client/warn.js b/src/listeners/client/warn.js new file mode 100644 index 0000000..b37bf6c --- /dev/null +++ b/src/listeners/client/warn.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'warn', + }); + } + + run(warn) { + this.client.log.warn(warn); + } +}; diff --git a/src/listeners/commands/error.js b/src/listeners/commands/error.js new file mode 100644 index 0000000..6383585 --- /dev/null +++ b/src/listeners/commands/error.js @@ -0,0 +1,19 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.commands, + event: 'error', + }); + } + + run({ + command, + error, + }) { + this.client.log.error.commands(`"${command.name}" command execution error:`, error); + return true; + } +}; diff --git a/src/listeners/commands/run.js b/src/listeners/commands/run.js new file mode 100644 index 0000000..94649a1 --- /dev/null +++ b/src/listeners/commands/run.js @@ -0,0 +1,24 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.commands, + event: 'run', + }); + } + + run({ + command, + interaction, + }) { + const types = { + 1: 'slash', + 2: 'user', + 3: 'message', + }; + this.client.log.info.commands(`${interaction.user.tag} used the "${command.name}" ${types[command.type]} command`); + return true; + } +}; diff --git a/src/menus/create.js b/src/menus/create.js index a0a0dfa..887068e 100644 --- a/src/menus/create.js +++ b/src/menus/create.js @@ -8,5 +8,15 @@ module.exports = class CreateMenu extends Menu { }); } - async run(id, interaction) { } + /** + * @param {*} id + * @param {import("discord.js").SelectMenuInteraction} interaction + */ + async run(id, interaction) { + await this.client.tickets.create({ + categoryId: interaction.values[0], + interaction, + topic: id.topic, + }); + } }; \ No newline at end of file diff --git a/src/modals/questions.js b/src/modals/questions.js index 5554551..f3063f5 100644 --- a/src/modals/questions.js +++ b/src/modals/questions.js @@ -8,5 +8,13 @@ module.exports = class QuestionsModal extends Modal { }); } - async run(id, interaction) { } + async run(id, interaction) { + console.log(id); + console.log(require('util').inspect(interaction, { + colors: true, + depth: 10, + })); + + // TODO: custom topic + } }; \ No newline at end of file diff --git a/src/modals/topic.js b/src/modals/topic.js new file mode 100644 index 0000000..3e98ba1 --- /dev/null +++ b/src/modals/topic.js @@ -0,0 +1,18 @@ +const { Modal } = require('@eartharoid/dbf'); + +module.exports = class TopicModal extends Modal { + constructor(client, options) { + super(client, { + ...options, + id: 'topic', + }); + } + + async run(id, interaction) { + console.log(id); + console.log(require('util').inspect(interaction, { + colors: true, + depth: 10, + })); + } +}; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/panels.js b/src/routes/api/admin/guilds/[guild]/panels.js index 7b39f1a..c1d928a 100644 --- a/src/routes/api/admin/guilds/[guild]/panels.js +++ b/src/routes/api/admin/guilds/[guild]/panels.js @@ -84,36 +84,35 @@ module.exports.post = fastify => ({ .setLabel(getMessage('buttons.create.text')) .setEmoji(getMessage('buttons.create.emoji')), ); + } else if (data.type === 'BUTTON') { + components.push( + ...categories.map(category => + new ButtonBuilder() + .setCustomId(JSON.stringify({ + action: 'create', + target: category.id, + })) + .setStyle(Secondary) + .setLabel(category.name) + .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), + ), + ); } else { - if (data.type === 'BUTTON') { - components.push( - ...categories.map(category => - new ButtonBuilder() - .setCustomId(JSON.stringify({ - action: 'create', - target: category.id, - })) - .setStyle(Secondary) - .setLabel(category.name) - .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), - ), - ); - } else { - components.push( - new SelectMenuBuilder() - .setCustomId('create') - .setPlaceholder(getMessage('menus.create.placeholder')) - .setOptions( - categories.map(category => - new SelectMenuOptionBuilder() - .setValue(String(category.id)) - .setLabel(category.name) - .setDescription(category.description) - .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), - ), + components.push( + new SelectMenuBuilder() + .setCustomId(JSON.stringify({ action: 'create' })) + .setPlaceholder(getMessage('menus.category.placeholder')) + .setOptions( + categories.map(category => + new SelectMenuOptionBuilder() + .setValue(String(category.id)) + .setLabel(category.name) + .setDescription(category.description) + .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), ), - ); - } + ), + ); + } await channel.send({ diff --git a/src/schemas/settings.js b/src/schemas/settings.js new file mode 100644 index 0000000..e4af137 --- /dev/null +++ b/src/schemas/settings.js @@ -0,0 +1,24 @@ +module.exports = joi.object({ + archive: joi.boolean().optional(), + autoClose: joi.number().min(3600000).optional(), + autoTag: [joi.array(), joi.string().valid('ticket', '!ticket', 'all')].optional(), + blocklist: joi.array().optional(), + createdAt: joi.string().optional(), + errorColour: joi.string().optional(), + footer: joi.string().optional(), + id: joi.string().optional(), + logChannel: joi.string().optional(), + primaryColour: joi.string().optional(), + staleAfter: joi.number().min(60000).optional(), + successColour: joi.string().optional(), + workingHours: joi.array().length(8).items( + joi.string(), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + ).optional(), +}); \ No newline at end of file diff --git a/user/example.config.yml b/user/example.config.yml index 3519d93..9a5da22 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -11,7 +11,7 @@ ## |_| |_| \___| |_|\_\ \___| \__| |___/ ## ## ## ## Documentation: https://discordtickets.app ## -## Support: https://go.eartharoid.me/discord ## +## Support: https://lnk.earth/discord ## ##################################################### logs: From 3318ddcaa690d013adcec7473c95ab4661aba964 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 6 Aug 2022 21:07:40 +0100 Subject: [PATCH 077/409] Add presence & placeholders (closes #302) --- src/listeners/client/ready.js | 39 +++++++++++++++++++++++++++++++++++ user/example.config.yml | 12 ++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 26a436f..092bfb5 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -1,4 +1,5 @@ const { Listener } = require('@eartharoid/dbf'); +const ms = require('ms'); module.exports = class extends Listener { constructor(client, options) { @@ -14,5 +15,43 @@ module.exports = class extends Listener { // process.title = `"[Discord Tickets] ${this.client.user.tag}"`; // too long and gets cut off process.title = 'tickets'; this.client.log.success('Connected to Discord as "%s"', this.client.user.tag); + + let next = 0; + const setPresence = async () => { + const cacheKey = 'cache/presence'; + let cached = await this.client.keyv.get(cacheKey); + + if (!cached) { + const tickets = await this.client.prisma.ticket.findMany({ + select: { + createdAt: true, + firstResponseAt: true, + }, + }); + cached = { + avgResolutionTime: ms(tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / tickets.length), + avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length), + openTickets: tickets.filter(t => t.open).length, + totalTickets: tickets.length, + }; + await this.client.keyv.set(cacheKey, cached, ms('15m')); + } + const activity = this.client.config.presence.activities[next]; + activity.name = activity.name + .replace(/{+avgResolutionTime}+/gi, cached.avgResolutionTime) + .replace(/{+avgResponseTime}+/gi, cached.avgResponseTime) + .replace(/{+openTickets}+/gi, cached.openTickets) + .replace(/{+totalTickets}+/gi, cached.totalTickets); + this.client.user.setPresence({ + activities: [activity], + status: this.client.config.presence.status, + }); + next++; + if (next === this.client.config.presence.activities.length) next = 0; + + }; + + setPresence(); + if (this.client.config.presence.activities.length > 1) setInterval(() => setPresence(), this.client.config.presence.interval * 1000); } }; diff --git a/user/example.config.yml b/user/example.config.yml index 9a5da22..dd83e36 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -19,6 +19,16 @@ logs: directory: ./logs enabled: true keepFor: 30 # days - level: info + level: info # debug|verbose|info|success|warn|notice|error|critical +presence: + activities: + - name: /new + - name: with {totalTickets} tickets + - name: '{openTickets} tickets' + type: 3 # https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityType + - name: '{avgResponseTime} response time' + type: 3 + interval: 60 # seconds, only used if activities.length > 1 + status: online # online|idle|invisible|dnd overrides: disableArchives: false \ No newline at end of file From 7b692facc1338739470b608d9a1b93267b867780 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 6 Aug 2022 21:35:42 +0100 Subject: [PATCH 078/409] Add category cache --- src/lib/tickets/manager.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 6554bfe..7fb450e 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -7,6 +7,7 @@ const { TextInputStyle, } = require('discord.js'); const emoji = require('node-emoji'); +const ms = require('ms'); module.exports = class TicketManager { constructor(client) { @@ -23,13 +24,20 @@ module.exports = class TicketManager { async create({ categoryId, interaction, topic, reference, }) { - const category = await this.client.prisma.category.findUnique({ - include: { - guild: true, - questions: true, - }, - where: { id: Number(categoryId) }, - }); + const cacheKey = `cache/category+guild+questions:${categoryId}`; + let category = await this.client.keyv.get(cacheKey); + + if (!category) { + category = await this.client.prisma.category.findUnique({ + include: { + guild: true, + questions: true, + }, + where: { id: Number(categoryId) }, + }); + this.client.keyv.set(cacheKey, category, ms('5m')); + } + // TODO: if member !required roles -> stop @@ -41,6 +49,8 @@ module.exports = class TicketManager { // TODO: if cooldown -> stop + // TODO: if 10s ratelimit -> stop + const getMessage = this.client.i18n.getLocale(category.guild.locale); if (category.questions.length >= 1) { From 0a10d91f8de59a1b3b25b9cd2a698248c0d6e9e6 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 6 Aug 2022 21:52:08 +0100 Subject: [PATCH 079/409] Closes #288 --- src/listeners/client/guildCreate.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/listeners/client/guildCreate.js b/src/listeners/client/guildCreate.js index 5cfbb4c..6867f0f 100644 --- a/src/listeners/client/guildCreate.js +++ b/src/listeners/client/guildCreate.js @@ -9,7 +9,22 @@ module.exports = class extends Listener { }); } - run(guild) { + /** + * @param {import("discord.js").Guild} guild + */ + async run(guild) { + /** @type {import("client")} */ + const client = this.client; + this.client.log.success(`Added to guild "${guild.name}"`); + let settings = await client.prisma.guild.findUnique({ where: { id: guild.id } }); + if (!settings) { + settings = await client.prisma.guild.create({ + data: { + id: guild.id, + locale: client.i18n.locales.includes(guild.preferredLocale) ? guild.preferredLocale : 'en-GB', + }, + }); + } } }; From efe7ede041086696204631653667edee05781c89 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 6 Aug 2022 21:58:02 +0100 Subject: [PATCH 080/409] Make sure category hasn't been deleted --- src/i18n/en-GB.yml | 5 ++++- src/lib/tickets/manager.js | 26 ++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 5eb9901..f8eb71d 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -162,4 +162,7 @@ modals: misc: no_categories: description: No ticket categories have been configured. - title: ❌ There are no ticket categories \ No newline at end of file + title: ❌ There are no ticket categories + unknown_category: + description: Please try a different category. + title: ❌ That ticket category doesn't exist \ No newline at end of file diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 7fb450e..4431fd0 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -8,6 +8,7 @@ const { } = require('discord.js'); const emoji = require('node-emoji'); const ms = require('ms'); +const { EmbedBuilder } = require('discord.js'); module.exports = class TicketManager { constructor(client) { @@ -35,10 +36,30 @@ module.exports = class TicketManager { }, where: { id: Number(categoryId) }, }); + if (!category) { + let settings; + if (interaction.guild) { + settings = await this.client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + } else { + settings = { + errorColour: 'Red', + locale: 'en-GB', + }; + } + const getMessage = this.client.i18n.getLocale(settings.locale); + return await interaction.reply({ + embeds: [ + new EmbedBuilder() + .setColor(settings.errorColour) + .setTitle(getMessage('misc.unknown_category.title')) + .setDescription(getMessage('misc.unknown_category.description')) + .setFooter(settings.footer), + ], + }); + } this.client.keyv.set(cacheKey, category, ms('5m')); } - // TODO: if member !required roles -> stop // TODO: if discordCategory has 50 channels -> stop @@ -51,7 +72,8 @@ module.exports = class TicketManager { // TODO: if 10s ratelimit -> stop - const getMessage = this.client.i18n.getLocale(category.guild.locale); + + const getMessage = this.client.i18n.getLocale(category.guild.locale); if (category.questions.length >= 1) { await interaction.showModal( From ae72266b82174e7ac0d4d59462ce975d54851f09 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 6 Aug 2022 22:50:28 +0100 Subject: [PATCH 081/409] Ticket creation rate limiting + fixes --- src/i18n/en-GB.yml | 3 ++ src/lib/tickets/manager.js | 49 ++++++++++++++----- src/routes/api/admin/guilds/[guild]/panels.js | 7 ++- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index f8eb71d..2836cec 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -163,6 +163,9 @@ misc: no_categories: description: No ticket categories have been configured. title: ❌ There are no ticket categories + ratelimited: + description: Try again in a few seconds. + title: 🐢 Slow down unknown_category: description: Please try a different category. title: ❌ That ticket category doesn't exist \ No newline at end of file diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 4431fd0..ea1600f 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -27,7 +27,6 @@ module.exports = class TicketManager { }) { const cacheKey = `cache/category+guild+questions:${categoryId}`; let category = await this.client.keyv.get(cacheKey); - if (!category) { category = await this.client.prisma.category.findUnique({ include: { @@ -47,19 +46,47 @@ module.exports = class TicketManager { }; } const getMessage = this.client.i18n.getLocale(settings.locale); + const embed = new EmbedBuilder() + .setColor(settings.errorColour) + .setTitle(getMessage('misc.unknown_category.title')) + .setDescription(getMessage('misc.unknown_category.description')); + if (settings.footer) { + embed.setFooter({ + iconURL: interaction.guild?.iconURL(), + text: settings.footer, + }); + } return await interaction.reply({ - embeds: [ - new EmbedBuilder() - .setColor(settings.errorColour) - .setTitle(getMessage('misc.unknown_category.title')) - .setDescription(getMessage('misc.unknown_category.description')) - .setFooter(settings.footer), - ], + embeds: [embed], + ephemeral: true, }); } this.client.keyv.set(cacheKey, category, ms('5m')); } + const getMessage = this.client.i18n.getLocale(category.guild.locale); + + const rlKey = `ratelimits/guild-user:${interaction.guild.id}-${interaction.user.id}`; + const rl = await this.client.keyv.get(rlKey); + if (rl) { + const embed = new EmbedBuilder() + .setColor(category.guild.errorColour) + .setTitle(getMessage('misc.ratelimited.title')) + .setDescription(getMessage('misc.ratelimited.description')); + if (category.guild.footer) { + embed.setFooter({ + iconURL: interaction.guild.iconURL(), + text: category.guild.footer, + }); + } + return await interaction.reply({ + embeds: [embed], + ephemeral: true, + }); + } else { + this.client.keyv.set(rlKey, true, ms('10s')); + } + // TODO: if member !required roles -> stop // TODO: if discordCategory has 50 channels -> stop @@ -70,11 +97,6 @@ module.exports = class TicketManager { // TODO: if cooldown -> stop - // TODO: if 10s ratelimit -> stop - - - const getMessage = this.client.i18n.getLocale(category.guild.locale); - if (category.questions.length >= 1) { await interaction.showModal( new ModalBuilder() @@ -86,6 +108,7 @@ module.exports = class TicketManager { .setTitle(category.name) .setComponents( category.questions + .filter(q => q.type === 'TEXT') // TODO: remove this when modals support select menus .sort((a, b) => a.order - b.order) .map(q => { if (q.type === 'TEXT') { diff --git a/src/routes/api/admin/guilds/[guild]/panels.js b/src/routes/api/admin/guilds/[guild]/panels.js index c1d928a..e60c02b 100644 --- a/src/routes/api/admin/guilds/[guild]/panels.js +++ b/src/routes/api/admin/guilds/[guild]/panels.js @@ -58,11 +58,14 @@ module.exports.post = fastify => ({ const embed = new EmbedBuilder() .setColor(settings.primaryColour) - .setTitle(data.title) - .setFooter({ + .setTitle(data.title); + + if (settings.footer) { + embed.setFooter({ iconURL: guild.iconURL(), text: settings.footer, }); + } if (data.description) embed.setDescription(data.description); if (data.image) embed.setImage(data.image); From fcd390bc9d950b3abc34ad3df70c13ebcb92fa40 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 6 Aug 2022 22:55:36 +0100 Subject: [PATCH 082/409] Fix DM --- src/lib/tickets/manager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index ea1600f..bb16650 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -26,6 +26,7 @@ module.exports = class TicketManager { categoryId, interaction, topic, reference, }) { const cacheKey = `cache/category+guild+questions:${categoryId}`; + /** @type {import('@prisma/client').Category} */ let category = await this.client.keyv.get(cacheKey); if (!category) { category = await this.client.prisma.category.findUnique({ @@ -66,7 +67,7 @@ module.exports = class TicketManager { const getMessage = this.client.i18n.getLocale(category.guild.locale); - const rlKey = `ratelimits/guild-user:${interaction.guild.id}-${interaction.user.id}`; + const rlKey = `ratelimits/guild-user:${category.guildId}-${interaction.user.id}`; const rl = await this.client.keyv.get(rlKey); if (rl) { const embed = new EmbedBuilder() From 01e479dab5a79a290433d44d6403e2bf87bea6d3 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 8 Aug 2022 01:36:14 +0100 Subject: [PATCH 083/409] Make progress on ticket creations + fixes --- db/mysql/schema.prisma | 30 ++-- db/postgresql/schema.prisma | 30 ++-- db/sqlite/schema.prisma | 30 ++-- src/commands/message/create.js | 4 +- src/i18n/en-GB.yml | 16 +- src/lib/tickets/manager.js | 148 ++++++++++++++++-- src/listeners/client/ready.js | 5 +- src/modals/questions.js | 16 +- .../admin/guilds/[guild]/categories/index.js | 6 +- src/routes/api/admin/guilds/[guild]/index.js | 5 +- src/routes/api/client.js | 5 +- 11 files changed, 220 insertions(+), 75 deletions(-) diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index 46dde9c..f4fc83a 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -20,15 +20,16 @@ model ArchivedChannel { } model ArchivedMessage { - author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) - authorId String @db.VarChar(19) - content String @db.Text - createdAt DateTime @default(now()) - deleted Boolean @default(false) - edited Boolean @default(false) - id String @id @db.VarChar(19) - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) + authorId String @db.VarChar(19) + content String @db.Text + createdAt DateTime @default(now()) + deleted Boolean @default(false) + edited Boolean @default(false) + id String @id @db.VarChar(19) + referencedBy Ticket[] @relation("MessageReferencedByTicket") + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) @@map("archivedMessages") } @@ -189,11 +190,11 @@ model Ticket { archivedUsers ArchivedUser[] category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) categoryId Int? - claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) - claimedById String @db.VarChar(19) + claimedBy User? @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) + claimedById String? @db.VarChar(19) closedAt DateTime? - closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) - closedById String @db.VarChar(19) + closedBy User? @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) + closedById String? @db.VarChar(19) closedReason String? @db.Text createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) @@ -213,7 +214,8 @@ model Ticket { pinnedMessages Json @default("[]") priority TicketPriority? referencedBy Ticket[] @relation("TicketsReferencedByTicket") - referencesMessageId String @db.VarChar(19) + referencesMessage ArchivedMessage? @relation(name: "MessageReferencedByTicket", fields: [referencesMessageId], references: [id], onDelete: SetNull) + referencesMessageId String? @db.VarChar(19) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? @db.VarChar(19) topic String? @db.Text diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index d093f69..d33a429 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -20,15 +20,16 @@ model ArchivedChannel { } model ArchivedMessage { - author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) - authorId String @db.VarChar(19) - content String @db.Text - createdAt DateTime @default(now()) - deleted Boolean @default(false) - edited Boolean @default(false) - id String @id @db.VarChar(19) - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) + authorId String @db.VarChar(19) + content String @db.Text + createdAt DateTime @default(now()) + deleted Boolean @default(false) + edited Boolean @default(false) + id String @id @db.VarChar(19) + referencedBy Ticket[] @relation("MessageReferencedByTicket") + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) @@map("archivedMessages") } @@ -189,11 +190,11 @@ model Ticket { archivedUsers ArchivedUser[] category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) categoryId Int? - claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) - claimedById String @db.VarChar(19) + claimedBy User? @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) + claimedById String? @db.VarChar(19) closedAt DateTime? - closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) - closedById String @db.VarChar(19) + closedBy User? @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) + closedById String? @db.VarChar(19) closedReason String? @db.Text createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) @@ -213,7 +214,8 @@ model Ticket { pinnedMessages Json @default("[]") priority TicketPriority? referencedBy Ticket[] @relation("TicketsReferencedByTicket") - referencesMessageId String @db.VarChar(19) + referencesMessage ArchivedMessage? @relation(name: "MessageReferencedByTicket", fields: [referencesMessageId], references: [id], onDelete: SetNull) + referencesMessageId String? @db.VarChar(19) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? @db.VarChar(19) topic String? @db.Text diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma index cf3337b..eb4e180 100644 --- a/db/sqlite/schema.prisma +++ b/db/sqlite/schema.prisma @@ -20,15 +20,16 @@ model ArchivedChannel { } model ArchivedMessage { - author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) - authorId String - content String - createdAt DateTime @default(now()) - deleted Boolean @default(false) - edited Boolean @default(false) - id String @id - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) + authorId String + content String + createdAt DateTime @default(now()) + deleted Boolean @default(false) + edited Boolean @default(false) + id String @id + referencedBy Ticket[] @relation("MessageReferencedByTicket") + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @@map("archivedMessages") } @@ -189,11 +190,11 @@ model Ticket { archivedUsers ArchivedUser[] category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) categoryId Int? - claimedBy User @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) - claimedById String + claimedBy User? @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) + claimedById String? closedAt DateTime? - closedBy User @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) - closedById String + closedBy User? @relation(name: "TicketsClosedByUser", fields: [closedById], references: [id]) + closedById String? closedReason String? createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) @@ -213,7 +214,8 @@ model Ticket { pinnedMessages String @default("[]") priority String? referencedBy Ticket[] @relation("TicketsReferencedByTicket") - referencesMessageId String + referencesMessage ArchivedMessage? @relation(name: "MessageReferencedByTicket", fields: [referencesMessageId], references: [id], onDelete: SetNull) + referencesMessageId String? referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? topic String? diff --git a/src/commands/message/create.js b/src/commands/message/create.js index 774e8fd..ba8f240 100644 --- a/src/commands/message/create.js +++ b/src/commands/message/create.js @@ -13,5 +13,7 @@ module.exports = class CreateMessageCommand extends MessageCommand { }); } - async run(interaction) { } + async run(interaction) { + // TODO: archive message + } }; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 2836cec..435bfbf 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -1,6 +1,3 @@ -test: | - line 1 - line 2 buttons: confirm_open: emoji: ✅ @@ -165,7 +162,16 @@ misc: title: ❌ There are no ticket categories ratelimited: description: Try again in a few seconds. - title: 🐢 Slow down + title: 🐢 Please slow down unknown_category: description: Please try a different category. - title: ❌ That ticket category doesn't exist \ No newline at end of file + title: ❌ That ticket category doesn't exist +ticket: + answers: + no_value: '*No response*' + opening_message: + content: | + {staff} + {creator} has created a new ticket + fields: + topic: Topic \ No newline at end of file diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index bb16650..a8e802d 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1,3 +1,4 @@ +/* eslint-disable max-lines */ const { ActionRowBuilder, ModalBuilder, @@ -10,6 +11,9 @@ const emoji = require('node-emoji'); const ms = require('ms'); const { EmbedBuilder } = require('discord.js'); +/** + * @typedef {import('@prisma/client').Category & {guild: import('@prisma/client').Guild} & {questions: import('@prisma/client').Question[]}} CategoryGuildQuestions + */ module.exports = class TicketManager { constructor(client) { /** @type {import("client")} */ @@ -18,15 +22,15 @@ module.exports = class TicketManager { /** * @param {object} data - * @param {string} data.category + * @param {string} data.categoryId * @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction} data.interaction * @param {string?} [data.topic] */ async create({ - categoryId, interaction, topic, reference, + categoryId, interaction, topic, referencesMessage, referencesTicket, }) { const cacheKey = `cache/category+guild+questions:${categoryId}`; - /** @type {import('@prisma/client').Category} */ + /** @type {CategoryGuildQuestions} */ let category = await this.client.keyv.get(cacheKey); if (!category) { category = await this.client.prisma.category.findUnique({ @@ -104,7 +108,8 @@ module.exports = class TicketManager { .setCustomId(JSON.stringify({ action: 'questions', categoryId, - reference, + referencesMessage, + referencesTicket, })) .setTitle(category.name) .setComponents( @@ -154,7 +159,8 @@ module.exports = class TicketManager { .setCustomId(JSON.stringify({ action: 'topic', categoryId, - reference, + referencesMessage, + referencesTicket, })) .setTitle(category.name) .setComponents( @@ -183,20 +189,140 @@ module.exports = class TicketManager { * @param {string?} [data.topic] */ async postQuestions({ - categoryId, interaction, topic, reference, + categoryId, interaction, topic, referencesMessage, referencesTicket, }) { await interaction.deferReply({ ephemeral: true }); - console.log(require('util').inspect(interaction, { - colors: true, - depth: 10, - })); - if (interaction.isModalSubmit()) { + const cacheKey = `cache/category+guild+questions:${categoryId}`; + /** @type {CategoryGuildQuestions} */ + const category = await this.client.keyv.get(cacheKey); + + let answers; + if (interaction.isModalSubmit()) { + answers = category.questions.map(q => ({ + questionId: q.id, + userId: interaction.user.id, + value: interaction.fields.getTextInputValue(q.id), + })); + if (category.customTopic) topic = interaction.fields.getTextInputValue(category.customTopic); } + /** @type {import("discord.js").Guild} */ + const guild = this.client.guilds.cache.get(category.guild.id); + const getMessage = this.client.i18n.getLocale(category.guild.locale); + const creator = await guild.members.fetch(interaction.user.id); + const number = (await this.client.prisma.ticket.count({ where: { guildId: category.guild.id } })) + 1; + const channelName = category.channelName + .replace(/{+\s?(user)?name\s?}+/gi, creator.user.username) + .replace(/{+\s?(nick|display)(name)?\s?}+/gi, creator.displayName) + .replace(/{+\s?num(ber)?\s?}+/gi, number === 1488 ? '1487b' : number); + const allow = ['ViewChannel', 'ReadMessageHistory', 'SendMessages', 'EmbedLinks', 'AttachFiles']; + /** @type {import("discord.js").TextChannel} */ + const channel = await guild.channels.create({ + name: channelName, + parent: category.discordCategory, + permissionOverwrites: [ + { + deny: ['ViewChannel'], + id: guild.roles.everyone, + }, + { + allow: allow, + id: this.client.user.id, + }, + { + allow: allow, + id: creator.id, + }, + ...category.staffRoles.map(id => ({ + allow: allow, + id, + })), + ], + rateLimitPerUser: category.ratelimit, + reason: `${creator.user.tag} created a ticket`, + topic: `${creator}${topic?.length > 0 ? ` | ${topic}` : ''}`, + }); + + const embed = new EmbedBuilder() + .setColor(category.guild.primaryColour) + .setAuthor({ + iconURL: creator.displayAvatarURL(), + name: creator.displayName, + }) + .setDescription( + category.openingMessage + .replace(/{+\s?(user)?name\s?}+/gi, creator.user.toString()), + + ); + + if (answers) { + embed.setFields( + category.questions.map(q => ({ + name: q.label, + value: interaction.fields.getTextInputValue(q.id) || getMessage('ticket.answers.no_value'), + })), + ); + } else if (topic) { + embed.setFields({ + name: getMessage('ticket.opening_message.fields.topic'), + value: topic, + }); + } + + if (category.guild.footer) { + embed.setFooter({ + iconURL: guild.iconURL(), + text: category.guild.footer, + }); + } + + // TODO: add edit button (if topic or questions) + // TODO: add close and claim buttons if enabled + const pings = category.pingRoles.map(r => `<@&${r}>`).join(' '); + const sent = await channel.send({ + content: getMessage('ticket.opening_message.content', { + creator: interaction.user.toString(), + staff: pings ? pings + ',' : '', + }), + embeds: [embed], + }); + await sent.pin({ reason: 'Ticket opening message' }); + const pinned = channel.messages.cache.last(); + + if (pinned.system) { + pinned + .delete({ reason: 'Cleaning up system message' }) + .catch(() => this.client.log.warn('Failed to delete system pin message')); + } + + // TODO: referenced msg or ticket + + const data = { + category: { connect: { id: categoryId } }, + createdBy: { + connectOrCreate: { + create: { id: interaction.user.id }, + where: { id: interaction.user.id }, + }, + }, + guild: { connect: { id: category.guild.id } }, + id: channel.id, + number, + openingMessage: sent.id, + topic, + }; + if (referencesTicket) data.referencesTicket = { connect: { id: referencesTicket } }; + let message; + if (referencesMessage) message = this.client.prisma.archivedMessage.findUnique({ where: { id: referencesMessage } }); + if (message) data.referencesMessage = { connect: { id: referencesMessage } }; // only add if the message has been archived ^^ + if (answers) data.questionAnswers = { createMany: { data: answers } }; + const ticket = await this.client.prisma.ticket.create({ data }); + console.log(ticket); interaction.editReply({ components: [], embeds: [], }); + // TODO: log channel } }; \ No newline at end of file diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 092bfb5..2313360 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -28,9 +28,10 @@ module.exports = class extends Listener { firstResponseAt: true, }, }); + const closedTickets = tickets.filter(t => t.closedAt); cached = { - avgResolutionTime: ms(tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / tickets.length), - avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length), + avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), openTickets: tickets.filter(t => t.open).length, totalTickets: tickets.length, }; diff --git a/src/modals/questions.js b/src/modals/questions.js index f3063f5..f7c6b8e 100644 --- a/src/modals/questions.js +++ b/src/modals/questions.js @@ -8,13 +8,15 @@ module.exports = class QuestionsModal extends Modal { }); } + /** + * + * @param {*} id + * @param {import("discord.js").ModalSubmitInteraction} interaction + */ async run(id, interaction) { - console.log(id); - console.log(require('util').inspect(interaction, { - colors: true, - depth: 10, - })); - - // TODO: custom topic + await this.client.tickets.postQuestions({ + ...id, + interaction, + }); } }; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index ac30fce..95d1f81 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -27,13 +27,13 @@ module.exports.get = fastify => ({ }, where: { id: req.params.guild }, }); - categories = categories.map(c => { + const closedTickets = c.tickets.filter(t => t.closedAt); c = { ...c, stats: { - avgResolutionTime: ms(c.tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / c.tickets.length), - avgResponseTime: ms(c.tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / c.tickets.length), + avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), }, }; delete c.tickets; diff --git a/src/routes/api/admin/guilds/[guild]/index.js b/src/routes/api/admin/guilds/[guild]/index.js index 2c10f98..c3b22a0 100644 --- a/src/routes/api/admin/guilds/[guild]/index.js +++ b/src/routes/api/admin/guilds/[guild]/index.js @@ -28,14 +28,15 @@ module.exports.get = fastify => ({ }, where: { guildId: id }, }); + const closedTickets = tickets.filter(t => t.closedAt); cached = { createdAt: settings.createdAt, id: guild.id, logo: guild.iconURL(), name: guild.name, stats: { - avgResolutionTime: ms(tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / tickets.length), - avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length), + avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), categories: categories.map(c => ({ id: c.id, name: c.name, diff --git a/src/routes/api/client.js b/src/routes/api/client.js index b3321a6..f2a7649 100644 --- a/src/routes/api/client.js +++ b/src/routes/api/client.js @@ -14,6 +14,7 @@ module.exports.get = () => ({ firstResponseAt: true, }, }); + const closedTickets = tickets.filter(t => t.closedAt); const users = await client.prisma.user.findMany({ select: { messageCount: true } }); cached = { avatar: client.user.avatarURL(), @@ -23,8 +24,8 @@ module.exports.get = () => ({ stats: { activatedUsers: users.length, archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they get deleted - avgResolutionTime: ms(tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / tickets.length), - avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length), + avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), categories: await client.prisma.category.count(), guilds: client.guilds.cache.size, members: client.guilds.cache.reduce((t, g) => t + g.memberCount, 0), From bc3ccdcb82b49f3e5c32c55d84181f081105e4c0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 8 Aug 2022 21:55:09 +0100 Subject: [PATCH 084/409] ticket creation works --- src/buttons/edit.js | 12 ++ src/commands/slash/claim.js | 23 ++++ src/commands/slash/release.js | 23 ++++ src/commands/slash/topic.js | 23 ---- src/i18n/en-GB.yml | 35 +++-- src/lib/embed.js | 8 ++ src/lib/tickets/manager.js | 185 ++++++++++++++++++-------- src/listeners/client/messageCreate.js | 1 + src/listeners/client/ready.js | 2 +- src/modals/topic.js | 9 +- 10 files changed, 226 insertions(+), 95 deletions(-) create mode 100644 src/buttons/edit.js create mode 100644 src/commands/slash/claim.js create mode 100644 src/commands/slash/release.js create mode 100644 src/lib/embed.js diff --git a/src/buttons/edit.js b/src/buttons/edit.js new file mode 100644 index 0000000..479d222 --- /dev/null +++ b/src/buttons/edit.js @@ -0,0 +1,12 @@ +const { Button } = require('@eartharoid/dbf'); + +module.exports = class EditButton extends Button { + constructor(client, options) { + super(client, { + ...options, + id: 'edit', + }); + } + + async run(id, interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/claim.js b/src/commands/slash/claim.js new file mode 100644 index 0000000..814e702 --- /dev/null +++ b/src/commands/slash/claim.js @@ -0,0 +1,23 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class ClaimSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.claim.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.claim.name'))); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/release.js b/src/commands/slash/release.js new file mode 100644 index 0000000..7722fe2 --- /dev/null +++ b/src/commands/slash/release.js @@ -0,0 +1,23 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class ReleaseSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.release.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.release.name'))); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + }); + } + + async run(interaction) { } +}; \ No newline at end of file diff --git a/src/commands/slash/topic.js b/src/commands/slash/topic.js index 1774dc1..8fe6b39 100644 --- a/src/commands/slash/topic.js +++ b/src/commands/slash/topic.js @@ -9,28 +9,6 @@ module.exports = class TopicSlashCommand extends SlashCommand { const nameLocalizations = {}; client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.topic.name'))); - let opts = [ - { - name: 'new-topic', - required: true, - type: ApplicationCommandOptionType.String, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.topic.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.topic.options.${o.name}.name`))); - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - super(client, { ...options, description: descriptionLocalizations['en-GB'], @@ -38,7 +16,6 @@ module.exports = class TopicSlashCommand extends SlashCommand { dmPermission: false, name: nameLocalizations['en-GB'], nameLocalizations, - options: opts, }); } diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 435bfbf..da77e06 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -1,10 +1,22 @@ buttons: + claim: + emoji: 🙌 + text: Claim + close: + emoji: ✖️ + text: Close confirm_open: emoji: ✅ text: Create ticket create: emoji: 🎫 text: Create a ticket + edit: + emoji: ✏️ + text: Edit + unclaim: + emoji: ♻️ + text: Release commands: message: create: @@ -22,6 +34,9 @@ commands: ticket: description: The ticket to add the member to name: ticket + claim: + description: Claim a ticket + name: claim close: description: Close a ticket name: close @@ -79,6 +94,9 @@ commands: LOW: 🟢 Low description: The priority of the ticket name: priority + release: + description: Release (unclaim) a ticket + name: release remove: description: Remove a member from a ticket name: remove @@ -99,10 +117,6 @@ commands: topic: description: Change the topic of a ticket name: topic - options: - new-topic: - description: The new topic of the ticket - name: new-topic tickets: description: List your own or someone else's tickets name: tickets @@ -152,10 +166,6 @@ menus: placeholder: Select a ticket category guild: placeholder: Select a server -modals: - feedback: - title: 'Feedback' - topic: 'Topic' misc: no_categories: description: No ticket categories have been configured. @@ -166,7 +176,16 @@ misc: unknown_category: description: Please try a different category. title: ❌ That ticket category doesn't exist +modals: + feedback: + title: Feedback + topic: + label: Topic + placeholder: What is this ticket about? ticket: + created: + description: 'Your ticket channel has been created: {channel}.' + title: ✅ Ticket created answers: no_value: '*No response*' opening_message: diff --git a/src/lib/embed.js b/src/lib/embed.js new file mode 100644 index 0000000..4b3504d --- /dev/null +++ b/src/lib/embed.js @@ -0,0 +1,8 @@ +const { EmbedBuilder } = require('discord.js'); + +module.exports = class ExtendedEmbedBuilder extends EmbedBuilder { + constructor(footer, opts) { + super(opts); + if (footer && footer.text) this.setFooter(footer); + } +}; \ No newline at end of file diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index a8e802d..7256c80 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1,6 +1,7 @@ /* eslint-disable max-lines */ const { ActionRowBuilder, + ButtonStyle, ModalBuilder, SelectMenuBuilder, SelectMenuOptionBuilder, @@ -9,7 +10,8 @@ const { } = require('discord.js'); const emoji = require('node-emoji'); const ms = require('ms'); -const { EmbedBuilder } = require('discord.js'); +const ExtendedEmbedBuilder = require('../embed'); +const { ButtonBuilder } = require('discord.js'); /** * @typedef {import('@prisma/client').Category & {guild: import('@prisma/client').Guild} & {questions: import('@prisma/client').Question[]}} CategoryGuildQuestions @@ -29,6 +31,7 @@ module.exports = class TicketManager { async create({ categoryId, interaction, topic, referencesMessage, referencesTicket, }) { + categoryId = Number(categoryId); const cacheKey = `cache/category+guild+questions:${categoryId}`; /** @type {CategoryGuildQuestions} */ let category = await this.client.keyv.get(cacheKey); @@ -51,18 +54,16 @@ module.exports = class TicketManager { }; } const getMessage = this.client.i18n.getLocale(settings.locale); - const embed = new EmbedBuilder() - .setColor(settings.errorColour) - .setTitle(getMessage('misc.unknown_category.title')) - .setDescription(getMessage('misc.unknown_category.description')); - if (settings.footer) { - embed.setFooter({ - iconURL: interaction.guild?.iconURL(), - text: settings.footer, - }); - } return await interaction.reply({ - embeds: [embed], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild?.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.unknown_category.title')) + .setDescription(getMessage('misc.unknown_category.description')), + ], ephemeral: true, }); } @@ -74,24 +75,24 @@ module.exports = class TicketManager { const rlKey = `ratelimits/guild-user:${category.guildId}-${interaction.user.id}`; const rl = await this.client.keyv.get(rlKey); if (rl) { - const embed = new EmbedBuilder() - .setColor(category.guild.errorColour) - .setTitle(getMessage('misc.ratelimited.title')) - .setDescription(getMessage('misc.ratelimited.description')); - if (category.guild.footer) { - embed.setFooter({ - iconURL: interaction.guild.iconURL(), - text: category.guild.footer, - }); - } return await interaction.reply({ - embeds: [embed], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: category.guild.footer, + }) + .setColor(category.guild.errorColour) + .setTitle(getMessage('misc.ratelimited.title')) + .setDescription(getMessage('misc.ratelimited.description')), + ], ephemeral: true, }); } else { this.client.keyv.set(rlKey, true, ms('10s')); } + // TODO: if blacklisted role -> stop + // TODO: if member !required roles -> stop // TODO: if discordCategory has 50 channels -> stop @@ -124,7 +125,7 @@ module.exports = class TicketManager { .setCustomId(q.id) .setLabel(q.label) .setStyle(q.style) - .setMaxLength(q.maxLength) + .setMaxLength(Math.min(q.maxLength, 1000)) .setMinLength(q.minLength) .setPlaceholder(q.placeholder) .setRequired(q.required) @@ -168,8 +169,12 @@ module.exports = class TicketManager { .setComponents( new TextInputBuilder() .setCustomId('topic') - .setLabel(getMessage('modals.topic')) - .setStyle(TextInputStyle.Long), + .setLabel(getMessage('modals.topic.label')) + .setStyle(TextInputStyle.Paragraph) + .setMaxLength(1000) + .setMinLength(5) + .setPlaceholder(getMessage('modals.topic.placeholder')) + .setRequired(true), ), ), ); @@ -189,7 +194,7 @@ module.exports = class TicketManager { * @param {string?} [data.topic] */ async postQuestions({ - categoryId, interaction, topic, referencesMessage, referencesTicket, + action, categoryId, interaction, topic, referencesMessage, referencesTicket, }) { await interaction.deferReply({ ephemeral: true }); @@ -199,12 +204,16 @@ module.exports = class TicketManager { let answers; if (interaction.isModalSubmit()) { - answers = category.questions.map(q => ({ - questionId: q.id, - userId: interaction.user.id, - value: interaction.fields.getTextInputValue(q.id), - })); - if (category.customTopic) topic = interaction.fields.getTextInputValue(category.customTopic); + if (action === 'questions') { + answers = category.questions.map(q => ({ + questionId: q.id, + userId: interaction.user.id, + value: interaction.fields.getTextInputValue(q.id), + })); + if (category.customTopic) topic = interaction.fields.getTextInputValue(category.customTopic); + } else if (action === 'topic') { + topic = interaction.fields.getTextInputValue('topic'); + } } /** @type {import("discord.js").Guild} */ @@ -244,48 +253,101 @@ module.exports = class TicketManager { topic: `${creator}${topic?.length > 0 ? ` | ${topic}` : ''}`, }); - const embed = new EmbedBuilder() - .setColor(category.guild.primaryColour) - .setAuthor({ - iconURL: creator.displayAvatarURL(), - name: creator.displayName, - }) - .setDescription( - category.openingMessage - .replace(/{+\s?(user)?name\s?}+/gi, creator.user.toString()), + if (category.image) await channel.send(category.image); - ); + const embeds = [ + new ExtendedEmbedBuilder() + .setColor(category.guild.primaryColour) + .setAuthor({ + iconURL: creator.displayAvatarURL(), + name: creator.displayName, + }) + .setDescription( + category.openingMessage + .replace(/{+\s?(user)?name\s?}+/gi, creator.user.toString()), + + ), + ]; if (answers) { - embed.setFields( - category.questions.map(q => ({ - name: q.label, - value: interaction.fields.getTextInputValue(q.id) || getMessage('ticket.answers.no_value'), - })), + embeds.push( + new ExtendedEmbedBuilder() + .setColor(category.guild.primaryColour) + .setFields( + category.questions.map(q => ({ + name: q.label, + value: interaction.fields.getTextInputValue(q.id) || getMessage('ticket.answers.no_value'), + })), + ), ); + // embeds[0].setFields( + // category.questions.map(q => ({ + // name: q.label, + // value: interaction.fields.getTextInputValue(q.id) || getMessage('ticket.answers.no_value'), + // })), + // ); } else if (topic) { - embed.setFields({ - name: getMessage('ticket.opening_message.fields.topic'), - value: topic, - }); + embeds.push( + new ExtendedEmbedBuilder() + .setColor(category.guild.primaryColour) + .setFields({ + name: getMessage('ticket.opening_message.fields.topic'), + value: topic, + }), + ); + // embeds[0].setFields({ + // name: getMessage('ticket.opening_message.fields.topic'), + // value: topic, + // }); } if (category.guild.footer) { - embed.setFooter({ + embeds[embeds.length - 1].setFooter({ iconURL: guild.iconURL(), text: category.guild.footer, }); } - // TODO: add edit button (if topic or questions) - // TODO: add close and claim buttons if enabled + const components = new ActionRowBuilder(); + + if (topic || answers) { + components.addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ action: 'edit' })) + .setStyle(ButtonStyle.Secondary) + .setEmoji(getMessage('buttons.edit.emoji')) + .setLabel(getMessage('buttons.edit.text')), + ); + } + + if (category.guild.claimButton && category.claiming) { + components.addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ action: 'claim' })) + .setStyle(ButtonStyle.Secondary) + .setEmoji(getMessage('buttons.claim.emoji')) + .setLabel(getMessage('buttons.claim.text')), + ); + } + + if (category.guild.closeButton) { + components.addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ action: 'close' })) + .setStyle(ButtonStyle.Danger) + .setEmoji(getMessage('buttons.close.emoji')) + .setLabel(getMessage('buttons.close.text')), + ); + } + const pings = category.pingRoles.map(r => `<@&${r}>`).join(' '); const sent = await channel.send({ + components: components.components.length >=1 ? [components] : [], content: getMessage('ticket.opening_message.content', { creator: interaction.user.toString(), staff: pings ? pings + ',' : '', }), - embeds: [embed], + embeds, }); await sent.pin({ reason: 'Ticket opening message' }); const pinned = channel.messages.cache.last(); @@ -318,10 +380,17 @@ module.exports = class TicketManager { if (message) data.referencesMessage = { connect: { id: referencesMessage } }; // only add if the message has been archived ^^ if (answers) data.questionAnswers = { createMany: { data: answers } }; const ticket = await this.client.prisma.ticket.create({ data }); - console.log(ticket); interaction.editReply({ components: [], - embeds: [], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: guild.iconURL(), + text: category.guild.footer, + }) + .setColor(category.guild.successColour) + .setTitle(getMessage('ticket.created.title')) + .setDescription(getMessage('ticket.created.description', { channel: channel.toString() })), + ], }); // TODO: log channel } diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index 64bcd37..f4b7012 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -178,6 +178,7 @@ module.exports = class extends Listener { } } else { // TODO: archive messages in tickets + // TODO: first response // TODO: auto tag } } diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 2313360..5575f68 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -32,7 +32,7 @@ module.exports = class extends Listener { cached = { avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), - openTickets: tickets.filter(t => t.open).length, + openTickets: tickets.length - closedTickets.length, totalTickets: tickets.length, }; await this.client.keyv.set(cacheKey, cached, ms('15m')); diff --git a/src/modals/topic.js b/src/modals/topic.js index 3e98ba1..84544d3 100644 --- a/src/modals/topic.js +++ b/src/modals/topic.js @@ -9,10 +9,9 @@ module.exports = class TopicModal extends Modal { } async run(id, interaction) { - console.log(id); - console.log(require('util').inspect(interaction, { - colors: true, - depth: 10, - })); + await this.client.tickets.postQuestions({ + ...id, + interaction, + }); } }; \ No newline at end of file From f419cbf96755ac95e30dbf19c29611abf0500ff0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 8 Aug 2022 22:38:54 +0100 Subject: [PATCH 085/409] Fix schema issues --- README.md | 5 +- db/mysql/schema.prisma | 73 +++++++++++++-------------- db/postgresql/schema.prisma | 73 +++++++++++++-------------- db/sqlite/schema.prisma | 73 +++++++++++++-------------- src/lib/middleware/prisma-sqlite.js | 2 +- src/lib/tickets/manager.js | 2 +- src/listeners/client/messageCreate.js | 2 + 7 files changed, 115 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index ef2dc3b..63965c1 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ SUPER= https://www.prisma.io/docs/reference/database-reference/supported-databases -![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create, slash/force-close and slash/move +![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create, slash/force-close, slash/claim, slash/release, and slash/move menu question max length cannot be higher than question options +- TODO: topic and question answer values not encrypted? - TODO: post stats - TODO: settings bundle download - TODO: update notifications @@ -29,4 +30,4 @@ creation requires an interaction: - message:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create - DM -> guild? -> category? -> topic or questions -> create - panel(interaction) -> topic or questions -> create -- panel(message) -> DM (channel fallback) button -> topic or questions -> create \ No newline at end of file +- ~~panel(message) -> DM (channel fallback) button -> topic or questions -> create~~ \ No newline at end of file diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index f4fc83a..3c07cf7 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -20,16 +20,16 @@ model ArchivedChannel { } model ArchivedMessage { - author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) - authorId String @db.VarChar(19) - content String @db.Text - createdAt DateTime @default(now()) - deleted Boolean @default(false) - edited Boolean @default(false) - id String @id @db.VarChar(19) - referencedBy Ticket[] @relation("MessageReferencedByTicket") - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) + authorId String @db.VarChar(19) + content Json + createdAt DateTime @default(now()) + deleted Boolean @default(false) + edited Boolean @default(false) + external Boolean @default(false) + id String @id @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) @@map("archivedMessages") } @@ -68,30 +68,30 @@ model ArchivedUser { } model Category { - channelName String - claiming Boolean @default(false) - createdAt DateTime @default(now()) - cooldown Int? - customTopic String? - description String - discordCategory String @db.VarChar(19) - emoji String - enableFeedback Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) - guildId String @db.VarChar(19) - id Int @id @default(autoincrement()) - image String? - memberLimit Int @default(1) - name String - openingMessage String @db.Text - pingRoles Json @default("[]") - questions Question[] - ratelimit Int? - requiredRoles Json @default("[]") - requireTopic Boolean @default(false) - staffRoles Json - tickets Ticket[] - totalLimit Int @default(50) + channelName String + claiming Boolean @default(false) + createdAt DateTime @default(now()) + cooldown Int? + customTopic String? + description String + discordCategory String @db.VarChar(19) + emoji String + enableFeedback Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + image String? + memberLimit Int @default(1) + name String + openingMessage String @db.Text + pingRoles Json @default("[]") + questions Question[] + ratelimit Int? + requiredRoles Json @default("[]") + requireTopic Boolean @default(false) + staffRoles Json + tickets Ticket[] + totalLimit Int @default(50) @@map("categories") } @@ -210,11 +210,10 @@ model Ticket { messageCount Int? number Int open Boolean @default(true) - openingMessage String @db.VarChar(19) - pinnedMessages Json @default("[]") + openingMessageId String @db.VarChar(19) + pinnedMessageIds Json @default("[]") priority TicketPriority? referencedBy Ticket[] @relation("TicketsReferencedByTicket") - referencesMessage ArchivedMessage? @relation(name: "MessageReferencedByTicket", fields: [referencesMessageId], references: [id], onDelete: SetNull) referencesMessageId String? @db.VarChar(19) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? @db.VarChar(19) diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index d33a429..faf816a 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -20,16 +20,16 @@ model ArchivedChannel { } model ArchivedMessage { - author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) - authorId String @db.VarChar(19) - content String @db.Text - createdAt DateTime @default(now()) - deleted Boolean @default(false) - edited Boolean @default(false) - id String @id @db.VarChar(19) - referencedBy Ticket[] @relation("MessageReferencedByTicket") - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String @db.VarChar(19) + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) + authorId String @db.VarChar(19) + content Json + createdAt DateTime @default(now()) + deleted Boolean @default(false) + edited Boolean @default(false) + external Boolean @default(false) + id String @id @db.VarChar(19) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @db.VarChar(19) @@map("archivedMessages") } @@ -68,30 +68,30 @@ model ArchivedUser { } model Category { - channelName String - claiming Boolean @default(false) - createdAt DateTime @default(now()) - cooldown Int? - customTopic String? - description String - discordCategory String @db.VarChar(19) - emoji String - enableFeedback Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) - guildId String @db.VarChar(19) - id Int @id @default(autoincrement()) - image String? - memberLimit Int @default(1) - name String - openingMessage String @db.Text - pingRoles Json @default("[]") - questions Question[] - ratelimit Int? - requiredRoles Json @default("[]") - requireTopic Boolean @default(false) - staffRoles Json - tickets Ticket[] - totalLimit Int @default(50) + channelName String + claiming Boolean @default(false) + createdAt DateTime @default(now()) + cooldown Int? + customTopic String? + description String + discordCategory String @db.VarChar(19) + emoji String + enableFeedback Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + image String? + memberLimit Int @default(1) + name String + openingMessage String @db.Text + pingRoles Json @default("[]") + questions Question[] + ratelimit Int? + requiredRoles Json @default("[]") + requireTopic Boolean @default(false) + staffRoles Json + tickets Ticket[] + totalLimit Int @default(50) @@map("categories") } @@ -210,11 +210,10 @@ model Ticket { messageCount Int? number Int open Boolean @default(true) - openingMessage String @db.VarChar(19) - pinnedMessages Json @default("[]") + openingMessageId String @db.VarChar(19) + pinnedMessageIds Json @default("[]") priority TicketPriority? referencedBy Ticket[] @relation("TicketsReferencedByTicket") - referencesMessage ArchivedMessage? @relation(name: "MessageReferencedByTicket", fields: [referencesMessageId], references: [id], onDelete: SetNull) referencesMessageId String? @db.VarChar(19) referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? @db.VarChar(19) diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma index eb4e180..6213988 100644 --- a/db/sqlite/schema.prisma +++ b/db/sqlite/schema.prisma @@ -20,16 +20,16 @@ model ArchivedChannel { } model ArchivedMessage { - author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) - authorId String - content String - createdAt DateTime @default(now()) - deleted Boolean @default(false) - edited Boolean @default(false) - id String @id - referencedBy Ticket[] @relation("MessageReferencedByTicket") - ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) - ticketId String + author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) + authorId String + content String + createdAt DateTime @default(now()) + deleted Boolean @default(false) + edited Boolean @default(false) + id String @id + external Boolean @default(false) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) + ticketId String @@map("archivedMessages") } @@ -68,30 +68,30 @@ model ArchivedUser { } model Category { - channelName String - claiming Boolean @default(false) - createdAt DateTime @default(now()) - cooldown Int? - customTopic String? - description String - discordCategory String - emoji String - enableFeedback Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) - guildId String - id Int @id @default(autoincrement()) - image String? - memberLimit Int @default(1) - name String - openingMessage String - pingRoles String @default("[]") - questions Question[] - ratelimit Int? - requiredRoles String @default("[]") - requireTopic Boolean @default(false) - staffRoles String - tickets Ticket[] - totalLimit Int @default(50) + channelName String + claiming Boolean @default(false) + createdAt DateTime @default(now()) + cooldown Int? + customTopic String? + description String + discordCategory String + emoji String + enableFeedback Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String + id Int @id @default(autoincrement()) + image String? + memberLimit Int @default(1) + name String + openingMessage String + pingRoles String @default("[]") + questions Question[] + ratelimit Int? + requiredRoles String @default("[]") + requireTopic Boolean @default(false) + staffRoles String + tickets Ticket[] + totalLimit Int @default(50) @@map("categories") } @@ -210,11 +210,10 @@ model Ticket { messageCount Int? number Int open Boolean @default(true) - openingMessage String - pinnedMessages String @default("[]") + openingMessageId String + pinnedMessageIds String @default("[]") priority String? referencedBy Ticket[] @relation("TicketsReferencedByTicket") - referencesMessage ArchivedMessage? @relation(name: "MessageReferencedByTicket", fields: [referencesMessageId], references: [id], onDelete: SetNull) referencesMessageId String? referencesTicket Ticket? @relation(name: "TicketsReferencedByTicket", fields: [referencesTicketId], references: [id], onDelete: SetNull) referencesTicketId String? diff --git a/src/lib/middleware/prisma-sqlite.js b/src/lib/middleware/prisma-sqlite.js index 8af1ca1..7606ee4 100644 --- a/src/lib/middleware/prisma-sqlite.js +++ b/src/lib/middleware/prisma-sqlite.js @@ -6,7 +6,7 @@ const jsonFields = [ 'blocklist', 'workingHours', 'options', - 'pinnedMessages', + 'pinnedMessageIds', ]; const traverse = (obj, action) => { diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 7256c80..626eae9 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -371,7 +371,7 @@ module.exports = class TicketManager { guild: { connect: { id: category.guild.id } }, id: channel.id, number, - openingMessage: sent.id, + openingMessageId: sent.id, topic, }; if (referencesTicket) data.referencesTicket = { connect: { id: referencesTicket } }; diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index f4b7012..7942cfa 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -179,7 +179,9 @@ module.exports = class extends Listener { } else { // TODO: archive messages in tickets // TODO: first response + // TODO: lastMessageAt // TODO: auto tag + // TODO: staff status alert, working hours alerts } } }; From 80399cd2940caa03b6bd59bbc646a8724bf77991 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 8 Aug 2022 23:36:45 +0100 Subject: [PATCH 086/409] Fix Discord's UI problem --- src/menus/create.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/menus/create.js b/src/menus/create.js index 887068e..e0b282d 100644 --- a/src/menus/create.js +++ b/src/menus/create.js @@ -13,6 +13,7 @@ module.exports = class CreateMenu extends Menu { * @param {import("discord.js").SelectMenuInteraction} interaction */ async run(id, interaction) { + interaction.message.edit({ components: interaction.message.components }); // reset the select menu (minor client-side UI issue) await this.client.tickets.create({ categoryId: interaction.values[0], interaction, From 25d70630a7fd6c9f1cae957dcb8b8f503a1f750b Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 8 Aug 2022 23:37:47 +0100 Subject: [PATCH 087/409] Add ticket logging & minor fixes --- src/i18n/en-GB.yml | 8 ++++- src/lib/logging.js | 71 +++++++++++++++++++++++++++++++++++--- src/lib/tickets/manager.js | 24 +++++++++---- 3 files changed, 90 insertions(+), 13 deletions(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index da77e06..4ca9725 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -160,7 +160,13 @@ log: create: created delete: deleted update: updated - tickets: + ticket: + description: '{user} {verb} ticket' + ticket: Ticket + title: Ticket {verb} + verb: + create: created + close: closed menus: category: placeholder: Select a ticket category diff --git a/src/lib/logging.js b/src/lib/logging.js index 8289cf9..7140b3a 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -60,8 +60,6 @@ async function getLogChannel(client, guildId) { async function logAdminEvent(client, { guildId, userId, action, target, diff, }) { - const user = await client.users.fetch(userId); - client.log.info.settings(`${user.tag} ${action}d ${target.type} ${target.id}`); const settings = await client.prisma.guild.findUnique({ select: { footer: true, @@ -70,6 +68,10 @@ async function logAdminEvent(client, { }, where: { id: guildId }, }); + /** @type {import("discord.js").Guild} */ + const guild = client.guilds.cache.get(guildId); + const member = await guild.members.fetch(userId); + client.log.info.settings(`${member.user.tag} ${action}d ${target.type} ${target.id}`); if (!settings.logChannel) return; const colour = action === 'create' ? 'Green' : action === 'update' @@ -77,7 +79,7 @@ async function logAdminEvent(client, { ? 'Red' : 'Default'; const getMessage = client.i18n.getLocale(settings.locale); const i18nOptions = { - user: `<@${user.id}>`, + user: `<@${member.user.id}>`, verb: getMessage(`log.admin.verb.${action}`), }; const channel = client.channels.cache.get(settings.logChannel); @@ -86,8 +88,8 @@ async function logAdminEvent(client, { new EmbedBuilder() .setColor(colour) .setAuthor({ - iconURL: user.avatarURL(), - name: user.username, + iconURL: member.displayAvatarURL(), + name: member.displayName, }) .setTitle(getMessage('log.admin.title.joined', { ...i18nOptions, @@ -119,7 +121,66 @@ async function logAdminEvent(client, { return await channel.send({ embeds }); } +/** + * @param {import("client")} client + * @param {object} details + * @param {string} details.guildId + * @param {string} details.userId + * @param {string} details.action +*/ +async function logTicketEvent(client, { + userId, action, target, +}) { + const ticket = await client.prisma.ticket.findUnique({ + include: { guild: true }, + where: { id: target.id }, + }); + if (!ticket) return; + /** @type {import("discord.js").Guild} */ + const guild = client.guilds.cache.get(ticket.guild.id); + const member = await guild.members.fetch(userId); + client.log.info.tickets(`${member.user.tag} ${action}d ticket ${target.id}`); + if (!ticket.guild.logChannel) return; + const colour = action === 'create' + ? 'Aqua' : action === 'close' + ? 'DarkAqua' : action === 'claim' + ? 'LuminousVividPink' : action === 'unclaim' + ? 'DarkVividPink' : 'Default'; + const getMessage = client.i18n.getLocale(ticket.guild.locale); + const i18nOptions = { + user: `<@${member.user.id}>`, + verb: getMessage(`log.ticket.verb.${action}`), + }; + const channel = client.channels.cache.get(ticket.guild.logChannel); + if (!channel) return; + const embeds = [ + new EmbedBuilder() + .setColor(colour) + .setAuthor({ + iconURL: member.displayAvatarURL(), + name: member.displayName, + }) + .setTitle(getMessage('log.ticket.title', { + ...i18nOptions, + verb: getMessage(`log.ticket.verb.${action}`), + })) + .setDescription(getMessage('log.ticket.description', { + ...i18nOptions, + verb: getMessage(`log.ticket.verb.${action}`), + })) + .addFields([ + { + name: getMessage('log.ticket.ticket'), + value: target.name ?? target.id, + }, + ]), + ]; + + return await channel.send({ embeds }); +} + module.exports = { getLogChannel, logAdminEvent, + logTicketEvent, }; \ No newline at end of file diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 626eae9..7a5194d 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1,6 +1,7 @@ /* eslint-disable max-lines */ const { ActionRowBuilder, + ButtonBuilder, ButtonStyle, ModalBuilder, SelectMenuBuilder, @@ -11,7 +12,7 @@ const { const emoji = require('node-emoji'); const ms = require('ms'); const ExtendedEmbedBuilder = require('../embed'); -const { ButtonBuilder } = require('discord.js'); +const { logTicketEvent } = require('../logging'); /** * @typedef {import('@prisma/client').Category & {guild: import('@prisma/client').Guild} & {questions: import('@prisma/client').Question[]}} CategoryGuildQuestions @@ -274,10 +275,12 @@ module.exports = class TicketManager { new ExtendedEmbedBuilder() .setColor(category.guild.primaryColour) .setFields( - category.questions.map(q => ({ - name: q.label, - value: interaction.fields.getTextInputValue(q.id) || getMessage('ticket.answers.no_value'), - })), + category.questions + .sort((a, b) => a.order - b.order) + .map(q => ({ + name: q.label, + value: interaction.fields.getTextInputValue(q.id) || getMessage('ticket.answers.no_value'), + })), ), ); // embeds[0].setFields( @@ -342,7 +345,7 @@ module.exports = class TicketManager { const pings = category.pingRoles.map(r => `<@&${r}>`).join(' '); const sent = await channel.send({ - components: components.components.length >=1 ? [components] : [], + components: components.components.length >= 1 ? [components] : [], content: getMessage('ticket.opening_message.content', { creator: interaction.user.toString(), staff: pings ? pings + ',' : '', @@ -392,6 +395,13 @@ module.exports = class TicketManager { .setDescription(getMessage('ticket.created.description', { channel: channel.toString() })), ], }); - // TODO: log channel + await logTicketEvent(this.client, { // FIXME: remove await + action: 'create', + target: { + id: ticket.id, + name: channel.toString(), + }, + userId: interaction.user.id, + }); } }; \ No newline at end of file From 5ed394f5c2c97ffd27196cd75252e852658384a9 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 8 Aug 2022 23:43:22 +0100 Subject: [PATCH 088/409] fix --- src/i18n/en-GB.yml | 2 ++ src/lib/logging.js | 2 +- src/lib/tickets/manager.js | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 4ca9725..2780f2e 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -166,7 +166,9 @@ log: title: Ticket {verb} verb: create: created + claim: claimed close: closed + unclaim: released menus: category: placeholder: Select a ticket category diff --git a/src/lib/logging.js b/src/lib/logging.js index 7140b3a..1cf2e23 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -139,7 +139,7 @@ async function logTicketEvent(client, { /** @type {import("discord.js").Guild} */ const guild = client.guilds.cache.get(ticket.guild.id); const member = await guild.members.fetch(userId); - client.log.info.tickets(`${member.user.tag} ${action}d ticket ${target.id}`); + client.log.info.tickets(`${member.user.tag} ${client.i18n.getMessage('en-GB', `log.ticket.verb.${action}`)} ticket ${target.id}`); if (!ticket.guild.logChannel) return; const colour = action === 'create' ? 'Aqua' : action === 'close' diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 7a5194d..a46ae47 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -383,7 +383,7 @@ module.exports = class TicketManager { if (message) data.referencesMessage = { connect: { id: referencesMessage } }; // only add if the message has been archived ^^ if (answers) data.questionAnswers = { createMany: { data: answers } }; const ticket = await this.client.prisma.ticket.create({ data }); - interaction.editReply({ + await interaction.editReply({ components: [], embeds: [ new ExtendedEmbedBuilder({ @@ -395,7 +395,7 @@ module.exports = class TicketManager { .setDescription(getMessage('ticket.created.description', { channel: channel.toString() })), ], }); - await logTicketEvent(this.client, { // FIXME: remove await + await logTicketEvent(this.client, { action: 'create', target: { id: ticket.id, From 64266d41f7ce595450bc98aba0dfe18e025f0d1f Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 8 Aug 2022 23:50:05 +0100 Subject: [PATCH 089/409] :) --- user/example.config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user/example.config.yml b/user/example.config.yml index dd83e36..f549d7a 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -28,7 +28,7 @@ presence: type: 3 # https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityType - name: '{avgResponseTime} response time' type: 3 - interval: 60 # seconds, only used if activities.length > 1 + interval: 20 # seconds, only used if activities.length > 1 status: online # online|idle|invisible|dnd overrides: disableArchives: false \ No newline at end of file From 53f00b715bc158c3cb8549fc7a2bce8213fd56f4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 9 Aug 2022 00:09:28 +0100 Subject: [PATCH 090/409] Implement blocklist --- src/i18n/en-GB.yml | 3 +++ src/lib/tickets/manager.js | 29 +++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 2780f2e..b046dd2 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -175,6 +175,9 @@ menus: guild: placeholder: Select a server misc: + blocked: + description: You are not allowed to create tickets. + title: ❌ Blocked no_categories: description: No ticket categories have been configured. title: ❌ There are no ticket categories diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index a46ae47..a3816f7 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -92,7 +92,28 @@ module.exports = class TicketManager { this.client.keyv.set(rlKey, true, ms('10s')); } - // TODO: if blacklisted role -> stop + /** @type {import("discord.js").Guild} */ + const guild = this.client.guilds.cache.get(category.guild.id); + const member = interaction.member ?? await guild.members.fetch(interaction.user.id); + + if (category.guild.blocklist.length !== 0) { + const blocked = category.guild.blocklist.some(r => member.roles.cache.has(r)); + if (blocked) { + return await interaction.reply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: category.guild.footer, + }) + .setColor(category.guild.errorColour) + .setTitle(getMessage('misc.blocked.title')) + .setDescription(getMessage('misc.blocked.description')), + ], + ephemeral: true, + }); + } + + } // TODO: if member !required roles -> stop @@ -382,8 +403,7 @@ module.exports = class TicketManager { if (referencesMessage) message = this.client.prisma.archivedMessage.findUnique({ where: { id: referencesMessage } }); if (message) data.referencesMessage = { connect: { id: referencesMessage } }; // only add if the message has been archived ^^ if (answers) data.questionAnswers = { createMany: { data: answers } }; - const ticket = await this.client.prisma.ticket.create({ data }); - await interaction.editReply({ + interaction.editReply({ components: [], embeds: [ new ExtendedEmbedBuilder({ @@ -395,7 +415,8 @@ module.exports = class TicketManager { .setDescription(getMessage('ticket.created.description', { channel: channel.toString() })), ], }); - await logTicketEvent(this.client, { + const ticket = await this.client.prisma.ticket.create({ data }); + logTicketEvent(this.client, { action: 'create', target: { id: ticket.id, From 913465a165971da6f79bafe684d6464aa3224de0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 9 Aug 2022 00:21:15 +0100 Subject: [PATCH 091/409] Implement required roles --- src/i18n/en-GB.yml | 5 ++++- src/lib/tickets/manager.js | 19 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index b046dd2..cb608e4 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -161,7 +161,7 @@ log: delete: deleted update: updated ticket: - description: '{user} {verb} ticket' + description: '{user} {verb} a ticket' ticket: Ticket title: Ticket {verb} verb: @@ -178,6 +178,9 @@ misc: blocked: description: You are not allowed to create tickets. title: ❌ Blocked + missing_roles: + description: You do not have the roles required to be able to create a ticket in this category. + title: ❌ Insufficient roles no_categories: description: No ticket categories have been configured. title: ❌ There are no ticket categories diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index a3816f7..89c3bb1 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -112,10 +112,25 @@ module.exports = class TicketManager { ephemeral: true, }); } - } - // TODO: if member !required roles -> stop + if (category.requiredRoles.length !== 0) { + const missing = category.requiredRoles.some(r => !member.roles.cache.has(r)); + if (missing) { + return await interaction.reply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: category.guild.footer, + }) + .setColor(category.guild.errorColour) + .setTitle(getMessage('misc.missing_roles.title')) + .setDescription(getMessage('misc.missing_roles.description')), + ], + ephemeral: true, + }); + } + } // TODO: if discordCategory has 50 channels -> stop From 19920724932f0b6c688f732db8d7e919b114ba4e Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 9 Aug 2022 01:19:03 +0100 Subject: [PATCH 092/409] Add other checks --- src/i18n/en-GB.yml | 15 ++++++ src/lib/tickets/manager.js | 105 +++++++++++++++++++++++++------------ 2 files changed, 87 insertions(+), 33 deletions(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index cb608e4..0e376ba 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -178,6 +178,21 @@ misc: blocked: description: You are not allowed to create tickets. title: ❌ Blocked + category_full: + description: | + The category has reached its maximum capacity. + Please try again later. + title: ❌ Category full + cooldown: + description: Please wait {time} before creating another ticket in this category. + title: ❌ Please wait + member_limit: + description: + - Please use your existing ticket or close it before creating another. + - Please close a ticket before creating another. + title: + - ❌ You already have a ticket + - ❌ You already have %d open tickets missing_roles: description: You do not have the roles required to be able to create a ticket in this category. title: ❌ Insufficient roles diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 89c3bb1..b9d2ae7 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -89,56 +89,95 @@ module.exports = class TicketManager { ephemeral: true, }); } else { - this.client.keyv.set(rlKey, true, ms('10s')); + this.client.keyv.set(rlKey, true, ms('5s')); } + const sendError = name => interaction.reply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: category.guild.footer, + }) + .setColor(category.guild.errorColour) + .setTitle(getMessage(`misc.${name}.title`)) + .setDescription(getMessage(`misc.${name}.description`)), + ], + ephemeral: true, + }); + /** @type {import("discord.js").Guild} */ const guild = this.client.guilds.cache.get(category.guild.id); const member = interaction.member ?? await guild.members.fetch(interaction.user.id); if (category.guild.blocklist.length !== 0) { const blocked = category.guild.blocklist.some(r => member.roles.cache.has(r)); - if (blocked) { - return await interaction.reply({ - embeds: [ - new ExtendedEmbedBuilder({ - iconURL: interaction.guild.iconURL(), - text: category.guild.footer, - }) - .setColor(category.guild.errorColour) - .setTitle(getMessage('misc.blocked.title')) - .setDescription(getMessage('misc.blocked.description')), - ], - ephemeral: true, - }); - } + if (blocked) return await sendError('blocked'); } if (category.requiredRoles.length !== 0) { const missing = category.requiredRoles.some(r => !member.roles.cache.has(r)); - if (missing) { - return await interaction.reply({ - embeds: [ - new ExtendedEmbedBuilder({ - iconURL: interaction.guild.iconURL(), - text: category.guild.footer, - }) - .setColor(category.guild.errorColour) - .setTitle(getMessage('misc.missing_roles.title')) - .setDescription(getMessage('misc.missing_roles.description')), - ], - ephemeral: true, - }); - } + if (missing) return await sendError('missing_roles'); } - // TODO: if discordCategory has 50 channels -> stop + const discordCategory = guild.channels.cache.get(category.discordCategory); + if (discordCategory.children.cache.size === 50) return await sendError('category_full'); - // TODO: if category has max channels -> stop + // TODO: store locally and sync regularly so this isn't done during an interaction? + const totalCount = await this.client.prisma.ticket.count({ + where: { + categoryId: category.id, + open: true, + }, + }); + if (totalCount >= category.totalLimit) return await sendError('category_full'); - // TODO: if member has max -> stop + const memberCount = await this.client.prisma.ticket.count({ + where: { + categoryId: category.id, + createdById: interaction.user.id, + open: true, + }, + }); - // TODO: if cooldown -> stop + if (memberCount >= category.memberLimit) { + return await interaction.reply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: category.guild.footer, + }) + .setColor(category.guild.errorColour) + .setTitle(getMessage('misc.member_limit.title', memberCount, memberCount)) + .setDescription(getMessage('misc.member_limit.description', memberCount)), + ], + ephemeral: true, + }); + } + + const lastTicket = await this.client.prisma.ticket.findFirst({ + orderBy: [{ closedAt: 'desc' }], + select: { closedAt: true }, + where: { + categoryId: category.id, + createdById: interaction.user.id, + open: false, + }, + }); + + if (Date.now() - lastTicket.closedAt < category.cooldown) { + return await interaction.reply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: category.guild.footer, + }) + .setColor(category.guild.errorColour) + .setTitle(getMessage('misc.cooldown.title')) + .setDescription(getMessage('misc.cooldown.description', { time: ms(category.cooldown - (Date.now() - lastTicket.closedAt)) })), + ], + ephemeral: true, + }); + } if (category.questions.length >= 1) { await interaction.showModal( From 0bc02e808a3ec171d42d6a4a7164a97bcd15c64f Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 9 Aug 2022 22:47:24 +0100 Subject: [PATCH 093/409] Use SQL sort --- src/lib/tickets/manager.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index b9d2ae7..fa21080 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -40,7 +40,7 @@ module.exports = class TicketManager { category = await this.client.prisma.category.findUnique({ include: { guild: true, - questions: true, + questions: { orderBy: { order: 'asc' } }, }, where: { id: Number(categoryId) }, }); @@ -192,7 +192,6 @@ module.exports = class TicketManager { .setComponents( category.questions .filter(q => q.type === 'TEXT') // TODO: remove this when modals support select menus - .sort((a, b) => a.order - b.order) .map(q => { if (q.type === 'TEXT') { return new ActionRowBuilder() @@ -351,7 +350,6 @@ module.exports = class TicketManager { .setColor(category.guild.primaryColour) .setFields( category.questions - .sort((a, b) => a.order - b.order) .map(q => ({ name: q.label, value: interaction.fields.getTextInputValue(q.id) || getMessage('ticket.answers.no_value'), From c6abefe30d3c493c7ba89bebb9180ef13473f0d1 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 10 Aug 2022 19:50:19 +0100 Subject: [PATCH 094/409] Rename `move` command to `transfer` --- src/commands/slash/{move.js => trasnfer.js} | 15 +++++++++------ src/i18n/en-GB.yml | 14 +++++++------- 2 files changed, 16 insertions(+), 13 deletions(-) rename src/commands/slash/{move.js => trasnfer.js} (67%) diff --git a/src/commands/slash/move.js b/src/commands/slash/trasnfer.js similarity index 67% rename from src/commands/slash/move.js rename to src/commands/slash/trasnfer.js index 6867570..71a8712 100644 --- a/src/commands/slash/move.js +++ b/src/commands/slash/trasnfer.js @@ -1,13 +1,13 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); -module.exports = class MoveSlashCommand extends SlashCommand { +module.exports = class TransferSlashCommand extends SlashCommand { constructor(client, options) { const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.move.description'))); + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transfer.description'))); const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.move.name'))); + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transfer.name'))); let opts = [ { @@ -19,10 +19,10 @@ module.exports = class MoveSlashCommand extends SlashCommand { ]; opts = opts.map(o => { const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.move.options.${o.name}.description`))); + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transfer.options.${o.name}.description`))); const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.move.options.${o.name}.name`))); + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transfer.options.${o.name}.name`))); return { ...o, @@ -43,5 +43,8 @@ module.exports = class MoveSlashCommand extends SlashCommand { }); } - async run(interaction) { } + async run(interaction) { + // TODO: check discordCategory max but not category max (ignore) + // TODO: update cached count for both categories and category-members (from and to) + } }; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 0e376ba..966afd3 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -69,13 +69,6 @@ commands: time: description: Close all tickets that have been inactive for the specific time name: time - move: - description: Move a ticket to another category - name: move - options: - category: - description: The category to move the ticket to - name: category new: description: Create a new ticket name: new @@ -131,6 +124,13 @@ commands: ticket: description: The number of the ticket to get the transcript of name: ticket + transfer: + description: Transfer a ticket to another category + name: transfer + options: + category: + description: The category to transfer the ticket to + name: category user: create: name: Create a ticket for user From a190c1ac277d59efa418856608ce1fe514318a45 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 11 Aug 2022 22:59:50 +0100 Subject: [PATCH 095/409] Finish pre-open checks, reduce database reads with more caching --- README.md | 16 ++- src/i18n/en-GB.yml | 5 + src/lib/tickets/manager.js | 237 ++++++++++++++++++++++------------ src/listeners/client/ready.js | 75 +++++++++-- 4 files changed, 237 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 63965c1..897d855 100644 --- a/README.md +++ b/README.md @@ -30,4 +30,18 @@ creation requires an interaction: - message:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create - DM -> guild? -> category? -> topic or questions -> create - panel(interaction) -> topic or questions -> create -- ~~panel(message) -> DM (channel fallback) button -> topic or questions -> create~~ \ No newline at end of file +- ~~panel(message) -> DM (channel fallback) button -> topic or questions -> create~~ + +> **Note** +> +> test + +> **Warning** +> +> test + + diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 966afd3..4009a7d 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -186,6 +186,11 @@ misc: cooldown: description: Please wait {time} before creating another ticket in this category. title: ❌ Please wait + error: + description: Sorry, an unexpected error occurred. + fields: + identifier: Identifier + title: ⚠️ Something's wrong member_limit: description: - Please use your existing ticket or close it before creating another. diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index fa21080..5c43597 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -3,6 +3,7 @@ const { ActionRowBuilder, ButtonBuilder, ButtonStyle, + inlineCode, ModalBuilder, SelectMenuBuilder, SelectMenuOptionBuilder, @@ -15,12 +16,73 @@ const ExtendedEmbedBuilder = require('../embed'); const { logTicketEvent } = require('../logging'); /** - * @typedef {import('@prisma/client').Category & {guild: import('@prisma/client').Guild} & {questions: import('@prisma/client').Question[]}} CategoryGuildQuestions + * @typedef {import('@prisma/client').Category & + * {guild: import('@prisma/client').Guild} & + * {questions: import('@prisma/client').Question[]}} CategoryGuildQuestions */ module.exports = class TicketManager { constructor(client) { /** @type {import("client")} */ this.client = client; + + this.$ = { categories: {} }; + } + + async getCategory(categoryId) { + const cacheKey = `cache/category+guild+questions:${categoryId}`; + /** @type {CategoryGuildQuestions} */ + let category = await this.client.keyv.get(cacheKey); + if (!category) { + category = await this.client.prisma.category.findUnique({ + include: { + guild: true, + questions: { orderBy: { order: 'asc' } }, + }, + where: { id: categoryId }, + }); + this.client.keyv.set(cacheKey, category, ms('5m')); + } + return category; + } + + // TODO: update when a ticket is closed or moved + async getTotalCount(categoryId) { + const category = this.$.categories[categoryId]; + if (!category) this.$.categories[categoryId] = {}; + let count = this.$.categories[categoryId].total; + if (!count) { + count = await this.client.prisma.ticket.count({ + where: { + categoryId, + open: true, + }, + }); + this.$.categories[categoryId].total = count; + } + return count; + } + + // TODO: update when a ticket is closed or moved + async getMemberCount(categoryId, memberId) { + const category = this.$.categories[categoryId]; + if (!category) this.$.categories[categoryId] = {}; + let count = this.$.categories[categoryId][memberId]; + if (!count) { + count = await this.client.prisma.ticket.count({ + where: { + categoryId: categoryId, + createdById: memberId, + open: true, + }, + }); + this.$.categories[categoryId][memberId] = count; + } + return count; + } + + async getCooldown(categoryId, memberId) { + const cacheKey = `cooldowns/category-member:${categoryId}-${memberId}`; + return await this.client.keyv.get(cacheKey); } /** @@ -33,42 +95,31 @@ module.exports = class TicketManager { categoryId, interaction, topic, referencesMessage, referencesTicket, }) { categoryId = Number(categoryId); - const cacheKey = `cache/category+guild+questions:${categoryId}`; - /** @type {CategoryGuildQuestions} */ - let category = await this.client.keyv.get(cacheKey); + const category = await this.getCategory(categoryId); + if (!category) { - category = await this.client.prisma.category.findUnique({ - include: { - guild: true, - questions: { orderBy: { order: 'asc' } }, - }, - where: { id: Number(categoryId) }, - }); - if (!category) { - let settings; - if (interaction.guild) { - settings = await this.client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); - } else { - settings = { - errorColour: 'Red', - locale: 'en-GB', - }; - } - const getMessage = this.client.i18n.getLocale(settings.locale); - return await interaction.reply({ - embeds: [ - new ExtendedEmbedBuilder({ - iconURL: interaction.guild?.iconURL(), - text: settings.footer, - }) - .setColor(settings.errorColour) - .setTitle(getMessage('misc.unknown_category.title')) - .setDescription(getMessage('misc.unknown_category.description')), - ], - ephemeral: true, - }); + let settings; + if (interaction.guild) { + settings = await this.client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + } else { + settings = { + errorColour: 'Red', + locale: 'en-GB', + }; } - this.client.keyv.set(cacheKey, category, ms('5m')); + const getMessage = this.client.i18n.getLocale(settings.locale); + return await interaction.reply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild?.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.unknown_category.title')) + .setDescription(getMessage('misc.unknown_category.description')), + ], + ephemeral: true, + }); } const getMessage = this.client.i18n.getLocale(category.guild.locale); @@ -122,23 +173,10 @@ module.exports = class TicketManager { const discordCategory = guild.channels.cache.get(category.discordCategory); if (discordCategory.children.cache.size === 50) return await sendError('category_full'); - // TODO: store locally and sync regularly so this isn't done during an interaction? - const totalCount = await this.client.prisma.ticket.count({ - where: { - categoryId: category.id, - open: true, - }, - }); + const totalCount = await this.getTotalCount(category.id); if (totalCount >= category.totalLimit) return await sendError('category_full'); - const memberCount = await this.client.prisma.ticket.count({ - where: { - categoryId: category.id, - createdById: interaction.user.id, - open: true, - }, - }); - + const memberCount = await this.getMemberCount(category.id, interaction.user.id); if (memberCount >= category.memberLimit) { return await interaction.reply({ embeds: [ @@ -154,17 +192,33 @@ module.exports = class TicketManager { }); } - const lastTicket = await this.client.prisma.ticket.findFirst({ - orderBy: [{ closedAt: 'desc' }], - select: { closedAt: true }, - where: { - categoryId: category.id, - createdById: interaction.user.id, - open: false, - }, - }); + // const lastTicket = await this.client.prisma.ticket.findFirst({ + // orderBy: [{ closedAt: 'desc' }], + // select: { closedAt: true }, + // where: { + // categoryId: category.id, + // createdById: interaction.user.id, + // open: false, + // }, + // }); - if (Date.now() - lastTicket.closedAt < category.cooldown) { + // if (Date.now() - lastTicket.closedAt < category.cooldown) { + // return await interaction.reply({ + // embeds: [ + // new ExtendedEmbedBuilder({ + // iconURL: interaction.guild.iconURL(), + // text: category.guild.footer, + // }) + // .setColor(category.guild.errorColour) + // .setTitle(getMessage('misc.cooldown.title')) + // .setDescription(getMessage('misc.cooldown.description', { time: ms(category.cooldown - (Date.now() - lastTicket.closedAt)) })), + // ], + // ephemeral: true, + // }); + // } + + const cooldown = await this.getCooldown(category.id, interaction.member.id); + if (cooldown) { return await interaction.reply({ embeds: [ new ExtendedEmbedBuilder({ @@ -173,7 +227,7 @@ module.exports = class TicketManager { }) .setColor(category.guild.errorColour) .setTitle(getMessage('misc.cooldown.title')) - .setDescription(getMessage('misc.cooldown.description', { time: ms(category.cooldown - (Date.now() - lastTicket.closedAt)) })), + .setDescription(getMessage('misc.cooldown.description', { time: ms(cooldown - Date.now()) })), ], ephemeral: true, }); @@ -356,12 +410,6 @@ module.exports = class TicketManager { })), ), ); - // embeds[0].setFields( - // category.questions.map(q => ({ - // name: q.label, - // value: interaction.fields.getTextInputValue(q.id) || getMessage('ticket.answers.no_value'), - // })), - // ); } else if (topic) { embeds.push( new ExtendedEmbedBuilder() @@ -371,10 +419,6 @@ module.exports = class TicketManager { value: topic, }), ); - // embeds[0].setFields({ - // name: getMessage('ticket.opening_message.fields.topic'), - // value: topic, - // }); } if (category.guild.footer) { @@ -455,7 +499,7 @@ module.exports = class TicketManager { if (referencesMessage) message = this.client.prisma.archivedMessage.findUnique({ where: { id: referencesMessage } }); if (message) data.referencesMessage = { connect: { id: referencesMessage } }; // only add if the message has been archived ^^ if (answers) data.questionAnswers = { createMany: { data: answers } }; - interaction.editReply({ + await interaction.editReply({ components: [], embeds: [ new ExtendedEmbedBuilder({ @@ -467,14 +511,45 @@ module.exports = class TicketManager { .setDescription(getMessage('ticket.created.description', { channel: channel.toString() })), ], }); - const ticket = await this.client.prisma.ticket.create({ data }); - logTicketEvent(this.client, { - action: 'create', - target: { - id: ticket.id, - name: channel.toString(), - }, - userId: interaction.user.id, - }); + + try { + const ticket = await this.client.prisma.ticket.create({ data }); + this.$.categories[categoryId].total++; + this.$.categories[categoryId][creator.id]++; + + if (category.cooldown) { + const cacheKey = `cooldowns/category-member:${category.id}-${ticket.createdById}`; + const expiresAt = ticket.createdAt.getTime() + category.cooldown; + const TTL = category.cooldown; + await this.client.keyv.set(cacheKey, expiresAt, TTL); + } + + logTicketEvent(this.client, { + action: 'create', + target: { + id: ticket.id, + name: channel.toString(), + }, + userId: interaction.user.id, + }); + } catch (error) { + const ref = require('crypto').randomUUID(); + this.client.log.warn.tickets('An error occurred whilst creating ticket', channel.id); + this.client.log.error.tickets(ref); + this.client.log.error.tickets(error); + await interaction.editReply({ + components: [], + embeds: [ + new ExtendedEmbedBuilder() + .setColor('Orange') + .setTitle(getMessage('misc.error.title')) + .setDescription(getMessage('misc.error.description')) + .addFields({ + name: getMessage('misc.error.fields.identifier'), + value: inlineCode(ref), + }), + ], + }); + } } }; \ No newline at end of file diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 5575f68..cd80e1d 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -11,18 +11,66 @@ module.exports = class extends Listener { }); } - run() { - // process.title = `"[Discord Tickets] ${this.client.user.tag}"`; // too long and gets cut off - process.title = 'tickets'; - this.client.log.success('Connected to Discord as "%s"', this.client.user.tag); + async run() { + /** @type {import("client")} */ + const client = this.client; + // process.title = `"[Discord Tickets] ${client.user.tag}"`; // too long and gets cut off + process.title = 'tickets'; + client.log.success('Connected to Discord as "%s"', client.user.tag); + + // load total number of open tickets + const categories = await client.prisma.category.findMany({ + select: { + cooldown: true, + id: true, + tickets: { + select: { createdById: true }, + where: { open: true }, + }, + }, + }); + let ticketCount = 0; + let cooldowns = 0; + for (const category of categories) { + ticketCount += category.tickets.length; + client.tickets.$.categories[category.id] = { total: category.tickets.length }; + for (const ticket of category.tickets) { + if (client.tickets.$.categories[category.id][ticket.createdById]) client.tickets.$.categories[category.id][ticket.createdById]++; + else client.tickets.$.categories[category.id][ticket.createdById] = 1; + } + if (category.cooldown) { + const recent = await client.prisma.ticket.findMany({ + orderBy: { createdAt: 'asc' }, + select: { + createdAt: true, + createdById: true, + }, + where: { + categoryId: category.id, + createdAt: { gt: new Date(Date.now() - category.cooldown) }, + }, + }); + cooldowns += recent.length; + for (const ticket of recent) { + const cacheKey = `cooldowns/category-member:${category.id}-${ticket.createdById}`; + const expiresAt = ticket.createdAt.getTime() + category.cooldown; + const TTL = expiresAt - Date.now(); + await client.keyv.set(cacheKey, expiresAt, TTL); + } + } + } + // const ticketCount = categories.reduce((total, category) => total + category.tickets.length, 0); + client.log.info(`Cached ticket count of ${categories.length} categories (${ticketCount} open tickets)`); + client.log.info(`Loaded ${cooldowns} active cooldowns`); + + // presence/activity let next = 0; const setPresence = async () => { const cacheKey = 'cache/presence'; - let cached = await this.client.keyv.get(cacheKey); - + let cached = await client.keyv.get(cacheKey); if (!cached) { - const tickets = await this.client.prisma.ticket.findMany({ + const tickets = await client.prisma.ticket.findMany({ select: { createdAt: true, firstResponseAt: true, @@ -35,24 +83,23 @@ module.exports = class extends Listener { openTickets: tickets.length - closedTickets.length, totalTickets: tickets.length, }; - await this.client.keyv.set(cacheKey, cached, ms('15m')); + await client.keyv.set(cacheKey, cached, ms('15m')); } - const activity = this.client.config.presence.activities[next]; + const activity = client.config.presence.activities[next]; activity.name = activity.name .replace(/{+avgResolutionTime}+/gi, cached.avgResolutionTime) .replace(/{+avgResponseTime}+/gi, cached.avgResponseTime) .replace(/{+openTickets}+/gi, cached.openTickets) .replace(/{+totalTickets}+/gi, cached.totalTickets); - this.client.user.setPresence({ + client.user.setPresence({ activities: [activity], - status: this.client.config.presence.status, + status: client.config.presence.status, }); next++; - if (next === this.client.config.presence.activities.length) next = 0; - + if (next === client.config.presence.activities.length) next = 0; }; setPresence(); - if (this.client.config.presence.activities.length > 1) setInterval(() => setPresence(), this.client.config.presence.interval * 1000); + if (client.config.presence.activities.length > 1) setInterval(() => setPresence(), client.config.presence.interval * 1000); } }; From 975d30c26160bd8ebfdef0ca0eeb78cb87baee13 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 12 Aug 2022 23:31:02 +0100 Subject: [PATCH 096/409] =?UTF-8?q?=F0=9F=98=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/client.js | 2 -- src/lib/middleware/prisma-encryption.js | 45 ------------------------- 2 files changed, 47 deletions(-) delete mode 100644 src/lib/middleware/prisma-encryption.js diff --git a/src/client.js b/src/client.js index f058fbb..0e91785 100644 --- a/src/client.js +++ b/src/client.js @@ -9,7 +9,6 @@ const fs = require('fs'); const { join } = require('path'); const YAML = require('yaml'); const TicketManager = require('./lib/tickets/manager'); -const encryptionMiddleware = require('./lib/middleware/prisma-encryption'); const sqliteMiddleware = require('./lib/middleware/prisma-sqlite'); module.exports = class Client extends FrameworkClient { @@ -52,7 +51,6 @@ module.exports = class Client extends FrameworkClient { async login(token) { /** @type {PrismaClient} */ this.prisma = new PrismaClient(); - this.prisma.$use(encryptionMiddleware); if (process.env.DB_PROVIDER === 'sqlite') this.prisma.$use(sqliteMiddleware); this.keyv = new Keyv(); return super.login(token); diff --git a/src/lib/middleware/prisma-encryption.js b/src/lib/middleware/prisma-encryption.js deleted file mode 100644 index 87836e4..0000000 --- a/src/lib/middleware/prisma-encryption.js +++ /dev/null @@ -1,45 +0,0 @@ -const Cryptr = require('cryptr'); -const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); -const encryptedFields = [ - // 'name', - 'content', - 'username', - 'displayName', - // 'channelName', - // 'openingMessage', - // 'description', - 'value', - // 'placeholder', - 'closedReason', - 'topic', - 'comment', - // 'label', - // 'regex', -]; - -const traverse = (obj, action) => { - for (const prop in obj) { - if (encryptedFields.includes(prop) && typeof obj[prop] === 'string' && obj[prop].length !== 0) { - try { - // prevent double encryption bug (from nested writes - notably upserting questions in category update). - // not sure why it happens - if (action === 'ENCRYPT' && cryptr.decrypt(obj[prop])) continue; // don't encrypt if it already encrypted - else obj[prop] = cryptr[action.toLowerCase()](obj[prop]); - } catch { - // do nothing - } - } else if (typeof obj[prop] === 'object') { - obj[prop] = traverse(obj[prop], action); - } - } - return obj; -}; - -module.exports = async (params, next) => { - if (params.args.create) params.args.create = traverse(params.args.create, 'ENCRYPT'); - if (params.args.data) params.args.data = traverse(params.args.data, 'ENCRYPT'); - if (params.args.update) params.args.update = traverse(params.args.update, 'ENCRYPT'); - let result = await next(params); - if (result) result = traverse(result, 'DECRYPT'); - return result; -}; \ No newline at end of file From f6666b103ec9249e211c15dff449489dd5dd2016 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 13 Aug 2022 00:21:03 +0100 Subject: [PATCH 097/409] fix DM --- src/lib/tickets/manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 5c43597..b24f1be 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -217,7 +217,7 @@ module.exports = class TicketManager { // }); // } - const cooldown = await this.getCooldown(category.id, interaction.member.id); + const cooldown = await this.getCooldown(category.id, interaction.user.id); if (cooldown) { return await interaction.reply({ embeds: [ From c64b18a3977fd5e5275185612b1d47a0140582a3 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 13 Aug 2022 23:01:44 +0100 Subject: [PATCH 098/409] Add message and ticket referencing, fixes --- src/autocomplete/references.js | 34 ++++++++- src/commands/message/create.js | 5 ++ src/commands/slash/new.js | 12 +++- src/i18n/en-GB.yml | 16 ++++- src/lib/tickets/archiver.js | 11 +++ src/lib/tickets/manager.js | 99 ++++++++++++++++++++++----- src/lib/tickets/utils.js | 77 +++++++++++++++++++++ src/listeners/client/messageCreate.js | 26 +++---- src/listeners/client/ready.js | 15 +++- src/menus/create.js | 5 +- 10 files changed, 262 insertions(+), 38 deletions(-) create mode 100644 src/lib/tickets/archiver.js create mode 100644 src/lib/tickets/utils.js diff --git a/src/autocomplete/references.js b/src/autocomplete/references.js index 5d3c128..bf80611 100644 --- a/src/autocomplete/references.js +++ b/src/autocomplete/references.js @@ -8,5 +8,37 @@ module.exports = class ReferencesCompleter extends Autocompleter { }); } - async run(value, comamnd, interaction) { } + /** + * @param {string} value + * @param {*} comamnd + * @param {import("discord.js").AutocompleteInteraction} interaction + */ + async run(value, comamnd, interaction) { + /** @type {import("client")} */ + const client = this.client; + const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + const tickets = await client.prisma.ticket.findMany({ + where: { + createdById: interaction.user.id, + guildId: interaction.guild.id, + open: false, + }, + }); + const options = value ? tickets.filter(t => + String(t.number).match(new RegExp(value, 'i')) || + t.topic?.match(new RegExp(value, 'i')) || + new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' })?.match(new RegExp(value, 'i')), + ) : tickets; + await interaction.respond( + options + .slice(0, 25) + .map(t => { + const date = new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' }); + return { + name: `#${t.number} - ${date} ${t.topic ? '| ' + t.topic.substring(0, 50) : ''}`, + value: t.id, + }; + }), + ); + } }; \ No newline at end of file diff --git a/src/commands/message/create.js b/src/commands/message/create.js index ba8f240..cda68bd 100644 --- a/src/commands/message/create.js +++ b/src/commands/message/create.js @@ -1,4 +1,5 @@ const { MessageCommand } = require('@eartharoid/dbf'); +const { useGuild } = require('../../lib/tickets/utils'); module.exports = class CreateMessageCommand extends MessageCommand { constructor(client, options) { @@ -13,7 +14,11 @@ module.exports = class CreateMessageCommand extends MessageCommand { }); } + /** + * @param {import("discord.js").MessageContextMenuCommandInteraction} interaction + */ async run(interaction) { // TODO: archive message + await useGuild(this.client, interaction, { referencesMessage: interaction.targetMessage.channelId + '/' + interaction.targetId }); } }; \ No newline at end of file diff --git a/src/commands/slash/new.js b/src/commands/slash/new.js index 2fd5da2..4b0aedc 100644 --- a/src/commands/slash/new.js +++ b/src/commands/slash/new.js @@ -1,5 +1,6 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); +const { useGuild } = require('../../lib/tickets/utils'); module.exports = class NewSlashCommand extends SlashCommand { constructor(client, options) { @@ -14,7 +15,7 @@ module.exports = class NewSlashCommand extends SlashCommand { autocomplete: true, name: 'references', required: false, - type: ApplicationCommandOptionType.Integer, + type: ApplicationCommandOptionType.String, }, ]; opts = opts.map(o => { @@ -43,5 +44,12 @@ module.exports = class NewSlashCommand extends SlashCommand { }); } - async run(interaction) { } + /** + * + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + await useGuild(this.client, interaction, { referencesTicketId: interaction.options.getString('references', false) }); + + } }; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 4009a7d..560edf7 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -194,7 +194,9 @@ misc: member_limit: description: - Please use your existing ticket or close it before creating another. - - Please close a ticket before creating another. + - | + Please close a ticket before creating another. + Use `/tickets` to view your existing tickets. title: - ❌ You already have a ticket - ❌ You already have %d open tickets @@ -227,4 +229,14 @@ ticket: {staff} {creator} has created a new ticket fields: - topic: Topic \ No newline at end of file + topic: Topic + references_message: + description: 'References [a message]({url}) sent {timestamp} by {author}.' + title: ℹ️ Reference + references_ticket: + description: 'This ticket is related to a previous ticket:' + fields: + date: Created at + number: Number + topic: Topic + title: ℹ️ Reference diff --git a/src/lib/tickets/archiver.js b/src/lib/tickets/archiver.js new file mode 100644 index 0000000..de41890 --- /dev/null +++ b/src/lib/tickets/archiver.js @@ -0,0 +1,11 @@ +const Cryptr = require('cryptr'); +const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); + +module.exports = class TicketArchiver { + constructor(client) { + /** @type {import("client")} */ + this.client = client; + } + + async addMessage() {} +}; \ No newline at end of file diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index b24f1be..74f125c 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1,4 +1,5 @@ /* eslint-disable max-lines */ +const TicketArchiver = require('./archiver'); const { ActionRowBuilder, ButtonBuilder, @@ -24,7 +25,7 @@ module.exports = class TicketManager { constructor(client) { /** @type {import("client")} */ this.client = client; - + this.archiver = new TicketArchiver(client); this.$ = { categories: {} }; } @@ -92,7 +93,7 @@ module.exports = class TicketManager { * @param {string?} [data.topic] */ async create({ - categoryId, interaction, topic, referencesMessage, referencesTicket, + categoryId, interaction, topic, referencesMessage, referencesTicketId, }) { categoryId = Number(categoryId); const category = await this.getCategory(categoryId); @@ -122,6 +123,9 @@ module.exports = class TicketManager { }); } + /** @type {import("discord.js").Guild} */ + const guild = this.client.guilds.cache.get(category.guild.id); + const member = interaction.member ?? await guild.members.fetch(interaction.user.id); const getMessage = this.client.i18n.getLocale(category.guild.locale); const rlKey = `ratelimits/guild-user:${category.guildId}-${interaction.user.id}`; @@ -130,7 +134,7 @@ module.exports = class TicketManager { return await interaction.reply({ embeds: [ new ExtendedEmbedBuilder({ - iconURL: interaction.guild.iconURL(), + iconURL: guild.iconURL(), text: category.guild.footer, }) .setColor(category.guild.errorColour) @@ -146,7 +150,7 @@ module.exports = class TicketManager { const sendError = name => interaction.reply({ embeds: [ new ExtendedEmbedBuilder({ - iconURL: interaction.guild.iconURL(), + iconURL: guild.iconURL(), text: category.guild.footer, }) .setColor(category.guild.errorColour) @@ -156,10 +160,6 @@ module.exports = class TicketManager { ephemeral: true, }); - /** @type {import("discord.js").Guild} */ - const guild = this.client.guilds.cache.get(category.guild.id); - const member = interaction.member ?? await guild.members.fetch(interaction.user.id); - if (category.guild.blocklist.length !== 0) { const blocked = category.guild.blocklist.some(r => member.roles.cache.has(r)); if (blocked) return await sendError('blocked'); @@ -181,7 +181,7 @@ module.exports = class TicketManager { return await interaction.reply({ embeds: [ new ExtendedEmbedBuilder({ - iconURL: interaction.guild.iconURL(), + iconURL: guild.iconURL(), text: category.guild.footer, }) .setColor(category.guild.errorColour) @@ -206,7 +206,7 @@ module.exports = class TicketManager { // return await interaction.reply({ // embeds: [ // new ExtendedEmbedBuilder({ - // iconURL: interaction.guild.iconURL(), + // iconURL: guild.iconURL(), // text: category.guild.footer, // }) // .setColor(category.guild.errorColour) @@ -222,7 +222,7 @@ module.exports = class TicketManager { return await interaction.reply({ embeds: [ new ExtendedEmbedBuilder({ - iconURL: interaction.guild.iconURL(), + iconURL: guild.iconURL(), text: category.guild.footer, }) .setColor(category.guild.errorColour) @@ -240,7 +240,7 @@ module.exports = class TicketManager { action: 'questions', categoryId, referencesMessage, - referencesTicket, + referencesTicketId, })) .setTitle(category.name) .setComponents( @@ -290,7 +290,7 @@ module.exports = class TicketManager { action: 'topic', categoryId, referencesMessage, - referencesTicket, + referencesTicketId, })) .setTitle(category.name) .setComponents( @@ -311,6 +311,8 @@ module.exports = class TicketManager { await this.postQuestions({ categoryId, interaction, + referencesMessage, + referencesTicketId, topic, }); } @@ -323,7 +325,7 @@ module.exports = class TicketManager { * @param {string?} [data.topic] */ async postQuestions({ - action, categoryId, interaction, topic, referencesMessage, referencesTicket, + action, categoryId, interaction, topic, referencesMessage, referencesTicketId, }) { await interaction.deferReply({ ephemeral: true }); @@ -480,6 +482,69 @@ module.exports = class TicketManager { // TODO: referenced msg or ticket + if (referencesMessage) { + referencesMessage = referencesMessage.split('/'); + /** @type {import("discord.js").Message} */ + const message = await (await this.client.channels.fetch(referencesMessage[0]))?.messages.fetch(referencesMessage[1]); + if (message) { + await channel.send({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(category.guild.primaryColour) + .setTitle(getMessage('ticket.references_message.title')) + .setDescription( + getMessage('ticket.references_message.description', { + author: message.author.toString(), + timestamp: ``, + url: message.url, + })), + new ExtendedEmbedBuilder({ + iconURL: guild.iconURL(), + text: category.guild.footer, + }) + .setColor(category.guild.primaryColour) + .setAuthor({ + iconURL: message.member?.displayAvatarURL(), + name: message.member?.displayName || 'Unknown', + }) + .setDescription(message.content.substring(0, 1000) + message.content.length > 1000 ? '...' : ''), + ], + }); + } + } else if (referencesTicketId) { + // TODO: add portal url + const ticket = await this.client.prisma.ticket.findUnique({ where: { id: referencesTicketId } }); + if (ticket) { + const embed = new ExtendedEmbedBuilder({ + iconURL: guild.iconURL(), + text: category.guild.footer, + }) + .setColor(category.guild.primaryColour) + .setTitle(getMessage('ticket.references_ticket.title')) + .setDescription(getMessage('ticket.references_ticket.description')) + .setFields([ + { + inline: true, + name: getMessage('ticket.references_ticket.fields.number'), + value: inlineCode(ticket.number), + }, + { + inline: true, + name: getMessage('ticket.references_ticket.fields.date'), + value: ``, + }, + ]); + if (ticket.topic) { + embed.addFields({ + inline: false, + name: getMessage('ticket.references_ticket.fields.topic'), + value: ticket.topic, + }); + } + await channel.send({ embeds: [embed] }); + } + } + const data = { category: { connect: { id: categoryId } }, createdBy: { @@ -494,10 +559,10 @@ module.exports = class TicketManager { openingMessageId: sent.id, topic, }; - if (referencesTicket) data.referencesTicket = { connect: { id: referencesTicket } }; + if (referencesTicketId) data.referencesTicket = { connect: { id: referencesTicketId } }; let message; - if (referencesMessage) message = this.client.prisma.archivedMessage.findUnique({ where: { id: referencesMessage } }); - if (message) data.referencesMessage = { connect: { id: referencesMessage } }; // only add if the message has been archived ^^ + if (referencesMessage) message = await this.client.prisma.archivedMessage.findUnique({ where: { id: referencesMessage[1] } }); + if (message) data.referencesMessage = { connect: { id: referencesMessage[0] } }; // only add if the message has been archived ^^ if (answers) data.questionAnswers = { createMany: { data: answers } }; await interaction.editReply({ components: [], diff --git a/src/lib/tickets/utils.js b/src/lib/tickets/utils.js new file mode 100644 index 0000000..1fac854 --- /dev/null +++ b/src/lib/tickets/utils.js @@ -0,0 +1,77 @@ +const { + ActionRowBuilder, + EmbedBuilder, + SelectMenuBuilder, + SelectMenuOptionBuilder, +} = require('discord.js'); +const emoji = require('node-emoji'); + +module.exports = { + /** + * @param {import("client")} client + * @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction} interaction + */ + async useGuild(client, interaction, { + referencesMessage, + referencesTicketId, + topic, + }) { + const settings = await client.prisma.guild.findUnique({ + select: { + categories: true, + errorColour: true, + locale: true, + primaryColour: true, + }, + where: { id: interaction.guild.id }, + }); + const getMessage = client.i18n.getLocale(settings.locale); + if (settings.categories.length === 0) { + interaction.reply({ + components: [], + embeds: [ + new EmbedBuilder() + .setColor(settings.errorColour) + .setTitle(getMessage('misc.no_categories.title')) + .setDescription(getMessage('misc.no_categories.description')), + ], + ephemeral: true, + }); + } else if (settings.categories.length === 1) { + await client.tickets.create({ + categoryId: settings.categories[0].id, + interaction, + referencesMessage, + referencesTicketId, + topic, + }); + } else { + await interaction.reply({ + components: [ + new ActionRowBuilder() + .setComponents( + new SelectMenuBuilder() + .setCustomId(JSON.stringify({ + action: 'create', + referencesMessage, + referencesTicketId, + topic, + })) + .setPlaceholder(getMessage('menus.category.placeholder')) + .setOptions( + settings.categories.map(category => + new SelectMenuOptionBuilder() + .setValue(String(category.id)) + .setLabel(category.name) + .setDescription(category.description) + .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), + ), + ), + ), + ], + ephemeral: true, + }); + } + + }, +}; \ No newline at end of file diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index 7942cfa..efa50f1 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -23,13 +23,13 @@ module.exports = class extends Listener { } /** - * @param {string} guildId + * @param {import('@prisma/client').Guild} settings * @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction} interaction */ async useGuild(settings, interaction, topic) { const getMessage = this.client.i18n.getLocale(settings.locale); if (settings.categories.length === 0) { - interaction.editReply({ + interaction.update({ components: [], embeds: [ new EmbedBuilder() @@ -45,7 +45,7 @@ module.exports = class extends Listener { topic, }); } else { - const sent = await interaction.editReply({ + await interaction.update({ components: [ new ActionRowBuilder() .setComponents( @@ -67,17 +67,17 @@ module.exports = class extends Listener { ), ], }); - sent.awaitMessageComponent({ + interaction.message.awaitMessageComponent({ componentType: ComponentType.SelectMenu, filter: () => true, time: ms('30s'), }) .then(async () => { - await sent.delete(); + interaction.message.delete(); }) .catch(error => { if (error) this.client.log.error(error); - sent.delete(); + interaction.message.delete(); }); } @@ -92,7 +92,7 @@ module.exports = class extends Listener { if (message.channel.type === ChannelType.DM) { if (message.author.bot) return false; - const commonGuilds = await getCommonGuilds(this.client, message.author.id); + const commonGuilds = await getCommonGuilds(client, message.author.id); if (commonGuilds.size === 0) { return false; } else if (commonGuilds.size === 1) { @@ -105,7 +105,7 @@ module.exports = class extends Listener { }, where: { id: commonGuilds.at(0).id }, }); - const getMessage = this.client.i18n.getLocale(settings.locale); + const getMessage = client.i18n.getLocale(settings.locale); const sent = await message.reply({ components: [ new ActionRowBuilder() @@ -126,16 +126,16 @@ module.exports = class extends Listener { }); sent.awaitMessageComponent({ componentType: ComponentType.Button, - filter: interaction => interaction.deferUpdate(), + filter: () => true, time: ms('30s'), }) .then(async interaction => await this.useGuild(settings, interaction, message.content)) .catch(error => { - if (error) this.client.log.error(error); + if (error) client.log.error(error); sent.delete(); }); } else { - const getMessage = this.client.i18n.getLocale(); + const getMessage = client.i18n.getLocale(); const sent = await message.reply({ components: [ new ActionRowBuilder() @@ -156,7 +156,7 @@ module.exports = class extends Listener { }); sent.awaitMessageComponent({ componentType: ComponentType.SelectMenu, - filter: interaction => interaction.deferUpdate(), + filter: () => true, time: ms('30s'), }) .then(async interaction => { @@ -172,7 +172,7 @@ module.exports = class extends Listener { await this.useGuild(settings, interaction, message.content); }) .catch(error => { - if (error) this.client.log.error(error); + if (error) client.log.error(error); sent.delete(); }); } diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index cd80e1d..9287648 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -25,11 +25,16 @@ module.exports = class extends Listener { cooldown: true, id: true, tickets: { - select: { createdById: true }, + select: { + createdById: true, + guildId: true, + id: true, + }, where: { open: true }, }, }, }); + let deleted = 0; let ticketCount = 0; let cooldowns = 0; for (const category of categories) { @@ -38,6 +43,13 @@ module.exports = class extends Listener { for (const ticket of category.tickets) { if (client.tickets.$.categories[category.id][ticket.createdById]) client.tickets.$.categories[category.id][ticket.createdById]++; else client.tickets.$.categories[category.id][ticket.createdById] = 1; + /** @type {import("discord.js").Guild} */ + const guild = client.guilds.cache.get(ticket.guildId); + if (guild && guild.available && !client.channels.cache.has(ticket.id)) { + deleted += 0; + await client.tickets.close(ticket.id); + } + } if (category.cooldown) { const recent = await client.prisma.ticket.findMany({ @@ -63,6 +75,7 @@ module.exports = class extends Listener { // const ticketCount = categories.reduce((total, category) => total + category.tickets.length, 0); client.log.info(`Cached ticket count of ${categories.length} categories (${ticketCount} open tickets)`); client.log.info(`Loaded ${cooldowns} active cooldowns`); + client.log.info(`Closed ${deleted} deleted tickets`); // presence/activity let next = 0; diff --git a/src/menus/create.js b/src/menus/create.js index e0b282d..47943f4 100644 --- a/src/menus/create.js +++ b/src/menus/create.js @@ -1,4 +1,5 @@ const { Menu } = require('@eartharoid/dbf'); +const { MessageFlags } = require('discord.js'); module.exports = class CreateMenu extends Menu { constructor(client, options) { @@ -13,11 +14,11 @@ module.exports = class CreateMenu extends Menu { * @param {import("discord.js").SelectMenuInteraction} interaction */ async run(id, interaction) { - interaction.message.edit({ components: interaction.message.components }); // reset the select menu (minor client-side UI issue) + if (!interaction.message.flags.has(MessageFlags.Ephemeral)) interaction.message.edit({ components: interaction.message.components }); // reset the select menu (minor client-side UI issue) await this.client.tickets.create({ + ...id, categoryId: interaction.values[0], interaction, - topic: id.topic, }); } }; \ No newline at end of file From 4c176d082f581710bb2e49b6f424d6608ed175fe Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 13 Aug 2022 23:58:41 +0100 Subject: [PATCH 099/409] #303 and other stuff --- src/autocomplete/ticket.js | 34 ++++++++++++++++++- src/commands/slash/close.js | 16 ++++----- src/commands/slash/new.js | 1 - src/commands/user/create.js | 5 ++- src/i18n/en-GB.yml | 9 ++--- src/lib/tickets/manager.js | 21 ++++++++++-- src/listeners/client/messageDelete.js | 1 + src/listeners/client/messageUpdate.js | 1 + .../admin/guilds/[guild]/categories/index.js | 2 +- src/stdin/commands.js | 7 ++-- 10 files changed, 70 insertions(+), 27 deletions(-) diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js index 6c7214b..60b5e8b 100644 --- a/src/autocomplete/ticket.js +++ b/src/autocomplete/ticket.js @@ -8,5 +8,37 @@ module.exports = class TicketCompleter extends Autocompleter { }); } - async run(value, comamnd, interaction) { } + /** + * @param {string} value + * @param {*} comamnd + * @param {import("discord.js").AutocompleteInteraction} interaction + */ + async run(value, comamnd, interaction) { + /** @type {import("client")} */ + const client = this.client; + const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + const tickets = await client.prisma.ticket.findMany({ + where: { + createdById: interaction.user.id, + guildId: interaction.guild.id, + open: false, + }, + }); + const options = value ? tickets.filter(t => + String(t.number).match(new RegExp(value, 'i')) || + t.topic?.match(new RegExp(value, 'i')) || + new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' })?.match(new RegExp(value, 'i')), + ) : tickets; + await interaction.respond( + options + .slice(0, 25) + .map(t => { + const date = new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' }); + return { + name: `#${t.number} - ${date} ${t.topic ? '| ' + t.topic.substring(0, 50) : ''}`, + value: t.id, + }; + }), + ); + } }; \ No newline at end of file diff --git a/src/commands/slash/close.js b/src/commands/slash/close.js index 74bad52..3705e54 100644 --- a/src/commands/slash/close.js +++ b/src/commands/slash/close.js @@ -10,21 +10,17 @@ module.exports = class CloseSlashCommand extends SlashCommand { client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.close.name'))); let opts = [ - { - name: 'channel', - required: false, - type: ApplicationCommandOptionType.Channel, - }, - { - name: 'number', - required: false, - type: ApplicationCommandOptionType.Integer, - }, { name: 'reason', required: false, type: ApplicationCommandOptionType.String, }, + { + autocomplete: true, + name: 'ticket', + required: false, + type: ApplicationCommandOptionType.String, + }, { name: 'time', required: false, diff --git a/src/commands/slash/new.js b/src/commands/slash/new.js index 4b0aedc..ccf978d 100644 --- a/src/commands/slash/new.js +++ b/src/commands/slash/new.js @@ -50,6 +50,5 @@ module.exports = class NewSlashCommand extends SlashCommand { */ async run(interaction) { await useGuild(this.client, interaction, { referencesTicketId: interaction.options.getString('references', false) }); - } }; \ No newline at end of file diff --git a/src/commands/user/create.js b/src/commands/user/create.js index 8a9579b..648096d 100644 --- a/src/commands/user/create.js +++ b/src/commands/user/create.js @@ -13,5 +13,8 @@ module.exports = class CreateUserCommand extends UserCommand { }); } - async run(interaction) { } + async run(interaction) { + // select category + // send button + } }; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 560edf7..df0515e 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -41,15 +41,12 @@ commands: description: Close a ticket name: close options: - channel: - description: The ticket channel to close - name: channel - number: - description: The number of the ticket to close - name: number reason: description: The reason for closing the ticket(s) name: reason + ticket: + description: The ticket to close + name: ticket time: description: Close all tickets that have been inactive for the specific time name: time diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 74f125c..159ac08 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -386,6 +386,20 @@ module.exports = class TicketManager { if (category.image) await channel.send(category.image); + const statsCacheKey = `cache/category-stats/${categoryId}`; + let stats = await this.client.keyv.get(statsCacheKey); + if (!stats) { + const { tickets } = await this.client.prisma.category.findUnique({ + select: { tickets: { where: { open: false } } }, + where: { id: categoryId }, + }); + stats = { + avgResolutionTime: ms(tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / tickets.length), + avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length), + }; + this.client.keyv.set(statsCacheKey, stats, ms('1h')); + } + const embeds = [ new ExtendedEmbedBuilder() .setColor(category.guild.primaryColour) @@ -395,11 +409,14 @@ module.exports = class TicketManager { }) .setDescription( category.openingMessage - .replace(/{+\s?(user)?name\s?}+/gi, creator.user.toString()), - + .replace(/{+\s?(user)?name\s?}+/gi, creator.user.toString()) + .replace(/{+\s?avgResponseTime\s?}+/gi, stats.avgResponseTime) + .replace(/{+\s?avgResolutionTime\s?}+/gi, stats.avgResolutionTime), ), ]; + // TODO: !staff || workingHours + if (answers) { embeds.push( new ExtendedEmbedBuilder() diff --git a/src/listeners/client/messageDelete.js b/src/listeners/client/messageDelete.js index 11504a0..a6dd3dc 100644 --- a/src/listeners/client/messageDelete.js +++ b/src/listeners/client/messageDelete.js @@ -11,5 +11,6 @@ module.exports = class extends Listener { run(message) { // TODO: archive messages in tickets + // TODO: log channel } }; diff --git a/src/listeners/client/messageUpdate.js b/src/listeners/client/messageUpdate.js index 59a2a63..6792eb6 100644 --- a/src/listeners/client/messageUpdate.js +++ b/src/listeners/client/messageUpdate.js @@ -11,5 +11,6 @@ module.exports = class extends Listener { run(oldMessage, newMessage) { // TODO: archive messages in tickets + // TODO: log channel } }; diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index 95d1f81..254a8d5 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -21,7 +21,7 @@ module.exports.get = fastify => ({ name: true, requiredRoles: true, staffRoles: true, - tickets: true, + tickets: { where: { open: false } }, }, }, }, diff --git a/src/stdin/commands.js b/src/stdin/commands.js index 6b1242b..29e4a71 100644 --- a/src/stdin/commands.js +++ b/src/stdin/commands.js @@ -12,11 +12,8 @@ module.exports = class Commands extends StdinCommand { switch (args[0]) { case 'publish': { this.client.commands.publish() - .then(commands => { - if (!commands) return console.log('None published'); - console.log('Published %d commands', commands.size); - }) - .catch(console.error); + .then(commands => this.client.log.success('Published %d commands', commands?.size)) + .catch(this.client.log.error); break; } } From 07af37b4a2841e21e6cbaafa40012954718cd8c7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 17 Aug 2022 20:41:51 +0100 Subject: [PATCH 100/409] Improve option format --- src/autocomplete/ticket.js | 13 ++++++++++++- src/lib/tickets/manager.js | 25 ------------------------- src/lib/tickets/utils.js | 1 - 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js index 60b5e8b..ea4473b 100644 --- a/src/autocomplete/ticket.js +++ b/src/autocomplete/ticket.js @@ -1,4 +1,5 @@ const { Autocompleter } = require('@eartharoid/dbf'); +const emoji = require('node-emoji'); module.exports = class TicketCompleter extends Autocompleter { constructor(client, options) { @@ -18,6 +19,14 @@ module.exports = class TicketCompleter extends Autocompleter { const client = this.client; const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); const tickets = await client.prisma.ticket.findMany({ + include: { + category: { + select: { + emoji: true, + name: true, + }, + }, + }, where: { createdById: interaction.user.id, guildId: interaction.guild.id, @@ -34,8 +43,10 @@ module.exports = class TicketCompleter extends Autocompleter { .slice(0, 25) .map(t => { const date = new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' }); + const topic = t.topic ? '| ' + t.topic.substring(0, 50) : ''; + const category = emoji.hasEmoji(t.category.emoji) ? emoji.get(t.category.emoji) + ' ' + t.category.name : t.category.name; return { - name: `#${t.number} - ${date} ${t.topic ? '| ' + t.topic.substring(0, 50) : ''}`, + name: `${category} #${t.number} - ${date} ${topic}`, value: t.id, }; }), diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 159ac08..cf215d7 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -192,31 +192,6 @@ module.exports = class TicketManager { }); } - // const lastTicket = await this.client.prisma.ticket.findFirst({ - // orderBy: [{ closedAt: 'desc' }], - // select: { closedAt: true }, - // where: { - // categoryId: category.id, - // createdById: interaction.user.id, - // open: false, - // }, - // }); - - // if (Date.now() - lastTicket.closedAt < category.cooldown) { - // return await interaction.reply({ - // embeds: [ - // new ExtendedEmbedBuilder({ - // iconURL: guild.iconURL(), - // text: category.guild.footer, - // }) - // .setColor(category.guild.errorColour) - // .setTitle(getMessage('misc.cooldown.title')) - // .setDescription(getMessage('misc.cooldown.description', { time: ms(category.cooldown - (Date.now() - lastTicket.closedAt)) })), - // ], - // ephemeral: true, - // }); - // } - const cooldown = await this.getCooldown(category.id, interaction.user.id); if (cooldown) { return await interaction.reply({ diff --git a/src/lib/tickets/utils.js b/src/lib/tickets/utils.js index 1fac854..9cae372 100644 --- a/src/lib/tickets/utils.js +++ b/src/lib/tickets/utils.js @@ -72,6 +72,5 @@ module.exports = { ephemeral: true, }); } - }, }; \ No newline at end of file From 9936b05fbb5f93e40ddbbd4a32037434f3b721fe Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 25 Aug 2022 23:29:07 +0100 Subject: [PATCH 101/409] fix: start script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ad5ee6f..ef593e2 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "lint": "eslint src scripts --ext mjs --fix", "preinstall": "node scripts/preinstall", "postinstall": "node scripts/postinstall", - "start": "npm run prisma && node .", + "start": "node .", "studio": "npx prisma studio", "test": "echo \"There's nothing to test\" && exit 1" }, From f74069deb6b3078301c425959b989ae1f49a53d0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 25 Aug 2022 23:35:17 +0100 Subject: [PATCH 102/409] fix: API not working on Windows --- src/http.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/http.js b/src/http.js index f695fad..4c71c2f 100644 --- a/src/http.js +++ b/src/http.js @@ -135,6 +135,7 @@ module.exports = client => { const path = file .substring(0, file.length - 3) // remove `.js` .substring(dir.length) // remove higher directories + .replace(/\\/g, '/') // replace `\` with `/` because Windows is stupid .replace(/\[(\w+)\]/gi, ':$1') // convert [] to : .replace('/index', '') || '/'; // remove index const route = require(file); From 3697423fda7dfb91e035194915df3a8d7d6b8856 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 4 Sep 2022 19:55:32 +0100 Subject: [PATCH 103/409] Fix sqlite middleware --- src/http.js | 13 ++++++------- src/index.js | 3 +-- src/lib/middleware/prisma-sqlite.js | 12 ++++++++++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/http.js b/src/http.js index 4c71c2f..8880f06 100644 --- a/src/http.js +++ b/src/http.js @@ -6,8 +6,7 @@ const { join } = require('path'); const { files } = require('node-dir'); module.exports = client => { - - // cors plugins + // cors plugin fastify.register(require('@fastify/cors'), { credentials: true, methods: ['DELETE', 'GET', 'PATCH', 'PUT', 'POST'], @@ -112,13 +111,13 @@ module.exports = client => { : res.statusCode >= 200 ? '&2' : '&f') + res.statusCode; - let response_time = res.getResponseTime().toFixed(2); - response_time = (response_time >= 20 + let responseTime = res.getResponseTime().toFixed(2); + responseTime = (responseTime >= 20 ? '&c' - : response_time >= 5 + : responseTime >= 5 ? '&e' - : '&a') + response_time + 'ms'; - client.log.info.http(short(`${req.ip} ${req.method} ${req.routerPath ?? '*'} &m-+>&r ${status}&b in ${response_time}`)); + : '&a') + responseTime + 'ms'; + client.log.info.http(short(`${req.ip} ${req.method} ${req.routerPath ?? '*'} &m-+>&r ${status}&b in ${responseTime}`)); done(); }); diff --git a/src/index.js b/src/index.js index 8019b3b..35b5791 100644 --- a/src/index.js +++ b/src/index.js @@ -21,7 +21,6 @@ * @license GNU-GPLv3 */ - const pkg = require('../package.json'); const fs = require('fs'); const semver = require('semver'); @@ -42,7 +41,7 @@ if (!semver.satisfies(process.versions.node, pkg.engines.node)) { } if (process.env.ENCRYPTION_KEY === undefined) { - console.log('\x07' + colours.redBright('Error: The "ENCRYPTION_KEY" environment variable is not set.\nRun "npm run keygen" to generate a key, or set it to "false" to disable encryption (not recommended).')); + console.log('\x07' + colours.redBright('Error: The "ENCRYPTION_KEY" environment variable is not set.\nRun "npm run keygen" to generate a key.')); process.exit(1); } diff --git a/src/lib/middleware/prisma-sqlite.js b/src/lib/middleware/prisma-sqlite.js index 7606ee4..776b647 100644 --- a/src/lib/middleware/prisma-sqlite.js +++ b/src/lib/middleware/prisma-sqlite.js @@ -17,8 +17,16 @@ const traverse = (obj, action) => { prop = 'create'; traverse(obj[prop], action); } else if (jsonFields.includes(prop) && obj[prop] !== null && obj[prop] !== undefined) { - if (action === 'SERIALISE' && typeof obj[prop] !== 'string') { - obj[prop] = JSON.stringify(obj[prop]); + if (action === 'SERIALISE') { + if (typeof obj[prop] === 'string') { + try { + JSON.parse(obj[prop]); + } catch { + obj[prop] = JSON.stringify(obj[prop]); + } + } else { + obj[prop] = JSON.stringify(obj[prop]); + } } else if (action === 'PARSE' && typeof obj[prop] === 'string') { obj[prop] = JSON.parse(obj[prop]); } From 956648c265f991ba572f8c8d5068a98cc9dbf28d Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 4 Sep 2022 21:00:18 +0100 Subject: [PATCH 104/409] Update `ticket` autocompleter --- src/autocomplete/ticket.js | 6 +++--- src/commands/slash/add.js | 3 ++- src/commands/slash/close.js | 2 +- src/commands/slash/remove.js | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js index ea4473b..7f86dbe 100644 --- a/src/autocomplete/ticket.js +++ b/src/autocomplete/ticket.js @@ -11,10 +11,10 @@ module.exports = class TicketCompleter extends Autocompleter { /** * @param {string} value - * @param {*} comamnd + * @param {*} command * @param {import("discord.js").AutocompleteInteraction} interaction */ - async run(value, comamnd, interaction) { + async run(value, command, interaction) { /** @type {import("client")} */ const client = this.client; const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); @@ -30,7 +30,7 @@ module.exports = class TicketCompleter extends Autocompleter { where: { createdById: interaction.user.id, guildId: interaction.guild.id, - open: false, + open: ['add', 'close', 'force-close', 'remove'].includes(command.name), // false for `new`, `transcript` etc }, }); const options = value ? tickets.filter(t => diff --git a/src/commands/slash/add.js b/src/commands/slash/add.js index c126a41..6768441 100644 --- a/src/commands/slash/add.js +++ b/src/commands/slash/add.js @@ -16,9 +16,10 @@ module.exports = class AddSlashCommand extends SlashCommand { type: ApplicationCommandOptionType.User, }, { + autocomplete: true, name: 'ticket', required: false, - type: ApplicationCommandOptionType.Channel, + type: ApplicationCommandOptionType.Integer, }, ]; opts = opts.map(o => { diff --git a/src/commands/slash/close.js b/src/commands/slash/close.js index 3705e54..4e55840 100644 --- a/src/commands/slash/close.js +++ b/src/commands/slash/close.js @@ -19,7 +19,7 @@ module.exports = class CloseSlashCommand extends SlashCommand { autocomplete: true, name: 'ticket', required: false, - type: ApplicationCommandOptionType.String, + type: ApplicationCommandOptionType.Integer, }, { name: 'time', diff --git a/src/commands/slash/remove.js b/src/commands/slash/remove.js index aded8e2..6923bd4 100644 --- a/src/commands/slash/remove.js +++ b/src/commands/slash/remove.js @@ -16,9 +16,10 @@ module.exports = class RemoveSlashCommand extends SlashCommand { type: ApplicationCommandOptionType.User, }, { + autocomplete: true, name: 'ticket', required: false, - type: ApplicationCommandOptionType.Channel, + type: ApplicationCommandOptionType.Integer, }, ]; opts = opts.map(o => { From 84505c2a5f723fed0c94180f4af3451c3f676295 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 4 Sep 2022 21:04:05 +0100 Subject: [PATCH 105/409] Add `for` option to `/tag` command --- src/commands/slash/tag.js | 5 +++++ src/i18n/en-GB.yml | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/commands/slash/tag.js b/src/commands/slash/tag.js index 832be57..b6f0a05 100644 --- a/src/commands/slash/tag.js +++ b/src/commands/slash/tag.js @@ -16,6 +16,11 @@ module.exports = class TagSlashCommand extends SlashCommand { required: true, type: ApplicationCommandOptionType.String, }, + { + name: 'for', + required: false, + type: ApplicationCommandOptionType.User, + }, ]; opts = opts.map(o => { const descriptionLocalizations = {}; diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index df0515e..c9c169c 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -101,6 +101,9 @@ commands: description: Use a tag name: tag options: + for: + description: The user to target the tag to + name: for tag: description: The name of the tag to use name: tag From bae6af96f7795ec0753a2da708c13fc5adfd986d Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 4 Sep 2022 21:08:36 +0100 Subject: [PATCH 106/409] Add exit stdin command --- src/stdin/exit.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/stdin/exit.js diff --git a/src/stdin/exit.js b/src/stdin/exit.js new file mode 100644 index 0000000..e58317e --- /dev/null +++ b/src/stdin/exit.js @@ -0,0 +1,15 @@ +const { StdinCommand } = require('@eartharoid/dbf'); + +module.exports = class extends StdinCommand { + constructor(client, options) { + super(client, { + ...options, + id: 'exit', + }); + } + + async run() { + this.client.log.info('Exiting'); + process.exit(); + } +}; \ No newline at end of file From 44ee84d8f70d6f4720758b301361a8c818ccd8fb Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 4 Sep 2022 21:20:24 +0100 Subject: [PATCH 107/409] Added more logging --- src/listeners/autocomplete/componentLoad.js | 16 ++++++++++++++++ src/listeners/buttons/componentLoad.js | 16 ++++++++++++++++ src/listeners/menus/componentLoad.js | 16 ++++++++++++++++ src/listeners/modals/componentLoad.js | 16 ++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 src/listeners/autocomplete/componentLoad.js create mode 100644 src/listeners/buttons/componentLoad.js create mode 100644 src/listeners/menus/componentLoad.js create mode 100644 src/listeners/modals/componentLoad.js diff --git a/src/listeners/autocomplete/componentLoad.js b/src/listeners/autocomplete/componentLoad.js new file mode 100644 index 0000000..26e81b0 --- /dev/null +++ b/src/listeners/autocomplete/componentLoad.js @@ -0,0 +1,16 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.autocomplete, + event: 'componentLoad', + }); + } + + run(autocompleter) { + this.client.log.info(`Loaded "${autocompleter.id}" autocompleter`); + return true; + } +}; diff --git a/src/listeners/buttons/componentLoad.js b/src/listeners/buttons/componentLoad.js new file mode 100644 index 0000000..246948d --- /dev/null +++ b/src/listeners/buttons/componentLoad.js @@ -0,0 +1,16 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.buttons, + event: 'componentLoad', + }); + } + + run(button) { + this.client.log.info(`Loaded "${button.id}" button`); + return true; + } +}; diff --git a/src/listeners/menus/componentLoad.js b/src/listeners/menus/componentLoad.js new file mode 100644 index 0000000..03290de --- /dev/null +++ b/src/listeners/menus/componentLoad.js @@ -0,0 +1,16 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.menus, + event: 'componentLoad', + }); + } + + run(menu) { + this.client.log.info(`Loaded "${menu.id}" menu`); + return true; + } +}; diff --git a/src/listeners/modals/componentLoad.js b/src/listeners/modals/componentLoad.js new file mode 100644 index 0000000..a9454e2 --- /dev/null +++ b/src/listeners/modals/componentLoad.js @@ -0,0 +1,16 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.modals, + event: 'componentLoad', + }); + } + + run(modal) { + this.client.log.info(`Loaded "${modal.id}" modal`); + return true; + } +}; From 34a4e071b5dca1acefdbb2b0089f4f19e821c808 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 5 Sep 2022 12:43:27 +0100 Subject: [PATCH 108/409] Make edit button work --- scripts/preinstall.js | 1 + src/buttons/edit.js | 99 +++++++++++++++++++++++++++++++++++- src/i18n/en-GB.yml | 4 ++ src/lib/logging.js | 28 ++++++----- src/modals/questions.js | 105 +++++++++++++++++++++++++++++++++++++-- src/modals/topic.js | 86 ++++++++++++++++++++++++++++++-- src/routes/api/client.js | 3 +- 7 files changed, 304 insertions(+), 22 deletions(-) diff --git a/scripts/preinstall.js b/scripts/preinstall.js index 2f00282..a6cba15 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -10,6 +10,7 @@ const env = { HTTP_BIND: 8080, HTTP_EXTERNAL: 'http://localhost:8080', PORTAL: '', + PUBLIC: false, SUPER: '319467558166069248', }; diff --git a/src/buttons/edit.js b/src/buttons/edit.js index 479d222..dc82a10 100644 --- a/src/buttons/edit.js +++ b/src/buttons/edit.js @@ -1,4 +1,13 @@ const { Button } = require('@eartharoid/dbf'); +const { + ActionRowBuilder, + ModalBuilder, + SelectMenuBuilder, + SelectMenuOptionBuilder, + TextInputBuilder, + TextInputStyle, +} = require('discord.js'); +const emoji = require('node-emoji'); module.exports = class EditButton extends Button { constructor(client, options) { @@ -8,5 +17,93 @@ module.exports = class EditButton extends Button { }); } - async run(id, interaction) { } + async run(id, interaction) { + /** @type {import("client")} */ + const client = this.client; + + const ticket = await client.prisma.ticket.findUnique({ + select: { + category: { select: { name: true } }, + guild: { select: { locale: true } }, + questionAnswers: { include: { question: true } }, + topic: true, + }, + where: { id: interaction.channel.id }, + }); + + const getMessage = client.i18n.getLocale(ticket.guild.locale); + + if (ticket.questionAnswers.length === 0) { + await interaction.showModal( + new ModalBuilder() + .setCustomId(JSON.stringify({ + action: 'topic', + edit: true, + })) + .setTitle(ticket.category.name) + .setComponents( + new ActionRowBuilder() + .setComponents( + new TextInputBuilder() + .setCustomId('topic') + .setLabel(getMessage('modals.topic.label')) + .setStyle(TextInputStyle.Paragraph) + .setMaxLength(1000) + .setMinLength(5) + .setPlaceholder(getMessage('modals.topic.placeholder')) + .setRequired(true) + .setValue(ticket.topic || ''), + ), + ), + ); + } else { + await interaction.showModal( + new ModalBuilder() + .setCustomId(JSON.stringify({ + action: 'questions', + edit: true, + })) + .setTitle(ticket.category.name) + .setComponents( + ticket.questionAnswers + .filter(a => a.question.type === 'TEXT') // TODO: remove this when modals support select menus + .map(a => { + if (a.question.type === 'TEXT') { + return new ActionRowBuilder() + .setComponents( + new TextInputBuilder() + .setCustomId(String(a.id)) + .setLabel(a.question.label) + .setStyle(a.question.style) + .setMaxLength(Math.min(a.question.maxLength, 1000)) + .setMinLength(a.question.minLength) + .setPlaceholder(a.question.placeholder) + .setRequired(a.question.required) + .setValue(a.value || a.question.value), + ); + } else if (a.question.type === 'MENU') { + return new ActionRowBuilder() + .setComponents( + new SelectMenuBuilder() + .setCustomId(a.question.id) + .setPlaceholder(a.question.placeholder || a.question.label) + .setMaxValues(a.question.maxLength) + .setMinValues(a.question.minLength) + .setOptions( + a.question.options.map((o, i) => { + const builder = new SelectMenuOptionBuilder() + .setValue(String(i)) + .setLabel(o.label); + if (o.description) builder.setDescription(o.description); + if (o.emoji) builder.setEmoji(emoji.hasEmoji(o.emoji) ? emoji.get(o.emoji) : { id: o.emoji }); + return builder; + }), + ), + ); + } + }), + ), + ); + } + } }; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index c9c169c..8a66dbd 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -169,6 +169,7 @@ log: claim: claimed close: closed unclaim: released + update: updated menus: category: placeholder: Select a ticket category @@ -224,6 +225,9 @@ ticket: title: ✅ Ticket created answers: no_value: '*No response*' + edited: + description: Your changes have been saved. + title: ✅ Ticket updated opening_message: content: | {staff} diff --git a/src/lib/logging.js b/src/lib/logging.js index 1cf2e23..2be4d98 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -129,7 +129,7 @@ async function logAdminEvent(client, { * @param {string} details.action */ async function logTicketEvent(client, { - userId, action, target, + userId, action, target, diff, }) { const ticket = await client.prisma.ticket.findUnique({ include: { guild: true }, @@ -143,9 +143,10 @@ async function logTicketEvent(client, { if (!ticket.guild.logChannel) return; const colour = action === 'create' ? 'Aqua' : action === 'close' - ? 'DarkAqua' : action === 'claim' - ? 'LuminousVividPink' : action === 'unclaim' - ? 'DarkVividPink' : 'Default'; + ? 'DarkAqua' : action === 'update' + ? 'Purple' : action === 'claim' + ? 'LuminousVividPink' : action === 'unclaim' + ? 'DarkVividPink' : 'Default'; const getMessage = client.i18n.getLocale(ticket.guild.locale); const i18nOptions = { user: `<@${member.user.id}>`, @@ -160,14 +161,8 @@ async function logTicketEvent(client, { iconURL: member.displayAvatarURL(), name: member.displayName, }) - .setTitle(getMessage('log.ticket.title', { - ...i18nOptions, - verb: getMessage(`log.ticket.verb.${action}`), - })) - .setDescription(getMessage('log.ticket.description', { - ...i18nOptions, - verb: getMessage(`log.ticket.verb.${action}`), - })) + .setTitle(getMessage('log.ticket.title', i18nOptions)) + .setDescription(getMessage('log.ticket.description', i18nOptions)) .addFields([ { name: getMessage('log.ticket.ticket'), @@ -176,6 +171,15 @@ async function logTicketEvent(client, { ]), ]; + if (diff && diff.original) { + embeds.push( + new EmbedBuilder() + .setColor(colour) + .setTitle(getMessage('log.admin.changes')) + .setFields(makeDiff(diff)), + ); + } + return await channel.send({ embeds }); } diff --git a/src/modals/questions.js b/src/modals/questions.js index f7c6b8e..3f35db1 100644 --- a/src/modals/questions.js +++ b/src/modals/questions.js @@ -1,4 +1,7 @@ const { Modal } = require('@eartharoid/dbf'); +const { EmbedBuilder } = require('discord.js'); +const ExtendedEmbedBuilder = require('../lib/embed'); +const { logTicketEvent } = require('../lib/logging'); module.exports = class QuestionsModal extends Modal { constructor(client, options) { @@ -14,9 +17,103 @@ module.exports = class QuestionsModal extends Modal { * @param {import("discord.js").ModalSubmitInteraction} interaction */ async run(id, interaction) { - await this.client.tickets.postQuestions({ - ...id, - interaction, - }); + /** @type {import("client")} */ + const client = this.client; + + if (id.edit) { + await interaction.deferReply({ ephemeral: true }); + const { category } = await client.prisma.ticket.findUnique({ + select: { category: { select: { customTopic: true } } }, + where: { id: interaction.channel.id }, + }); + let topic; + if (category.customTopic) topic = interaction.fields.getTextInputValue(category.customTopic); + const select = { + createdById: true, + guild: { + select: { + footer: true, + locale: true, + successColour: true, + }, + }, + id: true, + openingMessageId: true, + questionAnswers: { include: { question: true } }, + }; + const original = await client.prisma.ticket.findUnique({ + select, + where: { id: interaction.channel.id }, + }); + const ticket = await client.prisma.ticket.update({ + data: { + questionAnswers: { + update: interaction.fields.fields.map(f => ({ + data: { value: f.value }, + where: { id: Number(f.customId) }, + })), + }, + topic, + }, + select, + where: { id: interaction.channel.id }, + }); + const getMessage = client.i18n.getLocale(ticket.guild.locale); + + if (topic) await interaction.channel.setTopic(`<@${ticket.createdById}> | ${topic}`); + + const opening = await interaction.channel.messages.fetch(ticket.openingMessageId); + if (opening && opening.embeds.length >= 2) { + const embeds = [...opening.embeds]; + embeds[1] = new EmbedBuilder(embeds[1].data) + .setFields( + ticket.questionAnswers + .map(a => ({ + name: a.question.label, + value: a.value || getMessage('ticket.answers.no_value'), + })), + ); + await opening.edit({ embeds }); + } + + await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.successColour) + .setTitle(getMessage('ticket.edited.title')) + .setDescription(getMessage('ticket.edited.description')), + ], + }); + + /** @param {ticket} ticket */ + const makeDiff = ticket => { + const diff = {}; + ticket.questionAnswers.forEach(a => { + diff[a.question.label] = a.value || getMessage('ticket.answers.no_value'); + }); + return diff; + }; + + logTicketEvent(this.client, { + action: 'update', + diff: { + original: makeDiff(original), + updated: makeDiff(ticket), + }, + target: { + id: ticket.id, + name: `<#${ticket.id}>`, + }, + userId: interaction.user.id, + }); + } else { + await this.client.tickets.postQuestions({ + ...id, + interaction, + }); + } } }; \ No newline at end of file diff --git a/src/modals/topic.js b/src/modals/topic.js index 84544d3..e9af947 100644 --- a/src/modals/topic.js +++ b/src/modals/topic.js @@ -1,4 +1,7 @@ const { Modal } = require('@eartharoid/dbf'); +const { EmbedBuilder } = require('discord.js'); +const ExtendedEmbedBuilder = require('../lib/embed'); +const { logTicketEvent } = require('../lib/logging'); module.exports = class TopicModal extends Modal { constructor(client, options) { @@ -9,9 +12,84 @@ module.exports = class TopicModal extends Modal { } async run(id, interaction) { - await this.client.tickets.postQuestions({ - ...id, - interaction, - }); + /** @type {import("client")} */ + const client = this.client; + + if (id.edit) { + await interaction.deferReply({ ephemeral: true }); + const topic = interaction.fields.getTextInputValue('topic'); + const select = { + createdById: true, + guild: { + select: { + footer: true, + locale: true, + successColour: true, + }, + }, + id: true, + openingMessageId: true, + }; + const original = await client.prisma.ticket.findUnique({ + select, + where: { id: interaction.channel.id }, + }); + const ticket = await client.prisma.ticket.update({ + data: { topic }, + select, + where: { id: interaction.channel.id }, + }); + const getMessage = client.i18n.getLocale(ticket.guild.locale); + + if (topic) await interaction.channel.setTopic(`<@${ticket.createdById}> | ${topic}`); + + const opening = await interaction.channel.messages.fetch(ticket.openingMessageId); + if (opening && opening.embeds.length >= 2) { + const embeds = [...opening.embeds]; + embeds[1] = new EmbedBuilder(embeds[1].data) + .setFields({ + name: getMessage('ticket.opening_message.fields.topic'), + value: topic, + }); + await opening.edit({ embeds }); + } + + await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.successColour) + .setTitle(getMessage('ticket.edited.title')) + .setDescription(getMessage('ticket.edited.description')), + ], + }); + + /** @param {ticket} ticket */ + const makeDiff = ticket => { + const diff = {}; + diff[getMessage('ticket.opening_message.fields.topic')] = ticket.topic; + return diff; + }; + + logTicketEvent(this.client, { + action: 'update', + diff: { + original: makeDiff(original), + updated: makeDiff(ticket), + }, + target: { + id: ticket.id, + name: `<#${ticket.id}>`, + }, + userId: interaction.user.id, + }); + } else { + await this.client.tickets.postQuestions({ + ...id, + interaction, + }); + } } }; \ No newline at end of file diff --git a/src/routes/api/client.js b/src/routes/api/client.js index f2a7649..1e8262c 100644 --- a/src/routes/api/client.js +++ b/src/routes/api/client.js @@ -21,9 +21,10 @@ module.exports.get = () => ({ discriminator: client.user.discriminator, id: client.user.id, portal: process.env.PORTAL || null, + public: !!process.env.PUBLIC, stats: { activatedUsers: users.length, - archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they get deleted + archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they can be deleted avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), categories: await client.prisma.category.count(), From b1965909e69ad9021b7561d1645278f3417b8fc0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 5 Sep 2022 12:52:26 +0100 Subject: [PATCH 109/409] Fix env `PUBLIC` is a path to the Public user dir on Windows, and !! does not convert a string to a boolean as expected --- package.json | 1 + scripts/preinstall.js | 2 +- src/routes/api/client.js | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ef593e2..df90d76 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", "@prisma/client": "^4.1.1", + "boolean": "^3.2.0", "cryptr": "^6.0.3", "discord.js": "^14.1.2", "dotenv": "^16.0.1", diff --git a/scripts/preinstall.js b/scripts/preinstall.js index a6cba15..5085603 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -10,7 +10,7 @@ const env = { HTTP_BIND: 8080, HTTP_EXTERNAL: 'http://localhost:8080', PORTAL: '', - PUBLIC: false, + PUBLIC_BOT: false, SUPER: '319467558166069248', }; diff --git a/src/routes/api/client.js b/src/routes/api/client.js index 1e8262c..40a6a60 100644 --- a/src/routes/api/client.js +++ b/src/routes/api/client.js @@ -1,4 +1,5 @@ const ms = require('ms'); +const { boolean } = require('boolean'); module.exports.get = () => ({ handler: async (req, res) => { @@ -21,7 +22,7 @@ module.exports.get = () => ({ discriminator: client.user.discriminator, id: client.user.id, portal: process.env.PORTAL || null, - public: !!process.env.PUBLIC, + public: boolean(process.env.PUBLIC_BOT), stats: { activatedUsers: users.length, archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they can be deleted From 3c8ba79b87e671a47bc37544e20453c0a35caca4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 6 Sep 2022 00:07:12 +0100 Subject: [PATCH 110/409] Detect theme on first run --- src/lib/tickets/archiver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tickets/archiver.js b/src/lib/tickets/archiver.js index de41890..7db83ef 100644 --- a/src/lib/tickets/archiver.js +++ b/src/lib/tickets/archiver.js @@ -1,5 +1,5 @@ const Cryptr = require('cryptr'); -const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); +// const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class TicketArchiver { constructor(client) { From 3ee972c9ff80f324435188c421b85d427cc97cc4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 6 Sep 2022 00:16:07 +0100 Subject: [PATCH 111/409] fix edit topic log --- src/modals/topic.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modals/topic.js b/src/modals/topic.js index e9af947..db3e458 100644 --- a/src/modals/topic.js +++ b/src/modals/topic.js @@ -29,6 +29,7 @@ module.exports = class TopicModal extends Modal { }, id: true, openingMessageId: true, + topic: true, }; const original = await client.prisma.ticket.findUnique({ select, @@ -41,7 +42,7 @@ module.exports = class TopicModal extends Modal { }); const getMessage = client.i18n.getLocale(ticket.guild.locale); - if (topic) await interaction.channel.setTopic(`<@${ticket.createdById}> | ${topic}`); + if (topic) interaction.channel.setTopic(`<@${ticket.createdById}> | ${topic}`); const opening = await interaction.channel.messages.fetch(ticket.openingMessageId); if (opening && opening.embeds.length >= 2) { From 75be93db77757be2448652e29480fcfc04cfbb22 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 6 Sep 2022 00:07:12 +0100 Subject: [PATCH 112/409] . --- src/lib/tickets/archiver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tickets/archiver.js b/src/lib/tickets/archiver.js index de41890..7db83ef 100644 --- a/src/lib/tickets/archiver.js +++ b/src/lib/tickets/archiver.js @@ -1,5 +1,5 @@ const Cryptr = require('cryptr'); -const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); +// const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class TicketArchiver { constructor(client) { From 98dca5b974020aa2ea8b1b048b43b9483a3425ad Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 6 Sep 2022 00:16:07 +0100 Subject: [PATCH 113/409] fix edit topic log --- src/modals/topic.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modals/topic.js b/src/modals/topic.js index e9af947..db3e458 100644 --- a/src/modals/topic.js +++ b/src/modals/topic.js @@ -29,6 +29,7 @@ module.exports = class TopicModal extends Modal { }, id: true, openingMessageId: true, + topic: true, }; const original = await client.prisma.ticket.findUnique({ select, @@ -41,7 +42,7 @@ module.exports = class TopicModal extends Modal { }); const getMessage = client.i18n.getLocale(ticket.guild.locale); - if (topic) await interaction.channel.setTopic(`<@${ticket.createdById}> | ${topic}`); + if (topic) interaction.channel.setTopic(`<@${ticket.createdById}> | ${topic}`); const opening = await interaction.channel.messages.fetch(ticket.openingMessageId); if (opening && opening.embeds.length >= 2) { From c101562dcc0166ab779063b71de1656ea4523a6d Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 6 Sep 2022 20:30:28 +0100 Subject: [PATCH 114/409] Integrate settings UI --- package.json | 15 ++++++++------- src/http.js | 38 +++++++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index df90d76..1f1fcd4 100644 --- a/package.json +++ b/package.json @@ -41,29 +41,30 @@ "@fastify/cors": "^8.1.0", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", - "@prisma/client": "^4.1.1", + "@prisma/client": "^4.3.1", "boolean": "^3.2.0", "cryptr": "^6.0.3", - "discord.js": "^14.1.2", - "dotenv": "^16.0.1", - "fastify": "^4.3.0", + "discord.js": "^14.3.0", + "dotenv": "^16.0.2", + "express": "^4.18.1", + "fastify": "^4.5.3", "figlet": "^1.5.2", "fs-extra": "^10.1.0", - "keyv": "^4.3.3", + "keyv": "^4.5.0", "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", "ms": "^2.1.3", "node-dir": "^0.1.17", "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", - "prisma": "^4.1.1", + "prisma": "^4.3.1", "semver": "^7.3.7", "terminal-link": "^2.1.1", "yaml": "^1.10.2" }, "devDependencies": { "all-contributors-cli": "^6.20.0", - "eslint": "^8.21.0", + "eslint": "^8.23.0", "eslint-plugin-unused-imports": "^2.0.0", "nodemon": "^2.0.19" } diff --git a/src/http.js b/src/http.js index 8880f06..9466ba7 100644 --- a/src/http.js +++ b/src/http.js @@ -5,7 +5,11 @@ const { short } = require('leeks.js'); const { join } = require('path'); const { files } = require('node-dir'); -module.exports = client => { +process.env.PUBLIC_HOST = process.env.HTTP_EXTERNAL; + +async function build(client) { + // await fastify.register(require('@fastify/express')); + // cors plugin fastify.register(require('@fastify/cors'), { credentials: true, @@ -125,7 +129,6 @@ module.exports = client => { // route loading const dir = join(__dirname, '/routes'); - files(dir, { exclude: /^\./, match: /.js$/, @@ -138,7 +141,6 @@ module.exports = client => { .replace(/\[(\w+)\]/gi, ':$1') // convert [] to : .replace('/index', '') || '/'; // remove index const route = require(file); - Object.keys(route).forEach(method => fastify.route({ config: { client }, method: method.toUpperCase(), @@ -147,9 +149,31 @@ module.exports = client => { })); // register route }); - // start server - fastify.listen({ port: process.env.HTTP_BIND }, (err, addr) => { - if (err) client.log.error.http(err); - else client.log.success.http(`Listening at ${addr}`); + // const { handler: app } = await import('@discord-tickets/settings/build/handler.js'); + // fastify.use('/*', app); + // fastify.use(app); + + return fastify; +} + +module.exports = async client => { + build(client) + .then(fastify => { + // start server + fastify.listen({ port: process.env.HTTP_BIND }, (err, addr) => { + if (err) client.log.error.http(err); + else client.log.success.http(`Listening at ${addr}`); + }); + }) + .catch(client.log.error.http); + + const express = require('express')(); + const { handler } = await import('@discord-tickets/settings/build/handler.js'); + express.get('/api/client', (req, res) => { + res.end('ok'); + }); + express.use(handler); + express.listen(3000, () => { + console.log('listening on port 3000'); }); }; \ No newline at end of file From fdce924b22cc51b68c5bcf24d34cdec1a8b64a13 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 6 Sep 2022 20:46:18 +0100 Subject: [PATCH 115/409] Complete settings integration --- README.md | 4 ++-- scripts/preinstall.js | 5 +++-- src/http.js | 46 +++++++++++++++++-------------------------- src/lib/http.js | 2 +- 4 files changed, 24 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 897d855..18e5312 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ DISCORD_TOKEN= DB_CONNECTION_URL="mysql://test:password@localhost/tickets0" DB_PROVIDER=mysql ENCRYPTION_KEY= -HTTP_BIND=8080 -HTTP_EXTERNAL=http://localhost:8080 +API_BIND=8080 +API_EXTERNAL=http://localhost:8080 PORTAL=http://localhost:3000 SUPER= diff --git a/scripts/preinstall.js b/scripts/preinstall.js index 5085603..fb82431 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -2,15 +2,16 @@ const { randomBytes } = require('crypto'); const fs = require('fs'); const env = { + API_BIND: 8080, + API_EXTERNAL: 'http://localhost:8080', DB_CONNECTION_URL: '', DB_PROVIDER: '', // don't default to sqlite, postinstall checks if empty DISCORD_SECRET: '', DISCORD_TOKEN: '', ENCRYPTION_KEY: randomBytes(24).toString('hex'), - HTTP_BIND: 8080, - HTTP_EXTERNAL: 'http://localhost:8080', PORTAL: '', PUBLIC_BOT: false, + SETTINGS_BIND: 80, SUPER: '319467558166069248', }; diff --git a/src/http.js b/src/http.js index 9466ba7..953714d 100644 --- a/src/http.js +++ b/src/http.js @@ -1,15 +1,12 @@ const fastify = require('fastify')(); const oauth = require('@fastify/oauth2'); -// const { randomBytes } = require('crypto'); const { short } = require('leeks.js'); const { join } = require('path'); const { files } = require('node-dir'); -process.env.PUBLIC_HOST = process.env.HTTP_EXTERNAL; - -async function build(client) { - // await fastify.register(require('@fastify/express')); +process.env.PUBLIC_HOST = process.env.API_EXTERNAL; // the SvelteKit app expects `PUBLIC_HOST` +module.exports = async client => { // cors plugin fastify.register(require('@fastify/cors'), { credentials: true, @@ -19,7 +16,7 @@ async function build(client) { // oauth2 plugin fastify.register(oauth, { - callbackUri: `${process.env.HTTP_EXTERNAL}/auth/callback`, + callbackUri: `${process.env.API_EXTERNAL}/auth/callback`, credentials: { auth: oauth.DISCORD_CONFIGURATION, client: { @@ -41,7 +38,6 @@ async function build(client) { cookieName: 'token', signed: false, }, - // secret: randomBytes(16).toString('hex'), secret: process.env.ENCRYPTION_KEY, }); @@ -49,7 +45,6 @@ async function build(client) { fastify.decorate('authenticate', async (req, res) => { try { const data = await req.jwtVerify(); - // if (data.payload.expiresAt < Date.now()) res.redirect('/auth/login'); if (data.payload.expiresAt < Date.now()) { return res.code(401).send({ error: 'Unauthorised', @@ -121,7 +116,7 @@ async function build(client) { : responseTime >= 5 ? '&e' : '&a') + responseTime + 'ms'; - client.log.info.http(short(`${req.ip} ${req.method} ${req.routerPath ?? '*'} &m-+>&r ${status}&b in ${responseTime}`)); + client.log.info.http(short(`API ${req.ip} ${req.method} ${req.routerPath ?? '*'} &m-+>&r ${status}&b in ${responseTime}`)); done(); }); @@ -129,6 +124,7 @@ async function build(client) { // route loading const dir = join(__dirname, '/routes'); + files(dir, { exclude: /^\./, match: /.js$/, @@ -141,6 +137,7 @@ async function build(client) { .replace(/\[(\w+)\]/gi, ':$1') // convert [] to : .replace('/index', '') || '/'; // remove index const route = require(file); + Object.keys(route).forEach(method => fastify.route({ config: { client }, method: method.toUpperCase(), @@ -149,31 +146,24 @@ async function build(client) { })); // register route }); - // const { handler: app } = await import('@discord-tickets/settings/build/handler.js'); - // fastify.use('/*', app); - // fastify.use(app); - - return fastify; -} - -module.exports = async client => { - build(client) - .then(fastify => { - // start server - fastify.listen({ port: process.env.HTTP_BIND }, (err, addr) => { - if (err) client.log.error.http(err); - else client.log.success.http(`Listening at ${addr}`); - }); - }) - .catch(client.log.error.http); + // start server + fastify.listen({ port: process.env.API_BIND }, (err, addr) => { + if (err) client.log.error.http(err); + else client.log.success.http(`API Listening at ${addr}`); + }); const express = require('express')(); const { handler } = await import('@discord-tickets/settings/build/handler.js'); express.get('/api/client', (req, res) => { res.end('ok'); }); + express.use((req, res, next) => { + next(); + // req.route?.path ?? '*' + client.log.verbose.http(short(`SETTINGS ${req.ip} ${req.method} ${req.path}`)); // verbose because little information & SvelteKit is very spammy (lots of routes) + }); express.use(handler); - express.listen(3000, () => { - console.log('listening on port 3000'); + express.listen(process.env.SETTINGS_BIND, () => { + client.log.success.http(`SETTINGS Listening at ${process.env.SETTINGS_BIND}`); }); }; \ No newline at end of file diff --git a/src/lib/http.js b/src/lib/http.js index 9dbcdf2..aee58e9 100644 --- a/src/lib/http.js +++ b/src/lib/http.js @@ -1 +1 @@ -module.exports.domain = process.env.HTTP_EXTERNAL.match(/http(s?):\/\/(?[a-zA-Z0-9\-_.]+)(:\d+)?/).groups.domain; \ No newline at end of file +module.exports.domain = process.env.API_EXTERNAL.match(/http(s?):\/\/(?[a-zA-Z0-9\-_.]+)(:\d+)?/).groups.domain; \ No newline at end of file From f2d04993fbe1b012ffb50a03521398209983c01f Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 6 Sep 2022 20:54:15 +0100 Subject: [PATCH 116/409] Update README.md --- README.md | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 18e5312..1b30c1d 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,3 @@ -DISCORD_SECRET= -DISCORD_TOKEN= -DB_CONNECTION_URL="mysql://test:password@localhost/tickets0" -DB_PROVIDER=mysql -ENCRYPTION_KEY= -API_BIND=8080 -API_EXTERNAL=http://localhost:8080 -PORTAL=http://localhost:3000 -SUPER= - - https://www.prisma.io/docs/reference/database-reference/supported-databases ![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create, slash/force-close, slash/claim, slash/release, and slash/move @@ -23,14 +12,14 @@ menu question max length cannot be higher than question options creation requires an interaction: -- /new -> category? -> topic or questions -> create -- user:create(self) -> category? -> topic or questions -> create -- user:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create -- message:create(self) -> category? -> topic or questions -> create -- message:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create -- DM -> guild? -> category? -> topic or questions -> create -- panel(interaction) -> topic or questions -> create -- ~~panel(message) -> DM (channel fallback) button -> topic or questions -> create~~ +- [x] /new -> category? -> topic or questions -> create +- [ ] user:create(self) -> category? -> topic or questions -> create +- [ ] user:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create +- [x] message:create(self) -> category? -> topic or questions -> create +- [x] message:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create +- [x] DM -> guild? -> category? -> topic or questions -> create +- [x] panel(interaction) -> topic or questions -> create +- [ ] ~~panel(message) -> DM (channel fallback) button -> topic or questions -> create~~ > **Note** > From 742dedd635277b3568ab30babd1fd8a9af4c5dbb Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 6 Sep 2022 21:32:03 +0100 Subject: [PATCH 117/409] @discord-tickets/settings --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1f1fcd4..a974768 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "node": ">=18.0" }, "dependencies": { + "@discord-tickets/settings": "^1.0.0", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.0.4", From be0795cde01d3cd67595d64abde891e0879fabcd Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 7 Sep 2022 21:24:16 +0100 Subject: [PATCH 118/409] Settings reverse proxy (doesn't work) --- package.json | 2 +- scripts/postinstall.js | 1 - scripts/preinstall.js | 6 ++--- src/http.js | 45 +++++++++++++++++++++---------------- src/lib/http.js | 2 +- src/lib/logger.js | 1 + src/routes/auth/callback.js | 2 +- src/routes/index.js | 1 + src/stdin/settings.js | 14 ++++++++++++ 9 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 src/stdin/settings.js diff --git a/package.json b/package.json index a974768..9ece98f 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,12 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.0.0", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.0.4", "@fastify/cookie": "^6.0.0", "@fastify/cors": "^8.1.0", + "@fastify/http-proxy": "^8.2.2", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", "@prisma/client": "^4.3.1", diff --git a/scripts/postinstall.js b/scripts/postinstall.js index bd0cdc5..3b541d1 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -2,7 +2,6 @@ require('dotenv').config(); const fs = require('fs-extra'); const { spawnSync } = require('child_process'); - const providers = ['mysql', 'postgresql', 'sqlite']; const provider = process.env.DB_PROVIDER; diff --git a/scripts/preinstall.js b/scripts/preinstall.js index fb82431..10fd114 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -2,16 +2,16 @@ const { randomBytes } = require('crypto'); const fs = require('fs'); const env = { - API_BIND: 8080, - API_EXTERNAL: 'http://localhost:8080', DB_CONNECTION_URL: '', DB_PROVIDER: '', // don't default to sqlite, postinstall checks if empty DISCORD_SECRET: '', DISCORD_TOKEN: '', ENCRYPTION_KEY: randomBytes(24).toString('hex'), + HTTP_BIND: 8080, + HTTP_EXTERNAL: 'http://localhost', PORTAL: '', PUBLIC_BOT: false, - SETTINGS_BIND: 80, + SETTINGS_BIND: 8888, SUPER: '319467558166069248', }; diff --git a/src/http.js b/src/http.js index 953714d..30355b8 100644 --- a/src/http.js +++ b/src/http.js @@ -4,7 +4,7 @@ const { short } = require('leeks.js'); const { join } = require('path'); const { files } = require('node-dir'); -process.env.PUBLIC_HOST = process.env.API_EXTERNAL; // the SvelteKit app expects `PUBLIC_HOST` +process.env.PUBLIC_HOST = process.env.HTTP_EXTERNAL; // the SvelteKit app expects `PUBLIC_HOST` module.exports = async client => { // cors plugin @@ -16,7 +16,7 @@ module.exports = async client => { // oauth2 plugin fastify.register(oauth, { - callbackUri: `${process.env.API_EXTERNAL}/auth/callback`, + callbackUri: `${process.env.HTTP_EXTERNAL}/auth/callback`, credentials: { auth: oauth.DISCORD_CONFIGURATION, client: { @@ -41,6 +41,14 @@ module.exports = async client => { secret: process.env.ENCRYPTION_KEY, }); + // proxy /settings to express + fastify.register(require('@fastify/http-proxy'), { + http2: false, + prefix: '/settings', + rewritePrefix: '/settings', + upstream: `http://127.0.0.1:${process.env.SETTINGS_BIND}`, + }); + // auth fastify.decorate('authenticate', async (req, res) => { try { @@ -54,6 +62,8 @@ module.exports = async client => { }); } } catch (err) { + console.log(req); + console.log(req.cookies); res.send(err); } }); @@ -116,15 +126,15 @@ module.exports = async client => { : responseTime >= 5 ? '&e' : '&a') + responseTime + 'ms'; - client.log.info.http(short(`API ${req.ip} ${req.method} ${req.routerPath ?? '*'} &m-+>&r ${status}&b in ${responseTime}`)); + client.log.info.http(short(`${req.id} ${req.ip} ${req.method} ${req.routerPath ?? '*'} &m-+>&r ${status}&b in ${responseTime}`)); + if (!req.routerPath) client.log.verbose.http(`${req.id} ${req.method} ${req.url}`); done(); }); - fastify.addHook('onError', async (req, res, err) => client.log.error.http(err)); + fastify.addHook('onError', async (req, res, err) => client.log.error.http(req.id, err)); // route loading const dir = join(__dirname, '/routes'); - files(dir, { exclude: /^\./, match: /.js$/, @@ -146,24 +156,21 @@ module.exports = async client => { })); // register route }); - // start server - fastify.listen({ port: process.env.API_BIND }, (err, addr) => { - if (err) client.log.error.http(err); - else client.log.success.http(`API Listening at ${addr}`); - }); - + // express server for settings const express = require('express')(); const { handler } = await import('@discord-tickets/settings/build/handler.js'); - express.get('/api/client', (req, res) => { - res.end('ok'); - }); express.use((req, res, next) => { next(); - // req.route?.path ?? '*' - client.log.verbose.http(short(`SETTINGS ${req.ip} ${req.method} ${req.path}`)); // verbose because little information & SvelteKit is very spammy (lots of routes) + client.log.verbose.http(short(`Express ${req.ip} ${req.method} ${req.route?.path ?? req.path}`)); }); - express.use(handler); - express.listen(process.env.SETTINGS_BIND, () => { - client.log.success.http(`SETTINGS Listening at ${process.env.SETTINGS_BIND}`); + express.use(handler); // let SvelteKit handle everything + express.listen(process.env.SETTINGS_BIND, () => { // start the express server + client.log.verbose.http(`Express listening on port ${process.env.SETTINGS_BIND}`); + }); + + // start the fastify server + fastify.listen({ port: process.env.HTTP_BIND }, (err, addr) => { + if (err) client.log.error.http(err); + else client.log.success.http(`Listening at ${addr}`); }); }; \ No newline at end of file diff --git a/src/lib/http.js b/src/lib/http.js index aee58e9..9dbcdf2 100644 --- a/src/lib/http.js +++ b/src/lib/http.js @@ -1 +1 @@ -module.exports.domain = process.env.API_EXTERNAL.match(/http(s?):\/\/(?[a-zA-Z0-9\-_.]+)(:\d+)?/).groups.domain; \ No newline at end of file +module.exports.domain = process.env.HTTP_EXTERNAL.match(/http(s?):\/\/(?[a-zA-Z0-9\-_.]+)(:\d+)?/).groups.domain; \ No newline at end of file diff --git a/src/lib/logger.js b/src/lib/logger.js index 9ba59e7..e30e15e 100644 --- a/src/lib/logger.js +++ b/src/lib/logger.js @@ -10,6 +10,7 @@ const colours = { info: ['&3', '&b'], notice: ['&!6&0', '&!6&0'], success: ['&2', '&a'], + verbose: ['&7', '&f'], warn: ['&6', '&e'], }; diff --git a/src/routes/auth/callback.js b/src/routes/auth/callback.js index 400fcb3..328d3a8 100644 --- a/src/routes/auth/callback.js +++ b/src/routes/auth/callback.js @@ -23,6 +23,6 @@ module.exports.get = () => ({ sameSite: true, secure: false, }) - .redirect('/'); + .redirect('/settings'); }, }); \ No newline at end of file diff --git a/src/routes/index.js b/src/routes/index.js index 0a10ed6..203bc5b 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -2,5 +2,6 @@ module.exports.get = () => ({ handler: (req, res) => { const { client } = res.context.config; return `Hello, I am ${client.user.username}!`; + // res.redirect(process.env.SETTINGS_EXTERNAL); }, }); \ No newline at end of file diff --git a/src/stdin/settings.js b/src/stdin/settings.js new file mode 100644 index 0000000..187f972 --- /dev/null +++ b/src/stdin/settings.js @@ -0,0 +1,14 @@ +const { StdinCommand } = require('@eartharoid/dbf'); + +module.exports = class extends StdinCommand { + constructor(client, options) { + super(client, { + ...options, + id: 'settings', + }); + } + + async run() { + this.client.log.info.settings(process.env.HTTP_EXTERNAL + '/settings'); + } +}; \ No newline at end of file From 98c6f7a445c345d0aa967cfcbac485016e11aa8f Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 7 Sep 2022 22:15:46 +0100 Subject: [PATCH 119/409] fix http logging spam, and login loop --- README.md | 2 ++ src/http.js | 12 +++++++++--- src/routes/auth/callback.js | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1b30c1d..2251cd4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ https://www.prisma.io/docs/reference/database-reference/supported-databases menu question max length cannot be higher than question options +ports <1024 require root + - TODO: topic and question answer values not encrypted? - TODO: post stats - TODO: settings bundle download diff --git a/src/http.js b/src/http.js index 30355b8..e216246 100644 --- a/src/http.js +++ b/src/http.js @@ -1,5 +1,6 @@ const fastify = require('fastify')(); const oauth = require('@fastify/oauth2'); +const { domain } = require('./lib/http'); const { short } = require('leeks.js'); const { join } = require('path'); const { files } = require('node-dir'); @@ -45,6 +46,12 @@ module.exports = async client => { fastify.register(require('@fastify/http-proxy'), { http2: false, prefix: '/settings', + replyOptions: { + rewriteRequestHeaders: (req, headers) => ({ + ...headers, + 'host': domain, + }), + }, rewritePrefix: '/settings', upstream: `http://127.0.0.1:${process.env.SETTINGS_BIND}`, }); @@ -62,8 +69,6 @@ module.exports = async client => { }); } } catch (err) { - console.log(req); - console.log(req.cookies); res.send(err); } }); @@ -126,7 +131,8 @@ module.exports = async client => { : responseTime >= 5 ? '&e' : '&a') + responseTime + 'ms'; - client.log.info.http(short(`${req.id} ${req.ip} ${req.method} ${req.routerPath ?? '*'} &m-+>&r ${status}&b in ${responseTime}`)); + const level = req.routerPath.startsWith('/settings') ? 'verbose' : 'info'; + client.log[level].http(short(`${req.id} ${req.ip} ${req.method} ${req.routerPath ?? '*'} &m-+>&r ${status}&b in ${responseTime}`)); if (!req.routerPath) client.log.verbose.http(`${req.id} ${req.method} ${req.url}`); done(); }); diff --git a/src/routes/auth/callback.js b/src/routes/auth/callback.js index 328d3a8..fccb57c 100644 --- a/src/routes/auth/callback.js +++ b/src/routes/auth/callback.js @@ -23,6 +23,8 @@ module.exports.get = () => ({ sameSite: true, secure: false, }) - .redirect('/settings'); + // .redirect('/settings') + .type('text/html') + .send('/settings'); // temp fix: redirecting causes weird discord<->callback loop, probably caching? }, }); \ No newline at end of file From d884789a49ee46efeb1c18a94442a5791a6ce523 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 7 Sep 2022 22:33:55 +0100 Subject: [PATCH 120/409] settings npm --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 9ece98f..3b387dc 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "node": ">=18.0" }, "dependencies": { + "@discord-tickets/settings": "^1.1.0", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.0.4", From 7dd1478de17dff4245db8005ae85b4e31677799b Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 8 Sep 2022 00:54:32 +0100 Subject: [PATCH 121/409] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2251cd4..59a2f02 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ creation requires an interaction: - [ ] user:create(self) -> category? -> topic or questions -> create - [ ] user:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create - [x] message:create(self) -> category? -> topic or questions -> create -- [x] message:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create +- [ ] ~~message:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create~~ - [x] DM -> guild? -> category? -> topic or questions -> create - [x] panel(interaction) -> topic or questions -> create - [ ] ~~panel(message) -> DM (channel fallback) button -> topic or questions -> create~~ From 94c6fc21e7c22d1bc872841fe6e5e9c7c73dc715 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 11 Sep 2022 19:46:53 +0100 Subject: [PATCH 122/409] Question answer & topic encryption --- src/buttons/edit.js | 6 ++++-- src/lib/tickets/manager.js | 6 ++++-- src/modals/questions.js | 8 +++++--- src/modals/topic.js | 6 ++++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/buttons/edit.js b/src/buttons/edit.js index dc82a10..208e027 100644 --- a/src/buttons/edit.js +++ b/src/buttons/edit.js @@ -8,6 +8,8 @@ const { TextInputStyle, } = require('discord.js'); const emoji = require('node-emoji'); +const Cryptr = require('cryptr'); +const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class EditButton extends Button { constructor(client, options) { @@ -52,7 +54,7 @@ module.exports = class EditButton extends Button { .setMinLength(5) .setPlaceholder(getMessage('modals.topic.placeholder')) .setRequired(true) - .setValue(ticket.topic || ''), + .setValue(ticket.topic ? cryptr.decrypt(ticket.topic) : ''), ), ), ); @@ -79,7 +81,7 @@ module.exports = class EditButton extends Button { .setMinLength(a.question.minLength) .setPlaceholder(a.question.placeholder) .setRequired(a.question.required) - .setValue(a.value || a.question.value), + .setValue(a.value ? cryptr.decrypt(a.value) : a.question.value), ); } else if (a.question.type === 'MENU') { return new ActionRowBuilder() diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index cf215d7..17a7618 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -15,6 +15,8 @@ const emoji = require('node-emoji'); const ms = require('ms'); const ExtendedEmbedBuilder = require('../embed'); const { logTicketEvent } = require('../logging'); +const Cryptr = require('cryptr'); +const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); /** * @typedef {import('@prisma/client').Category & @@ -314,7 +316,7 @@ module.exports = class TicketManager { answers = category.questions.map(q => ({ questionId: q.id, userId: interaction.user.id, - value: interaction.fields.getTextInputValue(q.id), + value: interaction.fields.getTextInputValue(q.id) ? cryptr.encrypt(interaction.fields.getTextInputValue(q.id)) : '', })); if (category.customTopic) topic = interaction.fields.getTextInputValue(category.customTopic); } else if (action === 'topic') { @@ -549,7 +551,7 @@ module.exports = class TicketManager { id: channel.id, number, openingMessageId: sent.id, - topic, + topic: topic ? cryptr.encrypt(topic) : null, }; if (referencesTicketId) data.referencesTicket = { connect: { id: referencesTicketId } }; let message; diff --git a/src/modals/questions.js b/src/modals/questions.js index 3f35db1..6996dbb 100644 --- a/src/modals/questions.js +++ b/src/modals/questions.js @@ -2,6 +2,8 @@ const { Modal } = require('@eartharoid/dbf'); const { EmbedBuilder } = require('discord.js'); const ExtendedEmbedBuilder = require('../lib/embed'); const { logTicketEvent } = require('../lib/logging'); +const Cryptr = require('cryptr'); +const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class QuestionsModal extends Modal { constructor(client, options) { @@ -49,7 +51,7 @@ module.exports = class QuestionsModal extends Modal { data: { questionAnswers: { update: interaction.fields.fields.map(f => ({ - data: { value: f.value }, + data: { value: f.value ? cryptr.encrypt(f.value) : '' }, where: { id: Number(f.customId) }, })), }, @@ -70,7 +72,7 @@ module.exports = class QuestionsModal extends Modal { ticket.questionAnswers .map(a => ({ name: a.question.label, - value: a.value || getMessage('ticket.answers.no_value'), + value: a.value ? cryptr.decrypt(a.value) : getMessage('ticket.answers.no_value'), })), ); await opening.edit({ embeds }); @@ -92,7 +94,7 @@ module.exports = class QuestionsModal extends Modal { const makeDiff = ticket => { const diff = {}; ticket.questionAnswers.forEach(a => { - diff[a.question.label] = a.value || getMessage('ticket.answers.no_value'); + diff[a.question.label] = a.value ? cryptr.decrypt(a.value) : getMessage('ticket.answers.no_value'); }); return diff; }; diff --git a/src/modals/topic.js b/src/modals/topic.js index db3e458..d0bb08d 100644 --- a/src/modals/topic.js +++ b/src/modals/topic.js @@ -2,6 +2,8 @@ const { Modal } = require('@eartharoid/dbf'); const { EmbedBuilder } = require('discord.js'); const ExtendedEmbedBuilder = require('../lib/embed'); const { logTicketEvent } = require('../lib/logging'); +const Cryptr = require('cryptr'); +const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class TopicModal extends Modal { constructor(client, options) { @@ -36,7 +38,7 @@ module.exports = class TopicModal extends Modal { where: { id: interaction.channel.id }, }); const ticket = await client.prisma.ticket.update({ - data: { topic }, + data: { topic: topic ? cryptr.encrypt(topic) : null }, select, where: { id: interaction.channel.id }, }); @@ -70,7 +72,7 @@ module.exports = class TopicModal extends Modal { /** @param {ticket} ticket */ const makeDiff = ticket => { const diff = {}; - diff[getMessage('ticket.opening_message.fields.topic')] = ticket.topic; + diff[getMessage('ticket.opening_message.fields.topic')] = ticket.topic ? cryptr.decrypt(ticket.topic) : ' '; return diff; }; From 50d7e016ed689678b639ab8cb853d431241d4fd2 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 11 Sep 2022 19:55:41 +0100 Subject: [PATCH 123/409] Use correct blurple --- db/mysql/schema.prisma | 2 +- db/postgresql/schema.prisma | 2 +- db/sqlite/schema.prisma | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index 3c07cf7..fa22ce6 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -36,7 +36,7 @@ model ArchivedMessage { model ArchivedRole { archivedUsers ArchivedUser[] - colour String @default("7289DA") @db.Char(6) + colour String @default("5865F2") @db.Char(6) // 7289DA createdAt DateTime @default(now()) name String roleId String @db.VarChar(19) diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index faf816a..c20cd83 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -36,7 +36,7 @@ model ArchivedMessage { model ArchivedRole { archivedUsers ArchivedUser[] - colour String @default("7289DA") @db.Char(6) + colour String @default("5865F2") @db.Char(6) // 7289DA createdAt DateTime @default(now()) name String roleId String @db.VarChar(19) diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma index 6213988..f82e005 100644 --- a/db/sqlite/schema.prisma +++ b/db/sqlite/schema.prisma @@ -36,7 +36,7 @@ model ArchivedMessage { model ArchivedRole { archivedUsers ArchivedUser[] - colour String @default("7289DA") + colour String @default("5865F2") // 7289DA createdAt DateTime @default(now()) name String roleId String From 7eabce1a832441230fce65eb0157ca7bd7aced68 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 11 Sep 2022 21:11:12 +0100 Subject: [PATCH 124/409] Add stats posting --- README.md | 4 --- package.json | 1 + src/index.js | 2 +- src/lib/misc.js | 1 + src/listeners/client/ready.js | 49 +++++++++++++++++++++++++++++++++++ user/example.config.yml | 3 ++- 6 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 src/lib/misc.js diff --git a/README.md b/README.md index 59a2f02..9829ffe 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,7 @@ menu question max length cannot be higher than question options ports <1024 require root -- TODO: topic and question answer values not encrypted? -- TODO: post stats -- TODO: settings bundle download - TODO: update notifications -- TODO: check inline to-dos creation requires an interaction: diff --git a/package.json b/package.json index 3b387dc..280ff73 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "keyv": "^4.5.0", "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", + "md5": "^2.3.0", "ms": "^2.1.3", "node-dir": "^0.1.17", "node-emoji": "^1.11.0", diff --git a/src/index.js b/src/index.js index 35b5791..9a71ee7 100644 --- a/src/index.js +++ b/src/index.js @@ -63,7 +63,7 @@ const config = YAML.parse(fs.readFileSync('./user/config.yml', 'utf8')); const log = logger(config); process.on('unhandledRejection', error => { - log.notice(`Discord Tickets v${pkg.version} on Node.js v${process.versions.node} (${process.platform})`); + log.notice(`Discord Tickets v${pkg.version} on Node.js ${process.version} (${process.platform})`); log.notice('An error was not caught'); if (error instanceof Error) log.warn(`Uncaught ${error.name}`); log.error(error); diff --git a/src/lib/misc.js b/src/lib/misc.js new file mode 100644 index 0000000..8b0d15a --- /dev/null +++ b/src/lib/misc.js @@ -0,0 +1 @@ +module.exports.msToMins = ms => Number((ms / 1000 / 60).toFixed(1)); \ No newline at end of file diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 9287648..ca75621 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -1,5 +1,8 @@ const { Listener } = require('@eartharoid/dbf'); +const md5 = require('md5'); const ms = require('ms'); +const { version } = require('../../../package.json'); +const { msToMins } = require('../../lib/misc'); module.exports = class extends Listener { constructor(client, options) { @@ -114,5 +117,51 @@ module.exports = class extends Listener { setPresence(); if (client.config.presence.activities.length > 1) setInterval(() => setPresence(), client.config.presence.interval * 1000); + + if (client.config.stats) { + const send = async () => { + const tickets = await client.prisma.ticket.findMany({ + select: { + createdAt: true, + firstResponseAt: true, + }, + }); + const closedTickets = tickets.filter(t => t.closedAt); + const users = await client.prisma.user.findMany({ select: { messageCount: true } }); + const stats = { + activated_users: users.length, + arch: process.arch, + avg_resolution_time: msToMins(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + avg_response_time: msToMins(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + categories: await client.prisma.category.count(), + database: process.env.DB_PROVIDER, + guilds: client.guilds.cache.size, + id: md5(client.user.id), + members: client.guilds.cache.reduce((t, g) => t + g.memberCount, 0), + messages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they can be deleted, + node: process.version, + os: process.platform, + tags: await client.prisma.tag.count(), + tickets: tickets.length, + version, + }; + try { + const res = await fetch('https://stats.discordtickets.app/api/v3/houston', { + body: JSON.stringify(stats), + headers: { 'content-type': 'application/json' }, + method: 'POST', + }); + if (!res.ok) throw res; + client.log.success('Posted client stats'); + client.log.verbose(stats); + client.log.debug(res); + } catch (error) { + client.log.error('An error occurred whilst posting stats', stats, error); + } + }; + + send(); + setInterval(() => send(), ms('12h')); + } } }; diff --git a/user/example.config.yml b/user/example.config.yml index f549d7a..b823c24 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -31,4 +31,5 @@ presence: interval: 20 # seconds, only used if activities.length > 1 status: online # online|idle|invisible|dnd overrides: - disableArchives: false \ No newline at end of file + disableArchives: false +stats: true \ No newline at end of file From 00b85cd5dd0e92b56cd926f159ab78d1ddaac8a5 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 12 Sep 2022 13:50:08 +0100 Subject: [PATCH 125/409] Add `avatars` and `uploads` directories --- .gitignore | 4 +++- user/avatars/.gitkeep | 0 user/uploads/.gitkeep | 0 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 user/avatars/.gitkeep create mode 100644 user/uploads/.gitkeep diff --git a/.gitignore b/.gitignore index cba93c1..278566d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ prisma/ *.db-journal *.log *-lock.* -user/config.yml \ No newline at end of file +user/config.yml +user/**/*.* +!user/**/.gitkeep \ No newline at end of file diff --git a/user/avatars/.gitkeep b/user/avatars/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/user/uploads/.gitkeep b/user/uploads/.gitkeep new file mode 100644 index 0000000..e69de29 From 9d46abcbb32569c7b7c8de10bb155944548a6860 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 26 Sep 2022 16:39:32 +0100 Subject: [PATCH 126/409] Improve logging --- .eslintrc.json | 2 +- src/lib/logger.js | 2 +- src/listeners/autocomplete/componentLoad.js | 2 +- src/listeners/autocomplete/run.js | 19 +++++++++++++++++++ src/listeners/buttons/componentLoad.js | 2 +- src/listeners/buttons/run.js | 19 +++++++++++++++++++ src/listeners/menus/componentLoad.js | 2 +- src/listeners/menus/run.js | 19 +++++++++++++++++++ src/listeners/modals/componentLoad.js | 2 +- src/listeners/modals/run.js | 19 +++++++++++++++++++ src/listeners/stdin/unknown.js | 2 +- 11 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 src/listeners/autocomplete/run.js create mode 100644 src/listeners/buttons/run.js create mode 100644 src/listeners/menus/run.js create mode 100644 src/listeners/modals/run.js diff --git a/.eslintrc.json b/.eslintrc.json index dc66be5..0e95594 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -127,7 +127,7 @@ "off" ], "no-console": [ - "off" + "warn" ], "no-prototype-builtins": [ "off" diff --git a/src/lib/logger.js b/src/lib/logger.js index e30e15e..a0af18a 100644 --- a/src/lib/logger.js +++ b/src/lib/logger.js @@ -38,7 +38,7 @@ module.exports = config => { } return new Logger({ - namespaces: ['commands', 'http', 'listeners', 'settings', 'tickets'], + namespaces: ['autocomplete', 'buttons', 'commands', 'http', 'listeners', 'menus', 'modals', 'settings', 'tickets'], transports, }); }; diff --git a/src/listeners/autocomplete/componentLoad.js b/src/listeners/autocomplete/componentLoad.js index 26e81b0..04a1743 100644 --- a/src/listeners/autocomplete/componentLoad.js +++ b/src/listeners/autocomplete/componentLoad.js @@ -10,7 +10,7 @@ module.exports = class extends Listener { } run(autocompleter) { - this.client.log.info(`Loaded "${autocompleter.id}" autocompleter`); + this.client.log.info.autocomplete(`Loaded "${autocompleter.id}" autocompleter`); return true; } }; diff --git a/src/listeners/autocomplete/run.js b/src/listeners/autocomplete/run.js new file mode 100644 index 0000000..e691629 --- /dev/null +++ b/src/listeners/autocomplete/run.js @@ -0,0 +1,19 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.autocomplete, + event: 'run', + }); + } + + run({ + autocompleter, + interaction, + }) { + this.client.log.verbose.autocomplete(`${interaction.user.tag} used the "${autocompleter.id}" autocompleter`); + return true; + } +}; diff --git a/src/listeners/buttons/componentLoad.js b/src/listeners/buttons/componentLoad.js index 246948d..2bcc5c2 100644 --- a/src/listeners/buttons/componentLoad.js +++ b/src/listeners/buttons/componentLoad.js @@ -10,7 +10,7 @@ module.exports = class extends Listener { } run(button) { - this.client.log.info(`Loaded "${button.id}" button`); + this.client.log.info.buttons(`Loaded "${button.id}" button`); return true; } }; diff --git a/src/listeners/buttons/run.js b/src/listeners/buttons/run.js new file mode 100644 index 0000000..d33167e --- /dev/null +++ b/src/listeners/buttons/run.js @@ -0,0 +1,19 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.buttons, + event: 'run', + }); + } + + run({ + button, + interaction, + }) { + this.client.log.info.buttons(`${interaction.user.tag} used the "${button.id}" button`); + return true; + } +}; diff --git a/src/listeners/menus/componentLoad.js b/src/listeners/menus/componentLoad.js index 03290de..4e1c3d3 100644 --- a/src/listeners/menus/componentLoad.js +++ b/src/listeners/menus/componentLoad.js @@ -10,7 +10,7 @@ module.exports = class extends Listener { } run(menu) { - this.client.log.info(`Loaded "${menu.id}" menu`); + this.client.log.info.menus(`Loaded "${menu.id}" menu`); return true; } }; diff --git a/src/listeners/menus/run.js b/src/listeners/menus/run.js new file mode 100644 index 0000000..ab1fba2 --- /dev/null +++ b/src/listeners/menus/run.js @@ -0,0 +1,19 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.menus, + event: 'run', + }); + } + + run({ + menu, + interaction, + }) { + this.client.log.info.menus(`${interaction.user.tag} used the "${menu.id}" menu`); + return true; + } +}; diff --git a/src/listeners/modals/componentLoad.js b/src/listeners/modals/componentLoad.js index a9454e2..cccd669 100644 --- a/src/listeners/modals/componentLoad.js +++ b/src/listeners/modals/componentLoad.js @@ -10,7 +10,7 @@ module.exports = class extends Listener { } run(modal) { - this.client.log.info(`Loaded "${modal.id}" modal`); + this.client.log.info.modals(`Loaded "${modal.id}" modal`); return true; } }; diff --git a/src/listeners/modals/run.js b/src/listeners/modals/run.js new file mode 100644 index 0000000..87003e0 --- /dev/null +++ b/src/listeners/modals/run.js @@ -0,0 +1,19 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.modals, + event: 'run', + }); + } + + run({ + modal, + interaction, + }) { + this.client.log.info.modals(`${interaction.user.tag} used the "${modal.id}" modal`); + return true; + } +}; diff --git a/src/listeners/stdin/unknown.js b/src/listeners/stdin/unknown.js index d68e27a..976ce95 100644 --- a/src/listeners/stdin/unknown.js +++ b/src/listeners/stdin/unknown.js @@ -10,6 +10,6 @@ module.exports = class extends Listener { } run(commandName) { - console.log('Unknown command:', commandName); + this.client.log.warn('Unknown command:', commandName); } }; From 10ef6bb6176a6f8794bc5770c93875e77cb15225 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 29 Sep 2022 16:08:46 +0100 Subject: [PATCH 127/409] Add pin command --- src/commands/message/pin.js | 60 ++++++++++++++++++++++++++++++++++++- src/i18n/en-GB.yml | 11 +++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/commands/message/pin.js b/src/commands/message/pin.js index fbcfb05..be09d7e 100644 --- a/src/commands/message/pin.js +++ b/src/commands/message/pin.js @@ -1,4 +1,5 @@ const { MessageCommand } = require('@eartharoid/dbf'); +const ExtendedEmbedBuilder = require('../../lib/embed'); module.exports = class PinMessageCommand extends MessageCommand { constructor(client, options) { @@ -13,5 +14,62 @@ module.exports = class PinMessageCommand extends MessageCommand { }); } - async run(interaction) { } + /** + * @param {import("discord.js").MessageContextMenuCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply({ ephemeral: true }); + const ticket = await client.prisma.ticket.findUnique({ + include: { guild: true }, + where: { id: interaction.channel.id }, + }); + + if (!ticket) { + const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + const getMessage = client.i18n.getLocale(settings.locale); + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('commands.message.pin.not_ticket.title')) + .setDescription(getMessage('commands.message.pin.not_ticket.description')), + ], + }); + } + + const getMessage = client.i18n.getLocale(ticket.guild.locale); + + if (!interaction.targetMessage.pinnable) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setTitle(getMessage('commands.message.pin.not_pinnable.title')) + .setDescription(getMessage('commands.message.pin.not_pinnable.description')), + ], + }); + } + + await interaction.targetMessage.pin(); + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.successColour) + .setTitle(getMessage('commands.message.pin.pinned.title')) + .setDescription(getMessage('commands.message.pin.pinned.description')), + ], + }); + } }; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 8a66dbd..a5aedf9 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -23,6 +23,17 @@ commands: name: Create a ticket from message pin: name: Pin message + not_pinnable: + description: | + This message can't be pinned. + Please ask an administrator to check the bot's permissions. + title: ❌ Error + not_ticket: + description: You can only pin messages in tickets. + title: ❌ This isn't a ticket channel + pinned: + description: The message has been pinned. + title: ✅ Pinned message slash: add: description: Add a member to a ticket From 8e0d9a6a5dc0d32deadf6c72c602e30ce5c14bfc Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 30 Sep 2022 16:05:28 +0100 Subject: [PATCH 128/409] Add `sync` stdin command --- src/lib/sync.js | 63 +++++++++++++++++++++++++++++++++++ src/listeners/client/ready.js | 62 ++-------------------------------- src/stdin/sync.js | 15 +++++++++ 3 files changed, 81 insertions(+), 59 deletions(-) create mode 100644 src/lib/sync.js create mode 100644 src/stdin/sync.js diff --git a/src/lib/sync.js b/src/lib/sync.js new file mode 100644 index 0000000..ff30a66 --- /dev/null +++ b/src/lib/sync.js @@ -0,0 +1,63 @@ +/** + * @param {import("client")} client + */ +module.exports = async client => { + // load total number of open tickets + const categories = await client.prisma.category.findMany({ + select: { + cooldown: true, + id: true, + tickets: { + select: { + createdById: true, + guildId: true, + id: true, + }, + where: { open: true }, + }, + }, + }); + let deleted = 0; + let ticketCount = 0; + let cooldowns = 0; + for (const category of categories) { + ticketCount += category.tickets.length; + client.tickets.$.categories[category.id] = { total: category.tickets.length }; + for (const ticket of category.tickets) { + if (client.tickets.$.categories[category.id][ticket.createdById]) client.tickets.$.categories[category.id][ticket.createdById]++; + else client.tickets.$.categories[category.id][ticket.createdById] = 1; + /** @type {import("discord.js").Guild} */ + const guild = client.guilds.cache.get(ticket.guildId); + if (guild && guild.available && !client.channels.cache.has(ticket.id)) { + deleted += 0; + await client.tickets.close(ticket.id); + } + + } + if (category.cooldown) { + const recent = await client.prisma.ticket.findMany({ + orderBy: { createdAt: 'asc' }, + select: { + createdAt: true, + createdById: true, + }, + where: { + categoryId: category.id, + createdAt: { gt: new Date(Date.now() - category.cooldown) }, + }, + }); + cooldowns += recent.length; + for (const ticket of recent) { + const cacheKey = `cooldowns/category-member:${category.id}-${ticket.createdById}`; + const expiresAt = ticket.createdAt.getTime() + category.cooldown; + const TTL = expiresAt - Date.now(); + await client.keyv.set(cacheKey, expiresAt, TTL); + } + } + } + // const ticketCount = categories.reduce((total, category) => total + category.tickets.length, 0); + client.log.info(`Cached ticket count of ${categories.length} categories (${ticketCount} open tickets)`); + client.log.info(`Loaded ${cooldowns} active cooldowns`); + client.log.info(`Closed ${deleted} deleted tickets`); + return true; +}; \ No newline at end of file diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index ca75621..eef6218 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -3,6 +3,7 @@ const md5 = require('md5'); const ms = require('ms'); const { version } = require('../../../package.json'); const { msToMins } = require('../../lib/misc'); +const sync = require('../../lib/sync'); module.exports = class extends Listener { constructor(client, options) { @@ -22,63 +23,7 @@ module.exports = class extends Listener { process.title = 'tickets'; client.log.success('Connected to Discord as "%s"', client.user.tag); - // load total number of open tickets - const categories = await client.prisma.category.findMany({ - select: { - cooldown: true, - id: true, - tickets: { - select: { - createdById: true, - guildId: true, - id: true, - }, - where: { open: true }, - }, - }, - }); - let deleted = 0; - let ticketCount = 0; - let cooldowns = 0; - for (const category of categories) { - ticketCount += category.tickets.length; - client.tickets.$.categories[category.id] = { total: category.tickets.length }; - for (const ticket of category.tickets) { - if (client.tickets.$.categories[category.id][ticket.createdById]) client.tickets.$.categories[category.id][ticket.createdById]++; - else client.tickets.$.categories[category.id][ticket.createdById] = 1; - /** @type {import("discord.js").Guild} */ - const guild = client.guilds.cache.get(ticket.guildId); - if (guild && guild.available && !client.channels.cache.has(ticket.id)) { - deleted += 0; - await client.tickets.close(ticket.id); - } - - } - if (category.cooldown) { - const recent = await client.prisma.ticket.findMany({ - orderBy: { createdAt: 'asc' }, - select: { - createdAt: true, - createdById: true, - }, - where: { - categoryId: category.id, - createdAt: { gt: new Date(Date.now() - category.cooldown) }, - }, - }); - cooldowns += recent.length; - for (const ticket of recent) { - const cacheKey = `cooldowns/category-member:${category.id}-${ticket.createdById}`; - const expiresAt = ticket.createdAt.getTime() + category.cooldown; - const TTL = expiresAt - Date.now(); - await client.keyv.set(cacheKey, expiresAt, TTL); - } - } - } - // const ticketCount = categories.reduce((total, category) => total + category.tickets.length, 0); - client.log.info(`Cached ticket count of ${categories.length} categories (${ticketCount} open tickets)`); - client.log.info(`Loaded ${cooldowns} active cooldowns`); - client.log.info(`Closed ${deleted} deleted tickets`); + await sync(client); // presence/activity let next = 0; @@ -114,10 +59,10 @@ module.exports = class extends Listener { next++; if (next === client.config.presence.activities.length) next = 0; }; - setPresence(); if (client.config.presence.activities.length > 1) setInterval(() => setPresence(), client.config.presence.interval * 1000); + // stats posting if (client.config.stats) { const send = async () => { const tickets = await client.prisma.ticket.findMany({ @@ -159,7 +104,6 @@ module.exports = class extends Listener { client.log.error('An error occurred whilst posting stats', stats, error); } }; - send(); setInterval(() => send(), ms('12h')); } diff --git a/src/stdin/sync.js b/src/stdin/sync.js new file mode 100644 index 0000000..b022b93 --- /dev/null +++ b/src/stdin/sync.js @@ -0,0 +1,15 @@ +const { StdinCommand } = require('@eartharoid/dbf'); +const sync = require('../lib/sync'); + +module.exports = class extends StdinCommand { + constructor(client, options) { + super(client, { + ...options, + id: 'sync', + }); + } + + async run() { + await sync(this.client); + } +}; \ No newline at end of file From 9682fcf22b41abb71d4b8fda2a9718c4abd5d3f0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 30 Sep 2022 16:58:31 +0100 Subject: [PATCH 129/409] trust proxy --- src/http.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/http.js b/src/http.js index e216246..422b2cc 100644 --- a/src/http.js +++ b/src/http.js @@ -1,4 +1,4 @@ -const fastify = require('fastify')(); +const fastify = require('fastify')({ trustProxy: true }); const oauth = require('@fastify/oauth2'); const { domain } = require('./lib/http'); const { short } = require('leeks.js'); @@ -165,6 +165,7 @@ module.exports = async client => { // express server for settings const express = require('express')(); const { handler } = await import('@discord-tickets/settings/build/handler.js'); + express.set('trust proxy', true); express.use((req, res, next) => { next(); client.log.verbose.http(short(`Express ${req.ip} ${req.method} ${req.route?.path ?? req.path}`)); From 460138fb7340e3820deee4850e5b5805f56874ba Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 30 Sep 2022 17:09:35 +0100 Subject: [PATCH 130/409] Progress on message archiving --- db/mysql/schema.prisma | 62 +++---- db/postgresql/schema.prisma | 62 +++---- db/sqlite/schema.prisma | 60 +++---- src/commands/message/create.js | 1 - src/lib/tickets/archiver.js | 248 +++++++++++++++++++++++++- src/lib/tickets/manager.js | 79 +++++--- src/listeners/client/messageCreate.js | 33 +++- 7 files changed, 416 insertions(+), 129 deletions(-) diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index fa22ce6..a3e1490 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -22,7 +22,7 @@ model ArchivedChannel { model ArchivedMessage { author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) authorId String @db.VarChar(19) - content Json + content String @db.Text createdAt DateTime @default(now()) deleted Boolean @default(false) edited Boolean @default(false) @@ -50,17 +50,17 @@ model ArchivedRole { model ArchivedUser { archivedMessages ArchivedMessage[] - avatar String + avatar String? bot Boolean @default(false) createdAt DateTime @default(now()) - discriminator String @db.Char(4) - displayName String @db.Text - role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) - roleId String @db.VarChar(19) + discriminator String? @db.Char(4) + displayName String? @db.Text + role ArchivedRole? @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) + roleId String? @db.VarChar(19) ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @db.VarChar(19) userId String @db.VarChar(19) - username String @db.Text + username String? @db.Text @@id([ticketId, userId]) @@unique([ticketId, userId]) @@ -68,30 +68,30 @@ model ArchivedUser { } model Category { - channelName String - claiming Boolean @default(false) - createdAt DateTime @default(now()) - cooldown Int? - customTopic String? - description String - discordCategory String @db.VarChar(19) - emoji String - enableFeedback Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) - guildId String @db.VarChar(19) - id Int @id @default(autoincrement()) - image String? - memberLimit Int @default(1) - name String - openingMessage String @db.Text - pingRoles Json @default("[]") - questions Question[] - ratelimit Int? - requiredRoles Json @default("[]") - requireTopic Boolean @default(false) - staffRoles Json - tickets Ticket[] - totalLimit Int @default(50) + channelName String + claiming Boolean @default(false) + createdAt DateTime @default(now()) + cooldown Int? + customTopic String? + description String + discordCategory String @db.VarChar(19) + emoji String + enableFeedback Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + image String? + memberLimit Int @default(1) + name String + openingMessage String @db.Text + pingRoles Json @default("[]") + questions Question[] + ratelimit Int? + requiredRoles Json @default("[]") + requireTopic Boolean @default(false) + staffRoles Json + tickets Ticket[] + totalLimit Int @default(50) @@map("categories") } diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index c20cd83..c711a95 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -22,7 +22,7 @@ model ArchivedChannel { model ArchivedMessage { author ArchivedUser @relation(fields: [ticketId, authorId], references: [ticketId, userId], onDelete: Cascade) authorId String @db.VarChar(19) - content Json + content String @db.Text createdAt DateTime @default(now()) deleted Boolean @default(false) edited Boolean @default(false) @@ -50,17 +50,17 @@ model ArchivedRole { model ArchivedUser { archivedMessages ArchivedMessage[] - avatar String + avatar String? bot Boolean @default(false) createdAt DateTime @default(now()) - discriminator String @db.Char(4) - displayName String @db.Text - role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) - roleId String @db.VarChar(19) + discriminator String? @db.Char(4) + displayName String? @db.Text + role ArchivedRole? @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) + roleId String? @db.VarChar(19) ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @db.VarChar(19) userId String @db.VarChar(19) - username String @db.Text + username String? @db.Text @@id([ticketId, userId]) @@unique([ticketId, userId]) @@ -68,30 +68,30 @@ model ArchivedUser { } model Category { - channelName String - claiming Boolean @default(false) - createdAt DateTime @default(now()) - cooldown Int? - customTopic String? - description String - discordCategory String @db.VarChar(19) - emoji String - enableFeedback Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) - guildId String @db.VarChar(19) - id Int @id @default(autoincrement()) - image String? - memberLimit Int @default(1) - name String - openingMessage String @db.Text - pingRoles Json @default("[]") - questions Question[] - ratelimit Int? - requiredRoles Json @default("[]") - requireTopic Boolean @default(false) - staffRoles Json - tickets Ticket[] - totalLimit Int @default(50) + channelName String + claiming Boolean @default(false) + createdAt DateTime @default(now()) + cooldown Int? + customTopic String? + description String + discordCategory String @db.VarChar(19) + emoji String + enableFeedback Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String @db.VarChar(19) + id Int @id @default(autoincrement()) + image String? + memberLimit Int @default(1) + name String + openingMessage String @db.Text + pingRoles Json @default("[]") + questions Question[] + ratelimit Int? + requiredRoles Json @default("[]") + requireTopic Boolean @default(false) + staffRoles Json + tickets Ticket[] + totalLimit Int @default(50) @@map("categories") } diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma index f82e005..2a45cf5 100644 --- a/db/sqlite/schema.prisma +++ b/db/sqlite/schema.prisma @@ -50,17 +50,17 @@ model ArchivedRole { model ArchivedUser { archivedMessages ArchivedMessage[] - avatar String + avatar String? bot Boolean @default(false) createdAt DateTime @default(now()) - discriminator String - displayName String - role ArchivedRole @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) - roleId String + discriminator String? + displayName String? + role ArchivedRole? @relation(fields: [ticketId, roleId], references: [ticketId, roleId], onDelete: Cascade) + roleId String? ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String userId String - username String + username String? @@id([ticketId, userId]) @@unique([ticketId, userId]) @@ -68,30 +68,30 @@ model ArchivedUser { } model Category { - channelName String - claiming Boolean @default(false) - createdAt DateTime @default(now()) - cooldown Int? - customTopic String? - description String - discordCategory String - emoji String - enableFeedback Boolean @default(false) - guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) - guildId String - id Int @id @default(autoincrement()) - image String? - memberLimit Int @default(1) - name String - openingMessage String - pingRoles String @default("[]") - questions Question[] - ratelimit Int? - requiredRoles String @default("[]") - requireTopic Boolean @default(false) - staffRoles String - tickets Ticket[] - totalLimit Int @default(50) + channelName String + claiming Boolean @default(false) + createdAt DateTime @default(now()) + cooldown Int? + customTopic String? + description String + discordCategory String + emoji String + enableFeedback Boolean @default(false) + guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) + guildId String + id Int @id @default(autoincrement()) + image String? + memberLimit Int @default(1) + name String + openingMessage String + pingRoles String @default("[]") + questions Question[] + ratelimit Int? + requiredRoles String @default("[]") + requireTopic Boolean @default(false) + staffRoles String + tickets Ticket[] + totalLimit Int @default(50) @@map("categories") } diff --git a/src/commands/message/create.js b/src/commands/message/create.js index cda68bd..51dc5cf 100644 --- a/src/commands/message/create.js +++ b/src/commands/message/create.js @@ -18,7 +18,6 @@ module.exports = class CreateMessageCommand extends MessageCommand { * @param {import("discord.js").MessageContextMenuCommandInteraction} interaction */ async run(interaction) { - // TODO: archive message await useGuild(this.client, interaction, { referencesMessage: interaction.targetMessage.channelId + '/' + interaction.targetId }); } }; \ No newline at end of file diff --git a/src/lib/tickets/archiver.js b/src/lib/tickets/archiver.js index 7db83ef..7756976 100644 --- a/src/lib/tickets/archiver.js +++ b/src/lib/tickets/archiver.js @@ -1,11 +1,255 @@ const Cryptr = require('cryptr'); -// const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); +const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); + +/** + * Returns highest (roles.highest) hoisted role , or everyone + * @param {import("discord.js").GuildMember} member + * @returns {import("discord.js").Role} + */ +const hoistedRole = member => member.roles.hoist || member.guild.roles.everyone; module.exports = class TicketArchiver { constructor(client) { /** @type {import("client")} */ this.client = client; + this.encrypt = cryptr.encrypt; + this.decrypt = cryptr.decrypt; } - async addMessage() {} + /** Add or update a message + * @param {string} ticketId + * @param {import("discord.js").Message} message + * @param {boolean?} external + * @returns {import("@prisma/client").ArchivedMessage|boolean} + */ + async saveMessage(ticketId, message, external = false) { + if (this.client.config.overrides.disableArchives) return false; + + if (!message.member) { + try { + message.member = await message.guild.members.fetch(message.author.id); + } catch { + this.client.log.verbose('Failed to fetch member %s of %s', message.author.id, message.guild.id); + } + } + + const channels = message.mentions.channels; + const members = [...message.mentions.members]; + const roles = [...message.mentions.roles]; + + if (message.member) { + members.push(message.member); + roles.push(hoistedRole(message.member)); + } else { + this.client.log.warn('Message member does not exist'); + await this.client.prisma.archivedUser.upsert({ + create: {}, + update: {}, + where: { + ticketId_userId: { + ticketId, + userId: 'default', + }, + }, + }); + } + + for (const role of roles) { + const data = { + colour: role.hexColor.slice(1), + name: role.name, + roleId: role.id, + ticket: { connect: { id: ticketId } }, + }; + await this.client.prisma.archivedRole.upsert({ + create: data, + update: data, + where: { + ticketId_roleId: { + roleId: role.id, + ticketId, + }, + }, + }); + } + + for (const member of members) { + const data = { + avatar: member.avatar || member.user.avatar, // TODO: save avatar in user/avatars/ + bot: member.user.bot, + discriminator: member.user.discriminator, + displayName: member.displayName ? this.encrypt(member.displayName) : null, + roleId: !!member && hoistedRole(member).id, + ticketId, + userId: member.user.id, + username: this.encrypt(member.user.username), + }; + await this.client.prisma.archivedUser.upsert({ + create: data, + update: data, + where: { + ticketId_userId: { + ticketId, + userId: member.user.id, + }, + }, + }); + } + + const messageD = { + author: { + connect: { + ticketId_userId: { + ticketId, + userId: message.author?.id || 'default', + }, + }, + }, + content: cryptr.encrypt( + JSON.stringify({ + attachments: [...message.attachments.values()], + components: [...message.components.values()], + content: message.content, + embeds: message.embeds.map(embed => ({ embed })), + }), + ), + createdAt: message.createdAt, + edited: !!message.editedAt, + external, + id: message.id, + }; + + await this.client.prisma.ticket.update({ + data: { + archivedChannels: { + upsert: channels.map(channel => { + const data = { + channelId: channel.id, + name: channel.name, + }; + return { + create: data, + update: data, + where: { + ticketId_channelId: { + channelId: channel.id, + ticketId, + }, + }, + }; + }), + }, + archivedMessages: { + upsert: { + create: messageD, + update: messageD, + where: { id: message.id }, + }, + }, + }, + where: { id: ticketId }, + }); + + // await this.client.prisma.ticket.update({ + // data: { + // archivedChannels: { + // upsert: channels.map(channel => { + // const data = { + // channelId: channel.id, + // name: channel.name, + // }; + // return { + // create: data, + // update: data, + // where: { + // ticketId_channelId: { + // channelId: channel.id, + // ticketId, + // }, + // }, + // }; + // }), + // }, + // archivedRoles: { + // upsert: roles.map(role => { + // const data = { + // colour: role.hexColor.slice(1), + // name: role.name, + // roleId: role.id, + // }; + // return { + // create: data, + // update: data, + // where: { + // ticketId_roleId: { + // roleId: role.id, + // ticketId, + // }, + // }, + // }; + // }), + // }, + // archivedUsers: { + // upsert: members.map(member => { + // // message author might have left the server (this message could be external/referenced) + // const data = { + // avatar: member?.avatar || member.user?.avatar, // TODO: save avatar in user/avatars/ + // bot: member.user?.bot, + // discriminator: member.user?.discriminator, + // displayName: member?.displayName ? this.encrypt(member?.displayName) : null, + // // role: !!member && { + // // connectOrCreate: { + // // create: { + // // colour: hoistedRole(member).hexColor.slice(1), + // // name: hoistedRole(member).name, + // // roleId: hoistedRole(member).id, + // // ticket: { connect: { id: ticketId } }, + // // }, + // // where: { + // // roleId: hoistedRole(member).id, + // // ticketId, + // // }, + // // }, + // // }, + // roleId: !!member && hoistedRole(member).id, + // userId: member.user?.id || 'default', + // username: member.user?.username ? this.encrypt(member.user.username) : null, + // }; + // return { + // create: data, + // update: data, + // where: { + // ticketId_userId: { + // ticketId, + // userId: member.user.id, + // }, + // }, + // }; + // }), + // }, + // }, + // where: { id: ticketId }, + // }); + + // const messageD = { + // author: { connect: { id: message.author?.id || 'default' } }, + // content: cryptr.encrypt( + // JSON.stringify({ + // attachments: [...message.attachments.values()], + // components: [...message.components.values()], + // content: message.content, + // embeds: message.embeds.map(embed => ({ embed })), + // }), + // ), + // createdAt: message.createdAt, + // edited: !!message.editedAt, + // external, + // }; + + // return await this.client.prisma.archivedMessage.upsert({ + // create: messageD, + // update: messageD, + // where: { id: message.id }, + // }); + } }; \ No newline at end of file diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 17a7618..40d76aa 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -474,36 +474,50 @@ module.exports = class TicketManager { .catch(() => this.client.log.warn('Failed to delete system pin message')); } - // TODO: referenced msg or ticket - + /** @type {import("discord.js").Message|undefined} */ + let message; if (referencesMessage) { referencesMessage = referencesMessage.split('/'); /** @type {import("discord.js").Message} */ - const message = await (await this.client.channels.fetch(referencesMessage[0]))?.messages.fetch(referencesMessage[1]); + message = await (await this.client.channels.fetch(referencesMessage[0]))?.messages.fetch(referencesMessage[1]); if (message) { - await channel.send({ - embeds: [ - new ExtendedEmbedBuilder() - .setColor(category.guild.primaryColour) - .setTitle(getMessage('ticket.references_message.title')) - .setDescription( - getMessage('ticket.references_message.description', { - author: message.author.toString(), - timestamp: ``, - url: message.url, - })), - new ExtendedEmbedBuilder({ - iconURL: guild.iconURL(), - text: category.guild.footer, - }) - .setColor(category.guild.primaryColour) - .setAuthor({ - iconURL: message.member?.displayAvatarURL(), - name: message.member?.displayName || 'Unknown', - }) - .setDescription(message.content.substring(0, 1000) + message.content.length > 1000 ? '...' : ''), - ], - }); + // not worth the effort of making system messages work atm + if (message.system) { + referencesMessage = null; + message = null; + } else { + if (!message.member) { + try { + message.member = await message.guild.members.fetch(message.author.id); + } catch { + this.client.log.verbose('Failed to fetch member %s of %s', message.author.id, message.guild.id); + } + await channel.send({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(category.guild.primaryColour) + .setTitle(getMessage('ticket.references_message.title')) + .setDescription( + getMessage('ticket.references_message.description', { + author: message.author.toString(), + timestamp: ``, + url: message.url, + })), + new ExtendedEmbedBuilder({ + iconURL: guild.iconURL(), + text: category.guild.footer, + }) + .setColor(category.guild.primaryColour) + .setAuthor({ + iconURL: message.member?.displayAvatarURL(), + name: message.member?.displayName || 'Unknown', + }) + .setDescription(message.content.substring(0, 1000) + (message.content.length > 1000 ? '...' : '')), + ], + }); + } + } + } } else if (referencesTicketId) { // TODO: add portal url @@ -554,9 +568,6 @@ module.exports = class TicketManager { topic: topic ? cryptr.encrypt(topic) : null, }; if (referencesTicketId) data.referencesTicket = { connect: { id: referencesTicketId } }; - let message; - if (referencesMessage) message = await this.client.prisma.archivedMessage.findUnique({ where: { id: referencesMessage[1] } }); - if (message) data.referencesMessage = { connect: { id: referencesMessage[0] } }; // only add if the message has been archived ^^ if (answers) data.questionAnswers = { createMany: { data: answers } }; await interaction.editReply({ components: [], @@ -583,6 +594,16 @@ module.exports = class TicketManager { await this.client.keyv.set(cacheKey, expiresAt, TTL); } + if (category.guild.archive && message) { + const row = await this.archiver.saveMessage(ticket.id, message, true); + if (row) { + await this.client.prisma.ticket.update({ + data: { referencesMessageId: row.id }, + where: { id: ticket.id }, + }); + } + } + logTicketEvent(this.client, { action: 'create', target: { diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index efa50f1..c90db50 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -177,11 +177,34 @@ module.exports = class extends Listener { }); } } else { - // TODO: archive messages in tickets - // TODO: first response - // TODO: lastMessageAt - // TODO: auto tag - // TODO: staff status alert, working hours alerts + let ticket = await client.prisma.ticket.findUnique({ + include: { guild: true }, + where: { id: message.channel.id }, + }); + + if (ticket) { + if (ticket.guild.archive) { + try { + await client.tickets.archiver.saveMessage(ticket.id, message); + } catch (error) { + client.log.warn('Failed to archive message', message.id); + client.log.error(error); + } + } + + if (ticket.firstResponseAt === null) { + ticket = await client.prisma.ticket.update({ + data: { firstResponseAt: new Date() }, + where: { id: ticket.id }, + }); + } + + // TODO: lastMessageAt + // TODO: staff status alert, working hours alerts + } else { + // TODO: auto tag + } + } } }; From ce6e2b7d8fb55802070b95ffbf8a13bc57c9369b Mon Sep 17 00:00:00 2001 From: David Ralph Date: Fri, 30 Sep 2022 19:21:59 +0100 Subject: [PATCH 131/409] optimise by removing unneeded modules and replacing with built-in methods (#344) --- .gitignore | 2 ++ package.json | 2 -- src/listeners/client/ready.js | 4 ++-- src/routes/api/client.js | 3 +-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 278566d..a8cad29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # directories dist/ +.history/ +.vscode/ node_modules/ prisma/ diff --git a/package.json b/package.json index 280ff73..7d2f56c 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", "@prisma/client": "^4.3.1", - "boolean": "^3.2.0", "cryptr": "^6.0.3", "discord.js": "^14.3.0", "dotenv": "^16.0.2", @@ -55,7 +54,6 @@ "keyv": "^4.5.0", "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", - "md5": "^2.3.0", "ms": "^2.1.3", "node-dir": "^0.1.17", "node-emoji": "^1.11.0", diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index eef6218..5063e51 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -1,5 +1,5 @@ const { Listener } = require('@eartharoid/dbf'); -const md5 = require('md5'); +const crypto = require('crypto'); const ms = require('ms'); const { version } = require('../../../package.json'); const { msToMins } = require('../../lib/misc'); @@ -81,7 +81,7 @@ module.exports = class extends Listener { categories: await client.prisma.category.count(), database: process.env.DB_PROVIDER, guilds: client.guilds.cache.size, - id: md5(client.user.id), + id: crypto.createHash('md5').update(client.user.id).digest('hex'), members: client.guilds.cache.reduce((t, g) => t + g.memberCount, 0), messages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they can be deleted, node: process.version, diff --git a/src/routes/api/client.js b/src/routes/api/client.js index 40a6a60..fd3051a 100644 --- a/src/routes/api/client.js +++ b/src/routes/api/client.js @@ -1,5 +1,4 @@ const ms = require('ms'); -const { boolean } = require('boolean'); module.exports.get = () => ({ handler: async (req, res) => { @@ -22,7 +21,7 @@ module.exports.get = () => ({ discriminator: client.user.discriminator, id: client.user.id, portal: process.env.PORTAL || null, - public: boolean(process.env.PUBLIC_BOT), + public: (process.env.PUBLIC_BOT === 'true'), stats: { activatedUsers: users.length, archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they can be deleted From 96ec85f14ab685550cfe06ec8afce47e6adfe6a4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 30 Sep 2022 21:26:29 +0100 Subject: [PATCH 132/409] Add ticket first response --- src/listeners/client/messageCreate.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index c90db50..bc739ab 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -9,7 +9,9 @@ const { SelectMenuBuilder, SelectMenuOptionBuilder, } = require('discord.js'); -const { getCommonGuilds } = require('../../lib/users'); +const { + getCommonGuilds, isStaff, +} = require('../../lib/users'); const ms = require('ms'); const emoji = require('node-emoji'); @@ -192,14 +194,16 @@ module.exports = class extends Listener { } } - if (ticket.firstResponseAt === null) { - ticket = await client.prisma.ticket.update({ - data: { firstResponseAt: new Date() }, - where: { id: ticket.id }, - }); - } + const data = { lastMessageAt: new Date() }; + if ( + ticket.firstResponseAt === null && + await isStaff(message.guild, message.author.id) + ) data.firstResponseAt = new Date(); + ticket = await client.prisma.ticket.update({ + data, + where: { id: ticket.id }, + }); - // TODO: lastMessageAt // TODO: staff status alert, working hours alerts } else { // TODO: auto tag From 74c0fd0fd1568fe7d6a2cb4b10edd62d65e059e6 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 4 Oct 2022 20:54:48 +0100 Subject: [PATCH 133/409] fix auto complete --- src/listeners/autocomplete/run.js | 4 ++-- src/listeners/client/messageCreate.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/listeners/autocomplete/run.js b/src/listeners/autocomplete/run.js index e691629..88bf76e 100644 --- a/src/listeners/autocomplete/run.js +++ b/src/listeners/autocomplete/run.js @@ -10,10 +10,10 @@ module.exports = class extends Listener { } run({ - autocompleter, + completer, interaction, }) { - this.client.log.verbose.autocomplete(`${interaction.user.tag} used the "${autocompleter.id}" autocompleter`); + this.client.log.verbose.autocomplete(`${interaction.user.tag} used the "${completer.id}" autocompleter`); return true; } }; diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index bc739ab..3b393a8 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -205,10 +205,10 @@ module.exports = class extends Listener { }); // TODO: staff status alert, working hours alerts - } else { - // TODO: auto tag } + // TODO: auto tag + } } }; From bf4d17bd424471c73a5088e289c3e656634bea6d Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 4 Oct 2022 21:23:01 +0100 Subject: [PATCH 134/409] bug fixes --- src/http.js | 3 ++- src/lib/users.js | 4 +++- src/routes/api/admin/guilds/[guild]/problems.js | 6 ++++-- src/routes/api/admin/guilds/index.js | 4 +++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/http.js b/src/http.js index 422b2cc..ab41ac6 100644 --- a/src/http.js +++ b/src/http.js @@ -4,6 +4,7 @@ const { domain } = require('./lib/http'); const { short } = require('leeks.js'); const { join } = require('path'); const { files } = require('node-dir'); +const { PermissionsBitField } = require('discord.js'); process.env.PUBLIC_HOST = process.env.HTTP_EXTERNAL; // the SvelteKit app expects `PUBLIC_HOST` @@ -87,7 +88,7 @@ module.exports = async client => { }); } const guildMember = await guild.members.fetch(userId); - const isAdmin = guildMember?.permissions.has('MANAGE_GUILD') || client.supers.includes(userId); + const isAdmin = guildMember?.permissions.has(PermissionsBitField.Flags.ManageGuild) || client.supers.includes(userId); if (!isAdmin) { return res.code(403).send({ error: 'Forbidden', diff --git a/src/lib/users.js b/src/lib/users.js index f116db3..23a959d 100644 --- a/src/lib/users.js +++ b/src/lib/users.js @@ -1,3 +1,5 @@ +const { PermissionsBitField } = require('discord.js'); + /** * * @param {import("client")} client @@ -20,7 +22,7 @@ module.exports.isStaff = async (guild, userId) => { const client = guild.client; if (guild.client.supers.includes(userId)) return true; const guildMember = await guild.members.fetch(userId); - if (guildMember?.permissions.has('MANAGE_GUILD')) return true; + if (guildMember?.permissions.has(PermissionsBitField.Flags.ManageGuild)) return true; const { categories } = await client.prisma.guild.findUnique({ select: { categories: true }, where: { id: guild.id }, diff --git a/src/routes/api/admin/guilds/[guild]/problems.js b/src/routes/api/admin/guilds/[guild]/problems.js index d4c5b2b..130d6de 100644 --- a/src/routes/api/admin/guilds/[guild]/problems.js +++ b/src/routes/api/admin/guilds/[guild]/problems.js @@ -1,3 +1,5 @@ +const { PermissionsBitField } = require('discord.js'); + module.exports.get = fastify => ({ handler: async (req, res) => { /** @type {import('client')} */ @@ -11,14 +13,14 @@ module.exports.get = fastify => ({ if (settings.logChannel) { const permissions = guild.members.me.permissionsIn(settings.logChannel); - if (!permissions.has('SendMessages')) { + if (!permissions.has(PermissionsBitField.Flags.SendMessages)) { problems.push({ id: 'logChannelMissingPermission', permission: 'SendMessages', }); } - if (!permissions.has('EmbedLinks')) { + if (!permissions.has(PermissionsBitField.Flags.EmbedLinks)) { problems.push({ id: 'logChannelMissingPermission', permission: 'EmbedLinks', diff --git a/src/routes/api/admin/guilds/index.js b/src/routes/api/admin/guilds/index.js index 11ecb7e..add69fc 100644 --- a/src/routes/api/admin/guilds/index.js +++ b/src/routes/api/admin/guilds/index.js @@ -1,3 +1,5 @@ +const { PermissionsBitField } = require('discord.js'); + module.exports.get = fastify => ({ handler: async (req, res) => { const { client } = res.context.config; @@ -5,7 +7,7 @@ module.exports.get = fastify => ({ .filter(async guild => { const member = await guild.members.fetch(req.user.payload.id); if (!member) return false; - return member.permissions.has('MANAGE_GUILD'); + return member.permissions.has(PermissionsBitField.Flags.ManageGuild); }) .map(guild => ({ id: guild.id, From e723ce76f5357365c1b7f9b6e2f8e5657d90c4e7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 5 Oct 2022 00:31:02 +0100 Subject: [PATCH 135/409] Update dependencies --- package.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 7d2f56c..37897cc 100644 --- a/package.json +++ b/package.json @@ -34,21 +34,21 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.1.0", + "@discord-tickets/settings": "^1.1.2", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", - "@eartharoid/i18n": "^1.0.4", + "@eartharoid/i18n": "^1.1.0", "@fastify/cookie": "^6.0.0", "@fastify/cors": "^8.1.0", - "@fastify/http-proxy": "^8.2.2", + "@fastify/http-proxy": "^8.2.3", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", - "@prisma/client": "^4.3.1", + "@prisma/client": "^4.4.0", "cryptr": "^6.0.3", - "discord.js": "^14.3.0", - "dotenv": "^16.0.2", + "discord.js": "^14.5.0", + "dotenv": "^16.0.3", "express": "^4.18.1", - "fastify": "^4.5.3", + "fastify": "^4.7.0", "figlet": "^1.5.2", "fs-extra": "^10.1.0", "keyv": "^4.5.0", @@ -58,15 +58,15 @@ "node-dir": "^0.1.17", "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", - "prisma": "^4.3.1", - "semver": "^7.3.7", + "prisma": "^4.4.0", + "semver": "^7.3.8", "terminal-link": "^2.1.1", "yaml": "^1.10.2" }, "devDependencies": { - "all-contributors-cli": "^6.20.0", - "eslint": "^8.23.0", + "all-contributors-cli": "^6.23.0", + "eslint": "^8.24.0", "eslint-plugin-unused-imports": "^2.0.0", - "nodemon": "^2.0.19" + "nodemon": "^2.0.20" } } \ No newline at end of file From c2fe7f73d14f738b2e202c572a67f352d93cbc02 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 5 Oct 2022 00:35:54 +0100 Subject: [PATCH 136/409] env --- src/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 9a71ee7..82a159c 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,9 @@ * @license GNU-GPLv3 */ +process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set +require('dotenv').config(); // load env file + const pkg = require('../package.json'); const fs = require('fs'); const semver = require('semver'); @@ -31,9 +34,6 @@ const YAML = require('yaml'); const Client = require('./client'); const http = require('./http'); -process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set -require('dotenv').config(); // load env file - // check node version if (!semver.satisfies(process.versions.node, pkg.engines.node)) { console.log('\x07' + colours.redBright(`Error: Your current Node.js version, ${process.versions.node}, does not meet the requirement "${pkg.engines.node}".`)); From 8b55c351adeef3e0ff04df5db004c595d8e56aed Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 5 Oct 2022 17:09:08 +0100 Subject: [PATCH 137/409] why --- scripts/postinstall.js | 29 +++++++++++++++++++---------- src/stdin/npx.js | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 src/stdin/npx.js diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 3b541d1..6e1c473 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -1,6 +1,17 @@ require('dotenv').config(); const fs = require('fs-extra'); -const { spawnSync } = require('child_process'); +const util = require('util'); +const exec = util.promisify(require('child_process').exec); + +async function npx(cmd) { + console.log(`[postinstall] > ${cmd}`); + const { + stderr, + stdout, + } = await exec('npx ' + cmd); + if (stdout) console.log(stdout.toString()); + if (stderr) console.log(stderr.toString()); +} const providers = ['mysql', 'postgresql', 'sqlite']; const provider = process.env.DB_PROVIDER; @@ -12,16 +23,14 @@ if (!provider) { if (!providers.includes(provider)) throw new Error(`DB_PROVIDER must be one of: ${providers}`); +console.log(`[postinstall] provider=${provider}`); +console.log(`[postinstall] copying ${provider} schema & migrations`); + if (!fs.existsSync('./prisma')) fs.mkdirSync('./prisma'); fs.copySync(`./db/${provider}`, './prisma'); // copy schema & migrations -npx('prisma generate'); -npx('prisma migrate deploy'); +(async () => { + await npx('prisma generate'); + // await npx('prisma migrate deploy'); +})(); -function npx(cmd) { - console.log(`[postinstall] > ${cmd}`); - const child = spawnSync('npx', cmd.split(/\s/)); - if (child.stdout) console.log(child.stdout.toString()); - if (child.stderr) console.log(child.stderr.toString()); - if (child.status) process.exit(child.status); -} \ No newline at end of file diff --git a/src/stdin/npx.js b/src/stdin/npx.js new file mode 100644 index 0000000..992eebc --- /dev/null +++ b/src/stdin/npx.js @@ -0,0 +1,18 @@ +const { StdinCommand } = require('@eartharoid/dbf'); +const { spawn } = require('child_process'); + +module.exports = class extends StdinCommand { + constructor(client, options) { + super(client, { + ...options, + id: 'npx', + }); + } + + async run(input) { + if (!input[0]) return this.client.log.warn('Usage: npx [args]'); + const child = spawn('npx', input, { shell: true }); + for await (const data of child.stdout) this.client.log.info('npx:', data.toString()); + for await (const data of child.stderr) this.client.warn.info('npx:', data.toString()); + } +}; \ No newline at end of file From 8ea372ea363961dfff05fae14c5d62954aad4001 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 5 Oct 2022 18:04:29 +0100 Subject: [PATCH 138/409] Add `/priority` command --- src/commands/slash/priority.js | 76 +++++++++++++++++++++++++++++++++- src/i18n/en-GB.yml | 6 +++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/commands/slash/priority.js b/src/commands/slash/priority.js index d13aac9..00675ff 100644 --- a/src/commands/slash/priority.js +++ b/src/commands/slash/priority.js @@ -1,5 +1,6 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); +const ExtendedEmbedBuilder = require('../../lib/embed'); module.exports = class PrioritySlashCommand extends SlashCommand { constructor(client, options) { @@ -55,5 +56,78 @@ module.exports = class PrioritySlashCommand extends SlashCommand { }); } - async run(interaction) { } + getEmoji(priority) { + let emoji; + switch (priority) { + case 'HIGH': { + emoji = '🔴'; + break; + } + case 'MEDIUM': { + emoji = '🟠'; + break; + } + case 'LOW': { + emoji = '🟢'; + break; + } + } + return emoji; + } + + /** + * + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply(); + + const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + const getMessage = client.i18n.getLocale(settings.locale); + const ticket = await client.prisma.ticket.findUnique({ + include: { category: { select: { channelName: true } } }, + where: { id: interaction.channel.id }, + }); + + if (!ticket) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.not_ticket.title')) + .setDescription(getMessage('misc.not_ticket.description')), + ], + }); + } + + const priority = interaction.options.getString('priority', true); + let name = interaction.channel.name; + if (ticket.priority) name = name.replace(this.getEmoji(ticket.priority), this.getEmoji(priority)); + else name = this.getEmoji(priority) + name; + await interaction.channel.setName(name); + + await client.prisma.ticket.update({ + data: { priority }, + where: { id: interaction.channel.id }, + }); + + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.successColour) + .setTitle(getMessage('commands.slash.priority.success.title')) + .setDescription(getMessage('commands.slash.priority.success.description', { priority: getMessage(`commands.slash.priority.options.priority.choices.${priority}`) })), + ], + }); + + } }; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index a5aedf9..4fef492 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -95,6 +95,9 @@ commands: LOW: 🟢 Low description: The priority of the ticket name: priority + success: + description: This ticket's priority has been set to `{priority}`. + title: ✅ Priority set release: description: Release (unclaim) a ticket name: release @@ -218,6 +221,9 @@ misc: no_categories: description: No ticket categories have been configured. title: ❌ There are no ticket categories + not_ticket: + description: You can only use this command in tickets. + title: ❌ This isn't a ticket channel ratelimited: description: Try again in a few seconds. title: 🐢 Please slow down From 6c49544e7a1aad50353a93327e6f046032f8958a Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 5 Oct 2022 18:15:53 +0100 Subject: [PATCH 139/409] count user messages --- src/listeners/client/messageCreate.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index 3b393a8..bc45d18 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -194,6 +194,11 @@ module.exports = class extends Listener { } } + await client.prisma.user.update({ + data: { messageCount: { increment: 1 } }, + where: { id: message.author.id }, + }); + const data = { lastMessageAt: new Date() }; if ( ticket.firstResponseAt === null && From 3f8663675d2ead55eb755b0e0c8d5bc568a9b4cb Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 5 Oct 2022 18:17:13 +0100 Subject: [PATCH 140/409] remove bad code --- src/lib/tickets/archiver.js | 102 ------------------------------------ 1 file changed, 102 deletions(-) diff --git a/src/lib/tickets/archiver.js b/src/lib/tickets/archiver.js index 7756976..eaad1c8 100644 --- a/src/lib/tickets/archiver.js +++ b/src/lib/tickets/archiver.js @@ -149,107 +149,5 @@ module.exports = class TicketArchiver { }, where: { id: ticketId }, }); - - // await this.client.prisma.ticket.update({ - // data: { - // archivedChannels: { - // upsert: channels.map(channel => { - // const data = { - // channelId: channel.id, - // name: channel.name, - // }; - // return { - // create: data, - // update: data, - // where: { - // ticketId_channelId: { - // channelId: channel.id, - // ticketId, - // }, - // }, - // }; - // }), - // }, - // archivedRoles: { - // upsert: roles.map(role => { - // const data = { - // colour: role.hexColor.slice(1), - // name: role.name, - // roleId: role.id, - // }; - // return { - // create: data, - // update: data, - // where: { - // ticketId_roleId: { - // roleId: role.id, - // ticketId, - // }, - // }, - // }; - // }), - // }, - // archivedUsers: { - // upsert: members.map(member => { - // // message author might have left the server (this message could be external/referenced) - // const data = { - // avatar: member?.avatar || member.user?.avatar, // TODO: save avatar in user/avatars/ - // bot: member.user?.bot, - // discriminator: member.user?.discriminator, - // displayName: member?.displayName ? this.encrypt(member?.displayName) : null, - // // role: !!member && { - // // connectOrCreate: { - // // create: { - // // colour: hoistedRole(member).hexColor.slice(1), - // // name: hoistedRole(member).name, - // // roleId: hoistedRole(member).id, - // // ticket: { connect: { id: ticketId } }, - // // }, - // // where: { - // // roleId: hoistedRole(member).id, - // // ticketId, - // // }, - // // }, - // // }, - // roleId: !!member && hoistedRole(member).id, - // userId: member.user?.id || 'default', - // username: member.user?.username ? this.encrypt(member.user.username) : null, - // }; - // return { - // create: data, - // update: data, - // where: { - // ticketId_userId: { - // ticketId, - // userId: member.user.id, - // }, - // }, - // }; - // }), - // }, - // }, - // where: { id: ticketId }, - // }); - - // const messageD = { - // author: { connect: { id: message.author?.id || 'default' } }, - // content: cryptr.encrypt( - // JSON.stringify({ - // attachments: [...message.attachments.values()], - // components: [...message.components.values()], - // content: message.content, - // embeds: message.embeds.map(embed => ({ embed })), - // }), - // ), - // createdAt: message.createdAt, - // edited: !!message.editedAt, - // external, - // }; - - // return await this.client.prisma.archivedMessage.upsert({ - // create: messageD, - // update: messageD, - // where: { id: message.id }, - // }); } }; \ No newline at end of file From fad1abf579aa867c98eebe658d902e9cf0d30673 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 5 Oct 2022 18:21:53 +0100 Subject: [PATCH 141/409] log ticket update --- src/commands/slash/priority.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/commands/slash/priority.js b/src/commands/slash/priority.js index 00675ff..9274fc9 100644 --- a/src/commands/slash/priority.js +++ b/src/commands/slash/priority.js @@ -1,6 +1,7 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); const ExtendedEmbedBuilder = require('../../lib/embed'); +const { logTicketEvent } = require('../../lib/logging'); module.exports = class PrioritySlashCommand extends SlashCommand { constructor(client, options) { @@ -112,11 +113,25 @@ module.exports = class PrioritySlashCommand extends SlashCommand { else name = this.getEmoji(priority) + name; await interaction.channel.setName(name); + // don't reassign ticket because the original is used below await client.prisma.ticket.update({ data: { priority }, where: { id: interaction.channel.id }, }); + logTicketEvent(this.client, { + action: 'update', + diff: { + original: { priority: ticket.priority }, + updated: { priority: priority }, + }, + target: { + id: ticket.id, + name: `<#${ticket.id}>`, + }, + userId: interaction.user.id, + }); + return await interaction.editReply({ embeds: [ new ExtendedEmbedBuilder({ From b4238e26a1551d20aa43b640f56015e606d9502f Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 5 Oct 2022 18:26:20 +0100 Subject: [PATCH 142/409] fixes --- src/listeners/client/messageCreate.js | 36 ++++++++++++++++----------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index bc45d18..00aa22a 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -194,25 +194,31 @@ module.exports = class extends Listener { } } - await client.prisma.user.update({ - data: { messageCount: { increment: 1 } }, - where: { id: message.author.id }, - }); + if (!message.author.bot) { + await client.prisma.user.upsert({ + create: { + id: message.author.id, + messageCount: 1, + }, + update: { messageCount: { increment: 1 } }, + where: { id: message.author.id }, + }); - const data = { lastMessageAt: new Date() }; - if ( - ticket.firstResponseAt === null && - await isStaff(message.guild, message.author.id) - ) data.firstResponseAt = new Date(); - ticket = await client.prisma.ticket.update({ - data, - where: { id: ticket.id }, - }); + const data = { lastMessageAt: new Date() }; + if ( + ticket.firstResponseAt === null && + await isStaff(message.guild, message.author.id) + ) data.firstResponseAt = new Date(); + ticket = await client.prisma.ticket.update({ + data, + where: { id: ticket.id }, + }); + } - // TODO: staff status alert, working hours alerts + // TODO: if (!message.author.bot) staff status alert, working hours alerts } - // TODO: auto tag + // TODO: if (!message.author.bot) auto tag } } From 525e7528337aaa8026be09a0e8347decb8c198d3 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 5 Oct 2022 19:55:08 +0100 Subject: [PATCH 143/409] speed --- src/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 82a159c..2ff20b7 100644 --- a/src/index.js +++ b/src/index.js @@ -25,11 +25,13 @@ process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set require('dotenv').config(); // load env file const pkg = require('../package.json'); +const banner = require('./lib/banner'); +console.log(banner(pkg.version)); // print big title + const fs = require('fs'); const semver = require('semver'); const { colours } = require('leeks.js'); const logger = require('./lib/logger'); -const banner = require('./lib/banner'); const YAML = require('yaml'); const Client = require('./client'); const http = require('./http'); @@ -57,8 +59,6 @@ if (!fs.existsSync('./user/config.yml')) { } } -console.log(banner(pkg.version)); // print big title - const config = YAML.parse(fs.readFileSync('./user/config.yml', 'utf8')); const log = logger(config); From 52d880a038ab7aea72d541aa132e57f4d4dd6f6e Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 5 Oct 2022 19:55:52 +0100 Subject: [PATCH 144/409] disable warnings --- src/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.js b/src/index.js index 2ff20b7..2181c22 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,8 @@ * @license GNU-GPLv3 */ +/* eslint-disable no-console */ + process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set require('dotenv').config(); // load env file From c16a2a0639431fe9ef4774ea703b1e908391012c Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 5 Oct 2022 22:19:02 +0100 Subject: [PATCH 145/409] make `autoClose` required --- db/mysql/schema.prisma | 2 +- db/postgresql/schema.prisma | 2 +- db/sqlite/schema.prisma | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index a3e1490..6f25802 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -112,7 +112,7 @@ model Feedback { } model Guild { - autoClose Int? + autoClose Int @default(43200000) autoTag Json @default("[]") archive Boolean @default(true) blocklist Json @default("[]") diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index c711a95..02f97c8 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -112,7 +112,7 @@ model Feedback { } model Guild { - autoClose Int? + autoClose Int @default(43200000) autoTag Json @default("[]") archive Boolean @default(true) blocklist Json @default("[]") diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma index 2a45cf5..6e45541 100644 --- a/db/sqlite/schema.prisma +++ b/db/sqlite/schema.prisma @@ -112,7 +112,7 @@ model Feedback { } model Guild { - autoClose Int? + autoClose Int @default(43200000) autoTag String @default("[]") archive Boolean @default(true) blocklist String @default("[]") From 507e963efc0c8d9174b49d3b71413de40ac03cfd Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 6 Oct 2022 12:49:38 +0100 Subject: [PATCH 146/409] update `/force-close` options --- src/commands/slash/force-close.js | 16 ++++++---------- src/i18n/en-GB.yml | 9 +++------ 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/commands/slash/force-close.js b/src/commands/slash/force-close.js index a122652..affc2ac 100644 --- a/src/commands/slash/force-close.js +++ b/src/commands/slash/force-close.js @@ -10,21 +10,17 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.force-close.name'))); let opts = [ - { - name: 'channel', - required: false, - type: ApplicationCommandOptionType.Channel, - }, - { - name: 'number', - required: false, - type: ApplicationCommandOptionType.Integer, - }, { name: 'reason', required: false, type: ApplicationCommandOptionType.String, }, + { + autocomplete: true, + name: 'ticket', + required: false, + type: ApplicationCommandOptionType.Integer, + }, { name: 'time', required: false, diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 4fef492..4711284 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -65,15 +65,12 @@ commands: description: Forcibly close a ticket name: force-close options: - channel: - description: The ticket channel to close - name: channel - number: - description: The number of the ticket to close - name: number reason: description: The reason for closing the ticket(s) name: reason + ticket: + description: The ticket to close + name: ticket time: description: Close all tickets that have been inactive for the specific time name: time From fbc2b8c6b919344ed5619d81b7221fa4ee795801 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 6 Oct 2022 14:39:37 +0100 Subject: [PATCH 147/409] stdin help --- src/stdin/commands.js | 5 +++++ src/stdin/help.js | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/stdin/help.js diff --git a/src/stdin/commands.js b/src/stdin/commands.js index 29e4a71..7edb3fa 100644 --- a/src/stdin/commands.js +++ b/src/stdin/commands.js @@ -16,6 +16,11 @@ module.exports = class Commands extends StdinCommand { .catch(this.client.log.error); break; } + default: { + this.client.log.info('subcommands: \n' + [ + '> commands publish', + ].join('\n')); + } } } }; \ No newline at end of file diff --git a/src/stdin/help.js b/src/stdin/help.js new file mode 100644 index 0000000..109494c --- /dev/null +++ b/src/stdin/help.js @@ -0,0 +1,17 @@ +const { StdinCommand } = require('@eartharoid/dbf'); +const { homepage } = require('../../package.json'); + +module.exports = class extends StdinCommand { + constructor(client, options) { + super(client, { + ...options, + id: 'help', + }); + } + + async run() { + this.client.log.info('Documentation:', homepage); + this.client.log.info('Support: https://lnk.earth/discord'); + this.client.log.info('stdin commands:\n' + this.client.stdin.components.map(c => `> ${c.id}`).join('\n')); + } +}; \ No newline at end of file From c8b5a5c4bb670b9ab3794ca81ce8a04c71109532 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 6 Oct 2022 14:41:32 +0100 Subject: [PATCH 148/409] remove dist from .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index a8cad29..4b697a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ # directories -dist/ .history/ .vscode/ node_modules/ From 78583f70795864530887c391508090448fb3db57 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 9 Oct 2022 17:04:43 +0100 Subject: [PATCH 149/409] Add optional dependencies --- package.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 37897cc..9535ffe 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.1.2", + "@discord-tickets/settings": "^1.1.3", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.1.0", @@ -68,5 +68,11 @@ "eslint": "^8.24.0", "eslint-plugin-unused-imports": "^2.0.0", "nodemon": "^2.0.20" + }, + "optionalDependencies": { + "bufferutil": "^4.0.6", + "erlpack": "github:discord/erlpack", + "utf-8-validate": "^5.0.9", + "zlib-sync": "^0.1.7" } } \ No newline at end of file From e813dee884c0050b4fc79aee15b08146e8617632 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 9 Oct 2022 17:08:53 +0100 Subject: [PATCH 150/409] beta.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9535ffe..d592fb5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0", + "version": "4.0.0-beta.0", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", From 35ad490ba60544ea8827446c01c5e97c2e2c5b4d Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 10 Oct 2022 12:49:34 +0100 Subject: [PATCH 151/409] improve error handling --- src/listeners/client/ready.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 5063e51..26bdf35 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -100,8 +100,9 @@ module.exports = class extends Listener { client.log.success('Posted client stats'); client.log.verbose(stats); client.log.debug(res); - } catch (error) { - client.log.error('An error occurred whilst posting stats', stats, error); + } catch (res) { + client.log.error('An error occurred whilst posting stats', (await res.json())?.error); + client.log.debug(res); } }; send(); From 6f535e7149dcd6ac570d2ebce1e332e3a5d6cd75 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 10 Oct 2022 15:44:13 +0100 Subject: [PATCH 152/409] help --- src/commands/slash/help.js | 84 ++++++++++++++++++++++++++++++++++ src/i18n/en-GB.yml | 92 +++++++++++++++++++++++++++----------- 2 files changed, 149 insertions(+), 27 deletions(-) create mode 100644 src/commands/slash/help.js diff --git a/src/commands/slash/help.js b/src/commands/slash/help.js new file mode 100644 index 0000000..de2ef9f --- /dev/null +++ b/src/commands/slash/help.js @@ -0,0 +1,84 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { isStaff } = require('../../lib/users'); +const ExtendedEmbedBuilder = require('../../lib/embed'); +const { version } = require('../../../package.json'); + +module.exports = class ClaimSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.help.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.help.name'))); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + }); + } + + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + const staff = await isStaff(interaction.guild, interaction.member.id); + const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + const getMessage = client.i18n.getLocale(settings.locale); + await interaction.deferReply({ ephemeral: true }); + const commands = (await client.application.commands.fetch()) + .filter(c => c.type === 1) + .map(c => `> : ${c.description}`) + .join('\n'); + const ticket = client.application.commands.cache.find(c => c.name === 'new'); + const fields = [ + { + name: getMessage('commands.slash.help.response.commands'), + value: commands, + }, + ]; + + if (staff) { + fields.unshift( + { + inline: true, + name: getMessage('commands.slash.help.response.links.links'), + value: [ + ['commands', 'https://discordtickets.app/features/commands'], + ['docs', 'https://discordtickets.app'], + ['feedback', 'https://lnk.earth/dsctickets-feedback'], + ['support', 'https://lnk.earth/discord'], + ] + .map(([l, url]) => `> [${getMessage('commands.slash.help.response.links.' + l)}](${url})`) + .join('\n'), + }, + { + inline: true, + name: getMessage('commands.slash.help.response.settings'), + value: '> ' + process.env.HTTP_EXTERNAL + '/settings', + }, + ); + } + + interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.primaryColour) + .setTitle(getMessage('commands.slash.help.title')) + .setDescription(staff + ? `**Discord Tickets v${version} by eartharoid.**` + : getMessage('commands.slash.help.response.description', { command: `` })) + .setFields(fields), + ], + }); + } +}; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 4711284..f1f7cb0 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -1,4 +1,7 @@ buttons: + cancel: + emoji: 🚫 + text: Cancel claim: emoji: 🙌 text: Claim @@ -32,8 +35,8 @@ commands: description: You can only pin messages in tickets. title: ❌ This isn't a ticket channel pinned: - description: The message has been pinned. - title: ✅ Pinned message + description: The message has been pinned. + title: ✅ Pinned message slash: add: description: Add a member to a ticket @@ -49,7 +52,10 @@ commands: description: Claim a ticket name: claim close: - description: Close a ticket + description: Request a ticket to be closed + invalid_time: + description: "`{input}` is not a valid time format." + title: ❌ Invalid name: close options: reason: @@ -58,12 +64,24 @@ commands: ticket: description: The ticket to close name: ticket - time: - description: Close all tickets that have been inactive for the specific time - name: time force-close: + confirm_multiple: + description: > + You are about to close **{count}** tickets that have been inactive for + more than `{time}`: + + {tickets} + title: ❓ Are you sure? description: Forcibly close a ticket name: force-close + no_tickets: + description: >- + There are no open tickets that have been inactive for more than + `{time}`. + title: ❌ No tickets + not_staff: + description: Only staff members can force-close tickets. + title: ❌ Error options: reason: description: The reason for closing the ticket(s) @@ -72,8 +90,22 @@ commands: description: The ticket to close name: ticket time: - description: Close all tickets that have been inactive for the specific time + description: Close all tickets that have been inactive for the specified time name: time + help: + description: Show the help menu + name: help + response: + commands: Commands + description: "**Use {command} to create a ticket and get support.**" + links: + commands: Full command list + docs: Documentation + feedback: Feedback + links: Useful links + support: Support + settings: Bot settings + title: Help new: description: Create a new ticket name: new @@ -88,8 +120,8 @@ commands: priority: choices: HIGH: 🔴 High - MEDIUM: 🟠 Medium LOW: 🟢 Low + MEDIUM: 🟠 Medium description: The priority of the ticket name: priority success: @@ -118,9 +150,6 @@ commands: tag: description: The name of the tag to use name: tag - topic: - description: Change the topic of a ticket - name: topic tickets: description: List your own or someone else's tickets name: tickets @@ -128,8 +157,11 @@ commands: member: description: The member to list the tickets of name: member + topic: + description: Change the topic of a ticket + name: topic transcript: - description: Get the transcript of a ticket + description: Get the transcript of a ticket name: transcript options: ticket: @@ -147,12 +179,12 @@ commands: name: Create a ticket for user dm: confirm_open: - title: 'Do you want to open a ticket with the following topic?' + title: Do you want to open a ticket with the following topic? log: admin: changes: Changes description: - joined: '{user} {verb} {targetType}' + joined: "{user} {verb} {targetType}" target: category: a category panel: a panel @@ -160,7 +192,7 @@ log: settings: the settings tag: a tag title: - joined: '{targetType} {verb}' + joined: "{targetType} {verb}" target: category: Category panel: Panel @@ -172,13 +204,13 @@ log: delete: deleted update: updated ticket: - description: '{user} {verb} a ticket' + description: "{user} {verb} a ticket" ticket: Ticket title: Ticket {verb} verb: - create: created claim: claimed close: closed + create: created unclaim: released update: updated menus: @@ -201,8 +233,12 @@ misc: error: description: Sorry, an unexpected error occurred. fields: - identifier: Identifier + identifier: Identifier title: ⚠️ Something's wrong + expired: + description: You didn't respond in time. Please try again. + title: ⏰ Expired + expires_in: Expires in {time} member_limit: description: - Please use your existing ticket or close it before creating another. @@ -213,14 +249,16 @@ misc: - ❌ You already have a ticket - ❌ You already have %d open tickets missing_roles: - description: You do not have the roles required to be able to create a ticket in this category. + description: >- + You do not have the roles required to be able to create a ticket in this + category. title: ❌ Insufficient roles no_categories: description: No ticket categories have been configured. title: ❌ There are no ticket categories not_ticket: - description: You can only use this command in tickets. - title: ❌ This isn't a ticket channel + description: You can only use this command in tickets. + title: ❌ This isn't a ticket channel ratelimited: description: Try again in a few seconds. title: 🐢 Please slow down @@ -234,11 +272,11 @@ modals: label: Topic placeholder: What is this ticket about? ticket: - created: - description: 'Your ticket channel has been created: {channel}.' - title: ✅ Ticket created answers: - no_value: '*No response*' + no_value: "*No response*" + created: + description: "Your ticket channel has been created: {channel}." + title: ✅ Ticket created edited: description: Your changes have been saved. title: ✅ Ticket updated @@ -249,10 +287,10 @@ ticket: fields: topic: Topic references_message: - description: 'References [a message]({url}) sent {timestamp} by {author}.' + description: References [a message]({url}) sent {timestamp} by {author}. title: ℹ️ Reference references_ticket: - description: 'This ticket is related to a previous ticket:' + description: "This ticket is related to a previous ticket:" fields: date: Created at number: Number From e4b104eb7e1efc7f387514f92000d2cf702dcb14 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 10 Oct 2022 20:14:00 +0100 Subject: [PATCH 153/409] =?UTF-8?q?=F0=9F=91=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/slash/help.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/slash/help.js b/src/commands/slash/help.js index de2ef9f..f5e5c0b 100644 --- a/src/commands/slash/help.js +++ b/src/commands/slash/help.js @@ -28,10 +28,10 @@ module.exports = class ClaimSlashCommand extends SlashCommand { /** @type {import("client")} */ const client = this.client; + await interaction.deferReply({ ephemeral: true }); const staff = await isStaff(interaction.guild, interaction.member.id); const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); const getMessage = client.i18n.getLocale(settings.locale); - await interaction.deferReply({ ephemeral: true }); const commands = (await client.application.commands.fetch()) .filter(c => c.type === 1) .map(c => `> : ${c.description}`) From 2bb9873be8e3c90785c6f8db64a1c863299b7bcf Mon Sep 17 00:00:00 2001 From: Marius <43551856+M4rlus@users.noreply.github.com> Date: Mon, 10 Oct 2022 22:44:07 +0200 Subject: [PATCH 154/409] Rename trasnfer.js to transfer.js (#349) --- src/commands/slash/{trasnfer.js => transfer.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/commands/slash/{trasnfer.js => transfer.js} (99%) diff --git a/src/commands/slash/trasnfer.js b/src/commands/slash/transfer.js similarity index 99% rename from src/commands/slash/trasnfer.js rename to src/commands/slash/transfer.js index 71a8712..dfa7967 100644 --- a/src/commands/slash/trasnfer.js +++ b/src/commands/slash/transfer.js @@ -47,4 +47,4 @@ module.exports = class TransferSlashCommand extends SlashCommand { // TODO: check discordCategory max but not category max (ignore) // TODO: update cached count for both categories and category-members (from and to) } -}; \ No newline at end of file +}; From 54506cf422d4c644a41de1242effcaa554186380 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 11 Oct 2022 18:49:39 +0100 Subject: [PATCH 155/409] fix embeds --- src/lib/tickets/archiver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tickets/archiver.js b/src/lib/tickets/archiver.js index eaad1c8..80014d8 100644 --- a/src/lib/tickets/archiver.js +++ b/src/lib/tickets/archiver.js @@ -110,7 +110,7 @@ module.exports = class TicketArchiver { attachments: [...message.attachments.values()], components: [...message.components.values()], content: message.content, - embeds: message.embeds.map(embed => ({ embed })), + embeds: message.embeds.map(embed => ({ ...embed })), }), ), createdAt: message.createdAt, From f7b5031aab46da2795e12805ec383b610bd4034f Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 11 Oct 2022 18:58:02 +0100 Subject: [PATCH 156/409] Add message reference (for replies) to archives --- src/lib/tickets/archiver.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/tickets/archiver.js b/src/lib/tickets/archiver.js index 80014d8..97bbf8f 100644 --- a/src/lib/tickets/archiver.js +++ b/src/lib/tickets/archiver.js @@ -96,6 +96,9 @@ module.exports = class TicketArchiver { }); } + let reference; + if (message.reference) reference = await message.fetchReference(); + const messageD = { author: { connect: { @@ -111,6 +114,7 @@ module.exports = class TicketArchiver { components: [...message.components.values()], content: message.content, embeds: message.embeds.map(embed => ({ ...embed })), + reference: reference ? reference.id : null, }), ), createdAt: message.createdAt, From 4469aa4920ab4a5909e907e9ddc34dc07be789b5 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 11 Oct 2022 20:12:40 +0100 Subject: [PATCH 157/409] fix referencing messages --- src/lib/tickets/manager.js | 50 ++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 40d76aa..fa7d162 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -492,30 +492,31 @@ module.exports = class TicketManager { } catch { this.client.log.verbose('Failed to fetch member %s of %s', message.author.id, message.guild.id); } - await channel.send({ - embeds: [ - new ExtendedEmbedBuilder() - .setColor(category.guild.primaryColour) - .setTitle(getMessage('ticket.references_message.title')) - .setDescription( - getMessage('ticket.references_message.description', { - author: message.author.toString(), - timestamp: ``, - url: message.url, - })), - new ExtendedEmbedBuilder({ - iconURL: guild.iconURL(), - text: category.guild.footer, - }) - .setColor(category.guild.primaryColour) - .setAuthor({ - iconURL: message.member?.displayAvatarURL(), - name: message.member?.displayName || 'Unknown', - }) - .setDescription(message.content.substring(0, 1000) + (message.content.length > 1000 ? '...' : '')), - ], - }); } + await channel.send({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(category.guild.primaryColour) + .setTitle(getMessage('ticket.references_message.title')) + .setDescription( + getMessage('ticket.references_message.description', { + author: message.author.toString(), + timestamp: ``, + url: message.url, + })), + new ExtendedEmbedBuilder({ + iconURL: guild.iconURL(), + text: category.guild.footer, + }) + .setColor(category.guild.primaryColour) + .setAuthor({ + iconURL: message.member?.displayAvatarURL(), + name: message.member?.displayName || 'Unknown', + }) + .setDescription(message.content.substring(0, 1000) + (message.content.length > 1000 ? '...' : '')), + ], + }); + } } @@ -595,7 +596,8 @@ module.exports = class TicketManager { } if (category.guild.archive && message) { - const row = await this.archiver.saveMessage(ticket.id, message, true); + let row = await this.client.prisma.archivedMessage.findUnique({ where: { id: message.id } }); + if (!row) row = await this.archiver.saveMessage(ticket.id, message, true); if (row) { await this.client.prisma.ticket.update({ data: { referencesMessageId: row.id }, From 83ab003db57bdb2f35360e4f890cd28ceca070ba Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 11 Oct 2022 21:24:09 +0100 Subject: [PATCH 158/409] fix command options & types and start on closing --- src/buttons/close.js | 11 +- src/commands/slash/add.js | 2 +- src/commands/slash/claim.js | 5 +- src/commands/slash/close.js | 12 +- src/commands/slash/force-close.js | 193 +++++++++++++++++++++- src/commands/slash/release.js | 1 - src/commands/slash/remove.js | 2 +- src/commands/slash/transcript.js | 2 +- src/commands/slash/transfer.js | 2 +- src/commands/user/create.js | 2 + src/i18n/en-GB.yml | 3 + src/lib/sync.js | 2 +- src/lib/tickets/manager.js | 30 +++- src/listeners/client/guildMemberRemove.js | 21 ++- 14 files changed, 267 insertions(+), 21 deletions(-) diff --git a/src/buttons/close.js b/src/buttons/close.js index bd7b151..50a25a3 100644 --- a/src/buttons/close.js +++ b/src/buttons/close.js @@ -8,5 +8,14 @@ module.exports = class CloseButton extends Button { }); } - async run(id, interaction) { } + /** + * @param {*} id + * @param {import("discord.js").ButtonInteraction} interaction + */ + async run(id, interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply(); + } }; \ No newline at end of file diff --git a/src/commands/slash/add.js b/src/commands/slash/add.js index 6768441..ccd3894 100644 --- a/src/commands/slash/add.js +++ b/src/commands/slash/add.js @@ -19,7 +19,7 @@ module.exports = class AddSlashCommand extends SlashCommand { autocomplete: true, name: 'ticket', required: false, - type: ApplicationCommandOptionType.Integer, + type: ApplicationCommandOptionType.String, }, ]; opts = opts.map(o => { diff --git a/src/commands/slash/claim.js b/src/commands/slash/claim.js index 814e702..cde6c67 100644 --- a/src/commands/slash/claim.js +++ b/src/commands/slash/claim.js @@ -1,5 +1,4 @@ const { SlashCommand } = require('@eartharoid/dbf'); -const { ApplicationCommandOptionType } = require('discord.js'); module.exports = class ClaimSlashCommand extends SlashCommand { constructor(client, options) { @@ -19,5 +18,7 @@ module.exports = class ClaimSlashCommand extends SlashCommand { }); } - async run(interaction) { } + async run(interaction) { + // tickets/manager.js + } }; \ No newline at end of file diff --git a/src/commands/slash/close.js b/src/commands/slash/close.js index 4e55840..3d7d1c2 100644 --- a/src/commands/slash/close.js +++ b/src/commands/slash/close.js @@ -19,11 +19,6 @@ module.exports = class CloseSlashCommand extends SlashCommand { autocomplete: true, name: 'ticket', required: false, - type: ApplicationCommandOptionType.Integer, - }, - { - name: 'time', - required: false, type: ApplicationCommandOptionType.String, }, ]; @@ -53,5 +48,10 @@ module.exports = class CloseSlashCommand extends SlashCommand { }); } - async run(interaction) { } + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + + } }; \ No newline at end of file diff --git a/src/commands/slash/force-close.js b/src/commands/slash/force-close.js index affc2ac..0410e4c 100644 --- a/src/commands/slash/force-close.js +++ b/src/commands/slash/force-close.js @@ -1,5 +1,14 @@ const { SlashCommand } = require('@eartharoid/dbf'); -const { ApplicationCommandOptionType } = require('discord.js'); +const { + ApplicationCommandOptionType, + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ComponentType, +} = require('discord.js'); +const ExtendedEmbedBuilder = require('../../lib/embed'); +const { isStaff } = require('../../lib/users'); +const ms = require('ms'); module.exports = class ForceCloseSlashCommand extends SlashCommand { constructor(client, options) { @@ -19,7 +28,7 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { autocomplete: true, name: 'ticket', required: false, - type: ApplicationCommandOptionType.Integer, + type: ApplicationCommandOptionType.String, }, { name: 'time', @@ -53,5 +62,183 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { }); } - async run(interaction) { } + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply(); + + const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + const getMessage = this.client.i18n.getLocale(settings.locale); + let ticket; + + if (!isStaff(interaction.guild, interaction.user.id)) { // if user is not staff + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('commands.slash.force-close.not_staff.title')) + .setDescription(getMessage('commands.slash.force-close.not_staff.description')), + ], + }); + } + + if (interaction.options.getString('time', false)) { // if time option is passed + const time = ms(interaction.options.getString('time', false)); + + if (!time) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('commands.slash.close.invalid_time.title')) + .setDescription(getMessage('commands.slash.close.invalid_time.description', { input: interaction.options.getString('time', false) })), + ], + }); + } + + const tickets = await client.prisma.ticket.findMany({ + where: { + lastMessageAt: { lte: new Date(Date.now() - time) }, + open: true, + }, + }); + + if (tickets.length === 0) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('commands.slash.force-close.no_tickets.title')) + .setDescription(getMessage('commands.slash.force-close.no_tickets.description', { time: ms(time, { long: true }) })), + ], + }); + } + + let confirmed = false; + const collectorTime = ms('15s'); + const confirmationM = await interaction.editReply({ + components: [ + new ActionRowBuilder() + .addComponents([ + new ButtonBuilder() + .setCustomId(JSON.stringify({ + action: 'custom', + id: 'close', + })) + .setStyle(ButtonStyle.Danger) + .setEmoji(getMessage('buttons.close.emoji')) + .setLabel(getMessage('buttons.close.text')), + new ButtonBuilder() + .setCustomId(JSON.stringify({ + action: 'custom', + id: 'cancel', + })) + .setStyle(ButtonStyle.Secondary) + .setEmoji(getMessage('buttons.cancel.emoji')) + .setLabel(getMessage('buttons.cancel.text')), + ]), + ], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: getMessage('misc.expires_in', { time: ms(collectorTime, { long: true }) }), + }) + .setColor(settings.primaryColour) + .setTitle(getMessage('commands.slash.force-close.confirm_multiple.title')) + .setDescription(getMessage('commands.slash.force-close.confirm_multiple.description', { + count: tickets.length, + tickets: tickets.map(t => `> <#${t.id}>`).join('\n'), + time: ms(time, { long: true }), + })), + ], + }); + + + confirmationM.awaitMessageComponent({ + componentType: ComponentType.Button, + filter: i => { + i.deferUpdate(); + return i.user.id === interaction.user.id; + }, + time: collectorTime, + }) + .then(i => { + if (JSON.parse(i.customId).id === 'close') { + confirmed = true; + // TODO: i.editReply + } else { + // TODO: cancelled + } + }) + .catch(() => interaction.editReply({ + components: [], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.expired.title')) + .setDescription(getMessage('misc.expired.description', { time: ms(time, { long: true }) })), + ], + })); + + if (!confirmed) return; + + // TODO: tickets: for each, close (check reason) + } else if (interaction.options.getString('ticket', false)) { // if ticket option is passed + ticket = await client.prisma.ticket.findUnique({ + include: { category: true }, + where: { id: interaction.options.getString('ticket', false) }, + }); + + if (!ticket) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.invalid_ticket.title')) + .setDescription(getMessage('misc.invalid_ticket.description')), + ], + }); + } + } else { + ticket = await client.prisma.ticket.findUnique({ + include: { category: true }, + where: { id: interaction.channel.id }, + }); + + if (!ticket) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.not_ticket.title')) + .setDescription(getMessage('misc.not_ticket.description')), + ], + }); + } + } + + // TODO: close (reason) + } }; \ No newline at end of file diff --git a/src/commands/slash/release.js b/src/commands/slash/release.js index 7722fe2..6074705 100644 --- a/src/commands/slash/release.js +++ b/src/commands/slash/release.js @@ -1,5 +1,4 @@ const { SlashCommand } = require('@eartharoid/dbf'); -const { ApplicationCommandOptionType } = require('discord.js'); module.exports = class ReleaseSlashCommand extends SlashCommand { constructor(client, options) { diff --git a/src/commands/slash/remove.js b/src/commands/slash/remove.js index 6923bd4..29cc142 100644 --- a/src/commands/slash/remove.js +++ b/src/commands/slash/remove.js @@ -19,7 +19,7 @@ module.exports = class RemoveSlashCommand extends SlashCommand { autocomplete: true, name: 'ticket', required: false, - type: ApplicationCommandOptionType.Integer, + type: ApplicationCommandOptionType.String, }, ]; opts = opts.map(o => { diff --git a/src/commands/slash/transcript.js b/src/commands/slash/transcript.js index db9ad4b..3b6cc54 100644 --- a/src/commands/slash/transcript.js +++ b/src/commands/slash/transcript.js @@ -14,7 +14,7 @@ module.exports = class TranscriptSlashCommand extends SlashCommand { autocomplete: true, name: 'ticket', required: true, - type: ApplicationCommandOptionType.Integer, + type: ApplicationCommandOptionType.String, }, ]; opts = opts.map(o => { diff --git a/src/commands/slash/transfer.js b/src/commands/slash/transfer.js index dfa7967..3896f0e 100644 --- a/src/commands/slash/transfer.js +++ b/src/commands/slash/transfer.js @@ -14,7 +14,7 @@ module.exports = class TransferSlashCommand extends SlashCommand { autocomplete: true, name: 'category', required: true, - type: ApplicationCommandOptionType.String, + type: ApplicationCommandOptionType.Integer, }, ]; opts = opts.map(o => { diff --git a/src/commands/user/create.js b/src/commands/user/create.js index 648096d..5f0470a 100644 --- a/src/commands/user/create.js +++ b/src/commands/user/create.js @@ -14,6 +14,8 @@ module.exports = class CreateUserCommand extends UserCommand { } async run(interaction) { + // TODO: isStaff? + // TODO: user->create // select category // send button } diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index f1f7cb0..ceddfb3 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -259,6 +259,9 @@ misc: not_ticket: description: You can only use this command in tickets. title: ❌ This isn't a ticket channel + invalid_ticket: + description: Please specify a valid ticket. + title: ❌ Invalid ticket ratelimited: description: Try again in a few seconds. title: 🐢 Please slow down diff --git a/src/lib/sync.js b/src/lib/sync.js index ff30a66..0d186a5 100644 --- a/src/lib/sync.js +++ b/src/lib/sync.js @@ -30,7 +30,7 @@ module.exports = async client => { const guild = client.guilds.cache.get(ticket.guildId); if (guild && guild.available && !client.channels.cache.has(ticket.id)) { deleted += 0; - await client.tickets.close(ticket.id); + await client.tickets.close(ticket.id, true, 'channel deleted'); } } diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index fa7d162..3a46cbb 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -91,7 +91,7 @@ module.exports = class TicketManager { /** * @param {object} data * @param {string} data.categoryId - * @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction} data.interaction + * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction} data.interaction * @param {string?} [data.topic] */ async create({ @@ -634,4 +634,32 @@ module.exports = class TicketManager { }); } } + + + /** + * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction} interaction + */ + async preClose(interaction) { + const ticket = await this.client.prisma.ticket.findUnique({ + include: { + category: true, + guild: true, + }, + where: { id: interaction.channel.id }, + }); + const getMessage = this.client.i18n.getLocale(ticket.guild.locale); + } + + /** + * close a ticket + * @param {string} ticketId + * @param {boolean} skip + * @param {string} reason + */ + async close(ticketId, skip, reason) { + // TODO: update cache/cat count + // TODO: update cache/member count + // TODO: set messageCount on ticket + // delete + } }; \ No newline at end of file diff --git a/src/listeners/client/guildMemberRemove.js b/src/listeners/client/guildMemberRemove.js index 8e40061..40554b0 100644 --- a/src/listeners/client/guildMemberRemove.js +++ b/src/listeners/client/guildMemberRemove.js @@ -9,7 +9,24 @@ module.exports = class extends Listener { }); } - run(member) { - // TODO: close tickets + /** + * + * @param {import("discord.js").GuildMember} member + */ + async run(member) { + /** @type {import("client")} */ + const client = this.client; + + const tickets = await client.prisma.ticket.findMany({ + where: { + createdById: member.id, + guildId: member.guild.id, + open: true, + }, + }); + + for (const ticket of tickets) { + await client.tickets.close(ticket.id, true, 'user left server'); + } } }; From 33d61869d86550edb1567e43c2691ee9e0008dc2 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 11 Oct 2022 21:28:53 +0100 Subject: [PATCH 159/409] Fix `ticket` and `references` autocompleters --- src/autocomplete/references.js | 15 ++++++++++++++- src/autocomplete/ticket.js | 4 +++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/autocomplete/references.js b/src/autocomplete/references.js index bf80611..f947726 100644 --- a/src/autocomplete/references.js +++ b/src/autocomplete/references.js @@ -1,4 +1,7 @@ const { Autocompleter } = require('@eartharoid/dbf'); +const emoji = require('node-emoji'); +const Cryptr = require('cryptr'); +const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class ReferencesCompleter extends Autocompleter { constructor(client, options) { @@ -18,6 +21,14 @@ module.exports = class ReferencesCompleter extends Autocompleter { const client = this.client; const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); const tickets = await client.prisma.ticket.findMany({ + include: { + category: { + select: { + emoji: true, + name: true, + }, + }, + }, where: { createdById: interaction.user.id, guildId: interaction.guild.id, @@ -34,8 +45,10 @@ module.exports = class ReferencesCompleter extends Autocompleter { .slice(0, 25) .map(t => { const date = new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' }); + const topic = t.topic ? '| ' + cryptr.decrypt(t.topic).substring(0, 50) : ''; + const category = emoji.hasEmoji(t.category.emoji) ? emoji.get(t.category.emoji) + ' ' + t.category.name : t.category.name; return { - name: `#${t.number} - ${date} ${t.topic ? '| ' + t.topic.substring(0, 50) : ''}`, + name: `${category} #${t.number} - ${date} ${topic}`, value: t.id, }; }), diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js index 7f86dbe..bdf58a1 100644 --- a/src/autocomplete/ticket.js +++ b/src/autocomplete/ticket.js @@ -1,5 +1,7 @@ const { Autocompleter } = require('@eartharoid/dbf'); const emoji = require('node-emoji'); +const Cryptr = require('cryptr'); +const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class TicketCompleter extends Autocompleter { constructor(client, options) { @@ -43,7 +45,7 @@ module.exports = class TicketCompleter extends Autocompleter { .slice(0, 25) .map(t => { const date = new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' }); - const topic = t.topic ? '| ' + t.topic.substring(0, 50) : ''; + const topic = t.topic ? '| ' + cryptr.decrypt(t.topic).substring(0, 50) : ''; const category = emoji.hasEmoji(t.category.emoji) ? emoji.get(t.category.emoji) + ' ' + t.category.name : t.category.name; return { name: `${category} #${t.number} - ${date} ${topic}`, From 5b95610338228e08987e6647f2d8738392f97f7d Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 16 Oct 2022 14:07:10 +0100 Subject: [PATCH 160/409] rename `/transfer` back to `/move` --- src/commands/slash/{transfer.js => move.js} | 10 +++++----- src/i18n/en-GB.yml | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) rename src/commands/slash/{transfer.js => move.js} (78%) diff --git a/src/commands/slash/transfer.js b/src/commands/slash/move.js similarity index 78% rename from src/commands/slash/transfer.js rename to src/commands/slash/move.js index 3896f0e..e0c8b4f 100644 --- a/src/commands/slash/transfer.js +++ b/src/commands/slash/move.js @@ -1,13 +1,13 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); -module.exports = class TransferSlashCommand extends SlashCommand { +module.exports = class MoveSlashCommand extends SlashCommand { constructor(client, options) { const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transfer.description'))); + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.move.description'))); const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transfer.name'))); + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.move.name'))); let opts = [ { @@ -19,10 +19,10 @@ module.exports = class TransferSlashCommand extends SlashCommand { ]; opts = opts.map(o => { const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transfer.options.${o.name}.description`))); + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.move.options.${o.name}.description`))); const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transfer.options.${o.name}.name`))); + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.move.options.${o.name}.name`))); return { ...o, diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index ceddfb3..937c530 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -167,12 +167,12 @@ commands: ticket: description: The number of the ticket to get the transcript of name: ticket - transfer: - description: Transfer a ticket to another category - name: transfer + move: + description: move a ticket to another category + name: move options: category: - description: The category to transfer the ticket to + description: The category to move the ticket to name: category user: create: From 9b0a1fe50fd9fab69753d1508dd8213463209b1e Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 16 Oct 2022 14:11:40 +0100 Subject: [PATCH 161/409] Add new `/transfer` (ownership) command --- src/commands/slash/transfer.js | 46 ++++++++++++++++++++++++++++++++++ src/i18n/en-GB.yml | 25 +++++++++++------- 2 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 src/commands/slash/transfer.js diff --git a/src/commands/slash/transfer.js b/src/commands/slash/transfer.js new file mode 100644 index 0000000..5584c6f --- /dev/null +++ b/src/commands/slash/transfer.js @@ -0,0 +1,46 @@ +const { SlashCommand } = require('@eartharoid/dbf'); +const { ApplicationCommandOptionType } = require('discord.js'); + +module.exports = class TransferSlashCommand extends SlashCommand { + constructor(client, options) { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transfer.description'))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transfer.name'))); + + let opts = [ + { + name: 'member', + required: true, + type: ApplicationCommandOptionType.User, + }, + ]; + opts = opts.map(o => { + const descriptionLocalizations = {}; + client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transfer.options.${o.name}.description`))); + + const nameLocalizations = {}; + client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transfer.options.${o.name}.name`))); + + return { + ...o, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + nameLocalizations: nameLocalizations, + }; + }); + + super(client, { + ...options, + description: descriptionLocalizations['en-GB'], + descriptionLocalizations, + dmPermission: false, + name: nameLocalizations['en-GB'], + nameLocalizations, + options: opts, + }); + } + + async run(interaction) {} +}; diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 937c530..a17d9a4 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -106,6 +106,13 @@ commands: support: Support settings: Bot settings title: Help + move: + description: move a ticket to another category + name: move + options: + category: + description: The category to move the ticket to + name: category new: description: Create a new ticket name: new @@ -167,13 +174,13 @@ commands: ticket: description: The number of the ticket to get the transcript of name: ticket - move: - description: move a ticket to another category - name: move + transfer: + description: Transfer ownership of a ticket to another member + name: transfer options: - category: - description: The category to move the ticket to - name: category + member: + description: The member to transfer ownership to + name: member user: create: name: Create a ticket for user @@ -239,6 +246,9 @@ misc: description: You didn't respond in time. Please try again. title: ⏰ Expired expires_in: Expires in {time} + invalid_ticket: + description: Please specify a valid ticket. + title: ❌ Invalid ticket member_limit: description: - Please use your existing ticket or close it before creating another. @@ -259,9 +269,6 @@ misc: not_ticket: description: You can only use this command in tickets. title: ❌ This isn't a ticket channel - invalid_ticket: - description: Please specify a valid ticket. - title: ❌ Invalid ticket ratelimited: description: Try again in a few seconds. title: 🐢 Please slow down From 8176293089abd23a700b1a62fe6f7104a260d70a Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 16 Oct 2022 14:16:38 +0100 Subject: [PATCH 162/409] Add `category` option for closing multiple tickets --- src/commands/slash/force-close.js | 6 ++++++ src/i18n/en-GB.yml | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/commands/slash/force-close.js b/src/commands/slash/force-close.js index 0410e4c..ffdf9b9 100644 --- a/src/commands/slash/force-close.js +++ b/src/commands/slash/force-close.js @@ -19,6 +19,12 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.force-close.name'))); let opts = [ + { + autocomplete: true, + name: 'category', + required: false, + type: ApplicationCommandOptionType.Integer, + }, { name: 'reason', required: false, diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index a17d9a4..aa7adf7 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -83,6 +83,9 @@ commands: description: Only staff members can force-close tickets. title: ❌ Error options: + category: + description: Close all tickets in the specified category (can be used with `time`) + name: category reason: description: The reason for closing the ticket(s) name: reason @@ -90,7 +93,7 @@ commands: description: The ticket to close name: ticket time: - description: Close all tickets that have been inactive for the specified time + description: Close all tickets that have been inactive for the specified time (can be used with `category`) name: time help: description: Show the help menu From 90c0521e3511c31d27efa752d50ede74086291ce Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 18 Oct 2022 14:58:30 +0100 Subject: [PATCH 163/409] update dependencies --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index d592fb5..5334da0 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.1.3", + "@discord-tickets/settings": "^1.1.4", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.1.0", @@ -45,10 +45,10 @@ "@fastify/oauth2": "^5.1.0", "@prisma/client": "^4.4.0", "cryptr": "^6.0.3", - "discord.js": "^14.5.0", + "discord.js": "^14.6.0", "dotenv": "^16.0.3", - "express": "^4.18.1", - "fastify": "^4.7.0", + "express": "^4.18.2", + "fastify": "^4.9.2", "figlet": "^1.5.2", "fs-extra": "^10.1.0", "keyv": "^4.5.0", @@ -64,15 +64,15 @@ "yaml": "^1.10.2" }, "devDependencies": { - "all-contributors-cli": "^6.23.0", - "eslint": "^8.24.0", + "all-contributors-cli": "^6.24.0", + "eslint": "^8.25.0", "eslint-plugin-unused-imports": "^2.0.0", "nodemon": "^2.0.20" }, "optionalDependencies": { - "bufferutil": "^4.0.6", + "bufferutil": "^4.0.7", "erlpack": "github:discord/erlpack", - "utf-8-validate": "^5.0.9", + "utf-8-validate": "^5.0.10", "zlib-sync": "^0.1.7" } } \ No newline at end of file From 59c6ab353768d422d76b33b6f71f8f7472f0712f Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 18 Oct 2022 16:39:03 +0100 Subject: [PATCH 164/409] Make `/tag` and auto tag replies work (very cool) --- src/autocomplete/tag.js | 25 +++++++++++++++++++++- src/commands/slash/tag.js | 28 +++++++++++++++++++++++-- src/listeners/client/messageCreate.js | 30 ++++++++++++++++++++------- 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/autocomplete/tag.js b/src/autocomplete/tag.js index 126bd78..b75a76d 100644 --- a/src/autocomplete/tag.js +++ b/src/autocomplete/tag.js @@ -8,5 +8,28 @@ module.exports = class TagCompleter extends Autocompleter { }); } - async run(value, comamnd, interaction) { } + /** + * @param {string} value + * @param {*} command + * @param {import("discord.js").AutocompleteInteraction} interaction + */ + async run(value, command, interaction) { + /** @type {import("client")} */ + const client = this.client; + + const tags = await client.prisma.tag.findMany({ where: { guildId: interaction.guild.id } }); + const options = value ? tags.filter(tag => + tag.name.match(new RegExp(value, 'i')) || + tag.content.match(new RegExp(value, 'i')) || + tag.regex?.match(new RegExp(value, 'i')), + ) : tags; + await interaction.respond( + options + .slice(0, 25) + .map(tag => ({ + name: tag.name, + value: tag.id, + })), + ); + } }; \ No newline at end of file diff --git a/src/commands/slash/tag.js b/src/commands/slash/tag.js index b6f0a05..dd4b655 100644 --- a/src/commands/slash/tag.js +++ b/src/commands/slash/tag.js @@ -1,5 +1,6 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); +const ExtendedEmbedBuilder = require('../../lib/embed'); module.exports = class TagSlashCommand extends SlashCommand { constructor(client, options) { @@ -14,7 +15,7 @@ module.exports = class TagSlashCommand extends SlashCommand { autocomplete: true, name: 'tag', required: true, - type: ApplicationCommandOptionType.String, + type: ApplicationCommandOptionType.Integer, }, { name: 'for', @@ -48,5 +49,28 @@ module.exports = class TagSlashCommand extends SlashCommand { }); } - async run(interaction) { } + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + const user = interaction.options.getUser('for', false); + await interaction.deferReply({ ephemeral: !user }); + const tag = await client.prisma.tag.findUnique({ + include: { guild: true }, + where: { id: interaction.options.getInteger('tag', true) }, + }); + + await interaction.editReply({ + allowedMentions: { users: user ? [user.id]: [] }, + content: user?.toString(), + embeds: [ + new ExtendedEmbedBuilder() + .setColor(tag.guild.primaryColour) + .setDescription(tag.content), + ], + }); + } }; \ No newline at end of file diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index 00aa22a..842def2 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -179,13 +179,11 @@ module.exports = class extends Listener { }); } } else { - let ticket = await client.prisma.ticket.findUnique({ - include: { guild: true }, - where: { id: message.channel.id }, - }); + const settings = await client.prisma.guild.findUnique({ where: { id:message.guild.id } }); + let ticket = await client.prisma.ticket.findUnique({ where: { id: message.channel.id } }); if (ticket) { - if (ticket.guild.archive) { + if (settings.archive) { try { await client.tickets.archiver.saveMessage(ticket.id, message); } catch (error) { @@ -218,8 +216,26 @@ module.exports = class extends Listener { // TODO: if (!message.author.bot) staff status alert, working hours alerts } - // TODO: if (!message.author.bot) auto tag - + if (!message.author.bot) { + const enabled = + (settings.autoTag === 'all') || + (settings.autoTag === 'ticket' && ticket) || + (settings.autoTag === '!ticket' && !ticket) || + (settings.autoTag.includes(message.channel.id)); + if (enabled) { + const tags = await client.prisma.tag.findMany({ where: { guildId: message.guild.id } }); + const tag = tags.find(tag => message.content.match(new RegExp(tag.regex, 'mi'))); + if (tag) { + await message.reply({ + embeds: [ + new EmbedBuilder() + .setColor(settings.primaryColour) + .setDescription(tag.content), + ], + }); + } + } + } } } }; From bb4f872c41c7c5bbdc5e318717b6e5f602799d39 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 18 Oct 2022 17:25:31 +0100 Subject: [PATCH 165/409] Archive & log message updates/deletes --- src/i18n/en-GB.yml | 15 +++++++- src/lib/logging.js | 53 +++++++++++++++++++++++++++ src/listeners/client/messageDelete.js | 43 ++++++++++++++++++++-- src/listeners/client/messageUpdate.js | 38 +++++++++++++++++-- 4 files changed, 141 insertions(+), 8 deletions(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index aa7adf7..f2eb93f 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -84,7 +84,9 @@ commands: title: ❌ Error options: category: - description: Close all tickets in the specified category (can be used with `time`) + description: >- + Close all tickets in the specified category (can be used with + `time`) name: category reason: description: The reason for closing the ticket(s) @@ -93,7 +95,9 @@ commands: description: The ticket to close name: ticket time: - description: Close all tickets that have been inactive for the specified time (can be used with `category`) + description: >- + Close all tickets that have been inactive for the specified time + (can be used with `category`) name: time help: description: Show the help menu @@ -213,6 +217,13 @@ log: create: created delete: deleted update: updated + message: + description: "{user} {verb} a message" + message: Message + title: Message {verb} + verb: + delete: deleted + update: updated ticket: description: "{user} {verb} a ticket" ticket: Ticket diff --git a/src/lib/logging.js b/src/lib/logging.js index 2be4d98..5c5af8f 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -183,8 +183,61 @@ async function logTicketEvent(client, { return await channel.send({ embeds }); } +/** + * @param {import("client")} client + * @param {object} details + * @param {string} details.action + * @param {import("discord.js").Message} details.target + * @param {import("@prisma/client").Ticket & {guild: import("@prisma/client").Guild}} details.ticket +*/ +async function logMessageEvent(client, { + action, target, ticket, diff, +}) { + if (!ticket) return; + client.log.info.tickets(`${target.member.user.tag} ${client.i18n.getMessage('en-GB', `log.message.verb.${action}`)} message ${target.id}`); + if (!ticket.guild.logChannel) return; + const colour = action === 'update' + ? 'Purple' : action === 'delete' + ? 'DarkPurple' : 'Default'; + const getMessage = client.i18n.getLocale(ticket.guild.locale); + const i18nOptions = { + user: `<@${target.member.user.id}>`, + verb: getMessage(`log.message.verb.${action}`), + }; + const channel = client.channels.cache.get(ticket.guild.logChannel); + if (!channel) return; + const embeds = [ + new EmbedBuilder() + .setColor(colour) + .setAuthor({ + iconURL: target.member.displayAvatarURL(), + name: target.member.displayName, + }) + .setTitle(getMessage('log.message.title', i18nOptions)) + .setDescription(getMessage('log.message.description', i18nOptions)) + .addFields([ + { + name: getMessage('log.message.message'), + value: `[${target.id}](${target.url})`, + }, + ]), + ]; + + if (diff && diff.original) { + embeds.push( + new EmbedBuilder() + .setColor(colour) + .setTitle(getMessage('log.admin.changes')) + .setFields(makeDiff(diff)), + ); + } + + return await channel.send({ embeds }); +} + module.exports = { getLogChannel, logAdminEvent, + logMessageEvent, logTicketEvent, }; \ No newline at end of file diff --git a/src/listeners/client/messageDelete.js b/src/listeners/client/messageDelete.js index a6dd3dc..1d307a6 100644 --- a/src/listeners/client/messageDelete.js +++ b/src/listeners/client/messageDelete.js @@ -1,4 +1,5 @@ const { Listener } = require('@eartharoid/dbf'); +const { logMessageEvent } = require('../../lib/logging'); module.exports = class extends Listener { constructor(client, options) { @@ -9,8 +10,44 @@ module.exports = class extends Listener { }); } - run(message) { - // TODO: archive messages in tickets - // TODO: log channel + /** + * @param {import("discord.js").Message} oldMessage + * @param {import("discord.js").Message} message + */ + async run(message) { + /** @type {import("client")} */ + const client = this.client; + + if (message.partial) message.fetch().then(m => (message = m)).catch(client.log.error); + const ticket = await client.prisma.ticket.findUnique({ + include: { guild: true }, + where: { id: message.channel.id }, + }); + if (!ticket) return; + + if (ticket.guild.archive) { + try { + const archived = await client.prisma.archivedMessage.findUnique({ where: { id: message.id } }); + if (archived) { + await client.prisma.archivedMessage.update({ + data: { deleted: true }, + where: { id: message.id }, + }); + } + } catch (error) { + client.log.warn('Failed to "delete" archived message', message.id); + client.log.error(error); + } + } + + await logMessageEvent(this.client, { + action: 'delete', + diff: { + original: { content: message.cleanContent }, + updated: { content: '' }, + }, + target: message, + ticket, + }); } }; diff --git a/src/listeners/client/messageUpdate.js b/src/listeners/client/messageUpdate.js index 6792eb6..4239cb6 100644 --- a/src/listeners/client/messageUpdate.js +++ b/src/listeners/client/messageUpdate.js @@ -1,4 +1,5 @@ const { Listener } = require('@eartharoid/dbf'); +const { logMessageEvent } = require('../../lib/logging'); module.exports = class extends Listener { constructor(client, options) { @@ -9,8 +10,39 @@ module.exports = class extends Listener { }); } - run(oldMessage, newMessage) { - // TODO: archive messages in tickets - // TODO: log channel + + /** + * @param {import("discord.js").Message} oldMessage + * @param {import("discord.js").Message} newMessage + */ + async run(oldMessage, newMessage) { + /** @type {import("client")} */ + const client = this.client; + + if (newMessage.partial) newMessage.fetch().then(m => (newMessage = m)).catch(client.log.error); + const ticket = await client.prisma.ticket.findUnique({ + include: { guild: true }, + where: { id: newMessage.channel.id }, + }); + if (!ticket) return; + + if (ticket.guild.archive) { + try { + await client.tickets.archiver.saveMessage(ticket.id, newMessage); + } catch (error) { + client.log.warn('Failed to update archived message', newMessage.id); + client.log.error(error); + } + } + + await logMessageEvent(this.client, { + action: 'update', + diff: { + original: { content: oldMessage.cleanContent }, + updated: { content: newMessage.cleanContent }, + }, + target: newMessage, + ticket, + }); } }; From d350f644e3bd5b1d2b3799ef5f3ebb7991ab76a3 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 18 Oct 2022 17:57:27 +0100 Subject: [PATCH 166/409] update dependencies --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5334da0..a17c8e8 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.1.4", + "@discord-tickets/settings": "^1.1.5", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.1.0", @@ -43,7 +43,7 @@ "@fastify/http-proxy": "^8.2.3", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", - "@prisma/client": "^4.4.0", + "@prisma/client": "^4.5.0", "cryptr": "^6.0.3", "discord.js": "^14.6.0", "dotenv": "^16.0.3", @@ -58,7 +58,7 @@ "node-dir": "^0.1.17", "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", - "prisma": "^4.4.0", + "prisma": "^4.5.0", "semver": "^7.3.8", "terminal-link": "^2.1.1", "yaml": "^1.10.2" From c9c84f3d91c1a7f303c30cd2b2df942b4c34bc9c Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 22 Oct 2022 21:16:50 +0100 Subject: [PATCH 167/409] fix typo --- scripts/preinstall.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/preinstall.js b/scripts/preinstall.js index 10fd114..b43b18d 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -8,7 +8,7 @@ const env = { DISCORD_TOKEN: '', ENCRYPTION_KEY: randomBytes(24).toString('hex'), HTTP_BIND: 8080, - HTTP_EXTERNAL: 'http://localhost', + HTTP_EXTERNAL: 'http://localhost:8080', PORTAL: '', PUBLIC_BOT: false, SETTINGS_BIND: 8888, From 15df841774f9fd913a12ad5e60c97fa96c9a6ec3 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 24 Oct 2022 12:30:37 +0100 Subject: [PATCH 168/409] update config template --- user/example.config.yml | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/user/example.config.yml b/user/example.config.yml index b823c24..56e3b8c 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -18,18 +18,20 @@ logs: files: directory: ./logs enabled: true - keepFor: 30 # days - level: info # debug|verbose|info|success|warn|notice|error|critical + keepFor: 30 + level: info +overrides: + disableArchives: false presence: activities: - name: /new - name: with {totalTickets} tickets - - name: '{openTickets} tickets' - type: 3 # https://discord-api-types.dev/api/discord-api-types-v10/enum/ActivityType - - name: '{avgResponseTime} response time' + - name: "{openTickets} tickets" type: 3 - interval: 20 # seconds, only used if activities.length > 1 - status: online # online|idle|invisible|dnd -overrides: - disableArchives: false -stats: true \ No newline at end of file + - name: "{avgResponseTime} response time" + type: 3 + interval: 20 + status: online +stats: true +templates: + transcript: transcript.md From ecd23a150a95a31cb651f99870a3b2eade25165c Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 24 Oct 2022 17:44:07 +0100 Subject: [PATCH 169/409] refactor: remove cryptr prefix --- src/autocomplete/references.js | 4 ++-- src/autocomplete/ticket.js | 4 ++-- src/lib/tickets/archiver.js | 10 ++++------ src/modals/questions.js | 11 +++++++---- src/modals/topic.js | 9 ++++++--- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/autocomplete/references.js b/src/autocomplete/references.js index f947726..87b8d40 100644 --- a/src/autocomplete/references.js +++ b/src/autocomplete/references.js @@ -1,7 +1,7 @@ const { Autocompleter } = require('@eartharoid/dbf'); const emoji = require('node-emoji'); const Cryptr = require('cryptr'); -const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); +const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class ReferencesCompleter extends Autocompleter { constructor(client, options) { @@ -45,7 +45,7 @@ module.exports = class ReferencesCompleter extends Autocompleter { .slice(0, 25) .map(t => { const date = new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' }); - const topic = t.topic ? '| ' + cryptr.decrypt(t.topic).substring(0, 50) : ''; + const topic = t.topic ? '| ' + decrypt(t.topic).substring(0, 50) : ''; const category = emoji.hasEmoji(t.category.emoji) ? emoji.get(t.category.emoji) + ' ' + t.category.name : t.category.name; return { name: `${category} #${t.number} - ${date} ${topic}`, diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js index bdf58a1..f1bbb24 100644 --- a/src/autocomplete/ticket.js +++ b/src/autocomplete/ticket.js @@ -1,7 +1,7 @@ const { Autocompleter } = require('@eartharoid/dbf'); const emoji = require('node-emoji'); const Cryptr = require('cryptr'); -const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); +const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class TicketCompleter extends Autocompleter { constructor(client, options) { @@ -45,7 +45,7 @@ module.exports = class TicketCompleter extends Autocompleter { .slice(0, 25) .map(t => { const date = new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' }); - const topic = t.topic ? '| ' + cryptr.decrypt(t.topic).substring(0, 50) : ''; + const topic = t.topic ? '| ' + decrypt(t.topic).substring(0, 50) : ''; const category = emoji.hasEmoji(t.category.emoji) ? emoji.get(t.category.emoji) + ' ' + t.category.name : t.category.name; return { name: `${category} #${t.number} - ${date} ${topic}`, diff --git a/src/lib/tickets/archiver.js b/src/lib/tickets/archiver.js index 97bbf8f..c355046 100644 --- a/src/lib/tickets/archiver.js +++ b/src/lib/tickets/archiver.js @@ -1,5 +1,5 @@ const Cryptr = require('cryptr'); -const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); +const { encrypt } = new Cryptr(process.env.ENCRYPTION_KEY); /** * Returns highest (roles.highest) hoisted role , or everyone @@ -12,8 +12,6 @@ module.exports = class TicketArchiver { constructor(client) { /** @type {import("client")} */ this.client = client; - this.encrypt = cryptr.encrypt; - this.decrypt = cryptr.decrypt; } /** Add or update a message @@ -78,11 +76,11 @@ module.exports = class TicketArchiver { avatar: member.avatar || member.user.avatar, // TODO: save avatar in user/avatars/ bot: member.user.bot, discriminator: member.user.discriminator, - displayName: member.displayName ? this.encrypt(member.displayName) : null, + displayName: member.displayName ? encrypt(member.displayName) : null, roleId: !!member && hoistedRole(member).id, ticketId, userId: member.user.id, - username: this.encrypt(member.user.username), + username: encrypt(member.user.username), }; await this.client.prisma.archivedUser.upsert({ create: data, @@ -108,7 +106,7 @@ module.exports = class TicketArchiver { }, }, }, - content: cryptr.encrypt( + content: encrypt( JSON.stringify({ attachments: [...message.attachments.values()], components: [...message.components.values()], diff --git a/src/modals/questions.js b/src/modals/questions.js index 6996dbb..6874783 100644 --- a/src/modals/questions.js +++ b/src/modals/questions.js @@ -3,7 +3,10 @@ const { EmbedBuilder } = require('discord.js'); const ExtendedEmbedBuilder = require('../lib/embed'); const { logTicketEvent } = require('../lib/logging'); const Cryptr = require('cryptr'); -const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); +const { + encrypt, + decrypt, +} = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class QuestionsModal extends Modal { constructor(client, options) { @@ -51,7 +54,7 @@ module.exports = class QuestionsModal extends Modal { data: { questionAnswers: { update: interaction.fields.fields.map(f => ({ - data: { value: f.value ? cryptr.encrypt(f.value) : '' }, + data: { value: f.value ? encrypt(f.value) : '' }, where: { id: Number(f.customId) }, })), }, @@ -72,7 +75,7 @@ module.exports = class QuestionsModal extends Modal { ticket.questionAnswers .map(a => ({ name: a.question.label, - value: a.value ? cryptr.decrypt(a.value) : getMessage('ticket.answers.no_value'), + value: a.value ? decrypt(a.value) : getMessage('ticket.answers.no_value'), })), ); await opening.edit({ embeds }); @@ -94,7 +97,7 @@ module.exports = class QuestionsModal extends Modal { const makeDiff = ticket => { const diff = {}; ticket.questionAnswers.forEach(a => { - diff[a.question.label] = a.value ? cryptr.decrypt(a.value) : getMessage('ticket.answers.no_value'); + diff[a.question.label] = a.value ? decrypt(a.value) : getMessage('ticket.answers.no_value'); }); return diff; }; diff --git a/src/modals/topic.js b/src/modals/topic.js index d0bb08d..1b9ab94 100644 --- a/src/modals/topic.js +++ b/src/modals/topic.js @@ -3,7 +3,10 @@ const { EmbedBuilder } = require('discord.js'); const ExtendedEmbedBuilder = require('../lib/embed'); const { logTicketEvent } = require('../lib/logging'); const Cryptr = require('cryptr'); -const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); +const { + encrypt, + decrypt, +} = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class TopicModal extends Modal { constructor(client, options) { @@ -38,7 +41,7 @@ module.exports = class TopicModal extends Modal { where: { id: interaction.channel.id }, }); const ticket = await client.prisma.ticket.update({ - data: { topic: topic ? cryptr.encrypt(topic) : null }, + data: { topic: topic ? encrypt(topic) : null }, select, where: { id: interaction.channel.id }, }); @@ -72,7 +75,7 @@ module.exports = class TopicModal extends Modal { /** @param {ticket} ticket */ const makeDiff = ticket => { const diff = {}; - diff[getMessage('ticket.opening_message.fields.topic')] = ticket.topic ? cryptr.decrypt(ticket.topic) : ' '; + diff[getMessage('ticket.opening_message.fields.topic')] = ticket.topic ? decrypt(ticket.topic) : ' '; return diff; }; From 04369523279e8c8e2b2e02f3c12d23971584b327 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 24 Oct 2022 18:24:53 +0100 Subject: [PATCH 170/409] fix(logging): ignore ephemeral message updates --- src/listeners/client/messageUpdate.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/listeners/client/messageUpdate.js b/src/listeners/client/messageUpdate.js index 4239cb6..ab99ebc 100644 --- a/src/listeners/client/messageUpdate.js +++ b/src/listeners/client/messageUpdate.js @@ -1,4 +1,5 @@ const { Listener } = require('@eartharoid/dbf'); +const { MessageFlagsBitField } = require('discord.js'); const { logMessageEvent } = require('../../lib/logging'); module.exports = class extends Listener { @@ -20,6 +21,8 @@ module.exports = class extends Listener { const client = this.client; if (newMessage.partial) newMessage.fetch().then(m => (newMessage = m)).catch(client.log.error); + if (newMessage.flags.has(MessageFlagsBitField.Flags.Ephemeral)) return; + const ticket = await client.prisma.ticket.findUnique({ include: { guild: true }, where: { id: newMessage.channel.id }, From 7864c8d544169c30338f66013616ff09d9bbd3a9 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 24 Oct 2022 20:16:56 +0100 Subject: [PATCH 171/409] fix(logging): don't send empty changelogs --- src/lib/logging.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/logging.js b/src/lib/logging.js index 5c5af8f..bae66f8 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -109,7 +109,7 @@ async function logAdminEvent(client, { ]), ]; - if (diff && diff.original) { + if (diff?.original && makeDiff(diff)) { embeds.push( new EmbedBuilder() .setColor(colour) @@ -171,7 +171,7 @@ async function logTicketEvent(client, { ]), ]; - if (diff && diff.original) { + if (diff?.original && makeDiff(diff)) { embeds.push( new EmbedBuilder() .setColor(colour) @@ -223,7 +223,7 @@ async function logMessageEvent(client, { ]), ]; - if (diff && diff.original) { + if (diff?.original && makeDiff(diff)) { embeds.push( new EmbedBuilder() .setColor(colour) From 92d5a7ed96c6c56e8e3147a153da89115f1af88b Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 24 Oct 2022 20:17:40 +0100 Subject: [PATCH 172/409] feat(archives): add transcript command --- .gitignore | 5 +- package.json | 2 + src/autocomplete/references.js | 28 ++++---- src/autocomplete/ticket.js | 28 ++++---- src/commands/slash/transcript.js | 118 ++++++++++++++++++++++++++++++- user/templates/transcript.md | 26 +++++++ 6 files changed, 174 insertions(+), 33 deletions(-) create mode 100644 user/templates/transcript.md diff --git a/.gitignore b/.gitignore index 4b697a0..db3f820 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,12 @@ node_modules/ prisma/ # files -.env +*.env* *.db *.db-journal *.log *-lock.* user/config.yml user/**/*.* -!user/**/.gitkeep \ No newline at end of file +!user/**/.gitkeep +!user/templates/* \ No newline at end of file diff --git a/package.json b/package.json index a17c8e8..86575ea 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,11 @@ "leeks.js": "^0.2.4", "leekslazylogger": "^4.1.7", "ms": "^2.1.3", + "mustache": "^4.2.0", "node-dir": "^0.1.17", "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", + "pad": "^3.2.0", "prisma": "^4.5.0", "semver": "^7.3.8", "terminal-link": "^2.1.1", diff --git a/src/autocomplete/references.js b/src/autocomplete/references.js index 87b8d40..f5bd54c 100644 --- a/src/autocomplete/references.js +++ b/src/autocomplete/references.js @@ -11,6 +11,13 @@ module.exports = class ReferencesCompleter extends Autocompleter { }); } + format(ticket) { + const date = new Date(ticket.createdAt).toLocaleString(ticket.guild.locale, { dateStyle: 'short' }); + const topic = ticket.topic ? '| ' + decrypt(ticket.topic).substring(0, 50) : ''; + const category = emoji.hasEmoji(ticket.category.emoji) ? emoji.get(ticket.category.emoji) + ' ' + ticket.category.name : ticket.category.name; + return `${category} #${ticket.number} - ${date} ${topic}`; + } + /** * @param {string} value * @param {*} comamnd @@ -19,7 +26,6 @@ module.exports = class ReferencesCompleter extends Autocompleter { async run(value, comamnd, interaction) { /** @type {import("client")} */ const client = this.client; - const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); const tickets = await client.prisma.ticket.findMany({ include: { category: { @@ -28,6 +34,7 @@ module.exports = class ReferencesCompleter extends Autocompleter { name: true, }, }, + guild: true, }, where: { createdById: interaction.user.id, @@ -35,23 +42,14 @@ module.exports = class ReferencesCompleter extends Autocompleter { open: false, }, }); - const options = value ? tickets.filter(t => - String(t.number).match(new RegExp(value, 'i')) || - t.topic?.match(new RegExp(value, 'i')) || - new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' })?.match(new RegExp(value, 'i')), - ) : tickets; + const options = value ? tickets.filter(t => this.format(t).match(new RegExp(value, 'i'))) : tickets; await interaction.respond( options .slice(0, 25) - .map(t => { - const date = new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' }); - const topic = t.topic ? '| ' + decrypt(t.topic).substring(0, 50) : ''; - const category = emoji.hasEmoji(t.category.emoji) ? emoji.get(t.category.emoji) + ' ' + t.category.name : t.category.name; - return { - name: `${category} #${t.number} - ${date} ${topic}`, - value: t.id, - }; - }), + .map(t => ({ + name: this.format(t), + value: t.id, + })), ); } }; \ No newline at end of file diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js index f1bbb24..fec52e6 100644 --- a/src/autocomplete/ticket.js +++ b/src/autocomplete/ticket.js @@ -11,6 +11,13 @@ module.exports = class TicketCompleter extends Autocompleter { }); } + format(ticket) { + const date = new Date(ticket.createdAt).toLocaleString(ticket.guild.locale, { dateStyle: 'short' }); + const topic = ticket.topic ? '| ' + decrypt(ticket.topic).substring(0, 50) : ''; + const category = emoji.hasEmoji(ticket.category.emoji) ? emoji.get(ticket.category.emoji) + ' ' + ticket.category.name : ticket.category.name; + return `${category} #${ticket.number} - ${date} ${topic}`; + } + /** * @param {string} value * @param {*} command @@ -19,7 +26,6 @@ module.exports = class TicketCompleter extends Autocompleter { async run(value, command, interaction) { /** @type {import("client")} */ const client = this.client; - const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); const tickets = await client.prisma.ticket.findMany({ include: { category: { @@ -28,6 +34,7 @@ module.exports = class TicketCompleter extends Autocompleter { name: true, }, }, + guild: true, }, where: { createdById: interaction.user.id, @@ -35,23 +42,14 @@ module.exports = class TicketCompleter extends Autocompleter { open: ['add', 'close', 'force-close', 'remove'].includes(command.name), // false for `new`, `transcript` etc }, }); - const options = value ? tickets.filter(t => - String(t.number).match(new RegExp(value, 'i')) || - t.topic?.match(new RegExp(value, 'i')) || - new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' })?.match(new RegExp(value, 'i')), - ) : tickets; + const options = value ? tickets.filter(t => this.format(t).match(new RegExp(value, 'i'))) : tickets; await interaction.respond( options .slice(0, 25) - .map(t => { - const date = new Date(t.createdAt).toLocaleString(settings.locale, { dateStyle: 'short' }); - const topic = t.topic ? '| ' + decrypt(t.topic).substring(0, 50) : ''; - const category = emoji.hasEmoji(t.category.emoji) ? emoji.get(t.category.emoji) + ' ' + t.category.name : t.category.name; - return { - name: `${category} #${t.number} - ${date} ${topic}`, - value: t.id, - }; - }), + .map(t => ({ + name: this.format(t), + value: t.id, + })), ); } }; \ No newline at end of file diff --git a/src/commands/slash/transcript.js b/src/commands/slash/transcript.js index 3b6cc54..890f474 100644 --- a/src/commands/slash/transcript.js +++ b/src/commands/slash/transcript.js @@ -1,5 +1,12 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); +const fs = require('fs'); +const { join } = require('path'); +const Mustache = require('mustache'); +const { AttachmentBuilder } = require('discord.js'); +const Cryptr = require('cryptr'); +const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); +const pad = require('pad'); module.exports = class TranscriptSlashCommand extends SlashCommand { constructor(client, options) { @@ -41,7 +48,116 @@ module.exports = class TranscriptSlashCommand extends SlashCommand { nameLocalizations, options: opts, }); + + Mustache.escape = text => text; // don't HTML-escape + this.template = fs.readFileSync( + join('./user/templates/', this.client.config.templates.transcript), + { encoding: 'utf8' }, + ); } - async run(interaction) { } + async fillTemplate(ticketId) { + /** @type {import("client")} */ + const client = this.client; + const ticket = await client.prisma.ticket.findUnique({ + include: { + archivedChannels: true, + archivedMessages: { + orderBy: { createdAt: 'asc' }, + where: { external: false }, + }, + archivedRoles: true, + archivedUsers: true, + category: true, + claimedBy: true, + closedBy: true, + createdBy: true, + feedback: true, + guild: true, + questionAnswers: true, + }, + where: { id: ticketId }, + }); + if (!ticket) throw new Error(`Ticket ${ticketId} does not exist`); + + ticket.claimedBy = ticket.archivedUsers.find(u => u.userId === ticket.claimedById); + ticket.closedBy = ticket.archivedUsers.find(u => u.userId === ticket.closedById); + ticket.createdBy = ticket.archivedUsers.find(u => u.userId === ticket.createdById); + + if (ticket.closedReason) ticket.closedReason = decrypt(ticket.closedReason); + if (ticket.feedback?.comment) ticket.feedback.comment = decrypt(ticket.feedback.comment); + if (ticket.topic) ticket.topic = decrypt(ticket.topic); + + ticket.archivedUsers.forEach((user, i) => { + if (user.displayName) user.displayName = decrypt(user.displayName); + user.username = decrypt(user.username); + ticket.archivedUsers[i] = user; + }); + + ticket.archivedMessages.forEach((message, i) => { + message.author = ticket.archivedUsers.find(u => u.userId === message.authorId); + message.content = JSON.parse(decrypt(message.content)); + message.text = message.content.content?.replace(/\n/g, '\n\t') ?? ''; + message.content.attachments?.forEach(a => (message.text += '\n\t' + a.url)); + message.content.embeds?.forEach(() => (message.text += '\n\t[embedded content]')); + message.number = 'M' + pad(String(ticket.archivedMessages.length).length, i + 1, '0'); + ticket.archivedMessages[i] = message; + }); + + ticket.pinnedMessageIds = ticket.pinnedMessageIds.map(id => ticket.archivedMessages.find(message => message.id === id)?.number); + + const channelName = ticket.category.channelName + .replace(/{+\s?(user)?name\s?}+/gi, ticket.createdBy?.username) + .replace(/{+\s?(nick|display)(name)?\s?}+/gi, ticket.createdBy?.displayName) + .replace(/{+\s?num(ber)?\s?}+/gi, ticket.number); + const fileName = `${channelName}.${this.client.config.templates.transcript.split('.').slice(-1)[0]}`; + const transcript = Mustache.render(this.template, { + channelName, + closedAtFull: function () { + return new Intl.DateTimeFormat([ticket.guild.locale, 'en-GB'], { + dateStyle: 'full', + timeStyle: 'long', + timeZone: 'Etc/UTC', + }).format(this.closedAt); + }, + createdAtFull: function () { + return new Intl.DateTimeFormat([ticket.guild.locale, 'en-GB'], { + dateStyle: 'full', + timeStyle: 'long', + timeZone: 'Etc/UTC', + }).format(this.createdAt); + }, + createdAtTimestamp: function () { + return new Intl.DateTimeFormat([ticket.guild.locale, 'en-GB'], { + dateStyle: 'short', + timeStyle: 'long', + timeZone: 'Etc/UTC', + }).format(this.createdAt); + }, + guildName: client.guilds.cache.get(ticket.guildId)?.name, + pinned: ticket.pinnedMessageIds.join(', '), + ticket, + }); + + return { + fileName, + transcript, + }; + } + + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + await interaction.deferReply({ ephemeral: true }); + const { + fileName, + transcript, + } = await this.fillTemplate(interaction.options.getString('ticket', true)); + const attachment = new AttachmentBuilder() + .setFile(Buffer.from(transcript)) + .setName(fileName); + await interaction.editReply({ files: [attachment] }); + // TODO: add portal link + } }; \ No newline at end of file diff --git a/user/templates/transcript.md b/user/templates/transcript.md new file mode 100644 index 0000000..ad6bef9 --- /dev/null +++ b/user/templates/transcript.md @@ -0,0 +1,26 @@ +#{{ channelName }} ticket transcript +--- +ID: {{ ticket.id }} +Number: {{ guildName }} #{{ ticket.number }} +Topic: {{ #ticket.topic }}{{ . }}{{ /ticket.topic }} +Created on: {{ #ticket }}{{ createdAtFull }}{{ /ticket }} +Created by: {{ #ticket.createdBy }}"{{ displayName }}" @{{ username }}#{{ discriminator }}{{ /ticket.createdBy }} +Closed on: {{ #ticket }}{{ closedAtFull }}{{ /ticket }} +Closed by: {{ #ticket.closedBy }}"{{ displayName }}" @{{ username }}#{{ discriminator }}{{ /ticket.closedBy }}{{ ^ticket.closedBy }}(automated){{ /ticket.closedBy }} +Closed because: {{ #ticket.closedReason }}{{ ticket.closedReason }}{{ /ticket.closedReason }}{{ ^ticket.closedReason }}(no reason){{ /ticket.closedReason }} +Claimed by: {{ #ticket.claimedBy }}"{{ displayName }}" @{{ username }}#{{ discriminator }}{{ /ticket.claimedBy }}{{ ^ticket.claimedBy }}(not claimed){{ /ticket.claimedBy }} +{{ #ticket.feedback }} +Feedback: + Rating: {{ rating }}/5 + Comment: {{ comment }}{{ ^comment }}(no comment){{ /comment }} +{{ /ticket.feedback }} +Participants: +{{ #ticket.archivedUsers }} + - "{{ displayName }}" @{{ username }}#{{ discriminator }} ({{ userId }}) +{{ /ticket.archivedUsers }} +Pinned messages: {{ #pinned }}{{ . }}{{ /pinned }} +--- + +{{ #ticket.archivedMessages }} +<{{ number }}> [{{ createdAtTimestamp }}] {{author.displayName}}: {{ text }} +{{ /ticket.archivedMessages }} \ No newline at end of file From 353b232dcde2d6fa25290d966d2654ba751e8bb4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 24 Oct 2022 23:14:08 +0100 Subject: [PATCH 173/409] fix(archives): add missing null topic message --- user/templates/transcript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user/templates/transcript.md b/user/templates/transcript.md index ad6bef9..aba6671 100644 --- a/user/templates/transcript.md +++ b/user/templates/transcript.md @@ -2,7 +2,7 @@ --- ID: {{ ticket.id }} Number: {{ guildName }} #{{ ticket.number }} -Topic: {{ #ticket.topic }}{{ . }}{{ /ticket.topic }} +Topic: {{ #ticket.topic }}{{ . }}{{ /ticket.topic }}{{ ^ticket.topic }}(no topic){{ /ticket.topic }} Created on: {{ #ticket }}{{ createdAtFull }}{{ /ticket }} Created by: {{ #ticket.createdBy }}"{{ displayName }}" @{{ username }}#{{ discriminator }}{{ /ticket.createdBy }} Closed on: {{ #ticket }}{{ closedAtFull }}{{ /ticket }} From 066eb954e309896ccdc63ea5c6a95e15d8e6bc14 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 24 Oct 2022 23:53:42 +0100 Subject: [PATCH 174/409] feat(archives): update transcript template so it can actually be parsed as markdown if wanted --- src/commands/slash/transcript.js | 2 +- user/templates/transcript.md | 26 ----------------------- user/templates/transcript.md.mustache | 30 +++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 27 deletions(-) delete mode 100644 user/templates/transcript.md create mode 100644 user/templates/transcript.md.mustache diff --git a/src/commands/slash/transcript.js b/src/commands/slash/transcript.js index 890f474..25a9f30 100644 --- a/src/commands/slash/transcript.js +++ b/src/commands/slash/transcript.js @@ -51,7 +51,7 @@ module.exports = class TranscriptSlashCommand extends SlashCommand { Mustache.escape = text => text; // don't HTML-escape this.template = fs.readFileSync( - join('./user/templates/', this.client.config.templates.transcript), + join('./user/templates/', this.client.config.templates.transcript + '.mustache'), { encoding: 'utf8' }, ); } diff --git a/user/templates/transcript.md b/user/templates/transcript.md deleted file mode 100644 index aba6671..0000000 --- a/user/templates/transcript.md +++ /dev/null @@ -1,26 +0,0 @@ -#{{ channelName }} ticket transcript ---- -ID: {{ ticket.id }} -Number: {{ guildName }} #{{ ticket.number }} -Topic: {{ #ticket.topic }}{{ . }}{{ /ticket.topic }}{{ ^ticket.topic }}(no topic){{ /ticket.topic }} -Created on: {{ #ticket }}{{ createdAtFull }}{{ /ticket }} -Created by: {{ #ticket.createdBy }}"{{ displayName }}" @{{ username }}#{{ discriminator }}{{ /ticket.createdBy }} -Closed on: {{ #ticket }}{{ closedAtFull }}{{ /ticket }} -Closed by: {{ #ticket.closedBy }}"{{ displayName }}" @{{ username }}#{{ discriminator }}{{ /ticket.closedBy }}{{ ^ticket.closedBy }}(automated){{ /ticket.closedBy }} -Closed because: {{ #ticket.closedReason }}{{ ticket.closedReason }}{{ /ticket.closedReason }}{{ ^ticket.closedReason }}(no reason){{ /ticket.closedReason }} -Claimed by: {{ #ticket.claimedBy }}"{{ displayName }}" @{{ username }}#{{ discriminator }}{{ /ticket.claimedBy }}{{ ^ticket.claimedBy }}(not claimed){{ /ticket.claimedBy }} -{{ #ticket.feedback }} -Feedback: - Rating: {{ rating }}/5 - Comment: {{ comment }}{{ ^comment }}(no comment){{ /comment }} -{{ /ticket.feedback }} -Participants: -{{ #ticket.archivedUsers }} - - "{{ displayName }}" @{{ username }}#{{ discriminator }} ({{ userId }}) -{{ /ticket.archivedUsers }} -Pinned messages: {{ #pinned }}{{ . }}{{ /pinned }} ---- - -{{ #ticket.archivedMessages }} -<{{ number }}> [{{ createdAtTimestamp }}] {{author.displayName}}: {{ text }} -{{ /ticket.archivedMessages }} \ No newline at end of file diff --git a/user/templates/transcript.md.mustache b/user/templates/transcript.md.mustache new file mode 100644 index 0000000..db2b80d --- /dev/null +++ b/user/templates/transcript.md.mustache @@ -0,0 +1,30 @@ +#{{ channelName }} ticket transcript + +--- + +* ID: {{ ticket.id }} +* Number: {{ guildName }} #{{ ticket.number }} +* Topic: {{ #ticket.topic }}{{ . }}{{ /ticket.topic }}{{ ^ticket.topic }}(no topic){{ /ticket.topic }} +* Created on: {{ #ticket }}{{ createdAtFull }}{{ /ticket }} +* Created by: {{ #ticket.createdBy }}"{{ displayName }}" @{{ username }}#{{ discriminator }}{{ /ticket.createdBy }} +* Closed on: {{ #ticket }}{{ closedAtFull }}{{ /ticket }} +* Closed by: {{ #ticket.closedBy }}"{{ displayName }}" @{{ username }}#{{ discriminator }}{{ /ticket.closedBy }}{{ ^ticket.closedBy }}(automated){{ /ticket.closedBy }} +* Closed because: {{ #ticket.closedReason }}{{ ticket.closedReason }}{{ /ticket.closedReason }}{{ ^ticket.closedReason }}(no reason){{ /ticket.closedReason }} +* Claimed by: {{ #ticket.claimedBy }}"{{ displayName }}" @{{ username }}#{{ discriminator }}{{ /ticket.claimedBy }}{{ ^ticket.claimedBy }}(not claimed){{ /ticket.claimedBy }} +{{ #ticket.feedback }} +* Feedback: + * Rating: {{ rating }}/5 + * Comment: {{ comment }}{{ ^comment }}(no comment){{ /comment }} +{{ /ticket.feedback }} +* Participants: +{{ #ticket.archivedUsers }} + * "{{ displayName }}" @{{ username }}#{{ discriminator }} ({{ userId }}) +{{ /ticket.archivedUsers }} +* Pinned messages: {{ #pinned }}{{ . }}{{ /pinned }} + +--- + +{{ #ticket.archivedMessages }} +<{{ number }}> [{{ createdAtTimestamp }}] {{author.displayName}}: {{ text }} + +{{ /ticket.archivedMessages }} \ No newline at end of file From 7812e627768b3eb80145b915c4498759f41b2c80 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 25 Oct 2022 22:53:12 +0100 Subject: [PATCH 175/409] fix(logging): don't send empty changelogs The previous fix didn't work because `{}` is true, not false as expected. --- src/lib/logging.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/logging.js b/src/lib/logging.js index bae66f8..430331b 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -109,7 +109,7 @@ async function logAdminEvent(client, { ]), ]; - if (diff?.original && makeDiff(diff)) { + if (diff?.original && Object.entries(makeDiff(diff)).length) { embeds.push( new EmbedBuilder() .setColor(colour) @@ -171,7 +171,7 @@ async function logTicketEvent(client, { ]), ]; - if (diff?.original && makeDiff(diff)) { + if (diff?.original && Object.entries(makeDiff(diff)).length) { embeds.push( new EmbedBuilder() .setColor(colour) @@ -223,7 +223,7 @@ async function logMessageEvent(client, { ]), ]; - if (diff?.original && makeDiff(diff)) { + if (diff?.original && Object.entries(makeDiff(diff)).length) { embeds.push( new EmbedBuilder() .setColor(colour) From 30883032b776cfd8d9df460d1b75729e22589b6d Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 25 Oct 2022 22:55:53 +0100 Subject: [PATCH 176/409] fix(archives): role and member bug --- src/lib/tickets/archiver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/tickets/archiver.js b/src/lib/tickets/archiver.js index c355046..f57df97 100644 --- a/src/lib/tickets/archiver.js +++ b/src/lib/tickets/archiver.js @@ -32,8 +32,8 @@ module.exports = class TicketArchiver { } const channels = message.mentions.channels; - const members = [...message.mentions.members]; - const roles = [...message.mentions.roles]; + const members = [...message.mentions.members.values()]; + const roles = [...message.mentions.roles.values()]; if (message.member) { members.push(message.member); From f27feea2f9ddd7e3a2ace10ebcd9e375a5b0eb2c Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 25 Oct 2022 22:56:13 +0100 Subject: [PATCH 177/409] feat: add `/topic` command --- src/commands/slash/topic.js | 52 +++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/commands/slash/topic.js b/src/commands/slash/topic.js index 8fe6b39..c3e8049 100644 --- a/src/commands/slash/topic.js +++ b/src/commands/slash/topic.js @@ -1,5 +1,12 @@ const { SlashCommand } = require('@eartharoid/dbf'); -const { ApplicationCommandOptionType } = require('discord.js'); +const { + ActionRowBuilder, + ModalBuilder, + TextInputBuilder, + TextInputStyle, +} = require('discord.js'); +const Cryptr = require('cryptr'); +const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class TopicSlashCommand extends SlashCommand { constructor(client, options) { @@ -19,5 +26,46 @@ module.exports = class TopicSlashCommand extends SlashCommand { }); } - async run(interaction) { } + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + const ticket = await client.prisma.ticket.findUnique({ + select: { + category: { select: { name: true } }, + guild: { select: { locale: true } }, + questionAnswers: { include: { question: true } }, + topic: true, + }, + where: { id: interaction.channel.id }, + }); + + const getMessage = client.i18n.getLocale(ticket.guild.locale); + + await interaction.showModal( + new ModalBuilder() + .setCustomId(JSON.stringify({ + action: 'topic', + edit: true, + })) + .setTitle(ticket.category.name) + .setComponents( + new ActionRowBuilder() + .setComponents( + new TextInputBuilder() + .setCustomId('topic') + .setLabel(getMessage('modals.topic.label')) + .setStyle(TextInputStyle.Paragraph) + .setMaxLength(1000) + .setMinLength(5) + .setPlaceholder(getMessage('modals.topic.placeholder')) + .setRequired(true) + .setValue(ticket.topic ? decrypt(ticket.topic) : ''), + ), + ), + ); + } }; \ No newline at end of file From 09095f71c112b24017762feb7907b8a05600ecbb Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 26 Oct 2022 00:41:40 +0100 Subject: [PATCH 178/409] perf: improve `ticket`/`references` autocompleters (and de-duplicate) --- .eslintrc.json | 2 +- src/autocomplete/references.js | 37 ++------------- src/autocomplete/ticket.js | 85 ++++++++++++++++++++++------------ 3 files changed, 60 insertions(+), 64 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 0e95594..f2aff2d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -142,7 +142,7 @@ "error" ], "no-underscore-dangle": [ - "error", + "warn", { "allowAfterThis": true, "allowFunctionParams": true diff --git a/src/autocomplete/references.js b/src/autocomplete/references.js index f5bd54c..300371e 100644 --- a/src/autocomplete/references.js +++ b/src/autocomplete/references.js @@ -1,7 +1,4 @@ const { Autocompleter } = require('@eartharoid/dbf'); -const emoji = require('node-emoji'); -const Cryptr = require('cryptr'); -const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class ReferencesCompleter extends Autocompleter { constructor(client, options) { @@ -11,12 +8,6 @@ module.exports = class ReferencesCompleter extends Autocompleter { }); } - format(ticket) { - const date = new Date(ticket.createdAt).toLocaleString(ticket.guild.locale, { dateStyle: 'short' }); - const topic = ticket.topic ? '| ' + decrypt(ticket.topic).substring(0, 50) : ''; - const category = emoji.hasEmoji(ticket.category.emoji) ? emoji.get(ticket.category.emoji) + ' ' + ticket.category.name : ticket.category.name; - return `${category} #${ticket.number} - ${date} ${topic}`; - } /** * @param {string} value @@ -24,32 +15,12 @@ module.exports = class ReferencesCompleter extends Autocompleter { * @param {import("discord.js").AutocompleteInteraction} interaction */ async run(value, comamnd, interaction) { - /** @type {import("client")} */ - const client = this.client; - const tickets = await client.prisma.ticket.findMany({ - include: { - category: { - select: { - emoji: true, - name: true, - }, - }, - guild: true, - }, - where: { - createdById: interaction.user.id, + await interaction.respond( + await this.client.autocomplete.components.get('ticket').getOptions(value, { guildId: interaction.guild.id, open: false, - }, - }); - const options = value ? tickets.filter(t => this.format(t).match(new RegExp(value, 'i'))) : tickets; - await interaction.respond( - options - .slice(0, 25) - .map(t => ({ - name: this.format(t), - value: t.id, - })), + userId: interaction.user.id, + }), ); } }; \ No newline at end of file diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js index fec52e6..e6ac012 100644 --- a/src/autocomplete/ticket.js +++ b/src/autocomplete/ticket.js @@ -1,7 +1,10 @@ +/* eslint-disable no-underscore-dangle */ const { Autocompleter } = require('@eartharoid/dbf'); const emoji = require('node-emoji'); const Cryptr = require('cryptr'); const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); +const Keyv = require('keyv'); +const ms = require('ms'); module.exports = class TicketCompleter extends Autocompleter { constructor(client, options) { @@ -9,13 +12,55 @@ module.exports = class TicketCompleter extends Autocompleter { ...options, id: 'ticket', }); + + this.cache = new Keyv(); } - format(ticket) { - const date = new Date(ticket.createdAt).toLocaleString(ticket.guild.locale, { dateStyle: 'short' }); - const topic = ticket.topic ? '| ' + decrypt(ticket.topic).substring(0, 50) : ''; - const category = emoji.hasEmoji(ticket.category.emoji) ? emoji.get(ticket.category.emoji) + ' ' + ticket.category.name : ticket.category.name; - return `${category} #${ticket.number} - ${date} ${topic}`; + async getOptions(value, { + guildId, + open, + userId, + }) { + /** @type {import("client")} */ + const client = this.client; + const cacheKey = [guildId, userId, open].join('/'); + + let tickets = await this.cache.get(cacheKey); + + if (!tickets) { + tickets = await client.prisma.ticket.findMany({ + include: { + category: { + select: { + emoji: true, + name: true, + }, + }, + guild: true, + }, + where: { + createdById: userId, + guildId, + open, + }, + }); + tickets = tickets.map(ticket => { + const date = new Date(ticket.createdAt).toLocaleString([ticket.guild.locale, 'en-GB'], { dateStyle: 'short' }); + const topic = ticket.topic ? '- ' + decrypt(ticket.topic).substring(0, 50) : ''; + const category = emoji.hasEmoji(ticket.category.emoji) ? emoji.get(ticket.category.emoji) + ' ' + ticket.category.name : ticket.category.name; + ticket._name = `${category} #${ticket.number} (${date}) ${topic}`; + return ticket; + }); + this.cache.set(cacheKey, tickets, ms('1m')); + } + + const options = value ? tickets.filter(t => t._name.match(new RegExp(value, 'i'))) : tickets; + return options + .slice(0, 25) + .map(t => ({ + name: t._name, + value: t.id, + })); } /** @@ -24,32 +69,12 @@ module.exports = class TicketCompleter extends Autocompleter { * @param {import("discord.js").AutocompleteInteraction} interaction */ async run(value, command, interaction) { - /** @type {import("client")} */ - const client = this.client; - const tickets = await client.prisma.ticket.findMany({ - include: { - category: { - select: { - emoji: true, - name: true, - }, - }, - guild: true, - }, - where: { - createdById: interaction.user.id, - guildId: interaction.guild.id, - open: ['add', 'close', 'force-close', 'remove'].includes(command.name), // false for `new`, `transcript` etc - }, - }); - const options = value ? tickets.filter(t => this.format(t).match(new RegExp(value, 'i'))) : tickets; await interaction.respond( - options - .slice(0, 25) - .map(t => ({ - name: this.format(t), - value: t.id, - })), + await this.getOptions(value, { + guildId: interaction.guild.id, + open: ['add', 'close', 'force-close', 'remove'].includes(command.name), // false for `new`, `transcript` etc + userId: interaction.user.id, + }), ); } }; \ No newline at end of file From 741700578235e220e90f4bb1e67d2c9a3aec8ca7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 26 Oct 2022 00:46:07 +0100 Subject: [PATCH 179/409] perf: reduce database data transfer --- src/autocomplete/ticket.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js index e6ac012..df38807 100644 --- a/src/autocomplete/ticket.js +++ b/src/autocomplete/ticket.js @@ -28,6 +28,10 @@ module.exports = class TicketCompleter extends Autocompleter { let tickets = await this.cache.get(cacheKey); if (!tickets) { + const { locale } = await client.prisma.guild.findUnique({ + select: { locale: true }, + where: { id: guildId }, + }); tickets = await client.prisma.ticket.findMany({ include: { category: { @@ -36,7 +40,6 @@ module.exports = class TicketCompleter extends Autocompleter { name: true, }, }, - guild: true, }, where: { createdById: userId, @@ -45,7 +48,7 @@ module.exports = class TicketCompleter extends Autocompleter { }, }); tickets = tickets.map(ticket => { - const date = new Date(ticket.createdAt).toLocaleString([ticket.guild.locale, 'en-GB'], { dateStyle: 'short' }); + const date = new Date(ticket.createdAt).toLocaleString([locale, 'en-GB'], { dateStyle: 'short' }); const topic = ticket.topic ? '- ' + decrypt(ticket.topic).substring(0, 50) : ''; const category = emoji.hasEmoji(ticket.category.emoji) ? emoji.get(ticket.category.emoji) + ' ' + ticket.category.name : ticket.category.name; ticket._name = `${category} #${ticket.number} (${date}) ${topic}`; From 72e264d04c8108cf5fc6cb4911960a4926acd925 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 26 Oct 2022 01:06:26 +0100 Subject: [PATCH 180/409] feat: add help hint for invalid stdin commands --- src/listeners/stdin/unknown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/listeners/stdin/unknown.js b/src/listeners/stdin/unknown.js index 976ce95..1ddc9d3 100644 --- a/src/listeners/stdin/unknown.js +++ b/src/listeners/stdin/unknown.js @@ -10,6 +10,6 @@ module.exports = class extends Listener { } run(commandName) { - this.client.log.warn('Unknown command:', commandName); + this.client.log.warn(`Unknown command: "${commandName}"; type "help" for a list of commands`); } }; From 9a916339efb847b6940e382ee6eebca56a8e1153 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 26 Oct 2022 10:26:43 +0100 Subject: [PATCH 181/409] fix: only allow `/topic` in tickets --- src/commands/slash/topic.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/commands/slash/topic.js b/src/commands/slash/topic.js index c3e8049..751c93d 100644 --- a/src/commands/slash/topic.js +++ b/src/commands/slash/topic.js @@ -7,6 +7,7 @@ const { } = require('discord.js'); const Cryptr = require('cryptr'); const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); +const ExtendedEmbedBuilder = require('../../lib/embed'); module.exports = class TopicSlashCommand extends SlashCommand { constructor(client, options) { @@ -43,6 +44,22 @@ module.exports = class TopicSlashCommand extends SlashCommand { where: { id: interaction.channel.id }, }); + if (!ticket) { + const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + const getMessage = client.i18n.getLocale(settings.locale); + return await interaction.reply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.not_ticket.title')) + .setDescription(getMessage('misc.not_ticket.description')), + ], + }); + } + const getMessage = client.i18n.getLocale(ticket.guild.locale); await interaction.showModal( From 119f997ffe429f612d1b7afdad96eadff75c1d3a Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 26 Oct 2022 11:34:55 +0100 Subject: [PATCH 182/409] fix: unnecessary message update logs --- src/listeners/client/messageCreate.js | 3 ++- src/listeners/client/messageUpdate.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index 842def2..56a07b6 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -10,7 +10,8 @@ const { SelectMenuOptionBuilder, } = require('discord.js'); const { - getCommonGuilds, isStaff, + getCommonGuilds, + isStaff, } = require('../../lib/users'); const ms = require('ms'); const emoji = require('node-emoji'); diff --git a/src/listeners/client/messageUpdate.js b/src/listeners/client/messageUpdate.js index ab99ebc..1ff4cd9 100644 --- a/src/listeners/client/messageUpdate.js +++ b/src/listeners/client/messageUpdate.js @@ -22,6 +22,7 @@ module.exports = class extends Listener { if (newMessage.partial) newMessage.fetch().then(m => (newMessage = m)).catch(client.log.error); if (newMessage.flags.has(MessageFlagsBitField.Flags.Ephemeral)) return; + if (!newMessage.edited) return; const ticket = await client.prisma.ticket.findUnique({ include: { guild: true }, From c6f1261478fc29f261fdd0d32a7fb84feea66b76 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 26 Oct 2022 14:13:45 +0100 Subject: [PATCH 183/409] feat: add `/tickets` command --- src/commands/slash/force-close.js | 2 +- src/commands/slash/help.js | 4 +- src/commands/slash/tickets.js | 103 +++++++++++++++++++++++++++++- src/i18n/en-GB.yml | 30 +++++++-- 4 files changed, 129 insertions(+), 10 deletions(-) diff --git a/src/commands/slash/force-close.js b/src/commands/slash/force-close.js index ffdf9b9..af13a34 100644 --- a/src/commands/slash/force-close.js +++ b/src/commands/slash/force-close.js @@ -81,7 +81,7 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { const getMessage = this.client.i18n.getLocale(settings.locale); let ticket; - if (!isStaff(interaction.guild, interaction.user.id)) { // if user is not staff + if (!(await isStaff(interaction.guild, interaction.user.id))) { // if user is not staff return await interaction.editReply({ embeds: [ new ExtendedEmbedBuilder({ diff --git a/src/commands/slash/help.js b/src/commands/slash/help.js index f5e5c0b..aaa8192 100644 --- a/src/commands/slash/help.js +++ b/src/commands/slash/help.js @@ -36,7 +36,7 @@ module.exports = class ClaimSlashCommand extends SlashCommand { .filter(c => c.type === 1) .map(c => `> : ${c.description}`) .join('\n'); - const ticket = client.application.commands.cache.find(c => c.name === 'new'); + const newCommand = client.application.commands.cache.find(c => c.name === 'new'); const fields = [ { name: getMessage('commands.slash.help.response.commands'), @@ -76,7 +76,7 @@ module.exports = class ClaimSlashCommand extends SlashCommand { .setTitle(getMessage('commands.slash.help.title')) .setDescription(staff ? `**Discord Tickets v${version} by eartharoid.**` - : getMessage('commands.slash.help.response.description', { command: `` })) + : getMessage('commands.slash.help.response.description', { command: `` })) .setFields(fields), ], }); diff --git a/src/commands/slash/tickets.js b/src/commands/slash/tickets.js index 94a7cce..0f71997 100644 --- a/src/commands/slash/tickets.js +++ b/src/commands/slash/tickets.js @@ -1,5 +1,9 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); +const { isStaff } = require('../../lib/users'); +const ExtendedEmbedBuilder = require('../../lib/embed'); +const Cryptr = require('cryptr'); +const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class TicketsSlashCommand extends SlashCommand { constructor(client, options) { @@ -42,5 +46,102 @@ module.exports = class TicketsSlashCommand extends SlashCommand { }); } - async run(interaction) { } + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply({ ephemeral: true }); + await client.application.commands.fetch(); + + const member = interaction.options.getMember('member', false) ?? interaction.member; + const ownOrOther = member.id === interaction.member.id ? 'own' : 'other'; + const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + const getMessage = client.i18n.getLocale(settings.locale); + + if (member.id !== interaction.member.id && !(await isStaff(interaction.guild, interaction.member.id))) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('commands.slash.tickets.not_staff.title')) + .setDescription(getMessage('commands.slash.tickets.not_staff.description')), + ], + }); + } + + const fields = []; + + const open = await client.prisma.ticket.findMany({ + include: { category: true }, + where: { + createdById: member.id, + guildId: interaction.guild.id, + open: true, + }, + }); + + const closed = await client.prisma.ticket.findMany({ + include: { category: true }, + orderBy: { createdAt: 'desc' }, + where: { + createdById: member.id, + guildId: interaction.guild.id, + open: false, + }, + }); + + if (open.length >= 1) { + fields.push({ + name: getMessage('commands.slash.tickets.response.fields.open.name'), + value: open.map(ticket =>{ + const topic = ticket.topic ? `- \`${decrypt(ticket.topic).replace(/\n/g, ' ').slice(0, 30) }\`` : ''; + return `> <#${ticket.id}> ${topic}`; + }).join('\n'), + }); + } + + if (closed.length === 0) { + const newCommand = client.application.commands.cache.find(c => c.name === 'new'); + fields.push({ + name: getMessage('commands.slash.tickets.response.fields.closed.name'), + value: getMessage(`commands.slash.tickets.response.fields.closed.none.${ownOrOther}`, { + new: ``, + user: member.user.toString(), + }), + }); + } else { + fields.push({ + name: getMessage('commands.slash.tickets.response.fields.closed.name'), + value: closed.slice(0, 10).map(ticket => { // max 10 rows + const topic = ticket.topic ? `- \`${decrypt(ticket.topic).replace(/\n/g, ' ').slice(0, 30)}\`` : ''; + return `> ${ticket.category.name} #${ticket.number} ${topic}`; + }).join('\n'), + }); + } + + const embed = new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.primaryColour) + .setAuthor({ + iconURL: member.displayAvatarURL(), + name: member.displayName, + }) + .setTitle(getMessage(`commands.slash.tickets.response.title.${ownOrOther}`, { displayName: member.displayName })) + .setFields(fields); + + if (settings.archive && !client.config.overrides.disableArchives) { + const transcriptCommand = client.application.commands.cache.find(c => c.name === 'transcript'); + embed.setDescription(getMessage('commands.slash.tickets.response.description', { transcript: `` })); + } + + return await interaction.editReply({ embeds: [embed] }); + } }; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index f2eb93f..52a0328 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -67,8 +67,8 @@ commands: force-close: confirm_multiple: description: > - You are about to close **{count}** tickets that have been inactive for - more than `{time}`: + You are about to close **{count}** tickets that have been inactive + for more than `{time}`: {tickets} title: ❓ Are you sure? @@ -85,8 +85,7 @@ commands: options: category: description: >- - Close all tickets in the specified category (can be used with - `time`) + Close all tickets in the specified category (can be used with `time`) name: category reason: description: The reason for closing the ticket(s) @@ -166,11 +165,30 @@ commands: name: tag tickets: description: List your own or someone else's tickets + fields: name: tickets + not_staff: + description: Only staff members can view others' tickets. + title: ❌ Error options: member: description: The member to list the tickets of name: member + response: + description: Use {transcript} to download the transcript of a ticket. + fields: + closed: + name: Closed tickets + none: + other: "{user} hasn't made any tickets." + own: | + You haven't made any tickets. + Use {new} to open a ticket. + open: + name: Open tickets + title: + other: "{displayName}'s tickets" + own: Your tickets topic: description: Change the topic of a ticket name: topic @@ -274,8 +292,8 @@ misc: - ❌ You already have %d open tickets missing_roles: description: >- - You do not have the roles required to be able to create a ticket in this - category. + You do not have the roles required to be able to create a ticket in + this category. title: ❌ Insufficient roles no_categories: description: No ticket categories have been configured. From 184340000441597d05f4f658038da3247c682146 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 26 Oct 2022 18:58:46 +0100 Subject: [PATCH 184/409] refactor: simplify commands i18n --- package.json | 6 +-- src/commands/slash/add.js | 62 +++++++++-------------- src/commands/slash/claim.js | 15 ++---- src/commands/slash/close.js | 62 +++++++++-------------- src/commands/slash/force-close.js | 84 +++++++++++++------------------ src/commands/slash/help.js | 15 ++---- src/commands/slash/move.js | 52 +++++++------------ src/commands/slash/new.js | 52 +++++++------------ src/commands/slash/priority.js | 71 +++++++++----------------- src/commands/slash/release.js | 15 ++---- src/commands/slash/remove.js | 62 +++++++++-------------- src/commands/slash/tag.js | 62 +++++++++-------------- src/commands/slash/tickets.js | 51 +++++++------------ src/commands/slash/topic.js | 15 ++---- src/commands/slash/transcript.js | 52 +++++++------------ src/commands/slash/transfer.js | 50 +++++++----------- 16 files changed, 263 insertions(+), 463 deletions(-) diff --git a/package.json b/package.json index 86575ea..8443ed0 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,9 @@ "@discord-tickets/settings": "^1.1.5", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", - "@eartharoid/i18n": "^1.1.0", + "@eartharoid/i18n": "^1.2.1", "@fastify/cookie": "^6.0.0", - "@fastify/cors": "^8.1.0", + "@fastify/cors": "^8.1.1", "@fastify/http-proxy": "^8.2.3", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", @@ -67,7 +67,7 @@ }, "devDependencies": { "all-contributors-cli": "^6.24.0", - "eslint": "^8.25.0", + "eslint": "^8.26.0", "eslint-plugin-unused-imports": "^2.0.0", "nodemon": "^2.0.20" }, diff --git a/src/commands/slash/add.js b/src/commands/slash/add.js index ccd3894..73bd32d 100644 --- a/src/commands/slash/add.js +++ b/src/commands/slash/add.js @@ -3,48 +3,32 @@ const { ApplicationCommandOptionType } = require('discord.js'); module.exports = class AddSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.add.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.add.name'))); - - let opts = [ - { - name: 'member', - required: true, - type: ApplicationCommandOptionType.User, - }, - { - autocomplete: true, - name: 'ticket', - required: false, - type: ApplicationCommandOptionType.String, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.add.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.add.options.${o.name}.name`))); - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - + const name = 'add'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, - options: opts, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), + options: [ + { + name: 'member', + required: true, + type: ApplicationCommandOptionType.User, + }, + { + autocomplete: true, + name: 'ticket', + required: false, + type: ApplicationCommandOptionType.String, + }, + ].map(option => { + option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); + option.description = option.descriptionLocalizations['en-GB']; + option.nameLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.name`); + return option; + }), }); } diff --git a/src/commands/slash/claim.js b/src/commands/slash/claim.js index cde6c67..05fedfe 100644 --- a/src/commands/slash/claim.js +++ b/src/commands/slash/claim.js @@ -2,19 +2,14 @@ const { SlashCommand } = require('@eartharoid/dbf'); module.exports = class ClaimSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.claim.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.claim.name'))); - + const name = 'claim'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), }); } diff --git a/src/commands/slash/close.js b/src/commands/slash/close.js index 3d7d1c2..fc94113 100644 --- a/src/commands/slash/close.js +++ b/src/commands/slash/close.js @@ -3,48 +3,32 @@ const { ApplicationCommandOptionType } = require('discord.js'); module.exports = class CloseSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.close.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.close.name'))); - - let opts = [ - { - name: 'reason', - required: false, - type: ApplicationCommandOptionType.String, - }, - { - autocomplete: true, - name: 'ticket', - required: false, - type: ApplicationCommandOptionType.String, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.close.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.close.options.${o.name}.name`))); - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - + const name = 'close'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, - options: opts, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), + options: [ + { + name: 'reason', + required: false, + type: ApplicationCommandOptionType.String, + }, + { + autocomplete: true, + name: 'ticket', + required: false, + type: ApplicationCommandOptionType.String, + }, + ].map(option => { + option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); + option.description = option.descriptionLocalizations['en-GB']; + option.nameLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.name`); + return option; + }), }); } diff --git a/src/commands/slash/force-close.js b/src/commands/slash/force-close.js index af13a34..2e16b75 100644 --- a/src/commands/slash/force-close.js +++ b/src/commands/slash/force-close.js @@ -12,59 +12,43 @@ const ms = require('ms'); module.exports = class ForceCloseSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.force-close.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.force-close.name'))); - - let opts = [ - { - autocomplete: true, - name: 'category', - required: false, - type: ApplicationCommandOptionType.Integer, - }, - { - name: 'reason', - required: false, - type: ApplicationCommandOptionType.String, - }, - { - autocomplete: true, - name: 'ticket', - required: false, - type: ApplicationCommandOptionType.String, - }, - { - name: 'time', - required: false, - type: ApplicationCommandOptionType.String, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.force-close.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.force-close.options.${o.name}.name`))); - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - + const name = 'force-close'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, - options: opts, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), + options: [ + { + autocomplete: true, + name: 'category', + required: false, + type: ApplicationCommandOptionType.Integer, + }, + { + name: 'reason', + required: false, + type: ApplicationCommandOptionType.String, + }, + { + autocomplete: true, + name: 'ticket', + required: false, + type: ApplicationCommandOptionType.String, + }, + { + name: 'time', + required: false, + type: ApplicationCommandOptionType.String, + }, + ].map(option => { + option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); + option.description = option.descriptionLocalizations['en-GB']; + option.nameLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.name`); + return option; + }), }); } diff --git a/src/commands/slash/help.js b/src/commands/slash/help.js index aaa8192..cd41fbb 100644 --- a/src/commands/slash/help.js +++ b/src/commands/slash/help.js @@ -5,19 +5,14 @@ const { version } = require('../../../package.json'); module.exports = class ClaimSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.help.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.help.name'))); - + const name = 'help'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), }); } diff --git a/src/commands/slash/move.js b/src/commands/slash/move.js index e0c8b4f..b9fe64c 100644 --- a/src/commands/slash/move.js +++ b/src/commands/slash/move.js @@ -3,43 +3,27 @@ const { ApplicationCommandOptionType } = require('discord.js'); module.exports = class MoveSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.move.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.move.name'))); - - let opts = [ - { - autocomplete: true, - name: 'category', - required: true, - type: ApplicationCommandOptionType.Integer, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.move.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.move.options.${o.name}.name`))); - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - + const name = 'move'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, - options: opts, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), + options: [ + { + autocomplete: true, + name: 'category', + required: true, + type: ApplicationCommandOptionType.Integer, + }, + ].map(option => { + option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); + option.description = option.descriptionLocalizations['en-GB']; + option.nameLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.name`); + return option; + }), }); } diff --git a/src/commands/slash/new.js b/src/commands/slash/new.js index ccf978d..6fad360 100644 --- a/src/commands/slash/new.js +++ b/src/commands/slash/new.js @@ -4,43 +4,27 @@ const { useGuild } = require('../../lib/tickets/utils'); module.exports = class NewSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.new.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.new.name'))); - - let opts = [ - { - autocomplete: true, - name: 'references', - required: false, - type: ApplicationCommandOptionType.String, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.new.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.new.options.${o.name}.name`))); - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - + const name = 'new'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, - options: opts, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), + options: [ + { + autocomplete: true, + name: 'references', + required: false, + type: ApplicationCommandOptionType.String, + }, + ].map(option => { + option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); + option.description = option.descriptionLocalizations['en-GB']; + option.nameLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.name`); + return option; + }), }); } diff --git a/src/commands/slash/priority.js b/src/commands/slash/priority.js index 9274fc9..b46c0c8 100644 --- a/src/commands/slash/priority.js +++ b/src/commands/slash/priority.js @@ -5,55 +5,34 @@ const { logTicketEvent } = require('../../lib/logging'); module.exports = class PrioritySlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.priority.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.priority.name'))); - - let opts = [ - { - choices: ['HIGH', 'MEDIUM', 'LOW'], - name: 'priority', - required: true, - type: ApplicationCommandOptionType.String, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.priority.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.priority.options.${o.name}.name`))); - - if (o.choices) { - o.choices = o.choices.map(c => { - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.priority.options.${o.name}.choices.${c}`))); - return { - name: nameLocalizations['en-GB'], - nameLocalizations: nameLocalizations, - value: c, - }; - }); - } - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - + const name = 'priority'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, - options: opts, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), + options: [ + { + choices: ['HIGH', 'MEDIUM', 'LOW'], + name: 'priority', + required: true, + type: ApplicationCommandOptionType.String, + }, + ].map(option => { + option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); + option.description = option.descriptionLocalizations['en-GB']; + option.nameLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.name`); + if (option.choices) { + option.choices = option.choices.map(choice => ({ + name: client.i18n.getMessage(null, `commands.slash.priority.options.${option.name}.choices.${choice}`), + nameLocalizations: client.i18n.getAllMessages(`commands.slash.priority.options.${option.name}.choices.${choice}`), + value: choice, + })); + } + return option; + }), }); } diff --git a/src/commands/slash/release.js b/src/commands/slash/release.js index 6074705..b1f43d5 100644 --- a/src/commands/slash/release.js +++ b/src/commands/slash/release.js @@ -2,19 +2,14 @@ const { SlashCommand } = require('@eartharoid/dbf'); module.exports = class ReleaseSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.release.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.release.name'))); - + const name = 'release'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), }); } diff --git a/src/commands/slash/remove.js b/src/commands/slash/remove.js index 29cc142..0123fa0 100644 --- a/src/commands/slash/remove.js +++ b/src/commands/slash/remove.js @@ -3,48 +3,32 @@ const { ApplicationCommandOptionType } = require('discord.js'); module.exports = class RemoveSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.remove.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.remove.name'))); - - let opts = [ - { - name: 'member', - required: true, - type: ApplicationCommandOptionType.User, - }, - { - autocomplete: true, - name: 'ticket', - required: false, - type: ApplicationCommandOptionType.String, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.remove.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.remove.options.${o.name}.name`))); - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - + const name = 'remove'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, - options: opts, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), + options: [ + { + name: 'member', + required: true, + type: ApplicationCommandOptionType.User, + }, + { + autocomplete: true, + name: 'ticket', + required: false, + type: ApplicationCommandOptionType.String, + }, + ].map(option => { + option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); + option.description = option.descriptionLocalizations['en-GB']; + option.nameLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.name`); + return option; + }), }); } diff --git a/src/commands/slash/tag.js b/src/commands/slash/tag.js index dd4b655..4eea3b5 100644 --- a/src/commands/slash/tag.js +++ b/src/commands/slash/tag.js @@ -4,48 +4,32 @@ const ExtendedEmbedBuilder = require('../../lib/embed'); module.exports = class TagSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.tag.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.tag.name'))); - - let opts = [ - { - autocomplete: true, - name: 'tag', - required: true, - type: ApplicationCommandOptionType.Integer, - }, - { - name: 'for', - required: false, - type: ApplicationCommandOptionType.User, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.tag.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.tag.options.${o.name}.name`))); - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - + const name = 'tag'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, - options: opts, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), + options: [ + { + autocomplete: true, + name: 'tag', + required: true, + type: ApplicationCommandOptionType.Integer, + }, + { + name: 'for', + required: false, + type: ApplicationCommandOptionType.User, + }, + ].map(option => { + option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); + option.description = option.descriptionLocalizations['en-GB']; + option.nameLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.name`); + return option; + }), }); } diff --git a/src/commands/slash/tickets.js b/src/commands/slash/tickets.js index 0f71997..7ed1f2b 100644 --- a/src/commands/slash/tickets.js +++ b/src/commands/slash/tickets.js @@ -7,42 +7,26 @@ const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class TicketsSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.tickets.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.tickets.name'))); - - let opts = [ - { - name: 'member', - required: false, - type: ApplicationCommandOptionType.User, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.tickets.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.tickets.options.${o.name}.name`))); - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - + const name = 'tickets'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, - options: opts, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), + options: [ + { + name: 'member', + required: false, + type: ApplicationCommandOptionType.User, + }, + ].map(option => { + option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); + option.description = option.descriptionLocalizations['en-GB']; + option.nameLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.name`); + return option; + }), }); } @@ -124,6 +108,7 @@ module.exports = class TicketsSlashCommand extends SlashCommand { }).join('\n'), }); } + // TODO: add portal URL to view all (this list is limited to the last 10) const embed = new ExtendedEmbedBuilder({ iconURL: interaction.guild.iconURL(), diff --git a/src/commands/slash/topic.js b/src/commands/slash/topic.js index 751c93d..07e7617 100644 --- a/src/commands/slash/topic.js +++ b/src/commands/slash/topic.js @@ -11,19 +11,14 @@ const ExtendedEmbedBuilder = require('../../lib/embed'); module.exports = class TopicSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.topic.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.topic.name'))); - + const name = 'topic'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), }); } diff --git a/src/commands/slash/transcript.js b/src/commands/slash/transcript.js index 25a9f30..eef5a46 100644 --- a/src/commands/slash/transcript.js +++ b/src/commands/slash/transcript.js @@ -10,43 +10,27 @@ const pad = require('pad'); module.exports = class TranscriptSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transcript.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transcript.name'))); - - let opts = [ - { - autocomplete: true, - name: 'ticket', - required: true, - type: ApplicationCommandOptionType.String, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transcript.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transcript.options.${o.name}.name`))); - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - + const name = 'transcript'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, - options: opts, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), + options: [ + { + autocomplete: true, + name: 'ticket', + required: true, + type: ApplicationCommandOptionType.String, + }, + ].map(option => { + option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); + option.description = option.descriptionLocalizations['en-GB']; + option.nameLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.name`); + return option; + }), }); Mustache.escape = text => text; // don't HTML-escape diff --git a/src/commands/slash/transfer.js b/src/commands/slash/transfer.js index 5584c6f..fb23a3e 100644 --- a/src/commands/slash/transfer.js +++ b/src/commands/slash/transfer.js @@ -3,42 +3,26 @@ const { ApplicationCommandOptionType } = require('discord.js'); module.exports = class TransferSlashCommand extends SlashCommand { constructor(client, options) { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transfer.description'))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, 'commands.slash.transfer.name'))); - - let opts = [ - { - name: 'member', - required: true, - type: ApplicationCommandOptionType.User, - }, - ]; - opts = opts.map(o => { - const descriptionLocalizations = {}; - client.i18n.locales.forEach(l => (descriptionLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transfer.options.${o.name}.description`))); - - const nameLocalizations = {}; - client.i18n.locales.forEach(l => (nameLocalizations[l] = client.i18n.getMessage(l, `commands.slash.transfer.options.${o.name}.name`))); - - return { - ...o, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, - nameLocalizations: nameLocalizations, - }; - }); - + const name = 'transfer'; super(client, { ...options, - description: descriptionLocalizations['en-GB'], - descriptionLocalizations, + description: client.i18n.getMessage(null, `commands.slash.${name}.description`), + descriptionLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.description`), dmPermission: false, - name: nameLocalizations['en-GB'], - nameLocalizations, - options: opts, + name, + nameLocalizations: client.i18n.getAllMessages(`commands.slash.${name}.name`), + options: [ + { + name: 'member', + required: true, + type: ApplicationCommandOptionType.User, + }, + ].map(option => { + option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); + option.description = option.descriptionLocalizations['en-GB']; + option.nameLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.name`); + return option; + }), }); } From 15318df9e4325c0f89257b44f29f37af499b013a Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 28 Oct 2022 17:32:28 +0100 Subject: [PATCH 185/409] feat: `/add` and `/remove` commands --- src/commands/slash/add.js | 110 ++++++++++++++++++++++++++++++++++- src/commands/slash/remove.js | 109 +++++++++++++++++++++++++++++++++- src/i18n/en-GB.yml | 16 +++++ 3 files changed, 233 insertions(+), 2 deletions(-) diff --git a/src/commands/slash/add.js b/src/commands/slash/add.js index 73bd32d..b8a54bf 100644 --- a/src/commands/slash/add.js +++ b/src/commands/slash/add.js @@ -1,5 +1,8 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); +const ExtendedEmbedBuilder = require('../../lib/embed'); +const { isStaff } = require('../../lib/users'); +const { logTicketEvent } = require('../../lib/logging'); module.exports = class AddSlashCommand extends SlashCommand { constructor(client, options) { @@ -32,5 +35,110 @@ module.exports = class AddSlashCommand extends SlashCommand { }); } - async run(interaction) { } + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply({ ephemeral: true }); + + const ticket = await client.prisma.ticket.findUnique({ + include: { guild: true }, + where: { id: interaction.options.getString('ticket', false) || interaction.channel.id }, + }); + + if (!ticket) { + const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + const getMessage = client.i18n.getLocale(settings.locale); + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.invalid_ticket.title')) + .setDescription(getMessage('misc.invalid_ticket.description')), + ], + }); + } + + const getMessage = client.i18n.getLocale(ticket.guild.locale); + + if ( + ticket.id !== interaction.channel.id && + ticket.createdById !== interaction.member.id && + !(await isStaff(interaction.guild, interaction.member.id)) + ) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setTitle(getMessage('commands.slash.add.not_staff.title')) + .setDescription(getMessage('commands.slash.add.not_staff.description')), + ], + }); + } + + /** @type {import("discord.js").TextChannel} */ + const ticketChannel = await interaction.guild.channels.fetch(ticket.id); + const member = interaction.options.getMember('member', true); + + await ticketChannel.permissionOverwrites.edit( + member, + { + AttachFiles: true, + EmbedLinks: true, + ReadMessageHistory: true, + SendMessages: true, + ViewChannel: true, + }, + `${interaction.user.tag} added ${member.user.tag} to the ticket`, + ); + + await ticketChannel.send({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(ticket.guild.primaryColour) + .setDescription(getMessage('commands.slash.add.added', { + added: member.toString(), + by: interaction.member.toString(), + })), + ], + }); + + await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.successColour) + .setTitle(getMessage('commands.slash.add.success.title')) + .setDescription(getMessage('commands.slash.add.success.description', { + member: member.toString(), + ticket: ticketChannel.toString(), + })), + ], + }); + + logTicketEvent(this.client, { + action: 'update', + diff: { + original: {}, + updated: { [getMessage('log.ticket.added')]: member.user.tag }, + }, + target: { + id: ticket.id, + name: `<#${ticket.id}>`, + }, + userId: interaction.user.id, + }); + + } }; \ No newline at end of file diff --git a/src/commands/slash/remove.js b/src/commands/slash/remove.js index 0123fa0..51ca9bf 100644 --- a/src/commands/slash/remove.js +++ b/src/commands/slash/remove.js @@ -1,5 +1,8 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); +const ExtendedEmbedBuilder = require('../../lib/embed'); +const { isStaff } = require('../../lib/users'); +const { logTicketEvent } = require('../../lib/logging'); module.exports = class RemoveSlashCommand extends SlashCommand { constructor(client, options) { @@ -32,5 +35,109 @@ module.exports = class RemoveSlashCommand extends SlashCommand { }); } - async run(interaction) { } + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply({ ephemeral: true }); + + const ticket = await client.prisma.ticket.findUnique({ + include: { guild: true }, + where: { id: interaction.options.getString('ticket', false) || interaction.channel.id }, + }); + + if (!ticket) { + const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + const getMessage = client.i18n.getLocale(settings.locale); + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.invalid_ticket.title')) + .setDescription(getMessage('misc.invalid_ticket.description')), + ], + }); + } + + const getMessage = client.i18n.getLocale(ticket.guild.locale); + + if ( + ticket.id !== interaction.channel.id && + ticket.createdById !== interaction.member.id && + !(await isStaff(interaction.guild, interaction.member.id)) + ) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setTitle(getMessage('commands.slash.remove.not_staff.title')) + .setDescription(getMessage('commands.slash.remove.not_staff.description')), + ], + }); + } + + /** @type {import("discord.js").TextChannel} */ + const ticketChannel = await interaction.guild.channels.fetch(ticket.id); + const member = interaction.options.getMember('member', true); + + if (member.id === client.user.id) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(ticket.guild.errorColour) + .setTitle('❌'), + ], + }); + } + + await ticketChannel.permissionOverwrites.delete(member, `${interaction.user.tag} removed ${member.user.tag} from the ticket`); + + await ticketChannel.send({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(ticket.guild.primaryColour) + .setDescription(getMessage('commands.slash.remove.removed', { + by: interaction.member.toString(), + removed: member.toString(), + })), + ], + }); + + await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.successColour) + .setTitle(getMessage('commands.slash.remove.success.title')) + .setDescription(getMessage('commands.slash.remove.success.description', { + member: member.toString(), + ticket: ticketChannel.toString(), + })), + ], + }); + + logTicketEvent(this.client, { + action: 'update', + diff: { + original: { [getMessage('log.ticket.removed')]: member.user.tag }, + updated: {}, + }, + target: { + id: ticket.id, + name: `<#${ticket.id}>`, + }, + userId: interaction.user.id, + }); + } }; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 52a0328..a915d69 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -39,8 +39,12 @@ commands: title: ✅ Pinned message slash: add: + added: ➡️ {added} has been added by {by}. description: Add a member to a ticket name: add + not_staff: + description: Only staff members can add members to others' tickets. + title: ❌ Error options: member: description: The member to add to the ticket @@ -48,6 +52,9 @@ commands: ticket: description: The ticket to add the member to name: ticket + success: + description: "{member} has been added to {ticket}." + title: ✅ Added claim: description: Claim a ticket name: claim @@ -144,8 +151,12 @@ commands: description: Release (unclaim) a ticket name: release remove: + removed: ⬅️ {removed} has been removed by {by}. description: Remove a member from a ticket name: remove + not_staff: + description: Only staff members can removed members from others' tickets. + title: ❌ Error options: member: description: The member to remove from the ticket @@ -153,6 +164,9 @@ commands: ticket: description: The ticket to remove the member from name: ticket + success: + description: "{member} has been removed from {ticket}." + title: ✅ Added tag: description: Use a tag name: tag @@ -243,7 +257,9 @@ log: delete: deleted update: updated ticket: + added: Added members description: "{user} {verb} a ticket" + removed: Removed members ticket: Ticket title: Ticket {verb} verb: From 77216ba43d6d8565a530d16281e0e199a0da2bf7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 29 Oct 2022 00:33:19 +0100 Subject: [PATCH 186/409] feat: add ticket claiming --- src/buttons/claim.js | 12 ++- src/buttons/unclaim.js | 12 ++- src/commands/slash/claim.js | 9 +- src/commands/slash/release.js | 11 ++- src/i18n/en-GB.yml | 4 +- src/lib/tickets/manager.js | 171 +++++++++++++++++++++++++++++++++- 6 files changed, 211 insertions(+), 8 deletions(-) diff --git a/src/buttons/claim.js b/src/buttons/claim.js index 2461e4b..c57e5df 100644 --- a/src/buttons/claim.js +++ b/src/buttons/claim.js @@ -8,5 +8,15 @@ module.exports = class ClaimButton extends Button { }); } - async run (id, interaction) {} + /** + * @param {*} id + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(id, interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply({ ephemeral: false }); + await client.tickets.claim(interaction); + } }; \ No newline at end of file diff --git a/src/buttons/unclaim.js b/src/buttons/unclaim.js index 50ed4b5..e6c7f4e 100644 --- a/src/buttons/unclaim.js +++ b/src/buttons/unclaim.js @@ -8,5 +8,15 @@ module.exports = class UnclaimButton extends Button { }); } - async run(id, interaction) { } + /** + * @param {*} id + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(id, interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply({ ephemeral: false }); + await client.tickets.release(interaction); + } }; \ No newline at end of file diff --git a/src/commands/slash/claim.js b/src/commands/slash/claim.js index 05fedfe..0e457fa 100644 --- a/src/commands/slash/claim.js +++ b/src/commands/slash/claim.js @@ -13,7 +13,14 @@ module.exports = class ClaimSlashCommand extends SlashCommand { }); } + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ async run(interaction) { - // tickets/manager.js + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply({ ephemeral: false }); + await client.tickets.claim(interaction); } }; \ No newline at end of file diff --git a/src/commands/slash/release.js b/src/commands/slash/release.js index b1f43d5..b9430c7 100644 --- a/src/commands/slash/release.js +++ b/src/commands/slash/release.js @@ -13,5 +13,14 @@ module.exports = class ReleaseSlashCommand extends SlashCommand { }); } - async run(interaction) { } + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply({ ephemeral: false }); + await client.tickets.release(interaction); + } }; \ No newline at end of file diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index a915d69..0d3b138 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -151,7 +151,6 @@ commands: description: Release (unclaim) a ticket name: release remove: - removed: ⬅️ {removed} has been removed by {by}. description: Remove a member from a ticket name: remove not_staff: @@ -164,6 +163,7 @@ commands: ticket: description: The ticket to remove the member from name: ticket + removed: ⬅️ {removed} has been removed by {by}. success: description: "{member} has been removed from {ticket}." title: ✅ Added @@ -332,6 +332,8 @@ modals: ticket: answers: no_value: "*No response*" + claimed: 🙌 {user} has claimed this ticket. + released: ♻️ {user} has released this ticket. created: description: "Your ticket channel has been created: {channel}." title: ✅ Ticket created diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 3a46cbb..4e00194 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1,3 +1,4 @@ +/* eslint-disable no-underscore-dangle */ /* eslint-disable max-lines */ const TicketArchiver = require('./archiver'); const { @@ -344,15 +345,15 @@ module.exports = class TicketManager { id: guild.roles.everyone, }, { - allow: allow, + allow, id: this.client.user.id, }, { - allow: allow, + allow, id: creator.id, }, ...category.staffRoles.map(id => ({ - allow: allow, + allow, id, })), ], @@ -635,6 +636,170 @@ module.exports = class TicketManager { } } + /** + * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction} interaction + */ + async claim(interaction) { + const ticket = await this.client.prisma.ticket.findUnique({ + include: { + _count: { select: { questionAnswers: true } }, + category: true, + guild: true, + }, + where: { id: interaction.channel.id }, + }); + const getMessage = this.client.i18n.getLocale(ticket.guild.locale); + + await interaction.channel.permissionOverwrites.edit(interaction.user, { 'ViewChannel': true }, `Ticket claimed by ${interaction.user.tag}`); + + for (const role of ticket.category.staffRoles) await interaction.channel.permissionOverwrites.edit(role, { 'ViewChannel': false }, `Ticket claimed by ${interaction.user.tag}`); + + await this.client.prisma.ticket.update({ + data: { + claimedBy: { + connectOrCreate: { + create: { id: interaction.user.id }, + where: { id: interaction.user.id }, + }, + }, + }, + where: { id: interaction.channel.id }, + }); + + const openingMessage = await interaction.channel.messages.fetch(ticket.openingMessageId); + + if (openingMessage && openingMessage.components.length !== 0) { + const components = new ActionRowBuilder(); + + if (ticket.topic || ticket._count.questionAnswers !== 0) { + components.addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ action: 'edit' })) + .setStyle(ButtonStyle.Secondary) + .setEmoji(getMessage('buttons.edit.emoji')) + .setLabel(getMessage('buttons.edit.text')), + ); + } + + if (ticket.guild.claimButton && ticket.category.claiming) { + components.addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ action: 'unclaim' })) + .setStyle(ButtonStyle.Secondary) + .setEmoji(getMessage('buttons.unclaim.emoji')) + .setLabel(getMessage('buttons.unclaim.text')), + ); + } + + if (ticket.guild.closeButton) { + components.addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ action: 'close' })) + .setStyle(ButtonStyle.Danger) + .setEmoji(getMessage('buttons.close.emoji')) + .setLabel(getMessage('buttons.close.text')), + ); + } + + await openingMessage.edit({ components: [components] }); + } + + await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(ticket.guild.primaryColour) + .setDescription(getMessage('ticket.claimed', { user: interaction.user.toString() })), + ], + }); + + logTicketEvent(this.client, { + action: 'claim', + target: { + id: ticket.id, + name: interaction.channel.toString(), + }, + userId: interaction.user.id, + }); + } + + /** + * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction} interaction + */ + async release(interaction) { + const ticket = await this.client.prisma.ticket.findUnique({ + include: { + category: true, + guild: true, + }, + where: { id: interaction.channel.id }, + }); + const getMessage = this.client.i18n.getLocale(ticket.guild.locale); + + await interaction.channel.permissionOverwrites.delete(interaction.user, `Ticket released by ${interaction.user.tag}`); + + for (const role of ticket.category.staffRoles) await interaction.channel.permissionOverwrites.edit(role, { 'ViewChannel': true }, `Ticket released by ${interaction.user.tag}`); + + await this.client.prisma.ticket.update({ + data: { claimedBy: { disconnect: true } }, + where: { id: interaction.channel.id }, + }); + + const openingMessage = await interaction.channel.messages.fetch(ticket.openingMessageId); + + if (openingMessage && openingMessage.components.length !== 0) { + const components = new ActionRowBuilder(); + + if (ticket.topic || ticket._count.questionAnswers !== 0) { + components.addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ action: 'edit' })) + .setStyle(ButtonStyle.Secondary) + .setEmoji(getMessage('buttons.edit.emoji')) + .setLabel(getMessage('buttons.edit.text')), + ); + } + + if (ticket.guild.claimButton && ticket.category.claiming) { + components.addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ action: 'claim' })) + .setStyle(ButtonStyle.Secondary) + .setEmoji(getMessage('buttons.claim.emoji')) + .setLabel(getMessage('buttons.claim.text')), + ); + } + + if (ticket.guild.closeButton) { + components.addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ action: 'close' })) + .setStyle(ButtonStyle.Danger) + .setEmoji(getMessage('buttons.close.emoji')) + .setLabel(getMessage('buttons.close.text')), + ); + } + + await openingMessage.edit({ components: [components] }); + } + + await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(ticket.guild.primaryColour) + .setDescription(getMessage('ticket.released', { user: interaction.user.toString() })), + ], + }); + + logTicketEvent(this.client, { + action: 'unclaim', + target: { + id: ticket.id, + name: interaction.channel.toString(), + }, + userId: interaction.user.id, + }); + } + /** * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction} interaction From 1ecb6f5d3263d9656828515422aeb722466d016a Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 29 Oct 2022 00:54:19 +0100 Subject: [PATCH 187/409] fix: typo --- src/i18n/en-GB.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 0d3b138..9a10e85 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -166,7 +166,7 @@ commands: removed: ⬅️ {removed} has been removed by {by}. success: description: "{member} has been removed from {ticket}." - title: ✅ Added + title: ✅ Removed tag: description: Use a tag name: tag From 26ab229c97aace7f37671f98a3209d5e97a81c29 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 29 Oct 2022 00:54:38 +0100 Subject: [PATCH 188/409] feat: add target ID --- src/lib/logging.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/logging.js b/src/lib/logging.js index 430331b..4bb5b2b 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -104,7 +104,7 @@ async function logAdminEvent(client, { .addFields([ { name: getMessage(`log.admin.title.target.${target.type}`), - value: target.name ?? target.id, + value: target.name ? `${target.name} (\`${target.id}\`)` : target.id, }, ]), ]; @@ -166,7 +166,7 @@ async function logTicketEvent(client, { .addFields([ { name: getMessage('log.ticket.ticket'), - value: target.name ?? target.id, + value: target.name ? `${target.name} (\`${target.id}\`)` : target.id, }, ]), ]; From 4b40f2cdbd296f2325bdb0b9006a5be9fb4f5fc3 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 29 Oct 2022 19:59:09 +0100 Subject: [PATCH 189/409] feat: add `/transfer` command --- src/commands/slash/transfer.js | 50 ++++++++++++++++++++++++++++++++-- src/i18n/en-GB.yml | 1 + 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/commands/slash/transfer.js b/src/commands/slash/transfer.js index fb23a3e..66b1b4e 100644 --- a/src/commands/slash/transfer.js +++ b/src/commands/slash/transfer.js @@ -1,5 +1,10 @@ const { SlashCommand } = require('@eartharoid/dbf'); -const { ApplicationCommandOptionType } = require('discord.js'); +const { + ApplicationCommandOptionType, + EmbedBuilder, +} = require('discord.js'); +const Cryptr = require('cryptr'); +const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class TransferSlashCommand extends SlashCommand { constructor(client, options) { @@ -26,5 +31,46 @@ module.exports = class TransferSlashCommand extends SlashCommand { }); } - async run(interaction) {} + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply({ ephemeral: false }); + + const member = interaction.options.getMember('member', true); + + let ticket = await client.prisma.ticket.findUnique({ where: { id: interaction.channel.id } }); + const from = ticket.createdById; + + ticket = await client.prisma.ticket.update({ + data: { + createdBy: { + connectOrCreate: { + create: { id: member.id }, + where: { id: member.id }, + }, + }, + }, + include: { guild: true }, + where: { id: interaction.channel.id }, + }); + + await interaction.channel.setTopic(`${member.toString}${ticket.topic?.length > 0 ? ` | ${decrypt(ticket.topic)}` : ''}`); + + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setColor(ticket.guild.primaryColour) + .setDescription(client.i18n.getMessage(ticket.guild.locale, 'commands.slash.transfer.transferred', { + from: `<@${from}>`, + to: member.toString(), + user: interaction.user.toString(), + })), + + ], + }); + } }; diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 9a10e85..ccf46a6 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -220,6 +220,7 @@ commands: member: description: The member to transfer ownership to name: member + transferred: 📨 {user} has transferred this ticket from {from} to {to}. user: create: name: Create a ticket for user From a519dec2570d286211a1214db01042ead11a663b Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 29 Oct 2022 20:22:39 +0100 Subject: [PATCH 190/409] Update README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 9829ffe..4c12cd3 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,15 @@ creation requires an interaction: ... --> + +## Contributing + +https://commitlint.js.org/#/ + +huksy? +eslint +secret scan + +## Contributors + +[![Contributors](https://contrib.rocks/image?repo=discord-tickets/bot)](https://github.com/discord-tickets/bot/graphs/contributors) \ No newline at end of file From 4f0e91f2698e19fa829e37549461f561477cc549 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 31 Oct 2022 13:45:49 +0000 Subject: [PATCH 191/409] integrated portal --- scripts/preinstall.js | 1 - src/routes/api/client.js | 1 - 2 files changed, 2 deletions(-) diff --git a/scripts/preinstall.js b/scripts/preinstall.js index b43b18d..06bca43 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -9,7 +9,6 @@ const env = { ENCRYPTION_KEY: randomBytes(24).toString('hex'), HTTP_BIND: 8080, HTTP_EXTERNAL: 'http://localhost:8080', - PORTAL: '', PUBLIC_BOT: false, SETTINGS_BIND: 8888, SUPER: '319467558166069248', diff --git a/src/routes/api/client.js b/src/routes/api/client.js index fd3051a..8d89eec 100644 --- a/src/routes/api/client.js +++ b/src/routes/api/client.js @@ -20,7 +20,6 @@ module.exports.get = () => ({ avatar: client.user.avatarURL(), discriminator: client.user.discriminator, id: client.user.id, - portal: process.env.PORTAL || null, public: (process.env.PUBLIC_BOT === 'true'), stats: { activatedUsers: users.length, From 76ed77fe0ce5ca0b6877ea858fdecebeb545e32e Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 31 Oct 2022 14:17:59 +0000 Subject: [PATCH 192/409] fix: typo --- src/commands/slash/transfer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/slash/transfer.js b/src/commands/slash/transfer.js index 66b1b4e..4a488c6 100644 --- a/src/commands/slash/transfer.js +++ b/src/commands/slash/transfer.js @@ -58,7 +58,7 @@ module.exports = class TransferSlashCommand extends SlashCommand { where: { id: interaction.channel.id }, }); - await interaction.channel.setTopic(`${member.toString}${ticket.topic?.length > 0 ? ` | ${decrypt(ticket.topic)}` : ''}`); + await interaction.channel.setTopic(`${member.toString()}${ticket.topic?.length > 0 ? ` | ${decrypt(ticket.topic)}` : ''}`); await interaction.editReply({ embeds: [ From b61b3dd2b179b4fcb768e196b8dfd716e8e5297a Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 31 Oct 2022 15:35:41 +0000 Subject: [PATCH 193/409] Respond with error messages instead of giving the user no information. --- src/i18n/en-GB.yml | 13 ++++---- src/listeners/buttons/error.js | 57 +++++++++++++++++++++++++++++++++ src/listeners/commands/error.js | 42 ++++++++++++++++++++++-- 3 files changed, 103 insertions(+), 9 deletions(-) create mode 100644 src/listeners/buttons/error.js diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index ccf46a6..da30ed4 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -68,9 +68,6 @@ commands: reason: description: The reason for closing the ticket(s) name: reason - ticket: - description: The ticket to close - name: ticket force-close: confirm_multiple: description: > @@ -120,7 +117,7 @@ commands: settings: Bot settings title: Help move: - description: move a ticket to another category + description: Move a ticket to another category name: move options: category: @@ -220,7 +217,8 @@ commands: member: description: The member to transfer ownership to name: member - transferred: 📨 {user} has transferred this ticket from {from} to {to}. + transferred: 📨 {user} has transferred this ticket to {to}. + transferred_from: 📨 {user} has transferred this ticket from {from} to {to}. user: create: name: Create a ticket for user @@ -289,8 +287,9 @@ misc: error: description: Sorry, an unexpected error occurred. fields: + code: Error code identifier: Identifier - title: ⚠️ Something's wrong + title: ⚠️ Something went wrong expired: description: You didn't respond in time. Please try again. title: ⏰ Expired @@ -334,7 +333,6 @@ ticket: answers: no_value: "*No response*" claimed: 🙌 {user} has claimed this ticket. - released: ♻️ {user} has released this ticket. created: description: "Your ticket channel has been created: {channel}." title: ✅ Ticket created @@ -357,3 +355,4 @@ ticket: number: Number topic: Topic title: ℹ️ Reference + released: ♻️ {user} has released this ticket. diff --git a/src/listeners/buttons/error.js b/src/listeners/buttons/error.js new file mode 100644 index 0000000..ba3a2f8 --- /dev/null +++ b/src/listeners/buttons/error.js @@ -0,0 +1,57 @@ +const { Listener } = require('@eartharoid/dbf'); +const { + EmbedBuilder, + inlineCode, +} = require('discord.js'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.buttons, + event: 'error', + }); + } + + async run({ + button, + error, + interaction, + }) { + const ref = require('crypto').randomUUID(); + this.client.log.error.buttons(ref); + this.client.log.error.buttons(`"${button.name}" button execution error:`, error); + let locale = null; + if (interaction.guild) { + locale = (await this.client.prisma.guild.findUnique({ + select: { locale: true }, + where: { id: interaction.guild.id }, + })).locale; + } + const getMessage = this.client.i18n.getLocale(locale); + const fields = [ + { + name: getMessage('misc.error.fields.identifier'), + value: inlineCode(ref), + }, + ]; + if (error.name) { + fields.unshift({ + name: getMessage('misc.error.fields.code'), + value: inlineCode(error.name), + }); + } + const data = { + components: [], + embeds: [ + new EmbedBuilder() + .setColor('Orange') + .setTitle(getMessage('misc.error.title')) + .setDescription(getMessage('misc.error.description')) + .addFields(fields), + ], + }; + + interaction.reply(data).catch(() => interaction.editReply(data)); + } +}; diff --git a/src/listeners/commands/error.js b/src/listeners/commands/error.js index 6383585..8e4edbc 100644 --- a/src/listeners/commands/error.js +++ b/src/listeners/commands/error.js @@ -1,4 +1,8 @@ const { Listener } = require('@eartharoid/dbf'); +const { + EmbedBuilder, + inlineCode, +} = require('discord.js'); module.exports = class extends Listener { constructor(client, options) { @@ -9,11 +13,45 @@ module.exports = class extends Listener { }); } - run({ + async run({ command, error, + interaction, }) { + const ref = require('crypto').randomUUID(); + this.client.log.error.commands(ref); this.client.log.error.commands(`"${command.name}" command execution error:`, error); - return true; + let locale = null; + if (interaction.guild) { + locale = (await this.client.prisma.guild.findUnique({ + select: { locale: true }, + where: { id: interaction.guild.id }, + })).locale; + } + const getMessage = this.client.i18n.getLocale(locale); + const fields = [ + { + name: getMessage('misc.error.fields.identifier'), + value: inlineCode(ref), + }, + ]; + if (error.name) { + fields.unshift({ + name: getMessage('misc.error.fields.code'), + value: inlineCode(error.name), + }); + } + const data = { + components: [], + embeds: [ + new EmbedBuilder() + .setColor('Orange') + .setTitle(getMessage('misc.error.title')) + .setDescription(getMessage('misc.error.description')) + .addFields(fields), + ], + }; + + interaction.reply(data).catch(() => interaction.editReply(data)); } }; From d10965d541c01b2d85a9873cdcbe16ffa41bfa59 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 31 Oct 2022 15:36:35 +0000 Subject: [PATCH 194/409] fix typo --- src/listeners/buttons/error.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/listeners/buttons/error.js b/src/listeners/buttons/error.js index ba3a2f8..e3abb4c 100644 --- a/src/listeners/buttons/error.js +++ b/src/listeners/buttons/error.js @@ -20,7 +20,7 @@ module.exports = class extends Listener { }) { const ref = require('crypto').randomUUID(); this.client.log.error.buttons(ref); - this.client.log.error.buttons(`"${button.name}" button execution error:`, error); + this.client.log.error.buttons(`"${button.id}" button execution error:`, error); let locale = null; if (interaction.guild) { locale = (await this.client.prisma.guild.findUnique({ From 9f18958c7574510ebcb2b4c3283e2d712832967a Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 31 Oct 2022 21:15:44 +0000 Subject: [PATCH 195/409] feat: add `/move` command --- src/autocomplete/category.js | 23 ++++++++- src/commands/slash/close.js | 6 --- src/commands/slash/move.js | 89 +++++++++++++++++++++++++++++++++- src/commands/slash/transfer.js | 19 ++++++-- src/i18n/en-GB.yml | 5 +- src/lib/tickets/manager.js | 4 +- 6 files changed, 130 insertions(+), 16 deletions(-) diff --git a/src/autocomplete/category.js b/src/autocomplete/category.js index e8c25a1..2b78d6e 100644 --- a/src/autocomplete/category.js +++ b/src/autocomplete/category.js @@ -8,5 +8,26 @@ module.exports = class CategoryCompleter extends Autocompleter { }); } - async run(value, comamnd, interaction) { } + /** + * @param {string} value + * @param {*} command + * @param {import("discord.js").AutocompleteInteraction} interaction + */ + async run(value, command, interaction) { + /** @type {import("client")} */ + const client = this.client; + + let categories = await client.prisma.category.findMany({ where: { guildId: interaction.guild.id } }); + const ticket = await client.prisma.ticket.findUnique({ where: { id: interaction.channel.id } }); + if (ticket) categories = categories.filter(category => ticket.categoryId !== category.id); + const options = value ? categories.filter(category => category.name.match(new RegExp(value, 'i'))) : categories; + await interaction.respond( + options + .slice(0, 25) + .map(category => ({ + name: category.name, + value: category.id, + })), + ); + } }; \ No newline at end of file diff --git a/src/commands/slash/close.js b/src/commands/slash/close.js index fc94113..0086c0a 100644 --- a/src/commands/slash/close.js +++ b/src/commands/slash/close.js @@ -17,12 +17,6 @@ module.exports = class CloseSlashCommand extends SlashCommand { required: false, type: ApplicationCommandOptionType.String, }, - { - autocomplete: true, - name: 'ticket', - required: false, - type: ApplicationCommandOptionType.String, - }, ].map(option => { option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); option.description = option.descriptionLocalizations['en-GB']; diff --git a/src/commands/slash/move.js b/src/commands/slash/move.js index b9fe64c..2dc3b04 100644 --- a/src/commands/slash/move.js +++ b/src/commands/slash/move.js @@ -1,5 +1,6 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); +const ExtendedEmbedBuilder = require('../../lib/embed'); module.exports = class MoveSlashCommand extends SlashCommand { constructor(client, options) { @@ -27,8 +28,92 @@ module.exports = class MoveSlashCommand extends SlashCommand { }); } + /** + * @param {import("discord.js").ChatInputCommandInteraction} interaction + */ async run(interaction) { - // TODO: check discordCategory max but not category max (ignore) - // TODO: update cached count for both categories and category-members (from and to) + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply({ ephemeral: false }); + + const ticket = await client.prisma.ticket.findUnique({ + include: { + category: true, + guild: true, + }, + where: { id: interaction.channel.id }, + }); + + if (!ticket) { + const { locale } = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); + const getMessage = client.i18n.getLocale(locale); + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setTitle(getMessage('misc.not_ticket.title')) + .setDescription(getMessage('misc.not_ticket.description')), + ], + ephemeral: true, + }); + } + + const newCategory = await client.prisma.category.findUnique({ where: { id: interaction.options.getInteger('category', true) } }); + const discordCategory = await interaction.guild.channels.fetch(newCategory.discordCategory); + const getMessage = client.i18n.getLocale(ticket.guild.locale); + + if (discordCategory.children.cache.size === 50) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setTitle(getMessage('misc.category_full.title')) + .setDescription(getMessage('misc.category_full.description')), + ], + ephemeral: true, + }); + } else { + await client.prisma.ticket.update({ + data: { category: { connect: { id: newCategory.id } } }, + where: { id: ticket.id }, + }); + + const $oldCategory = client.tickets.$.categories[ticket.categoryId]; + const $newCategory = client.tickets.$.categories[newCategory.id]; + + $oldCategory.total--; + $oldCategory[ticket.createdById]--; + + if (!$newCategory.total) $newCategory.total = 0; + $newCategory.total++; + + if (!$newCategory[ticket.createdById]) $newCategory[ticket.createdById] = 0; + $newCategory[ticket.createdById]++; + + await interaction.channel.setParent(discordCategory, { + lockPermissions: false, + reason: `Moved by ${interaction.user.tag}`, + }); + + await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(ticket.guild.primaryColour) + .setDescription(getMessage('commands.slash.move.moved', { + by: interaction.user.toString(), + from: ticket.category.name, + to: newCategory.name, + })), + ], + }); + + } } }; diff --git a/src/commands/slash/transfer.js b/src/commands/slash/transfer.js index 4a488c6..785ad0b 100644 --- a/src/commands/slash/transfer.js +++ b/src/commands/slash/transfer.js @@ -44,7 +44,7 @@ module.exports = class TransferSlashCommand extends SlashCommand { let ticket = await client.prisma.ticket.findUnique({ where: { id: interaction.channel.id } }); const from = ticket.createdById; - + console.log(1) ticket = await client.prisma.ticket.update({ data: { createdBy: { @@ -58,13 +58,11 @@ module.exports = class TransferSlashCommand extends SlashCommand { where: { id: interaction.channel.id }, }); - await interaction.channel.setTopic(`${member.toString()}${ticket.topic?.length > 0 ? ` | ${decrypt(ticket.topic)}` : ''}`); - await interaction.editReply({ embeds: [ new EmbedBuilder() .setColor(ticket.guild.primaryColour) - .setDescription(client.i18n.getMessage(ticket.guild.locale, 'commands.slash.transfer.transferred', { + .setDescription(client.i18n.getMessage(ticket.guild.locale, `commands.slash.transfer.transferred${interaction.member.id !== from ? '_from' : ''}`, { from: `<@${from}>`, to: member.toString(), user: interaction.user.toString(), @@ -72,5 +70,18 @@ module.exports = class TransferSlashCommand extends SlashCommand { ], }); + + await interaction.channel.setTopic(`${member.toString()}${ticket.topic?.length > 0 ? ` | ${decrypt(ticket.topic)}` : ''}`); + + await interaction.channel.permissionOverwrites.edit( + member, + { + AttachFiles: true, + EmbedLinks: true, + ReadMessageHistory: true, + SendMessages: true, + ViewChannel: true, + }, + ); } }; diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index da30ed4..9c9fd9c 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -118,6 +118,7 @@ commands: title: Help move: description: Move a ticket to another category + moved: 🗃️ {by} has moved this ticket from **{from}** to **{to}**. name: move options: category: @@ -285,7 +286,9 @@ misc: description: Please wait {time} before creating another ticket in this category. title: ❌ Please wait error: - description: Sorry, an unexpected error occurred. + description: | + Sorry, an unexpected error occurred. + Please give this information to an administrator. fields: code: Error code identifier: Identifier diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 4e00194..1cf33b1 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -804,7 +804,7 @@ module.exports = class TicketManager { /** * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction} interaction */ - async preClose(interaction) { + async requestClose(interaction) { const ticket = await this.client.prisma.ticket.findUnique({ include: { category: true, @@ -821,7 +821,7 @@ module.exports = class TicketManager { * @param {boolean} skip * @param {string} reason */ - async close(ticketId, skip, reason) { + async final(ticketId, skip, reason) { // TODO: update cache/cat count // TODO: update cache/member count // TODO: set messageCount on ticket From 14df0637ae4e494e9907e5aa08ea3b4a11884926 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 31 Oct 2022 21:19:29 +0000 Subject: [PATCH 196/409] Improve errors --- src/listeners/buttons/error.js | 21 +++++++-------------- src/listeners/commands/error.js | 21 +++++++-------------- 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/listeners/buttons/error.js b/src/listeners/buttons/error.js index e3abb4c..d1f86d3 100644 --- a/src/listeners/buttons/error.js +++ b/src/listeners/buttons/error.js @@ -1,7 +1,7 @@ const { Listener } = require('@eartharoid/dbf'); const { EmbedBuilder, - inlineCode, + codeBlock, } = require('discord.js'); module.exports = class extends Listener { @@ -29,18 +29,6 @@ module.exports = class extends Listener { })).locale; } const getMessage = this.client.i18n.getLocale(locale); - const fields = [ - { - name: getMessage('misc.error.fields.identifier'), - value: inlineCode(ref), - }, - ]; - if (error.name) { - fields.unshift({ - name: getMessage('misc.error.fields.code'), - value: inlineCode(error.name), - }); - } const data = { components: [], embeds: [ @@ -48,7 +36,12 @@ module.exports = class extends Listener { .setColor('Orange') .setTitle(getMessage('misc.error.title')) .setDescription(getMessage('misc.error.description')) - .addFields(fields), + .addFields([ + { + name: getMessage('misc.error.fields.identifier'), + value: codeBlock(ref), + }, + ]), ], }; diff --git a/src/listeners/commands/error.js b/src/listeners/commands/error.js index 8e4edbc..3baccd9 100644 --- a/src/listeners/commands/error.js +++ b/src/listeners/commands/error.js @@ -1,7 +1,7 @@ const { Listener } = require('@eartharoid/dbf'); const { EmbedBuilder, - inlineCode, + codeBlock, } = require('discord.js'); module.exports = class extends Listener { @@ -29,18 +29,6 @@ module.exports = class extends Listener { })).locale; } const getMessage = this.client.i18n.getLocale(locale); - const fields = [ - { - name: getMessage('misc.error.fields.identifier'), - value: inlineCode(ref), - }, - ]; - if (error.name) { - fields.unshift({ - name: getMessage('misc.error.fields.code'), - value: inlineCode(error.name), - }); - } const data = { components: [], embeds: [ @@ -48,7 +36,12 @@ module.exports = class extends Listener { .setColor('Orange') .setTitle(getMessage('misc.error.title')) .setDescription(getMessage('misc.error.description')) - .addFields(fields), + .addFields([ + { + name: getMessage('misc.error.fields.identifier'), + value: codeBlock(ref), + }, + ]), ], }; From e105f0c34cebb482bc0c7f450ab6dd9bcca07f94 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 1 Nov 2022 21:10:57 +0000 Subject: [PATCH 197/409] update logger --- package.json | 2 +- src/lib/logger.js | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 8443ed0..7735814 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "fs-extra": "^10.1.0", "keyv": "^4.5.0", "leeks.js": "^0.2.4", - "leekslazylogger": "^4.1.7", + "leekslazylogger": "^5.0.0", "ms": "^2.1.3", "mustache": "^4.2.0", "node-dir": "^0.1.17", diff --git a/src/lib/logger.js b/src/lib/logger.js index a0af18a..701aa41 100644 --- a/src/lib/logger.js +++ b/src/lib/logger.js @@ -1,4 +1,8 @@ -const Logger = require('leekslazylogger'); +const { + ConsoleTransport, + FileTransport, + Logger, +} = require('leekslazylogger'); const DTF = require('@eartharoid/dtf'); const { short } = require('leeks.js'); @@ -16,11 +20,11 @@ const colours = { module.exports = config => { const transports = [ - new Logger.transports.ConsoleTransport({ + new ConsoleTransport({ format: log => { const timestamp = dtf.fill('DD/MM/YY HH:mm:ss', log.timestamp); const colour = colours[log.level.name]; - return short(`&f&!7${timestamp}&r ${colour[0]}[${log.level.name.toUpperCase()}]&r ${log.namespace ? `&d(${log.namespace.toUpperCase()})&r ` : ''}${colour[1]}${log.content}`); + return short(`&f&!7 ${timestamp} &r ${colour[0]}[${log.level.name.toUpperCase()}]&r ${log.namespace ? `&d(${log.namespace.toUpperCase()})&r ` : ''}${colour[1]}${log.content}`); }, level: config.logs.level, }), @@ -28,9 +32,10 @@ module.exports = config => { if (config.logs.files.enabled) { transports.push( - new Logger.transports.FileTransport({ + new FileTransport({ clean_directory: config.logs.files.keepFor, directory: config.logs.files.directory, + format: '[{timestamp}] [{LEVEL}] ({NAMESPACE}) @{file}:{line}:{column} {content}', level: config.logs.level, name: 'Discord Tickets by eartharoid', }), @@ -38,7 +43,17 @@ module.exports = config => { } return new Logger({ - namespaces: ['autocomplete', 'buttons', 'commands', 'http', 'listeners', 'menus', 'modals', 'settings', 'tickets'], + namespaces: [ + 'autocomplete', + 'buttons', + 'commands', + 'http', + 'listeners', + 'menus', + 'modals', + 'settings', + 'tickets', + ], transports, }); }; From f050d5c41fe7f130fb8fe40001e612d12e565dbf Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 3 Nov 2022 19:41:52 +0000 Subject: [PATCH 198/409] update dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7735814..d214d99 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.1.5", + "@discord-tickets/settings": "^1.1.6", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", From 7c011fbf9d4bdfef61c796ea472489a8e853fcbd Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 2 Jan 2023 12:21:37 +0000 Subject: [PATCH 199/409] Update dependencies --- package.json | 16 ++++++++-------- src/lib/middleware/prisma-sqlite.js | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index d214d99..7d82f2e 100644 --- a/package.json +++ b/package.json @@ -39,19 +39,19 @@ "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", "@fastify/cookie": "^6.0.0", - "@fastify/cors": "^8.1.1", - "@fastify/http-proxy": "^8.2.3", + "@fastify/cors": "^8.2.0", + "@fastify/http-proxy": "^8.4.0", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", - "@prisma/client": "^4.5.0", + "@prisma/client": "^4.8.0", "cryptr": "^6.0.3", - "discord.js": "^14.6.0", + "discord.js": "^14.7.1", "dotenv": "^16.0.3", "express": "^4.18.2", - "fastify": "^4.9.2", + "fastify": "^4.11.0", "figlet": "^1.5.2", "fs-extra": "^10.1.0", - "keyv": "^4.5.0", + "keyv": "^4.5.2", "leeks.js": "^0.2.4", "leekslazylogger": "^5.0.0", "ms": "^2.1.3", @@ -60,14 +60,14 @@ "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", "pad": "^3.2.0", - "prisma": "^4.5.0", + "prisma": "^4.8.0", "semver": "^7.3.8", "terminal-link": "^2.1.1", "yaml": "^1.10.2" }, "devDependencies": { "all-contributors-cli": "^6.24.0", - "eslint": "^8.26.0", + "eslint": "^8.31.0", "eslint-plugin-unused-imports": "^2.0.0", "nodemon": "^2.0.20" }, diff --git a/src/lib/middleware/prisma-sqlite.js b/src/lib/middleware/prisma-sqlite.js index 776b647..d858a4c 100644 --- a/src/lib/middleware/prisma-sqlite.js +++ b/src/lib/middleware/prisma-sqlite.js @@ -38,9 +38,9 @@ const traverse = (obj, action) => { }; module.exports = async (params, next) => { - if (params.args.create) params.args.create = traverse(params.args.create, 'SERIALISE'); - if (params.args.data) params.args.data = traverse(params.args.data, 'SERIALISE'); - if (params.args.update) params.args.update = traverse(params.args.update, 'SERIALISE'); + if (params.args?.create) params.args.create = traverse(params.args.create, 'SERIALISE'); + if (params.args?.data) params.args.data = traverse(params.args.data, 'SERIALISE'); + if (params.args?.update) params.args.update = traverse(params.args.update, 'SERIALISE'); let result = await next(params); if (result) result = traverse(result, 'PARSE'); return result; From 5c2eed2d7ba84b2d102c6e6b7ac6e2460ecf53cc Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 2 Jan 2023 12:21:53 +0000 Subject: [PATCH 200/409] Remove jsconfig.json --- jsconfig.json | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 jsconfig.json diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index 120fd76..0000000 --- a/jsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "module": "ESNext", - "target": "ESNext", - "moduleResolution": "Node", - "baseUrl": "src", - "resolveJsonModule": true, - "checkJs": false, - }, - "include": [ - "src/**/*.js" - ] -} \ No newline at end of file From a10a1663a3545063a94a94467bab2146211fd151 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 2 Jan 2023 12:23:14 +0000 Subject: [PATCH 201/409] SelectMenu -> StringSelectMenu --- src/lib/tickets/manager.js | 11 ++++++----- src/lib/tickets/utils.js | 8 ++++---- src/listeners/client/messageCreate.js | 12 ++++++------ src/routes/api/admin/guilds/[guild]/panels.js | 8 ++++---- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 1cf33b1..bb926ac 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -7,8 +7,8 @@ const { ButtonStyle, inlineCode, ModalBuilder, - SelectMenuBuilder, - SelectMenuOptionBuilder, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle, } = require('discord.js'); @@ -16,8 +16,9 @@ const emoji = require('node-emoji'); const ms = require('ms'); const ExtendedEmbedBuilder = require('../embed'); const { logTicketEvent } = require('../logging'); +const { Collection } = require('discord.js'); const Cryptr = require('cryptr'); -const cryptr = new Cryptr(process.env.ENCRYPTION_KEY); +const { encrypt } = new Cryptr(process.env.ENCRYPTION_KEY); /** * @typedef {import('@prisma/client').Category & @@ -241,14 +242,14 @@ module.exports = class TicketManager { } else if (q.type === 'MENU') { return new ActionRowBuilder() .setComponents( - new SelectMenuBuilder() + new StringSelectMenuBuilder() .setCustomId(q.id) .setPlaceholder(q.placeholder || q.label) .setMaxValues(q.maxLength) .setMinValues(q.minLength) .setOptions( q.options.map((o, i) => { - const builder = new SelectMenuOptionBuilder() + const builder = new StringSelectMenuOptionBuilder() .setValue(String(i)) .setLabel(o.label); if (o.description) builder.setDescription(o.description); diff --git a/src/lib/tickets/utils.js b/src/lib/tickets/utils.js index 9cae372..4949e05 100644 --- a/src/lib/tickets/utils.js +++ b/src/lib/tickets/utils.js @@ -1,8 +1,8 @@ const { ActionRowBuilder, EmbedBuilder, - SelectMenuBuilder, - SelectMenuOptionBuilder, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, } = require('discord.js'); const emoji = require('node-emoji'); @@ -50,7 +50,7 @@ module.exports = { components: [ new ActionRowBuilder() .setComponents( - new SelectMenuBuilder() + new StringSelectMenuBuilder() .setCustomId(JSON.stringify({ action: 'create', referencesMessage, @@ -60,7 +60,7 @@ module.exports = { .setPlaceholder(getMessage('menus.category.placeholder')) .setOptions( settings.categories.map(category => - new SelectMenuOptionBuilder() + new StringSelectMenuOptionBuilder() .setValue(String(category.id)) .setLabel(category.name) .setDescription(category.description) diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index 56a07b6..1832b4e 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -6,8 +6,8 @@ const { ChannelType, ComponentType, EmbedBuilder, - SelectMenuBuilder, - SelectMenuOptionBuilder, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, } = require('discord.js'); const { getCommonGuilds, @@ -52,7 +52,7 @@ module.exports = class extends Listener { components: [ new ActionRowBuilder() .setComponents( - new SelectMenuBuilder() + new StringSelectMenuBuilder() .setCustomId(JSON.stringify({ action: 'create', topic, @@ -60,7 +60,7 @@ module.exports = class extends Listener { .setPlaceholder(getMessage('menus.category.placeholder')) .setOptions( settings.categories.map(category => - new SelectMenuOptionBuilder() + new StringSelectMenuOptionBuilder() .setValue(String(category.id)) .setLabel(category.name) .setDescription(category.description) @@ -143,12 +143,12 @@ module.exports = class extends Listener { components: [ new ActionRowBuilder() .setComponents( - new SelectMenuBuilder() + new StringSelectMenuBuilder() .setCustomId(message.id) .setPlaceholder(getMessage('menus.guild.placeholder')) .setOptions( commonGuilds.map(g => - new SelectMenuOptionBuilder() + new StringSelectMenuOptionBuilder() .setValue(String(g.id)) .setLabel(g.name), ), diff --git a/src/routes/api/admin/guilds/[guild]/panels.js b/src/routes/api/admin/guilds/[guild]/panels.js index e60c02b..7181b1b 100644 --- a/src/routes/api/admin/guilds/[guild]/panels.js +++ b/src/routes/api/admin/guilds/[guild]/panels.js @@ -7,8 +7,8 @@ const { }, ChannelType: { GuildText }, EmbedBuilder, - SelectMenuBuilder, - SelectMenuOptionBuilder, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, } = require('discord.js'); const emoji = require('node-emoji'); const { logAdminEvent } = require('../../../../../lib/logging'); @@ -102,12 +102,12 @@ module.exports.post = fastify => ({ ); } else { components.push( - new SelectMenuBuilder() + new StringSelectMenuBuilder() .setCustomId(JSON.stringify({ action: 'create' })) .setPlaceholder(getMessage('menus.category.placeholder')) .setOptions( categories.map(category => - new SelectMenuOptionBuilder() + new StringSelectMenuOptionBuilder() .setValue(String(category.id)) .setLabel(category.name) .setDescription(category.description) From 8e82b2829f997e4c44499c70f78790ff64207768 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 2 Jan 2023 12:21:53 +0000 Subject: [PATCH 202/409] Revert "Remove jsconfig.json" This reverts commit 5c2eed2d7ba84b2d102c6e6b7ac6e2460ecf53cc. --- jsconfig.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 jsconfig.json diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..120fd76 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "module": "ESNext", + "target": "ESNext", + "moduleResolution": "Node", + "baseUrl": "src", + "resolveJsonModule": true, + "checkJs": false, + }, + "include": [ + "src/**/*.js" + ] +} \ No newline at end of file From 813beb44a0456a0870671fccecb458d6a1cd7560 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 2 Jan 2023 13:27:02 +0000 Subject: [PATCH 203/409] feat: close tickets on channel delete --- src/listeners/client/channelDelete.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/listeners/client/channelDelete.js diff --git a/src/listeners/client/channelDelete.js b/src/listeners/client/channelDelete.js new file mode 100644 index 0000000..58e5dc3 --- /dev/null +++ b/src/listeners/client/channelDelete.js @@ -0,0 +1,25 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'channelDelete', + }); + } + + async run(channel) { + /** @type {import("client")} */ + const client = this.client; + + const ticket = await client.prisma.ticket.findUnique({ + include: { guild: true }, + where: { id: channel.id }, + }); + if (!ticket) return; + + await client.tickets.close(ticket.id, true, 'channel deleted'); + this.client.log.info(`Closed ticket ${ticket.id} because the channel was deleted`); + } +}; From 0fa45e3e50d0ec4d3caf20f09307d0a97e922dd1 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 2 Jan 2023 13:27:20 +0000 Subject: [PATCH 204/409] fix: don't fetch partial messages on delete --- src/listeners/client/messageDelete.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/listeners/client/messageDelete.js b/src/listeners/client/messageDelete.js index 1d307a6..96f779e 100644 --- a/src/listeners/client/messageDelete.js +++ b/src/listeners/client/messageDelete.js @@ -10,15 +10,10 @@ module.exports = class extends Listener { }); } - /** - * @param {import("discord.js").Message} oldMessage - * @param {import("discord.js").Message} message - */ async run(message) { /** @type {import("client")} */ const client = this.client; - if (message.partial) message.fetch().then(m => (message = m)).catch(client.log.error); const ticket = await client.prisma.ticket.findUnique({ include: { guild: true }, where: { id: message.channel.id }, From 4dbbba6c0ba80d47f50cca66d1e46057c3f58c20 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 3 Jan 2023 16:51:11 +0000 Subject: [PATCH 205/409] feat: include category name in transcripts --- src/autocomplete/ticket.js | 2 +- user/templates/transcript.md.mustache | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js index df38807..489a807 100644 --- a/src/autocomplete/ticket.js +++ b/src/autocomplete/ticket.js @@ -49,7 +49,7 @@ module.exports = class TicketCompleter extends Autocompleter { }); tickets = tickets.map(ticket => { const date = new Date(ticket.createdAt).toLocaleString([locale, 'en-GB'], { dateStyle: 'short' }); - const topic = ticket.topic ? '- ' + decrypt(ticket.topic).substring(0, 50) : ''; + const topic = ticket.topic ? '- ' + decrypt(ticket.topic).replace(/\n/, ' ').substring(0, 50) : ''; const category = emoji.hasEmoji(ticket.category.emoji) ? emoji.get(ticket.category.emoji) + ' ' + ticket.category.name : ticket.category.name; ticket._name = `${category} #${ticket.number} (${date}) ${topic}`; return ticket; diff --git a/user/templates/transcript.md.mustache b/user/templates/transcript.md.mustache index db2b80d..cb69b1d 100644 --- a/user/templates/transcript.md.mustache +++ b/user/templates/transcript.md.mustache @@ -2,8 +2,8 @@ --- -* ID: {{ ticket.id }} -* Number: {{ guildName }} #{{ ticket.number }} +* ID: {{ ticket.id }} ({{ guildName }}) +* Number: {{ ticket.category.name }} #{{ ticket.number }} * Topic: {{ #ticket.topic }}{{ . }}{{ /ticket.topic }}{{ ^ticket.topic }}(no topic){{ /ticket.topic }} * Created on: {{ #ticket }}{{ createdAtFull }}{{ /ticket }} * Created by: {{ #ticket.createdBy }}"{{ displayName }}" @{{ username }}#{{ discriminator }}{{ /ticket.createdBy }} From cf6b34785cbeb2135037f25d208e50533db8d9e2 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 3 Jan 2023 16:59:26 +0000 Subject: [PATCH 206/409] fix: new line replacement --- src/autocomplete/ticket.js | 2 +- src/commands/slash/transcript.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js index 489a807..767a6e8 100644 --- a/src/autocomplete/ticket.js +++ b/src/autocomplete/ticket.js @@ -49,7 +49,7 @@ module.exports = class TicketCompleter extends Autocompleter { }); tickets = tickets.map(ticket => { const date = new Date(ticket.createdAt).toLocaleString([locale, 'en-GB'], { dateStyle: 'short' }); - const topic = ticket.topic ? '- ' + decrypt(ticket.topic).replace(/\n/, ' ').substring(0, 50) : ''; + const topic = ticket.topic ? '- ' + decrypt(ticket.topic).replace(/\n/g, ' ').substring(0, 50) : ''; const category = emoji.hasEmoji(ticket.category.emoji) ? emoji.get(ticket.category.emoji) + ' ' + ticket.category.name : ticket.category.name; ticket._name = `${category} #${ticket.number} (${date}) ${topic}`; return ticket; diff --git a/src/commands/slash/transcript.js b/src/commands/slash/transcript.js index eef5a46..6acf636 100644 --- a/src/commands/slash/transcript.js +++ b/src/commands/slash/transcript.js @@ -70,7 +70,7 @@ module.exports = class TranscriptSlashCommand extends SlashCommand { if (ticket.closedReason) ticket.closedReason = decrypt(ticket.closedReason); if (ticket.feedback?.comment) ticket.feedback.comment = decrypt(ticket.feedback.comment); - if (ticket.topic) ticket.topic = decrypt(ticket.topic); + if (ticket.topic) ticket.topic = decrypt(ticket.topic).replace(/\n/g, '\n\t'); ticket.archivedUsers.forEach((user, i) => { if (user.displayName) user.displayName = decrypt(user.displayName); From db94ab770de2bb2b7a2ca18412205a8b185528c6 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 12 Jan 2023 17:19:03 +0000 Subject: [PATCH 207/409] perf: cache tags --- src/autocomplete/tag.js | 17 +++++++- src/listeners/client/messageCreate.js | 58 +++++++++++++++++++-------- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/autocomplete/tag.js b/src/autocomplete/tag.js index b75a76d..90dcef6 100644 --- a/src/autocomplete/tag.js +++ b/src/autocomplete/tag.js @@ -1,4 +1,5 @@ const { Autocompleter } = require('@eartharoid/dbf'); +const ms = require('ms'); module.exports = class TagCompleter extends Autocompleter { constructor(client, options) { @@ -17,7 +18,21 @@ module.exports = class TagCompleter extends Autocompleter { /** @type {import("client")} */ const client = this.client; - const tags = await client.prisma.tag.findMany({ where: { guildId: interaction.guild.id } }); + const cacheKey = `cache/guild-tags:${interaction.guild.id}`; + let tags = await client.keyv.get(cacheKey); + if (!tags) { + tags = await client.prisma.tag.findMany({ + select: { + content: true, + id: true, + name: true, + regex: true, + }, + where: { guildId: interaction.guild.id }, + }); + client.keyv.set(cacheKey, tags, ms('1h')); + } + const options = value ? tags.filter(tag => tag.name.match(new RegExp(value, 'i')) || tag.content.match(new RegExp(value, 'i')) || diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index 1832b4e..a8eb04c 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -184,6 +184,7 @@ module.exports = class extends Listener { let ticket = await client.prisma.ticket.findUnique({ where: { id: message.channel.id } }); if (ticket) { + // archive messages if (settings.archive) { try { await client.tickets.archiver.saveMessage(ticket.id, message); @@ -194,6 +195,7 @@ module.exports = class extends Listener { } if (!message.author.bot) { + // update user's message count await client.prisma.user.upsert({ create: { id: message.author.id, @@ -203,6 +205,7 @@ module.exports = class extends Listener { where: { id: message.author.id }, }); + // set first and last message timestamps const data = { lastMessageAt: new Date() }; if ( ticket.firstResponseAt === null && @@ -212,30 +215,53 @@ module.exports = class extends Listener { data, where: { id: ticket.id }, }); + + // if the ticket was set as stale, unset it + if (client.tickets.$stale.has(ticket.id)) { + await message.channel.messages.delete(client.tickets.$stale.get(ticket.id).message.id); + client.tickets.$stale.delete(ticket.id); + } } // TODO: if (!message.author.bot) staff status alert, working hours alerts } - if (!message.author.bot) { - const enabled = - (settings.autoTag === 'all') || + // auto-tag + if ( + !message.author.bot && + ( + (settings.autoTag === 'all') || (settings.autoTag === 'ticket' && ticket) || (settings.autoTag === '!ticket' && !ticket) || - (settings.autoTag.includes(message.channel.id)); - if (enabled) { - const tags = await client.prisma.tag.findMany({ where: { guildId: message.guild.id } }); - const tag = tags.find(tag => message.content.match(new RegExp(tag.regex, 'mi'))); - if (tag) { - await message.reply({ - embeds: [ - new EmbedBuilder() - .setColor(settings.primaryColour) - .setDescription(tag.content), - ], - }); - } + (settings.autoTag.includes(message.channel.id)) + ) + ) { + const cacheKey = `cache/guild-tags:${message.guild.id}`; + let tags = await client.keyv.get(cacheKey); + if (!tags) { + tags = await client.prisma.tag.findMany({ + select: { + content: true, + id: true, + name: true, + regex: true, + }, + where: { guildId: message.guild.id }, + }); + client.keyv.set(cacheKey, tags, ms('1h')); } + + const tag = tags.find(tag => message.content.match(new RegExp(tag.regex, 'mi'))); + if (tag) { + await message.reply({ + embeds: [ + new EmbedBuilder() + .setColor(settings.primaryColour) + .setDescription(tag.content), + ], + }); + } + } } } From d7e1b05586d68cd90ff13fec6c5cbdc7a68522e1 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 12 Jan 2023 17:20:21 +0000 Subject: [PATCH 208/409] perf: cache staff roles --- src/lib/users.js | 36 ++++++++++++++----- .../[guild]/categories/[category]/index.js | 9 +++-- .../admin/guilds/[guild]/categories/index.js | 3 ++ 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/lib/users.js b/src/lib/users.js index 23a959d..ca196f8 100644 --- a/src/lib/users.js +++ b/src/lib/users.js @@ -11,6 +11,29 @@ module.exports.getCommonGuilds = async (client, userId) => await client.guilds.c return !!member; }); +/** + * @param {import("discord.js").Guild} guild + * @returns {Promise} + */ +const updateStaffRoles = async guild => { + const { categories } = await guild.client.prisma.guild.findUnique({ + select: { categories: { select: { staffRoles: true } } }, + where: { id: guild.id }, + }); + const staffRoles = [ + ...new Set( + categories.reduce((acc, c) => { + acc.push(...c.staffRoles); + return acc; + }, []), + ), + ]; + await guild.client.keyv.set(`cache/guild-staff:${guild.id}`, staffRoles); + return staffRoles; +}; + +module.exports.updateStaffRoles = updateStaffRoles; + /** * * @param {import("discord.js").Guild} guild @@ -20,12 +43,9 @@ module.exports.getCommonGuilds = async (client, userId) => await client.guilds.c module.exports.isStaff = async (guild, userId) => { /** @type {import("client")} */ const client = guild.client; - if (guild.client.supers.includes(userId)) return true; - const guildMember = await guild.members.fetch(userId); - if (guildMember?.permissions.has(PermissionsBitField.Flags.ManageGuild)) return true; - const { categories } = await client.prisma.guild.findUnique({ - select: { categories: true }, - where: { id: guild.id }, - }); - return categories.some(cat => cat.roles.some(r => guildMember.roles.cache.has(r))); + if (client.supers.includes(userId)) return true; + const guildMember = guild.members.cache.get(userId) || await guild.members.fetch(userId); + if (guildMember.permissions.has(PermissionsBitField.Flags.ManageGuild)) return true; + const staffRoles = await client.keyv.get(`cache/guild-staff:${guild.id}`) || await updateStaffRoles(guild); + return staffRoles.some(r => guildMember.roles.cache.has(r)); }; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js index 29fe1a7..3493c1e 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js @@ -1,16 +1,19 @@ const { logAdminEvent } = require('../../../../../../../lib/logging'); +const { updateStaffRoles } = require('../../../../../../../lib/users'); const { randomUUID } = require('crypto'); module.exports.delete = fastify => ({ handler: async (req, res) => { /** @type {import('client')} */ const client = res.context.config.client; - const guildId = req.params.guild; + const guild = client.guilds.cache.get(req.params.guild); const categoryId = Number(req.params.category); const original = categoryId && await client.prisma.category.findUnique({ where: { id: categoryId } }); - if (!original || original.guildId !== guildId) return res.status(404).send(new Error('Not Found')); + if (!original || original.guildId !== guild.id) return res.status(404).send(new Error('Not Found')); const category = await client.prisma.category.delete({ where: { id: categoryId } }); + await updateStaffRoles(guild); + logAdminEvent(client, { action: 'delete', guildId: req.params.guild, @@ -137,6 +140,8 @@ module.exports.patch = fastify => ({ where: { id: categoryId }, }); + await updateStaffRoles(guild); + logAdminEvent(client, { action: 'update', diff: { diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index 254a8d5..f92072a 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -1,4 +1,5 @@ const { logAdminEvent } = require('../../../../../../lib/logging'); +const { updateStaffRoles } = require('../../../../../../lib/users'); const emoji = require('node-emoji'); const { ChannelType: { GuildCategory } } = require('discord.js'); const ms = require('ms'); @@ -93,6 +94,8 @@ module.exports.post = fastify => ({ }, }); + await updateStaffRoles(guild); + logAdminEvent(client, { action: 'create', guildId: guild.id, From 8bf01aa520ac6ce6f82f23a23756e98a004669f7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 13 Jan 2023 20:48:37 +0000 Subject: [PATCH 209/409] feat: feedback, start of close requests --- db/mysql/schema.prisma | 8 +- db/postgresql/schema.prisma | 8 +- db/sqlite/schema.prisma | 8 +- package.json | 6 +- src/buttons/close.js | 26 +++- src/buttons/edit.js | 8 +- src/client.js | 3 +- src/commands/slash/close.js | 4 +- src/commands/slash/force-close.js | 2 - src/commands/slash/move.js | 4 +- src/commands/slash/transfer.js | 2 +- src/i18n/en-GB.yml | 30 ++++- src/lib/sync.js | 6 +- src/lib/tickets/manager.js | 208 ++++++++++++++++++++++++++---- src/listeners/client/ready.js | 8 ++ src/modals/feedback.js | 21 ++- 16 files changed, 293 insertions(+), 59 deletions(-) diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index 6f25802..e91ebac 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -101,10 +101,9 @@ model Feedback { createdAt DateTime @default(now()) guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) - id Int @id @default(autoincrement()) rating Int ticket Ticket @relation(fields: [ticketId], references: [id]) - ticketId String @unique @db.VarChar(19) + ticketId String @id @db.VarChar(19) user User? @relation(fields: [userId], references: [id]) userId String? @db.VarChar(19) @@ -199,10 +198,9 @@ model Ticket { createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String @db.VarChar(19) - feedback Feedback? - feedbackId Int? - firstResponseAt DateTime? deleted Boolean @default(false) + feedback Feedback? + firstResponseAt DateTime? guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) id String @id @db.VarChar(19) diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index 02f97c8..d06520c 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -101,10 +101,9 @@ model Feedback { createdAt DateTime @default(now()) guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) - id Int @id @default(autoincrement()) rating Int ticket Ticket @relation(fields: [ticketId], references: [id]) - ticketId String @unique @db.VarChar(19) + ticketId String @id @db.VarChar(19) user User? @relation(fields: [userId], references: [id]) userId String? @db.VarChar(19) @@ -199,10 +198,9 @@ model Ticket { createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String @db.VarChar(19) - feedback Feedback? - feedbackId Int? - firstResponseAt DateTime? deleted Boolean @default(false) + feedback Feedback? + firstResponseAt DateTime? guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) id String @id @db.VarChar(19) diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma index 6e45541..8581650 100644 --- a/db/sqlite/schema.prisma +++ b/db/sqlite/schema.prisma @@ -101,10 +101,9 @@ model Feedback { createdAt DateTime @default(now()) guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String - id Int @id @default(autoincrement()) rating Int ticket Ticket @relation(fields: [ticketId], references: [id]) - ticketId String @unique + ticketId String @id user User? @relation(fields: [userId], references: [id]) userId String? @@ -199,10 +198,9 @@ model Ticket { createdAt DateTime @default(now()) createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id]) createdById String - feedback Feedback? - feedbackId Int? - firstResponseAt DateTime? deleted Boolean @default(false) + feedback Feedback? + firstResponseAt DateTime? guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String id String @id diff --git a/package.json b/package.json index 7d82f2e..7c57e2e 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "@fastify/http-proxy": "^8.4.0", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", - "@prisma/client": "^4.8.0", - "cryptr": "^6.0.3", + "@prisma/client": "^4.8.1", + "cryptr": "^6.1.0", "discord.js": "^14.7.1", "dotenv": "^16.0.3", "express": "^4.18.2", @@ -60,7 +60,7 @@ "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", "pad": "^3.2.0", - "prisma": "^4.8.0", + "prisma": "^4.8.1", "semver": "^7.3.8", "terminal-link": "^2.1.1", "yaml": "^1.10.2" diff --git a/src/buttons/close.js b/src/buttons/close.js index 50a25a3..1a5cd54 100644 --- a/src/buttons/close.js +++ b/src/buttons/close.js @@ -1,4 +1,5 @@ const { Button } = require('@eartharoid/dbf'); +const { isStaff } = require('../lib/users'); module.exports = class CloseButton extends Button { constructor(client, options) { @@ -16,6 +17,29 @@ module.exports = class CloseButton extends Button { /** @type {import("client")} */ const client = this.client; - await interaction.deferReply(); + if (id.accepted === undefined) { + await client.tickets.beforeRequestClose(interaction); + } else { + // { + // action: 'close', + // expect: staff ? 'user' : 'staff', + // reason: interaction.options?.getString('reason', false) || null, // ?. because it could be a button interaction + // requestedBy: interaction.user.id, + // } + + await interaction.deferReply(); + const ticket = await client.prisma.ticket.findUnique({ + include: { guild: true }, + where: { id: interaction.channel.id }, + }); + + if (id.expect === 'staff' && !await isStaff(interaction.guild, interaction.user.id)) { + return; + } else if (interaction.user.id !== ticket.createdById) { + return; + // if user and expect user (or is creator), feedback modal (if enabled) + // otherwise add "Give feedback" button in DM message (if enabled) + } + } } }; \ No newline at end of file diff --git a/src/buttons/edit.js b/src/buttons/edit.js index 208e027..96e1764 100644 --- a/src/buttons/edit.js +++ b/src/buttons/edit.js @@ -2,8 +2,8 @@ const { Button } = require('@eartharoid/dbf'); const { ActionRowBuilder, ModalBuilder, - SelectMenuBuilder, - SelectMenuOptionBuilder, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, TextInputBuilder, TextInputStyle, } = require('discord.js'); @@ -86,14 +86,14 @@ module.exports = class EditButton extends Button { } else if (a.question.type === 'MENU') { return new ActionRowBuilder() .setComponents( - new SelectMenuBuilder() + new StringSelectMenuBuilder() .setCustomId(a.question.id) .setPlaceholder(a.question.placeholder || a.question.label) .setMaxValues(a.question.maxLength) .setMinValues(a.question.minLength) .setOptions( a.question.options.map((o, i) => { - const builder = new SelectMenuOptionBuilder() + const builder = new StringSelectMenuOptionBuilder() .setValue(String(i)) .setLabel(o.label); if (o.description) builder.setDescription(o.description); diff --git a/src/client.js b/src/client.js index 0e91785..b34f4ae 100644 --- a/src/client.js +++ b/src/client.js @@ -1,6 +1,7 @@ const { FrameworkClient } = require('@eartharoid/dbf'); const { - GatewayIntentBits, Partials, + GatewayIntentBits, + Partials, } = require('discord.js'); const { PrismaClient } = require('@prisma/client'); const Keyv = require('keyv'); diff --git a/src/commands/slash/close.js b/src/commands/slash/close.js index 0086c0a..af0010b 100644 --- a/src/commands/slash/close.js +++ b/src/commands/slash/close.js @@ -30,6 +30,8 @@ module.exports = class CloseSlashCommand extends SlashCommand { * @param {import("discord.js").ChatInputCommandInteraction} interaction */ async run(interaction) { - + /** @type {import("client")} */ + const client = this.client; + await client.tickets.beforeRequestClose(interaction); } }; \ No newline at end of file diff --git a/src/commands/slash/force-close.js b/src/commands/slash/force-close.js index 2e16b75..dea68f1 100644 --- a/src/commands/slash/force-close.js +++ b/src/commands/slash/force-close.js @@ -228,7 +228,5 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { }); } } - - // TODO: close (reason) } }; \ No newline at end of file diff --git a/src/commands/slash/move.js b/src/commands/slash/move.js index 2dc3b04..335699c 100644 --- a/src/commands/slash/move.js +++ b/src/commands/slash/move.js @@ -85,8 +85,8 @@ module.exports = class MoveSlashCommand extends SlashCommand { where: { id: ticket.id }, }); - const $oldCategory = client.tickets.$.categories[ticket.categoryId]; - const $newCategory = client.tickets.$.categories[newCategory.id]; + const $oldCategory = client.tickets.$count.categories[ticket.categoryId]; + const $newCategory = client.tickets.$count.categories[newCategory.id]; $oldCategory.total--; $oldCategory[ticket.createdById]--; diff --git a/src/commands/slash/transfer.js b/src/commands/slash/transfer.js index 785ad0b..aa03f98 100644 --- a/src/commands/slash/transfer.js +++ b/src/commands/slash/transfer.js @@ -44,7 +44,7 @@ module.exports = class TransferSlashCommand extends SlashCommand { let ticket = await client.prisma.ticket.findUnique({ where: { id: interaction.channel.id } }); const from = ticket.createdById; - console.log(1) + ticket = await client.prisma.ticket.update({ data: { createdBy: { diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 9c9fd9c..0be9cef 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -1,6 +1,9 @@ buttons: + accept_close_request: + emoji: ✅ + text: Accept cancel: - emoji: 🚫 + emoji: ✖️ text: Cancel claim: emoji: 🙌 @@ -17,6 +20,9 @@ buttons: edit: emoji: ✏️ text: Edit + reject_close_request: + emoji: ✖️ + text: Reject unclaim: emoji: ♻️ text: Release @@ -328,7 +334,13 @@ misc: title: ❌ That ticket category doesn't exist modals: feedback: - title: Feedback + comment: + label: Comment + placeholder: Do you have any additional feedback? + rating: + label: Rating + placeholder: 1-5 + title: How did we do? topic: label: Topic placeholder: What is this ticket about? @@ -336,6 +348,20 @@ ticket: answers: no_value: "*No response*" claimed: 🙌 {user} has claimed this ticket. + close: + forbidden: + description: You don't have permission to close this ticket. + title: ❌ Error + staff_request: + archived: | + + The messages in this channel will be archived for future reference. + description: | + {requestedBy} wants to close this ticket. + Click "Accept" to close it now, or "Reject" if you still need help. + title: ❓ Can this ticket be closed? + user_request: + title: ❓ {requestedBy} wants to close this ticket created: description: "Your ticket channel has been created: {channel}." title: ✅ Ticket created diff --git a/src/lib/sync.js b/src/lib/sync.js index 0d186a5..e7022b3 100644 --- a/src/lib/sync.js +++ b/src/lib/sync.js @@ -22,10 +22,10 @@ module.exports = async client => { let cooldowns = 0; for (const category of categories) { ticketCount += category.tickets.length; - client.tickets.$.categories[category.id] = { total: category.tickets.length }; + client.tickets.$count.categories[category.id] = { total: category.tickets.length }; for (const ticket of category.tickets) { - if (client.tickets.$.categories[category.id][ticket.createdById]) client.tickets.$.categories[category.id][ticket.createdById]++; - else client.tickets.$.categories[category.id][ticket.createdById] = 1; + if (client.tickets.$count.categories[category.id][ticket.createdById]) client.tickets.$count.categories[category.id][ticket.createdById]++; + else client.tickets.$count.categories[category.id][ticket.createdById] = 1; /** @type {import("discord.js").Guild} */ const guild = client.guilds.cache.get(ticket.guildId); if (guild && guild.available && !client.channels.cache.has(ticket.id)) { diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index bb926ac..9143a8e 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -16,6 +16,7 @@ const emoji = require('node-emoji'); const ms = require('ms'); const ExtendedEmbedBuilder = require('../embed'); const { logTicketEvent } = require('../logging'); +const { isStaff } = require('../users'); const { Collection } = require('discord.js'); const Cryptr = require('cryptr'); const { encrypt } = new Cryptr(process.env.ENCRYPTION_KEY); @@ -30,14 +31,21 @@ module.exports = class TicketManager { /** @type {import("client")} */ this.client = client; this.archiver = new TicketArchiver(client); - this.$ = { categories: {} }; + this.$count = { categories: {} }; + this.$stale = new Collection(); } - async getCategory(categoryId) { + /** + * Retrieve cached category data + * @param {string} categoryId the category ID + * @param {boolean} force bypass & update the cache? + * @returns {Promise} + */ + async getCategory(categoryId, force) { const cacheKey = `cache/category+guild+questions:${categoryId}`; /** @type {CategoryGuildQuestions} */ let category = await this.client.keyv.get(cacheKey); - if (!category) { + if (!category || force) { category = await this.client.prisma.category.findUnique({ include: { guild: true, @@ -45,16 +53,16 @@ module.exports = class TicketManager { }, where: { id: categoryId }, }); - this.client.keyv.set(cacheKey, category, ms('5m')); + await this.client.keyv.set(cacheKey, category, ms('12h')); } return category; } // TODO: update when a ticket is closed or moved async getTotalCount(categoryId) { - const category = this.$.categories[categoryId]; - if (!category) this.$.categories[categoryId] = {}; - let count = this.$.categories[categoryId].total; + const category = this.$count.categories[categoryId]; + if (!category) this.$count.categories[categoryId] = {}; + let count = this.$count.categories[categoryId].total; if (!count) { count = await this.client.prisma.ticket.count({ where: { @@ -62,16 +70,16 @@ module.exports = class TicketManager { open: true, }, }); - this.$.categories[categoryId].total = count; + this.$count.categories[categoryId].total = count; } return count; } // TODO: update when a ticket is closed or moved async getMemberCount(categoryId, memberId) { - const category = this.$.categories[categoryId]; - if (!category) this.$.categories[categoryId] = {}; - let count = this.$.categories[categoryId][memberId]; + const category = this.$count.categories[categoryId]; + if (!category) this.$count.categories[categoryId] = {}; + let count = this.$count.categories[categoryId][memberId]; if (!count) { count = await this.client.prisma.ticket.count({ where: { @@ -80,7 +88,7 @@ module.exports = class TicketManager { open: true, }, }); - this.$.categories[categoryId][memberId] = count; + this.$count.categories[categoryId][memberId] = count; } return count; } @@ -308,17 +316,15 @@ module.exports = class TicketManager { }) { await interaction.deferReply({ ephemeral: true }); - const cacheKey = `cache/category+guild+questions:${categoryId}`; - /** @type {CategoryGuildQuestions} */ - const category = await this.client.keyv.get(cacheKey); + const category = await this.getCategory(categoryId); let answers; if (interaction.isModalSubmit()) { if (action === 'questions') { - answers = category.questions.map(q => ({ + answers = category.questions.filter(q => q.type === 'TEXT').map(q => ({ questionId: q.id, userId: interaction.user.id, - value: interaction.fields.getTextInputValue(q.id) ? cryptr.encrypt(interaction.fields.getTextInputValue(q.id)) : '', + value: interaction.fields.getTextInputValue(q.id) ? encrypt(interaction.fields.getTextInputValue(q.id)) : '', })); if (category.customTopic) topic = interaction.fields.getTextInputValue(category.customTopic); } else if (action === 'topic') { @@ -568,7 +574,7 @@ module.exports = class TicketManager { id: channel.id, number, openingMessageId: sent.id, - topic: topic ? cryptr.encrypt(topic) : null, + topic: topic ? encrypt(topic) : null, }; if (referencesTicketId) data.referencesTicket = { connect: { id: referencesTicketId } }; if (answers) data.questionAnswers = { createMany: { data: answers } }; @@ -587,8 +593,8 @@ module.exports = class TicketManager { try { const ticket = await this.client.prisma.ticket.create({ data }); - this.$.categories[categoryId].total++; - this.$.categories[categoryId][creator.id]++; + this.$count.categories[categoryId].total++; + this.$count.categories[categoryId][creator.id]++; if (category.cooldown) { const cacheKey = `cooldowns/category-member:${category.id}-${ticket.createdById}`; @@ -805,15 +811,170 @@ module.exports = class TicketManager { /** * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction} interaction */ - async requestClose(interaction) { + async beforeRequestClose(interaction) { const ticket = await this.client.prisma.ticket.findUnique({ include: { - category: true, + category: { select: { enableFeedback: true } }, + feedback: { select: { id: true } }, guild: true, }, where: { id: interaction.channel.id }, }); + + if (!ticket) { + await interaction.deferReply({ ephemeral: true }); + const { + errorColour, + locale, + } = await this.client.prisma.guild.findUnique({ + select: { + errorColour: true, + locale: true, + }, + where: { id: interaction.guild.id }, + }); + const getMessage = this.client.i18n.getLocale(locale); + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(errorColour) + .setTitle(getMessage('misc.not_ticket.title')) + .setDescription(getMessage('misc.not_ticket.description')), + ], + }); + } + const getMessage = this.client.i18n.getLocale(ticket.guild.locale); + const staff = await isStaff(interaction.guild, interaction.user.id); + const reason = interaction.options?.getString('reason', false) || null; // ?. because it could be a button interaction) + + if (ticket.createdById !== interaction.user.id && !staff) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(ticket.guild.errorColour) + .setTitle(getMessage('ticket.close.forbidden.title')) + .setDescription(getMessage('ticket.close.forbidden.description')), + ], + }); + } + + if (ticket.createdById === interaction.user.id && ticket.category.enableFeedback && !ticket.feedback) { + return await interaction.showModal( + new ModalBuilder() + .setCustomId(JSON.stringify({ + action: 'feedback', + reason, + })) + .setTitle(getMessage('modals.feedback.title')) + .setComponents( + new ActionRowBuilder() + .setComponents( + new TextInputBuilder() + .setCustomId('rating') + .setLabel(getMessage('modals.feedback.rating.label')) + .setStyle(TextInputStyle.Short) + .setMaxLength(3) + .setMinLength(1) + .setPlaceholder(getMessage('modals.feedback.rating.placeholder')) + .setRequired(false), + ), + new ActionRowBuilder() + .setComponents( + new TextInputBuilder() + .setCustomId('comment') + .setLabel(getMessage('modals.feedback.comment.label')) + .setStyle(TextInputStyle.Paragraph) + .setMaxLength(1000) + .setMinLength(4) + .setPlaceholder(getMessage('modals.feedback.comment.placeholder')) + .setRequired(false), + ), + ), + ); + + } + + // defer asap + await interaction.deferReply(); + + // if the creator isn't in the guild , close the ticket immediately + // (although leaving should cause the ticket to be closed anyway) + try { + await interaction.guild.members.fetch(ticket.createdById); + } catch { + return this.close(ticket.id, true, reason); + } + + await this.requestClose(interaction, reason); + } + + /** + * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction|import("discord.js").ModalSubmitInteraction} interaction + */ + async requestClose(interaction, reason) { + // interaction could be command, button. or modal + const ticket = await this.client.prisma.ticket.findUnique({ + include: { guild: true }, + where: { id: interaction.channel.id }, + }); + const getMessage = this.client.i18n.getLocale(ticket.guild.locale); + const staff = await isStaff(interaction.guild, interaction.user.id); + const closeButtonId = { + action: 'close', + expect: staff ? 'user' : 'staff', + }; + const embed = new ExtendedEmbedBuilder() + .setColor(ticket.guild.primaryColour) + .setTitle(getMessage(`ticket.close.${staff ? 'staff' : 'user'}_request.title`, { requestedBy: interaction.member.displayName })); + + if (staff) { + embed.setDescription( + getMessage('ticket.close.staff_request.description', { requestedBy: interaction.user.toString() }) + + (ticket.guild.archive ? getMessage('ticket.close.staff_request.archived') : ''), + ); + } + + const sent = await interaction.editReply({ + components: [ + new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ + accepted: true, + ...closeButtonId, + })) + .setStyle(ButtonStyle.Success) + .setEmoji(getMessage('buttons.accept_close_request.emoji')) + .setLabel(getMessage('buttons.accept_close_request.text')), + new ButtonBuilder() + .setCustomId(JSON.stringify({ + accepted: false, + ...closeButtonId, + })) + .setStyle(ButtonStyle.Danger) + .setEmoji(getMessage('buttons.reject_close_request.emoji')) + .setLabel(getMessage('buttons.reject_close_request.text')), + ), + ], + content: staff ? `<@${ticket.createdById}>` : '', // ticket.category.pingRoles.map(r => `<@&${r}>`).join(' ') + embeds: [embed], + }); + + this.$stale.set(ticket.id, { + closeAt: ticket.guild.autoClose ? Date.now() + ticket.guild.autoClose : null, + closedBy: interaction.user.id, // null if set as stale due to inactivity + message: sent, + reason, + staleSince: Date.now(), + }); + + if (ticket.priority && ticket.priority !== 'LOW') { + await this.client.prisma.ticket.update({ + data: { priority: 'LOW' }, + where: { id: ticket.id }, + }); + } } /** @@ -822,10 +983,11 @@ module.exports = class TicketManager { * @param {boolean} skip * @param {string} reason */ - async final(ticketId, skip, reason) { + async close(ticketId, skip, reason) { // TODO: update cache/cat count // TODO: update cache/member count // TODO: set messageCount on ticket + // TODO: pinnedMessages, closedBy, closedAt // delete } }; \ No newline at end of file diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 26bdf35..bfb3cd4 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -108,5 +108,13 @@ module.exports = class extends Listener { send(); setInterval(() => send(), ms('12h')); } + + setInterval(() => { + // TODO: check lastMessageAt and set stale + + for (const [ticketId, $] of client.tickets.$stale) { + // ⌛ + } + }, ms('5m')); } }; diff --git a/src/modals/feedback.js b/src/modals/feedback.js index 74c19ac..cb68190 100644 --- a/src/modals/feedback.js +++ b/src/modals/feedback.js @@ -8,5 +8,24 @@ module.exports = class FeedbackModal extends Modal { }); } - async run(id, interaction) { } + async run(id, interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply(); + await client.prisma.ticket.update({ + data: { + feedback: { + create: { + comment: interaction.fields.getTextInputValue('comment'), + guild: { connect: { id: interaction.guild.id } }, + rating: parseInt(interaction.fields.getTextInputValue('rating')) || null, + user: { connect: { id: interaction.user.id } }, + }, + }, + }, + where: { id: interaction.channel.id }, + }); + await client.tickets.requestClose(interaction, id.reason); + } }; \ No newline at end of file From 2a8c1603f2d27491048371beafd0ecc90118f32f Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 13 Jan 2023 22:25:04 +0000 Subject: [PATCH 210/409] more on closing and feedback (WIP) --- src/buttons/close.js | 56 ++++++++--- src/commands/slash/force-close.js | 2 +- src/i18n/en-GB.yml | 4 +- src/lib/sync.js | 2 +- src/lib/tickets/manager.js | 111 +++++++++++++--------- src/listeners/client/channelDelete.js | 2 +- src/listeners/client/guildMemberRemove.js | 2 +- src/listeners/client/messageCreate.js | 10 +- src/modals/feedback.js | 38 +++++++- 9 files changed, 158 insertions(+), 69 deletions(-) diff --git a/src/buttons/close.js b/src/buttons/close.js index 1a5cd54..7893884 100644 --- a/src/buttons/close.js +++ b/src/buttons/close.js @@ -1,4 +1,5 @@ const { Button } = require('@eartharoid/dbf'); +const ExtendedEmbedBuilder = require('../lib/embed'); const { isStaff } = require('../lib/users'); module.exports = class CloseButton extends Button { @@ -17,28 +18,55 @@ module.exports = class CloseButton extends Button { /** @type {import("client")} */ const client = this.client; + // the close button on th opening message, the same as using /close if (id.accepted === undefined) { await client.tickets.beforeRequestClose(interaction); } else { - // { - // action: 'close', - // expect: staff ? 'user' : 'staff', - // reason: interaction.options?.getString('reason', false) || null, // ?. because it could be a button interaction - // requestedBy: interaction.user.id, - // } - await interaction.deferReply(); const ticket = await client.prisma.ticket.findUnique({ - include: { guild: true }, + include: { + category: true, + guild: true, + }, where: { id: interaction.channel.id }, }); + const getMessage = client.i18n.getLocale(ticket.guild.locale); + const staff = await isStaff(interaction.guild, interaction.user.id); - if (id.expect === 'staff' && !await isStaff(interaction.guild, interaction.user.id)) { - return; - } else if (interaction.user.id !== ticket.createdById) { - return; - // if user and expect user (or is creator), feedback modal (if enabled) - // otherwise add "Give feedback" button in DM message (if enabled) + if (id.expect === 'staff' && !staff) { + return; // TODO: please wait for staff to close the ticket + } else if (id.expect === 'user' && staff) { + return; // TODO: please wait for the user to respond + } else { + if (id.accepted) { + if ( + ticket.createdById === interaction.user.id && + ticket.category.enableFeedback && + !ticket.feedback + ) { + return await interaction.showModal(client.tickets.buildFeedbackModal(ticket.guild.locale, { next: 'acceptClose' })); + } else { + await client.tickets.acceptClose(interaction); + } + } else { + if (client.tickets.$stale.has(ticket.id)) { + await interaction.channel.messages.edit( + client.tickets.$stale.get(ticket.id).message.id, + { + components: [], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setDescription(getMessage('ticket.close.rejected', { user: interaction.user.toString() })), + ], + }, + ); + client.tickets.$stale.delete(ticket.id); + } + } } } } diff --git a/src/commands/slash/force-close.js b/src/commands/slash/force-close.js index dea68f1..1876f60 100644 --- a/src/commands/slash/force-close.js +++ b/src/commands/slash/force-close.js @@ -62,7 +62,7 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { await interaction.deferReply(); const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); - const getMessage = this.client.i18n.getLocale(settings.locale); + const getMessage = client.i18n.getLocale(settings.locale); let ticket; if (!(await isStaff(interaction.guild, interaction.user.id))) { // if user is not staff diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 0be9cef..20783f6 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -339,7 +339,7 @@ modals: placeholder: Do you have any additional feedback? rating: label: Rating - placeholder: 1-5 + placeholder: 1-5 title: How did we do? topic: label: Topic @@ -352,6 +352,7 @@ ticket: forbidden: description: You don't have permission to close this ticket. title: ❌ Error + rejected: ✋ {user} rejected a request to close this ticket. staff_request: archived: | @@ -368,6 +369,7 @@ ticket: edited: description: Your changes have been saved. title: ✅ Ticket updated + feedback: Thank you for your feedback. opening_message: content: | {staff} diff --git a/src/lib/sync.js b/src/lib/sync.js index e7022b3..91f3d58 100644 --- a/src/lib/sync.js +++ b/src/lib/sync.js @@ -30,7 +30,7 @@ module.exports = async client => { const guild = client.guilds.cache.get(ticket.guildId); if (guild && guild.available && !client.channels.cache.has(ticket.id)) { deleted += 0; - await client.tickets.close(ticket.id, true, 'channel deleted'); + await client.tickets.finallyClose(ticket.id, { reason: 'channel deleted' }); } } diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 9143a8e..a716368 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -807,6 +807,40 @@ module.exports = class TicketManager { }); } + buildFeedbackModal(locale, id) { + const getMessage = this.client.i18n.getLocale(locale); + return new ModalBuilder() + .setCustomId(JSON.stringify({ + action: 'feedback', + ...id, + })) + .setTitle(getMessage('modals.feedback.title')) + .setComponents( + new ActionRowBuilder() + .setComponents( + new TextInputBuilder() + .setCustomId('rating') + .setLabel(getMessage('modals.feedback.rating.label')) + .setStyle(TextInputStyle.Short) + .setMaxLength(3) + .setMinLength(1) + .setPlaceholder(getMessage('modals.feedback.rating.placeholder')) + .setRequired(true), + ), + new ActionRowBuilder() + .setComponents( + new TextInputBuilder() + .setCustomId('comment') + .setLabel(getMessage('modals.feedback.comment.label')) + .setStyle(TextInputStyle.Paragraph) + .setMaxLength(1000) + .setMinLength(4) + .setPlaceholder(getMessage('modals.feedback.comment.placeholder')) + .setRequired(false), + ), + ); + } + /** * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction} interaction @@ -815,7 +849,7 @@ module.exports = class TicketManager { const ticket = await this.client.prisma.ticket.findUnique({ include: { category: { select: { enableFeedback: true } }, - feedback: { select: { id: true } }, + feedback: true, guild: true, }, where: { id: interaction.channel.id }, @@ -836,7 +870,10 @@ module.exports = class TicketManager { const getMessage = this.client.i18n.getLocale(locale); return await interaction.editReply({ embeds: [ - new ExtendedEmbedBuilder() + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) .setColor(errorColour) .setTitle(getMessage('misc.not_ticket.title')) .setDescription(getMessage('misc.not_ticket.description')), @@ -846,7 +883,7 @@ module.exports = class TicketManager { const getMessage = this.client.i18n.getLocale(ticket.guild.locale); const staff = await isStaff(interaction.guild, interaction.user.id); - const reason = interaction.options?.getString('reason', false) || null; // ?. because it could be a button interaction) + const reason = interaction.options?.getString('reason', false) || null; // ?. because it could be a button interaction if (ticket.createdById !== interaction.user.id && !staff) { return await interaction.editReply({ @@ -859,42 +896,19 @@ module.exports = class TicketManager { }); } - if (ticket.createdById === interaction.user.id && ticket.category.enableFeedback && !ticket.feedback) { - return await interaction.showModal( - new ModalBuilder() - .setCustomId(JSON.stringify({ - action: 'feedback', - reason, - })) - .setTitle(getMessage('modals.feedback.title')) - .setComponents( - new ActionRowBuilder() - .setComponents( - new TextInputBuilder() - .setCustomId('rating') - .setLabel(getMessage('modals.feedback.rating.label')) - .setStyle(TextInputStyle.Short) - .setMaxLength(3) - .setMinLength(1) - .setPlaceholder(getMessage('modals.feedback.rating.placeholder')) - .setRequired(false), - ), - new ActionRowBuilder() - .setComponents( - new TextInputBuilder() - .setCustomId('comment') - .setLabel(getMessage('modals.feedback.comment.label')) - .setStyle(TextInputStyle.Paragraph) - .setMaxLength(1000) - .setMinLength(4) - .setPlaceholder(getMessage('modals.feedback.comment.placeholder')) - .setRequired(false), - ), - ), - ); - + if ( + ticket.createdById === interaction.user.id && + ticket.category.enableFeedback && + !ticket.feedback + ) { + return await interaction.showModal(this.buildFeedbackModal(ticket.guild.locale, { + next: 'requestClose', + reason, // known issue: a reason longer than a few words will cause an error due to 100 character ID limit + })); } + // not showing feedback, so send the close request + // defer asap await interaction.deferReply(); @@ -903,7 +917,7 @@ module.exports = class TicketManager { try { await interaction.guild.members.fetch(ticket.createdById); } catch { - return this.close(ticket.id, true, reason); + return this.finallyClose(ticket.id, { reason }); } await this.requestClose(interaction, reason); @@ -911,6 +925,7 @@ module.exports = class TicketManager { /** * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction|import("discord.js").ModalSubmitInteraction} interaction + * @param {string} reason */ async requestClose(interaction, reason) { // interaction could be command, button. or modal @@ -924,14 +939,17 @@ module.exports = class TicketManager { action: 'close', expect: staff ? 'user' : 'staff', }; - const embed = new ExtendedEmbedBuilder() + const embed = new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) .setColor(ticket.guild.primaryColour) .setTitle(getMessage(`ticket.close.${staff ? 'staff' : 'user'}_request.title`, { requestedBy: interaction.member.displayName })); if (staff) { embed.setDescription( getMessage('ticket.close.staff_request.description', { requestedBy: interaction.user.toString() }) + - (ticket.guild.archive ? getMessage('ticket.close.staff_request.archived') : ''), + (ticket.guild.archive ? getMessage('ticket.close.staff_request.archived') : ''), ); } @@ -965,6 +983,7 @@ module.exports = class TicketManager { closeAt: ticket.guild.autoClose ? Date.now() + ticket.guild.autoClose : null, closedBy: interaction.user.id, // null if set as stale due to inactivity message: sent, + messages: 0, reason, staleSince: Date.now(), }); @@ -977,13 +996,19 @@ module.exports = class TicketManager { } } + /** + * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction|import("discord.js").ModalSubmitInteraction} interaction + */ + async acceptClose(interaction) {} + /** * close a ticket * @param {string} ticketId - * @param {boolean} skip - * @param {string} reason */ - async close(ticketId, skip, reason) { + async finallyClose(ticketId, { + closedBy, + reason, + }) { // TODO: update cache/cat count // TODO: update cache/member count // TODO: set messageCount on ticket diff --git a/src/listeners/client/channelDelete.js b/src/listeners/client/channelDelete.js index 58e5dc3..ff09f4e 100644 --- a/src/listeners/client/channelDelete.js +++ b/src/listeners/client/channelDelete.js @@ -19,7 +19,7 @@ module.exports = class extends Listener { }); if (!ticket) return; - await client.tickets.close(ticket.id, true, 'channel deleted'); + await client.tickets.finallyClose(ticket.id, { reason: 'channel deleted' }); this.client.log.info(`Closed ticket ${ticket.id} because the channel was deleted`); } }; diff --git a/src/listeners/client/guildMemberRemove.js b/src/listeners/client/guildMemberRemove.js index 40554b0..eb14514 100644 --- a/src/listeners/client/guildMemberRemove.js +++ b/src/listeners/client/guildMemberRemove.js @@ -26,7 +26,7 @@ module.exports = class extends Listener { }); for (const ticket of tickets) { - await client.tickets.close(ticket.id, true, 'user left server'); + await client.tickets.finallyClose(ticket.id, { reason: 'user left server' }); } } }; diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index a8eb04c..c55ced3 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -218,8 +218,14 @@ module.exports = class extends Listener { // if the ticket was set as stale, unset it if (client.tickets.$stale.has(ticket.id)) { - await message.channel.messages.delete(client.tickets.$stale.get(ticket.id).message.id); - client.tickets.$stale.delete(ticket.id); + const $ticket = client.tickets.$stale.get(ticket.id); + $ticket.messages++; + if ($ticket.messages >= 5) { + await message.channel.messages.delete($ticket.message.id); + client.tickets.$stale.delete(ticket.id); + } else { + client.tickets.$stale.set(ticket.id, $ticket); + } } } diff --git a/src/modals/feedback.js b/src/modals/feedback.js index cb68190..dcc355a 100644 --- a/src/modals/feedback.js +++ b/src/modals/feedback.js @@ -1,5 +1,5 @@ const { Modal } = require('@eartharoid/dbf'); - +const ExtendedEmbedBuilder = require('../lib/embed'); module.exports = class FeedbackModal extends Modal { constructor(client, options) { super(client, { @@ -8,24 +8,52 @@ module.exports = class FeedbackModal extends Modal { }); } + /** + * @param {*} id + * @param {import("discord.js").ModalSubmitInteraction} interaction + */ async run(id, interaction) { /** @type {import("client")} */ const client = this.client; await interaction.deferReply(); - await client.prisma.ticket.update({ + + const comment = interaction.fields.getTextInputValue('comment'); + const rating = parseInt(interaction.fields.getTextInputValue('rating')) || null; + + const ticket = await client.prisma.ticket.update({ data: { feedback: { create: { - comment: interaction.fields.getTextInputValue('comment'), + comment, guild: { connect: { id: interaction.guild.id } }, - rating: parseInt(interaction.fields.getTextInputValue('rating')) || null, + rating, user: { connect: { id: interaction.user.id } }, }, }, }, + include: { guild: true }, where: { id: interaction.channel.id }, }); - await client.tickets.requestClose(interaction, id.reason); + + if (id.next === 'requestClose') await client.tickets.requestClose(interaction, id.reason); + else if (id.next === 'acceptClose') await client.tickets.acceptClose(interaction); + + const getMessage = client.i18n.getLocale(ticket.guild.locale); + + // `followUp` must go after `reply`/`editReply` (the above) + if (comment?.length > 0 && rating !== null) { + await interaction.followUp({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.primaryColour) + .setDescription(getMessage('ticket.feedback')), + ], + ephemeral: true, + }); + } } }; \ No newline at end of file From a60c998605010b934e0bc435086b4414d1c3a4bb Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 30 Jan 2023 12:49:15 +0000 Subject: [PATCH 211/409] fix: message logging --- src/lib/logging.js | 13 ++++++----- src/listeners/client/messageDelete.js | 32 ++++++++++++++++++++++++++- src/listeners/client/messageUpdate.js | 12 ++++++++-- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/lib/logging.js b/src/lib/logging.js index 4bb5b2b..1b4829c 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -191,17 +191,18 @@ async function logTicketEvent(client, { * @param {import("@prisma/client").Ticket & {guild: import("@prisma/client").Guild}} details.ticket */ async function logMessageEvent(client, { - action, target, ticket, diff, + action, executor, target, ticket, diff, }) { if (!ticket) return; - client.log.info.tickets(`${target.member.user.tag} ${client.i18n.getMessage('en-GB', `log.message.verb.${action}`)} message ${target.id}`); + if (executor === undefined) executor = target.member; + client.log.info.tickets(`${executor?.user.tag || 'Unknown'} ${client.i18n.getMessage('en-GB', `log.message.verb.${action}`)} message ${target.id}`); if (!ticket.guild.logChannel) return; const colour = action === 'update' ? 'Purple' : action === 'delete' ? 'DarkPurple' : 'Default'; const getMessage = client.i18n.getLocale(ticket.guild.locale); const i18nOptions = { - user: `<@${target.member.user.id}>`, + user: `<@${executor?.user.id}>`, verb: getMessage(`log.message.verb.${action}`), }; const channel = client.channels.cache.get(ticket.guild.logChannel); @@ -210,15 +211,15 @@ async function logMessageEvent(client, { new EmbedBuilder() .setColor(colour) .setAuthor({ - iconURL: target.member.displayAvatarURL(), - name: target.member.displayName, + iconURL: target.member?.displayAvatarURL() || 'https://discord.com/assets/1f0bfc0865d324c2587920a7d80c609b.png', + name: target.member?.displayName || 'Unknown', }) .setTitle(getMessage('log.message.title', i18nOptions)) .setDescription(getMessage('log.message.description', i18nOptions)) .addFields([ { name: getMessage('log.message.message'), - value: `[${target.id}](${target.url})`, + value: `[\`${target.id}\`](${target.url})`, }, ]), ]; diff --git a/src/listeners/client/messageDelete.js b/src/listeners/client/messageDelete.js index 96f779e..68c7953 100644 --- a/src/listeners/client/messageDelete.js +++ b/src/listeners/client/messageDelete.js @@ -1,5 +1,8 @@ const { Listener } = require('@eartharoid/dbf'); +const { AuditLogEvent } = require('discord.js'); const { logMessageEvent } = require('../../lib/logging'); +const Cryptr = require('cryptr'); +const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); module.exports = class extends Listener { constructor(client, options) { @@ -14,16 +17,26 @@ module.exports = class extends Listener { /** @type {import("client")} */ const client = this.client; + if (!message.guild) return; + const ticket = await client.prisma.ticket.findUnique({ include: { guild: true }, where: { id: message.channel.id }, }); if (!ticket) return; + let content = message.cleanContent; + + const logEvent = (await message.guild.fetchAuditLogs({ + limit: 1, + type: AuditLogEvent.MessageDelete, + })).entries.first(); + if (ticket.guild.archive) { try { const archived = await client.prisma.archivedMessage.findUnique({ where: { id: message.id } }); if (archived) { + if (!content) content = JSON.parse(decrypt(archived.content)).content; // won't be cleaned await client.prisma.archivedMessage.update({ data: { deleted: true }, where: { id: message.id }, @@ -35,12 +48,29 @@ module.exports = class extends Listener { } } + let { + executor, + target, + } = logEvent ?? {}; + + executor ||= undefined; + if (target?.id !== message.author?.id) executor = undefined; + + if (executor) { + try { + executor = await message.guild.members.fetch(executor.id); + } catch (error) { + client.log.error(error); + } + } + await logMessageEvent(this.client, { action: 'delete', diff: { - original: { content: message.cleanContent }, + original: { content }, updated: { content: '' }, }, + executor, target: message, ticket, }); diff --git a/src/listeners/client/messageUpdate.js b/src/listeners/client/messageUpdate.js index 1ff4cd9..db3b78d 100644 --- a/src/listeners/client/messageUpdate.js +++ b/src/listeners/client/messageUpdate.js @@ -20,9 +20,17 @@ module.exports = class extends Listener { /** @type {import("client")} */ const client = this.client; - if (newMessage.partial) newMessage.fetch().then(m => (newMessage = m)).catch(client.log.error); + if (newMessage.partial) { + try { + newMessage = await newMessage.fetch(); + } catch (error) { + client.log.error(error); + } + } + + if (!newMessage.guild) return; if (newMessage.flags.has(MessageFlagsBitField.Flags.Ephemeral)) return; - if (!newMessage.edited) return; + if (!newMessage.editedAt) return; const ticket = await client.prisma.ticket.findUnique({ include: { guild: true }, From d1c3620fcdcc89a408cae12a5b0dbb31909a75d4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 30 Jan 2023 15:56:41 +0000 Subject: [PATCH 212/409] fix: ticket closing --- .gitignore | 3 +- package.json | 4 +- src/buttons/close.js | 67 +++--- src/client.js | 7 +- src/commands/slash/move.js | 4 +- src/i18n/en-GB.yml | 5 + src/lib/tickets/archiver.js | 4 +- src/lib/tickets/manager.js | 190 ++++++++++++------ src/listeners/client/messageCreate.js | 2 +- src/listeners/client/messageUpdate.js | 2 + src/listeners/client/ready.js | 8 + src/modals/feedback.js | 9 +- .../[guild]/categories/[category]/index.js | 2 + .../admin/guilds/[guild]/categories/index.js | 2 + src/schemas/settings.js | 4 +- 15 files changed, 213 insertions(+), 100 deletions(-) diff --git a/.gitignore b/.gitignore index db3f820..bf2418f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,7 @@ prisma/ # files *.env* -*.db -*.db-journal +*.db* *.log *-lock.* user/config.yml diff --git a/package.json b/package.json index 7c57e2e..da125ba 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@fastify/http-proxy": "^8.4.0", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", - "@prisma/client": "^4.8.1", + "@prisma/client": "^4.9.0", "cryptr": "^6.1.0", "discord.js": "^14.7.1", "dotenv": "^16.0.3", @@ -60,7 +60,7 @@ "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", "pad": "^3.2.0", - "prisma": "^4.8.1", + "prisma": "^4.9.0", "semver": "^7.3.8", "terminal-link": "^2.1.1", "yaml": "^1.10.2" diff --git a/src/buttons/close.js b/src/buttons/close.js index 7893884..ff1a61e 100644 --- a/src/buttons/close.js +++ b/src/buttons/close.js @@ -18,25 +18,32 @@ module.exports = class CloseButton extends Button { /** @type {import("client")} */ const client = this.client; - // the close button on th opening message, the same as using /close if (id.accepted === undefined) { + // the close button on the opening message, the same as using /close await client.tickets.beforeRequestClose(interaction); } else { - await interaction.deferReply(); - const ticket = await client.prisma.ticket.findUnique({ - include: { - category: true, - guild: true, - }, - where: { id: interaction.channel.id }, - }); + const ticket = await client.tickets.getTicket(interaction.channel.id); const getMessage = client.i18n.getLocale(ticket.guild.locale); const staff = await isStaff(interaction.guild, interaction.user.id); if (id.expect === 'staff' && !staff) { - return; // TODO: please wait for staff to close the ticket - } else if (id.expect === 'user' && staff) { - return; // TODO: please wait for the user to respond + return await interaction.reply({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(ticket.guild.errorColour) + .setDescription(getMessage('ticket.close.wait_for_staff')), + ], + ephemeral: true, + }); + } else if (id.expect === 'user' && interaction.user.id !== ticket.createdById) { + return await interaction.reply({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(ticket.guild.errorColour) + .setDescription(getMessage('ticket.close.wait_for_user')), + ], + ephemeral: true, + }); } else { if (id.accepted) { if ( @@ -46,25 +53,31 @@ module.exports = class CloseButton extends Button { ) { return await interaction.showModal(client.tickets.buildFeedbackModal(ticket.guild.locale, { next: 'acceptClose' })); } else { + await interaction.deferReply(); await client.tickets.acceptClose(interaction); } } else { + // TODO: reply if (client.tickets.$stale.has(ticket.id)) { - await interaction.channel.messages.edit( - client.tickets.$stale.get(ticket.id).message.id, - { - components: [], - embeds: [ - new ExtendedEmbedBuilder({ - iconURL: interaction.guild.iconURL(), - text: ticket.guild.footer, - }) - .setColor(ticket.guild.errorColour) - .setDescription(getMessage('ticket.close.rejected', { user: interaction.user.toString() })), - ], - }, - ); - client.tickets.$stale.delete(ticket.id); + try { + await interaction.channel.messages.edit( + client.tickets.$stale.get(ticket.id).message.id, + { + components: [], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setDescription(getMessage('ticket.close.rejected', { user: interaction.user.toString() })) + .setFooter({ text: null }), + ], + }, + ); + } finally { // this should run regardless of whatever happens above + client.tickets.$stale.delete(ticket.id); + } } } } diff --git a/src/client.js b/src/client.js index b34f4ae..82122fb 100644 --- a/src/client.js +++ b/src/client.js @@ -52,7 +52,12 @@ module.exports = class Client extends FrameworkClient { async login(token) { /** @type {PrismaClient} */ this.prisma = new PrismaClient(); - if (process.env.DB_PROVIDER === 'sqlite') this.prisma.$use(sqliteMiddleware); + if (process.env.DB_PROVIDER === 'sqlite') { + this.prisma.$use(sqliteMiddleware); + // make sqlite faster (https://www.sqlite.org/wal.html), + // and the missing parentheses are not a mistake, `$queryRaw` is a tagged template literal + this.log.debug(await this.prisma.$queryRaw`PRAGMA journal_mode=WAL;`); + } this.keyv = new Keyv(); return super.login(token); } diff --git a/src/commands/slash/move.js b/src/commands/slash/move.js index 335699c..b5437a2 100644 --- a/src/commands/slash/move.js +++ b/src/commands/slash/move.js @@ -91,10 +91,10 @@ module.exports = class MoveSlashCommand extends SlashCommand { $oldCategory.total--; $oldCategory[ticket.createdById]--; - if (!$newCategory.total) $newCategory.total = 0; + $newCategory.total ||= 0; $newCategory.total++; - if (!$newCategory[ticket.createdById]) $newCategory[ticket.createdById] = 0; + $newCategory[ticket.createdById] ||= 0; $newCategory[ticket.createdById]++; await interaction.channel.setParent(discordCategory, { diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 20783f6..1e478f5 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -349,6 +349,9 @@ ticket: no_value: "*No response*" claimed: 🙌 {user} has claimed this ticket. close: + closed: + description: This channel will be deleted in a few seconds... + title: ✅ Ticket closed forbidden: description: You don't have permission to close this ticket. title: ❌ Error @@ -363,6 +366,8 @@ ticket: title: ❓ Can this ticket be closed? user_request: title: ❓ {requestedBy} wants to close this ticket + wait_for_staff: ✋ Please wait for staff to close this ticket. + wait_for_user: ✋ Please wait for the user to respond. created: description: "Your ticket channel has been created: {channel}." title: ✅ Ticket created diff --git a/src/lib/tickets/archiver.js b/src/lib/tickets/archiver.js index f57df97..04afac4 100644 --- a/src/lib/tickets/archiver.js +++ b/src/lib/tickets/archiver.js @@ -2,7 +2,7 @@ const Cryptr = require('cryptr'); const { encrypt } = new Cryptr(process.env.ENCRYPTION_KEY); /** - * Returns highest (roles.highest) hoisted role , or everyone + * Returns highest (roles.highest) hoisted role, or everyone * @param {import("discord.js").GuildMember} member * @returns {import("discord.js").Role} */ @@ -121,7 +121,7 @@ module.exports = class TicketArchiver { id: message.id, }; - await this.client.prisma.ticket.update({ + return await this.client.prisma.ticket.update({ data: { archivedChannels: { upsert: channels.map(channel => { diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index a716368..44e97f4 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -26,6 +26,14 @@ const { encrypt } = new Cryptr(process.env.ENCRYPTION_KEY); * {guild: import('@prisma/client').Guild} & * {questions: import('@prisma/client').Question[]}} CategoryGuildQuestions */ + +/** + * @typedef {import('@prisma/client').Ticket & + * {category: import('@prisma/client').Category} & + * {feedback: import('@prisma/client').Feedback} & + * {guild: import('@prisma/client').Guild}} TicketCategoryFeedbackGuild + */ + module.exports = class TicketManager { constructor(client) { /** @type {import("client")} */ @@ -58,10 +66,32 @@ module.exports = class TicketManager { return category; } - // TODO: update when a ticket is closed or moved + /** + * Retrieve cached ticket data for the closing sequence + * @param {string} ticketId the ticket ID + * @param {boolean} force bypass & update the cache? + * @returns {Promise} + */ + async getTicket(ticketId, force) { + const cacheKey = `cache/ticket+category+feedback+guild:${ticketId}`; + /** @type {TicketCategoryFeedbackGuild} */ + let ticket = await this.client.keyv.get(cacheKey); + if (!ticket || force) { + ticket = await this.client.prisma.ticket.findUnique({ + include: { + category: true, + feedback: true, + guild: true, + }, + where: { id: ticketId }, + }); + await this.client.keyv.set(cacheKey, ticket, ms('3m')); + } + return ticket; + } + async getTotalCount(categoryId) { - const category = this.$count.categories[categoryId]; - if (!category) this.$count.categories[categoryId] = {}; + this.$count.categories[categoryId] ||= {}; let count = this.$count.categories[categoryId].total; if (!count) { count = await this.client.prisma.ticket.count({ @@ -75,10 +105,8 @@ module.exports = class TicketManager { return count; } - // TODO: update when a ticket is closed or moved async getMemberCount(categoryId, memberId) { - const category = this.$count.categories[categoryId]; - if (!category) this.$count.categories[categoryId] = {}; + this.$count.categories[categoryId] ||= {}; let count = this.$count.categories[categoryId][memberId]; if (!count) { count = await this.client.prisma.ticket.count({ @@ -314,9 +342,10 @@ module.exports = class TicketManager { async postQuestions({ action, categoryId, interaction, topic, referencesMessage, referencesTicketId, }) { - await interaction.deferReply({ ephemeral: true }); - - const category = await this.getCategory(categoryId); + const [, category] = await Promise.all([ + interaction.deferReply({ ephemeral: true }), + this.getCategory(categoryId), + ]); let answers; if (interaction.isModalSubmit()) { @@ -604,11 +633,12 @@ module.exports = class TicketManager { } if (category.guild.archive && message) { - let row = await this.client.prisma.archivedMessage.findUnique({ where: { id: message.id } }); - if (!row) row = await this.archiver.saveMessage(ticket.id, message, true); - if (row) { + if ( + await this.client.prisma.archivedMessage.findUnique({ where: { id: message.id } })|| + await this.archiver.saveMessage(ticket.id, message, true) + ) { await this.client.prisma.ticket.update({ - data: { referencesMessageId: row.id }, + data: { referencesMessageId: message.id }, where: { id: ticket.id }, }); } @@ -657,21 +687,21 @@ module.exports = class TicketManager { }); const getMessage = this.client.i18n.getLocale(ticket.guild.locale); - await interaction.channel.permissionOverwrites.edit(interaction.user, { 'ViewChannel': true }, `Ticket claimed by ${interaction.user.tag}`); - - for (const role of ticket.category.staffRoles) await interaction.channel.permissionOverwrites.edit(role, { 'ViewChannel': false }, `Ticket claimed by ${interaction.user.tag}`); - - await this.client.prisma.ticket.update({ - data: { - claimedBy: { - connectOrCreate: { - create: { id: interaction.user.id }, - where: { id: interaction.user.id }, + await Promise.all([ + interaction.channel.permissionOverwrites.edit(interaction.user, { 'ViewChannel': true }, `Ticket claimed by ${interaction.user.tag}`), + ...ticket.category.staffRoles.map(role => interaction.channel.permissionOverwrites.edit(role, { 'ViewChannel': false }, `Ticket claimed by ${interaction.user.tag}`)), + this.client.prisma.ticket.update({ + data: { + claimedBy: { + connectOrCreate: { + create: { id: interaction.user.id }, + where: { id: interaction.user.id }, + }, }, }, - }, - where: { id: interaction.channel.id }, - }); + where: { id: interaction.channel.id }, + }), + ]); const openingMessage = await interaction.channel.messages.fetch(ticket.openingMessageId); @@ -735,6 +765,7 @@ module.exports = class TicketManager { async release(interaction) { const ticket = await this.client.prisma.ticket.findUnique({ include: { + _count: { select: { questionAnswers: true } }, category: true, guild: true, }, @@ -742,14 +773,14 @@ module.exports = class TicketManager { }); const getMessage = this.client.i18n.getLocale(ticket.guild.locale); - await interaction.channel.permissionOverwrites.delete(interaction.user, `Ticket released by ${interaction.user.tag}`); - - for (const role of ticket.category.staffRoles) await interaction.channel.permissionOverwrites.edit(role, { 'ViewChannel': true }, `Ticket released by ${interaction.user.tag}`); - - await this.client.prisma.ticket.update({ - data: { claimedBy: { disconnect: true } }, - where: { id: interaction.channel.id }, - }); + await Promise.all([ + interaction.channel.permissionOverwrites.delete(interaction.user, `Ticket released by ${interaction.user.tag}`), + ...ticket.category.staffRoles.map(role => interaction.channel.permissionOverwrites.edit(role, { 'ViewChannel': true }, `Ticket released by ${interaction.user.tag}`)), + this.client.prisma.ticket.update({ + data: { claimedBy: { disconnect: true } }, + where: { id: interaction.channel.id }, + }), + ]); const openingMessage = await interaction.channel.messages.fetch(ticket.openingMessageId); @@ -846,19 +877,12 @@ module.exports = class TicketManager { * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction} interaction */ async beforeRequestClose(interaction) { - const ticket = await this.client.prisma.ticket.findUnique({ - include: { - category: { select: { enableFeedback: true } }, - feedback: true, - guild: true, - }, - where: { id: interaction.channel.id }, - }); - + const ticket = await this.getTicket(interaction.channel.id); if (!ticket) { await interaction.deferReply({ ephemeral: true }); const { errorColour, + footer, locale, } = await this.client.prisma.guild.findUnique({ select: { @@ -872,7 +896,7 @@ module.exports = class TicketManager { embeds: [ new ExtendedEmbedBuilder({ iconURL: interaction.guild.iconURL(), - text: ticket.guild.footer, + text: footer, }) .setColor(errorColour) .setTitle(getMessage('misc.not_ticket.title')) @@ -903,7 +927,7 @@ module.exports = class TicketManager { ) { return await interaction.showModal(this.buildFeedbackModal(ticket.guild.locale, { next: 'requestClose', - reason, // known issue: a reason longer than a few words will cause an error due to 100 character ID limit + reason, // known issue: a reason longer than a few words will cause an error due to 100 character custom_id limit })); } @@ -920,7 +944,7 @@ module.exports = class TicketManager { return this.finallyClose(ticket.id, { reason }); } - await this.requestClose(interaction, reason); + this.requestClose(interaction, reason); } /** @@ -929,12 +953,9 @@ module.exports = class TicketManager { */ async requestClose(interaction, reason) { // interaction could be command, button. or modal - const ticket = await this.client.prisma.ticket.findUnique({ - include: { guild: true }, - where: { id: interaction.channel.id }, - }); + const ticket = await this.getTicket(interaction.channel.id); const getMessage = this.client.i18n.getLocale(ticket.guild.locale); - const staff = await isStaff(interaction.guild, interaction.user.id); + const staff = interaction.user.id !== ticket.createdById && await isStaff(interaction.guild, interaction.user.id); const closeButtonId = { action: 'close', expect: staff ? 'user' : 'staff', @@ -999,20 +1020,71 @@ module.exports = class TicketManager { /** * @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction|import("discord.js").ModalSubmitInteraction} interaction */ - async acceptClose(interaction) {} + async acceptClose(interaction) { + const ticket = await this.getTicket(interaction.channel.id); + const $ticket = this.$stale.get(interaction.channel.id); + const getMessage = this.client.i18n.getLocale(ticket.guild.locale); + await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.successColour) + .setTitle(getMessage('ticket.close.closed.title')) + .setDescription(getMessage('ticket.close.closed.description')), + ], + }); + await new Promise(resolve => setTimeout(resolve, 5000)); + await this.finallyClose(interaction.channel.id, $ticket); + } /** * close a ticket * @param {string} ticketId */ async finallyClose(ticketId, { - closedBy, - reason, + closedBy = null, + reason = null, }) { - // TODO: update cache/cat count - // TODO: update cache/member count - // TODO: set messageCount on ticket - // TODO: pinnedMessages, closedBy, closedAt - // delete + const ticket = await this.getTicket(ticketId); + this.$count.categories[ticket.categoryId].total -= 1; + this.$count.categories[ticket.categoryId][ticket.createdById] -= 1; + + const { _count: { archivedMessages } } = await this.client.prisma.ticket.findUnique({ + select: { _count: { select: { archivedMessages: true } } }, + where: { id: ticket.id }, + }); + + /** @type {import("@prisma/client").Ticket} */ + const data = { + closedAt: new Date(), + closedBy: closedBy && { + connectOrCreate: { + create: { id: closedBy }, + where: { id: closedBy }, + }, + } || undefined, // Prisma wants undefined not null because it is a relation + closedReason: reason && encrypt(reason), + messageCount: archivedMessages, + }; + + /** @type {import("discord.js").TextChannel} */ + const channel = this.client.channels.cache.get(ticketId); + if (channel) { + const pinned = await channel.messages.fetchPinned(); + data.pinnedMessageIds = pinned.keys(); + } + + await this.client.prisma.ticket.update({ + data, + where: { id: ticket.id }, + }); + + + if (channel?.deletable) { + const member = closedBy ? channel.guild.members.cache.get(closedBy) : null; + await channel.delete('Ticket closed' + (member ? ` by ${member.displayName}` : '') + reason ? `: ${reason}` : ''); + } } }; \ No newline at end of file diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index c55ced3..46386e4 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -180,7 +180,7 @@ module.exports = class extends Listener { }); } } else { - const settings = await client.prisma.guild.findUnique({ where: { id:message.guild.id } }); + const settings = await client.prisma.guild.findUnique({ where: { id: message.guild.id } }); let ticket = await client.prisma.ticket.findUnique({ where: { id: message.channel.id } }); if (ticket) { diff --git a/src/listeners/client/messageUpdate.js b/src/listeners/client/messageUpdate.js index db3b78d..98ee177 100644 --- a/src/listeners/client/messageUpdate.js +++ b/src/listeners/client/messageUpdate.js @@ -47,6 +47,8 @@ module.exports = class extends Listener { } } + if (newMessage.author.id === client.user.id) return; + await logMessageEvent(this.client, { action: 'update', diff: { diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index bfb3cd4..f19baba 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -111,6 +111,14 @@ module.exports = class extends Listener { setInterval(() => { // TODO: check lastMessageAt and set stale + // this.$stale.set(ticket.id, { + // closeAt: ticket.guild.autoClose ? Date.now() + ticket.guild.autoClose : null, + // closedBy: null, // null if set as stale due to inactivity + // message: sent, + // messages: 0, + // reason: 'inactivity', + // staleSince: Date.now(), + // }); for (const [ticketId, $] of client.tickets.$stale) { // ⌛ diff --git a/src/modals/feedback.js b/src/modals/feedback.js index dcc355a..05afa42 100644 --- a/src/modals/feedback.js +++ b/src/modals/feedback.js @@ -1,5 +1,8 @@ const { Modal } = require('@eartharoid/dbf'); const ExtendedEmbedBuilder = require('../lib/embed'); +const Cryptr = require('cryptr'); +const { encrypt } = new Cryptr(process.env.ENCRYPTION_KEY); + module.exports = class FeedbackModal extends Modal { constructor(client, options) { super(client, { @@ -19,13 +22,14 @@ module.exports = class FeedbackModal extends Modal { await interaction.deferReply(); const comment = interaction.fields.getTextInputValue('comment'); - const rating = parseInt(interaction.fields.getTextInputValue('rating')) || null; + let rating = parseInt(interaction.fields.getTextInputValue('rating')) || null; // any integer, or null if NaN + rating = Math.min(Math.max(rating, 1), 5); // clamp between 1 and 5 (0 and null become 1, 6 becomes 5) const ticket = await client.prisma.ticket.update({ data: { feedback: { create: { - comment, + comment: comment?.length > 0 ? encrypt(comment) : null, guild: { connect: { id: interaction.guild.id } }, rating, user: { connect: { id: interaction.user.id } }, @@ -36,6 +40,7 @@ module.exports = class FeedbackModal extends Modal { where: { id: interaction.channel.id }, }); + if (id.next === 'requestClose') await client.tickets.requestClose(interaction, id.reason); else if (id.next === 'acceptClose') await client.tickets.acceptClose(interaction); diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js index 3493c1e..2aeae9b 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js @@ -140,6 +140,8 @@ module.exports.patch = fastify => ({ where: { id: categoryId }, }); + // update caches + await client.tickets.getCategory(categoryId, true); await updateStaffRoles(guild); logAdminEvent(client, { diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index f92072a..e7d1c38 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -94,6 +94,8 @@ module.exports.post = fastify => ({ }, }); + // update caches + await client.tickets.getCategory(category.id, true); await updateStaffRoles(guild); logAdminEvent(client, { diff --git a/src/schemas/settings.js b/src/schemas/settings.js index e4af137..e1081b7 100644 --- a/src/schemas/settings.js +++ b/src/schemas/settings.js @@ -1,6 +1,6 @@ module.exports = joi.object({ archive: joi.boolean().optional(), - autoClose: joi.number().min(3600000).optional(), + autoClose: joi.number().min(3_600_000).optional(), autoTag: [joi.array(), joi.string().valid('ticket', '!ticket', 'all')].optional(), blocklist: joi.array().optional(), createdAt: joi.string().optional(), @@ -9,7 +9,7 @@ module.exports = joi.object({ id: joi.string().optional(), logChannel: joi.string().optional(), primaryColour: joi.string().optional(), - staleAfter: joi.number().min(60000).optional(), + staleAfter: joi.number().min(60_000).optional(), successColour: joi.string().optional(), workingHours: joi.array().length(8).items( joi.string(), From 5579362f262c07d879901d31f1a0ec78fa7c7acd Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 30 Jan 2023 16:58:36 +0000 Subject: [PATCH 213/409] feat: DM on close (fixes #338) A very basic message to notify the user of the ticket closure --- src/i18n/en-GB.yml | 3 +++ src/lib/tickets/manager.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 1e478f5..a6aeea8 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -230,6 +230,9 @@ commands: create: name: Create a ticket for user dm: + closed: + archived: Type `/transcript` in **{guild}** to view the archived messages. + title: Your ticket has been closed confirm_open: title: Do you want to open a ticket with the following topic? log: diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 44e97f4..0c58817 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1081,10 +1081,38 @@ module.exports = class TicketManager { where: { id: ticket.id }, }); - if (channel?.deletable) { const member = closedBy ? channel.guild.members.cache.get(closedBy) : null; await channel.delete('Ticket closed' + (member ? ` by ${member.displayName}` : '') + reason ? `: ${reason}` : ''); } + + if (closedBy) { + logTicketEvent(this.client, { + action: 'close', + target: { + id: ticket.id, + name: channel.toString(), + }, + userId: closedBy, + }); + } + + try { + const creator = await channel?.guild.members.fetch(ticket.createdById); + if (creator) { + const getMessage = this.client.i18n.getLocale(ticket.guild.locale); + const embed = new ExtendedEmbedBuilder({ + iconURL: channel.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.primaryColour) + .setTitle(getMessage('dm.closed.title')); + if (ticket.guild.archive) embed.setDescription(getMessage('dm.closed.archived', { guild: channel.guild.name })); + await creator.send({ embeds: [embed] }); + } + } catch (error) { + this.client.log.error(error); + } + } }; \ No newline at end of file From bb3179f28fe910ea46969613043e4b72df57633e Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 30 Jan 2023 17:10:02 +0000 Subject: [PATCH 214/409] minor improvements --- src/commands/slash/force-close.js | 1 + src/lib/logging.js | 5 +---- src/listeners/client/channelDelete.js | 3 ++- src/modals/feedback.js | 14 +++++++++----- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/commands/slash/force-close.js b/src/commands/slash/force-close.js index 1876f60..7fca14e 100644 --- a/src/commands/slash/force-close.js +++ b/src/commands/slash/force-close.js @@ -96,6 +96,7 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { }); } + // TODO: category const tickets = await client.prisma.ticket.findMany({ where: { lastMessageAt: { lte: new Date(Date.now() - time) }, diff --git a/src/lib/logging.js b/src/lib/logging.js index 1b4829c..61463d1 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -131,10 +131,7 @@ async function logAdminEvent(client, { async function logTicketEvent(client, { userId, action, target, diff, }) { - const ticket = await client.prisma.ticket.findUnique({ - include: { guild: true }, - where: { id: target.id }, - }); + const ticket = await client.tickets.getTicket(target.id); if (!ticket) return; /** @type {import("discord.js").Guild} */ const guild = client.guilds.cache.get(ticket.guild.id); diff --git a/src/listeners/client/channelDelete.js b/src/listeners/client/channelDelete.js index ff09f4e..b371997 100644 --- a/src/listeners/client/channelDelete.js +++ b/src/listeners/client/channelDelete.js @@ -17,7 +17,8 @@ module.exports = class extends Listener { include: { guild: true }, where: { id: channel.id }, }); - if (!ticket) return; + + if (!ticket?.open) return; await client.tickets.finallyClose(ticket.id, { reason: 'channel deleted' }); this.client.log.info(`Closed ticket ${ticket.id} because the channel was deleted`); diff --git a/src/modals/feedback.js b/src/modals/feedback.js index 05afa42..2c49243 100644 --- a/src/modals/feedback.js +++ b/src/modals/feedback.js @@ -25,14 +25,18 @@ module.exports = class FeedbackModal extends Modal { let rating = parseInt(interaction.fields.getTextInputValue('rating')) || null; // any integer, or null if NaN rating = Math.min(Math.max(rating, 1), 5); // clamp between 1 and 5 (0 and null become 1, 6 becomes 5) + const data = { + comment: comment?.length > 0 ? encrypt(comment) : null, + guild: { connect: { id: interaction.guild.id } }, + rating, + user: { connect: { id: interaction.user.id } }, + }; const ticket = await client.prisma.ticket.update({ data: { feedback: { - create: { - comment: comment?.length > 0 ? encrypt(comment) : null, - guild: { connect: { id: interaction.guild.id } }, - rating, - user: { connect: { id: interaction.user.id } }, + upsert: { + create: data, + update: data, }, }, }, From 457ede3ac75ebf0b02b788fdd2a5fa7a3bae1bda Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 30 Jan 2023 20:50:38 +0000 Subject: [PATCH 215/409] fix: don't allow removing the creator --- src/commands/slash/remove.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/slash/remove.js b/src/commands/slash/remove.js index 51ca9bf..a78ab0c 100644 --- a/src/commands/slash/remove.js +++ b/src/commands/slash/remove.js @@ -89,7 +89,7 @@ module.exports = class RemoveSlashCommand extends SlashCommand { const ticketChannel = await interaction.guild.channels.fetch(ticket.id); const member = interaction.options.getMember('member', true); - if (member.id === client.user.id) { + if (member.id === client.user.id || member.id === ticket.createdById) { return await interaction.editReply({ embeds: [ new ExtendedEmbedBuilder() From 63f5ea61f79233e24a2d328c5c4d66f10c853aef Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 30 Jan 2023 21:21:48 +0000 Subject: [PATCH 216/409] feat: database migrations --- .../20230130211315_4_0_0/migration.sql | 257 +++++++++++++++++ db/mysql/migrations/migration_lock.toml | 3 + db/mysql/schema.prisma | 5 +- .../20230130210021_4_0_0/migration.sql | 273 ++++++++++++++++++ db/postgresql/migrations/migration_lock.toml | 3 + .../20230130211842_4_0_0/migration.sql | 207 +++++++++++++ db/sqlite/migrations/migration_lock.toml | 3 + scripts/postinstall.js | 2 +- 8 files changed, 750 insertions(+), 3 deletions(-) create mode 100644 db/mysql/migrations/20230130211315_4_0_0/migration.sql create mode 100644 db/mysql/migrations/migration_lock.toml create mode 100644 db/postgresql/migrations/20230130210021_4_0_0/migration.sql create mode 100644 db/postgresql/migrations/migration_lock.toml create mode 100644 db/sqlite/migrations/20230130211842_4_0_0/migration.sql create mode 100644 db/sqlite/migrations/migration_lock.toml diff --git a/db/mysql/migrations/20230130211315_4_0_0/migration.sql b/db/mysql/migrations/20230130211315_4_0_0/migration.sql new file mode 100644 index 0000000..32b3b86 --- /dev/null +++ b/db/mysql/migrations/20230130211315_4_0_0/migration.sql @@ -0,0 +1,257 @@ +-- CreateTable +CREATE TABLE `archivedChannels` ( + `channelId` VARCHAR(19) NOT NULL, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `name` VARCHAR(191) NOT NULL, + `ticketId` VARCHAR(19) NOT NULL, + + UNIQUE INDEX `archivedChannels_ticketId_channelId_key`(`ticketId`, `channelId`), + PRIMARY KEY (`ticketId`, `channelId`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `archivedMessages` ( + `authorId` VARCHAR(19) NOT NULL, + `content` TEXT NOT NULL, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `deleted` BOOLEAN NOT NULL DEFAULT false, + `edited` BOOLEAN NOT NULL DEFAULT false, + `external` BOOLEAN NOT NULL DEFAULT false, + `id` VARCHAR(19) NOT NULL, + `ticketId` VARCHAR(19) NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `archivedRoles` ( + `colour` CHAR(6) NOT NULL DEFAULT '5865F2', + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `name` VARCHAR(191) NOT NULL, + `roleId` VARCHAR(19) NOT NULL, + `ticketId` VARCHAR(19) NOT NULL, + + UNIQUE INDEX `archivedRoles_ticketId_roleId_key`(`ticketId`, `roleId`), + PRIMARY KEY (`ticketId`, `roleId`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `archivedUsers` ( + `avatar` VARCHAR(191) NULL, + `bot` BOOLEAN NOT NULL DEFAULT false, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `discriminator` CHAR(4) NULL, + `displayName` TEXT NULL, + `roleId` VARCHAR(19) NULL, + `ticketId` VARCHAR(19) NOT NULL, + `userId` VARCHAR(19) NOT NULL, + `username` TEXT NULL, + + UNIQUE INDEX `archivedUsers_ticketId_userId_key`(`ticketId`, `userId`), + PRIMARY KEY (`ticketId`, `userId`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `categories` ( + `channelName` VARCHAR(191) NOT NULL, + `claiming` BOOLEAN NOT NULL DEFAULT false, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `cooldown` INTEGER NULL, + `customTopic` VARCHAR(191) NULL, + `description` VARCHAR(191) NOT NULL, + `discordCategory` VARCHAR(19) NOT NULL, + `emoji` VARCHAR(191) NOT NULL, + `enableFeedback` BOOLEAN NOT NULL DEFAULT false, + `guildId` VARCHAR(19) NOT NULL, + `id` INTEGER NOT NULL AUTO_INCREMENT, + `image` VARCHAR(191) NULL, + `memberLimit` INTEGER NOT NULL DEFAULT 1, + `name` VARCHAR(191) NOT NULL, + `openingMessage` TEXT NOT NULL, + `pingRoles` JSON NOT NULL, + `ratelimit` INTEGER NULL, + `requiredRoles` JSON NOT NULL, + `requireTopic` BOOLEAN NOT NULL DEFAULT false, + `staffRoles` JSON NOT NULL, + `totalLimit` INTEGER NOT NULL DEFAULT 50, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `feedback` ( + `comment` TEXT NULL, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `guildId` VARCHAR(19) NOT NULL, + `rating` INTEGER NOT NULL, + `ticketId` VARCHAR(19) NOT NULL, + `userId` VARCHAR(19) NULL, + + PRIMARY KEY (`ticketId`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `guilds` ( + `autoClose` INTEGER NOT NULL DEFAULT 43200000, + `autoTag` JSON NOT NULL, + `archive` BOOLEAN NOT NULL DEFAULT true, + `blocklist` JSON NOT NULL, + `claimButton` BOOLEAN NOT NULL DEFAULT false, + `closeButton` BOOLEAN NOT NULL DEFAULT false, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `errorColour` VARCHAR(191) NOT NULL DEFAULT 'Red', + `footer` VARCHAR(191) NULL DEFAULT 'Discord Tickets by eartharoid', + `id` VARCHAR(19) NOT NULL, + `locale` VARCHAR(191) NOT NULL DEFAULT 'en-GB', + `logChannel` VARCHAR(19) NULL, + `primaryColour` VARCHAR(191) NOT NULL DEFAULT '#009999', + `staleAfter` INTEGER NULL, + `successColour` VARCHAR(191) NOT NULL DEFAULT 'Green', + `workingHours` JSON NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `questions` ( + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `id` VARCHAR(191) NOT NULL, + `categoryId` INTEGER NOT NULL, + `label` VARCHAR(45) NOT NULL, + `maxLength` INTEGER NULL DEFAULT 4000, + `minLength` INTEGER NULL DEFAULT 0, + `options` JSON NOT NULL, + `order` INTEGER NOT NULL, + `placeholder` VARCHAR(100) NULL, + `required` BOOLEAN NOT NULL DEFAULT true, + `style` INTEGER NOT NULL DEFAULT 2, + `type` ENUM('MENU', 'TEXT') NOT NULL DEFAULT 'TEXT', + `value` TEXT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `questionAnswers` ( + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `id` INTEGER NOT NULL AUTO_INCREMENT, + `ticketId` VARCHAR(19) NOT NULL, + `questionId` VARCHAR(191) NOT NULL, + `userId` VARCHAR(19) NOT NULL, + `value` TEXT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `tags` ( + `content` TEXT NOT NULL, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `guildId` VARCHAR(19) NOT NULL, + `id` INTEGER NOT NULL AUTO_INCREMENT, + `name` VARCHAR(191) NOT NULL, + `regex` VARCHAR(191) NULL, + + UNIQUE INDEX `tags_guildId_name_key`(`guildId`, `name`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `tickets` ( + `categoryId` INTEGER NULL, + `claimedById` VARCHAR(19) NULL, + `closedAt` DATETIME(3) NULL, + `closedById` VARCHAR(19) NULL, + `closedReason` TEXT NULL, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `createdById` VARCHAR(19) NOT NULL, + `deleted` BOOLEAN NOT NULL DEFAULT false, + `firstResponseAt` DATETIME(3) NULL, + `guildId` VARCHAR(19) NOT NULL, + `id` VARCHAR(19) NOT NULL, + `lastMessageAt` DATETIME(3) NULL, + `messageCount` INTEGER NULL, + `number` INTEGER NOT NULL, + `open` BOOLEAN NOT NULL DEFAULT true, + `openingMessageId` VARCHAR(19) NOT NULL, + `pinnedMessageIds` JSON NOT NULL, + `priority` ENUM('LOW', 'MEDIUM', 'HIGH') NULL, + `referencesMessageId` VARCHAR(19) NULL, + `referencesTicketId` VARCHAR(19) NULL, + `topic` TEXT NULL, + + UNIQUE INDEX `tickets_guildId_number_key`(`guildId`, `number`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `users` ( + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `id` VARCHAR(19) NOT NULL, + `messageCount` INTEGER NOT NULL DEFAULT 0, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `archivedChannels` ADD CONSTRAINT `archivedChannels_ticketId_fkey` FOREIGN KEY (`ticketId`) REFERENCES `tickets`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `archivedMessages` ADD CONSTRAINT `archivedMessages_ticketId_authorId_fkey` FOREIGN KEY (`ticketId`, `authorId`) REFERENCES `archivedUsers`(`ticketId`, `userId`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `archivedMessages` ADD CONSTRAINT `archivedMessages_ticketId_fkey` FOREIGN KEY (`ticketId`) REFERENCES `tickets`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `archivedRoles` ADD CONSTRAINT `archivedRoles_ticketId_fkey` FOREIGN KEY (`ticketId`) REFERENCES `tickets`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `archivedUsers` ADD CONSTRAINT `archivedUsers_ticketId_roleId_fkey` FOREIGN KEY (`ticketId`, `roleId`) REFERENCES `archivedRoles`(`ticketId`, `roleId`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `archivedUsers` ADD CONSTRAINT `archivedUsers_ticketId_fkey` FOREIGN KEY (`ticketId`) REFERENCES `tickets`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `categories` ADD CONSTRAINT `categories_guildId_fkey` FOREIGN KEY (`guildId`) REFERENCES `guilds`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `feedback` ADD CONSTRAINT `feedback_guildId_fkey` FOREIGN KEY (`guildId`) REFERENCES `guilds`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `feedback` ADD CONSTRAINT `feedback_ticketId_fkey` FOREIGN KEY (`ticketId`) REFERENCES `tickets`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `feedback` ADD CONSTRAINT `feedback_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `questions` ADD CONSTRAINT `questions_categoryId_fkey` FOREIGN KEY (`categoryId`) REFERENCES `categories`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `questionAnswers` ADD CONSTRAINT `questionAnswers_ticketId_fkey` FOREIGN KEY (`ticketId`) REFERENCES `tickets`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `questionAnswers` ADD CONSTRAINT `questionAnswers_questionId_fkey` FOREIGN KEY (`questionId`) REFERENCES `questions`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `questionAnswers` ADD CONSTRAINT `questionAnswers_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `tags` ADD CONSTRAINT `tags_guildId_fkey` FOREIGN KEY (`guildId`) REFERENCES `guilds`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `tickets` ADD CONSTRAINT `tickets_categoryId_fkey` FOREIGN KEY (`categoryId`) REFERENCES `categories`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `tickets` ADD CONSTRAINT `tickets_claimedById_fkey` FOREIGN KEY (`claimedById`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `tickets` ADD CONSTRAINT `tickets_closedById_fkey` FOREIGN KEY (`closedById`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `tickets` ADD CONSTRAINT `tickets_createdById_fkey` FOREIGN KEY (`createdById`) REFERENCES `users`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `tickets` ADD CONSTRAINT `tickets_guildId_fkey` FOREIGN KEY (`guildId`) REFERENCES `guilds`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `tickets` ADD CONSTRAINT `tickets_referencesTicketId_fkey` FOREIGN KEY (`referencesTicketId`) REFERENCES `tickets`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/db/mysql/migrations/migration_lock.toml b/db/mysql/migrations/migration_lock.toml new file mode 100644 index 0000000..e5a788a --- /dev/null +++ b/db/mysql/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "mysql" \ No newline at end of file diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index e91ebac..aceb90d 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -3,8 +3,9 @@ generator client { } datasource db { - provider = "mysql" - url = env("DB_CONNECTION_URL") + provider = "mysql" + url = env("DB_CONNECTION_URL") + shadowDatabaseUrl = env("SHADOW_DATABASE_URL") } model ArchivedChannel { diff --git a/db/postgresql/migrations/20230130210021_4_0_0/migration.sql b/db/postgresql/migrations/20230130210021_4_0_0/migration.sql new file mode 100644 index 0000000..7519ba4 --- /dev/null +++ b/db/postgresql/migrations/20230130210021_4_0_0/migration.sql @@ -0,0 +1,273 @@ +-- CreateEnum +CREATE TYPE "TicketPriority" AS ENUM ('LOW', 'MEDIUM', 'HIGH'); + +-- CreateEnum +CREATE TYPE "QuestionType" AS ENUM ('MENU', 'TEXT'); + +-- CreateTable +CREATE TABLE "archivedChannels" ( + "channelId" VARCHAR(19) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "name" TEXT NOT NULL, + "ticketId" VARCHAR(19) NOT NULL, + + CONSTRAINT "archivedChannels_pkey" PRIMARY KEY ("ticketId","channelId") +); + +-- CreateTable +CREATE TABLE "archivedMessages" ( + "authorId" VARCHAR(19) NOT NULL, + "content" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" BOOLEAN NOT NULL DEFAULT false, + "edited" BOOLEAN NOT NULL DEFAULT false, + "external" BOOLEAN NOT NULL DEFAULT false, + "id" VARCHAR(19) NOT NULL, + "ticketId" VARCHAR(19) NOT NULL, + + CONSTRAINT "archivedMessages_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "archivedRoles" ( + "colour" CHAR(6) NOT NULL DEFAULT '5865F2', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "name" TEXT NOT NULL, + "roleId" VARCHAR(19) NOT NULL, + "ticketId" VARCHAR(19) NOT NULL, + + CONSTRAINT "archivedRoles_pkey" PRIMARY KEY ("ticketId","roleId") +); + +-- CreateTable +CREATE TABLE "archivedUsers" ( + "avatar" TEXT, + "bot" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "discriminator" CHAR(4), + "displayName" TEXT, + "roleId" VARCHAR(19), + "ticketId" VARCHAR(19) NOT NULL, + "userId" VARCHAR(19) NOT NULL, + "username" TEXT, + + CONSTRAINT "archivedUsers_pkey" PRIMARY KEY ("ticketId","userId") +); + +-- CreateTable +CREATE TABLE "categories" ( + "channelName" TEXT NOT NULL, + "claiming" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "cooldown" INTEGER, + "customTopic" TEXT, + "description" TEXT NOT NULL, + "discordCategory" VARCHAR(19) NOT NULL, + "emoji" TEXT NOT NULL, + "enableFeedback" BOOLEAN NOT NULL DEFAULT false, + "guildId" VARCHAR(19) NOT NULL, + "id" SERIAL NOT NULL, + "image" TEXT, + "memberLimit" INTEGER NOT NULL DEFAULT 1, + "name" TEXT NOT NULL, + "openingMessage" TEXT NOT NULL, + "pingRoles" JSONB NOT NULL DEFAULT '[]', + "ratelimit" INTEGER, + "requiredRoles" JSONB NOT NULL DEFAULT '[]', + "requireTopic" BOOLEAN NOT NULL DEFAULT false, + "staffRoles" JSONB NOT NULL, + "totalLimit" INTEGER NOT NULL DEFAULT 50, + + CONSTRAINT "categories_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "feedback" ( + "comment" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "guildId" VARCHAR(19) NOT NULL, + "rating" INTEGER NOT NULL, + "ticketId" VARCHAR(19) NOT NULL, + "userId" VARCHAR(19), + + CONSTRAINT "feedback_pkey" PRIMARY KEY ("ticketId") +); + +-- CreateTable +CREATE TABLE "guilds" ( + "autoClose" INTEGER NOT NULL DEFAULT 43200000, + "autoTag" JSONB NOT NULL DEFAULT '[]', + "archive" BOOLEAN NOT NULL DEFAULT true, + "blocklist" JSONB NOT NULL DEFAULT '[]', + "claimButton" BOOLEAN NOT NULL DEFAULT false, + "closeButton" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "errorColour" TEXT NOT NULL DEFAULT 'Red', + "footer" TEXT DEFAULT 'Discord Tickets by eartharoid', + "id" VARCHAR(19) NOT NULL, + "locale" TEXT NOT NULL DEFAULT 'en-GB', + "logChannel" VARCHAR(19), + "primaryColour" TEXT NOT NULL DEFAULT '#009999', + "staleAfter" INTEGER, + "successColour" TEXT NOT NULL DEFAULT 'Green', + "workingHours" JSONB NOT NULL DEFAULT '["UTC", ["00:00","23:59"], ["00:00","23:59"], ["00:00","23:59"], ["00:00","23:59"], ["00:00","23:59"], ["00:00","23:59"], ["00:00","23:59"]]', + + CONSTRAINT "guilds_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "questions" ( + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "id" TEXT NOT NULL, + "categoryId" INTEGER NOT NULL, + "label" VARCHAR(45) NOT NULL, + "maxLength" INTEGER DEFAULT 4000, + "minLength" INTEGER DEFAULT 0, + "options" JSONB NOT NULL DEFAULT '[]', + "order" INTEGER NOT NULL, + "placeholder" VARCHAR(100), + "required" BOOLEAN NOT NULL DEFAULT true, + "style" INTEGER NOT NULL DEFAULT 2, + "type" "QuestionType" NOT NULL DEFAULT 'TEXT', + "value" TEXT, + + CONSTRAINT "questions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "questionAnswers" ( + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "id" SERIAL NOT NULL, + "ticketId" VARCHAR(19) NOT NULL, + "questionId" TEXT NOT NULL, + "userId" VARCHAR(19) NOT NULL, + "value" TEXT, + + CONSTRAINT "questionAnswers_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "tags" ( + "content" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "guildId" VARCHAR(19) NOT NULL, + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "regex" TEXT, + + CONSTRAINT "tags_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "tickets" ( + "categoryId" INTEGER, + "claimedById" VARCHAR(19), + "closedAt" TIMESTAMP(3), + "closedById" VARCHAR(19), + "closedReason" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdById" VARCHAR(19) NOT NULL, + "deleted" BOOLEAN NOT NULL DEFAULT false, + "firstResponseAt" TIMESTAMP(3), + "guildId" VARCHAR(19) NOT NULL, + "id" VARCHAR(19) NOT NULL, + "lastMessageAt" TIMESTAMP(3), + "messageCount" INTEGER, + "number" INTEGER NOT NULL, + "open" BOOLEAN NOT NULL DEFAULT true, + "openingMessageId" VARCHAR(19) NOT NULL, + "pinnedMessageIds" JSONB NOT NULL DEFAULT '[]', + "priority" "TicketPriority", + "referencesMessageId" VARCHAR(19), + "referencesTicketId" VARCHAR(19), + "topic" TEXT, + + CONSTRAINT "tickets_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "users" ( + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "id" VARCHAR(19) NOT NULL, + "messageCount" INTEGER NOT NULL DEFAULT 0, + + CONSTRAINT "users_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "archivedChannels_ticketId_channelId_key" ON "archivedChannels"("ticketId", "channelId"); + +-- CreateIndex +CREATE UNIQUE INDEX "archivedRoles_ticketId_roleId_key" ON "archivedRoles"("ticketId", "roleId"); + +-- CreateIndex +CREATE UNIQUE INDEX "archivedUsers_ticketId_userId_key" ON "archivedUsers"("ticketId", "userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "tags_guildId_name_key" ON "tags"("guildId", "name"); + +-- CreateIndex +CREATE UNIQUE INDEX "tickets_guildId_number_key" ON "tickets"("guildId", "number"); + +-- AddForeignKey +ALTER TABLE "archivedChannels" ADD CONSTRAINT "archivedChannels_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "archivedMessages" ADD CONSTRAINT "archivedMessages_ticketId_authorId_fkey" FOREIGN KEY ("ticketId", "authorId") REFERENCES "archivedUsers"("ticketId", "userId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "archivedMessages" ADD CONSTRAINT "archivedMessages_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "archivedRoles" ADD CONSTRAINT "archivedRoles_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "archivedUsers" ADD CONSTRAINT "archivedUsers_ticketId_roleId_fkey" FOREIGN KEY ("ticketId", "roleId") REFERENCES "archivedRoles"("ticketId", "roleId") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "archivedUsers" ADD CONSTRAINT "archivedUsers_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "categories" ADD CONSTRAINT "categories_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "questions" ADD CONSTRAINT "questions_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "categories"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "questionAnswers" ADD CONSTRAINT "questionAnswers_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "questionAnswers" ADD CONSTRAINT "questionAnswers_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "questions"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "questionAnswers" ADD CONSTRAINT "questionAnswers_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tags" ADD CONSTRAINT "tags_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tickets" ADD CONSTRAINT "tickets_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "categories"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tickets" ADD CONSTRAINT "tickets_claimedById_fkey" FOREIGN KEY ("claimedById") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tickets" ADD CONSTRAINT "tickets_closedById_fkey" FOREIGN KEY ("closedById") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tickets" ADD CONSTRAINT "tickets_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tickets" ADD CONSTRAINT "tickets_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tickets" ADD CONSTRAINT "tickets_referencesTicketId_fkey" FOREIGN KEY ("referencesTicketId") REFERENCES "tickets"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/db/postgresql/migrations/migration_lock.toml b/db/postgresql/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/db/postgresql/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/db/sqlite/migrations/20230130211842_4_0_0/migration.sql b/db/sqlite/migrations/20230130211842_4_0_0/migration.sql new file mode 100644 index 0000000..1ee0b48 --- /dev/null +++ b/db/sqlite/migrations/20230130211842_4_0_0/migration.sql @@ -0,0 +1,207 @@ +-- CreateTable +CREATE TABLE "archivedChannels" ( + "channelId" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "name" TEXT NOT NULL, + "ticketId" TEXT NOT NULL, + + PRIMARY KEY ("ticketId", "channelId"), + CONSTRAINT "archivedChannels_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "archivedMessages" ( + "authorId" TEXT NOT NULL, + "content" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" BOOLEAN NOT NULL DEFAULT false, + "edited" BOOLEAN NOT NULL DEFAULT false, + "id" TEXT NOT NULL PRIMARY KEY, + "external" BOOLEAN NOT NULL DEFAULT false, + "ticketId" TEXT NOT NULL, + CONSTRAINT "archivedMessages_ticketId_authorId_fkey" FOREIGN KEY ("ticketId", "authorId") REFERENCES "archivedUsers" ("ticketId", "userId") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "archivedMessages_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "archivedRoles" ( + "colour" TEXT NOT NULL DEFAULT '5865F2', + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "name" TEXT NOT NULL, + "roleId" TEXT NOT NULL, + "ticketId" TEXT NOT NULL, + + PRIMARY KEY ("ticketId", "roleId"), + CONSTRAINT "archivedRoles_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "archivedUsers" ( + "avatar" TEXT, + "bot" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "discriminator" TEXT, + "displayName" TEXT, + "roleId" TEXT, + "ticketId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "username" TEXT, + + PRIMARY KEY ("ticketId", "userId"), + CONSTRAINT "archivedUsers_ticketId_roleId_fkey" FOREIGN KEY ("ticketId", "roleId") REFERENCES "archivedRoles" ("ticketId", "roleId") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "archivedUsers_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "categories" ( + "channelName" TEXT NOT NULL, + "claiming" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "cooldown" INTEGER, + "customTopic" TEXT, + "description" TEXT NOT NULL, + "discordCategory" TEXT NOT NULL, + "emoji" TEXT NOT NULL, + "enableFeedback" BOOLEAN NOT NULL DEFAULT false, + "guildId" TEXT NOT NULL, + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "image" TEXT, + "memberLimit" INTEGER NOT NULL DEFAULT 1, + "name" TEXT NOT NULL, + "openingMessage" TEXT NOT NULL, + "pingRoles" TEXT NOT NULL DEFAULT '[]', + "ratelimit" INTEGER, + "requiredRoles" TEXT NOT NULL DEFAULT '[]', + "requireTopic" BOOLEAN NOT NULL DEFAULT false, + "staffRoles" TEXT NOT NULL, + "totalLimit" INTEGER NOT NULL DEFAULT 50, + CONSTRAINT "categories_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "feedback" ( + "comment" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "guildId" TEXT NOT NULL, + "rating" INTEGER NOT NULL, + "ticketId" TEXT NOT NULL PRIMARY KEY, + "userId" TEXT, + CONSTRAINT "feedback_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "feedback_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "feedback_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "guilds" ( + "autoClose" INTEGER NOT NULL DEFAULT 43200000, + "autoTag" TEXT NOT NULL DEFAULT '[]', + "archive" BOOLEAN NOT NULL DEFAULT true, + "blocklist" TEXT NOT NULL DEFAULT '[]', + "claimButton" BOOLEAN NOT NULL DEFAULT false, + "closeButton" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "errorColour" TEXT NOT NULL DEFAULT 'Red', + "footer" TEXT DEFAULT 'Discord Tickets by eartharoid', + "id" TEXT NOT NULL PRIMARY KEY, + "locale" TEXT NOT NULL DEFAULT 'en-GB', + "logChannel" TEXT, + "primaryColour" TEXT NOT NULL DEFAULT '#009999', + "staleAfter" INTEGER, + "successColour" TEXT NOT NULL DEFAULT 'Green', + "workingHours" TEXT NOT NULL DEFAULT '["UTC", ["00:00","23:59"], ["00:00","23:59"], ["00:00","23:59"], ["00:00","23:59"], ["00:00","23:59"], ["00:00","23:59"], ["00:00","23:59"]]' +); + +-- CreateTable +CREATE TABLE "questions" ( + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "id" TEXT NOT NULL PRIMARY KEY, + "categoryId" INTEGER NOT NULL, + "label" TEXT NOT NULL, + "maxLength" INTEGER DEFAULT 4000, + "minLength" INTEGER DEFAULT 0, + "options" TEXT NOT NULL DEFAULT '[]', + "order" INTEGER NOT NULL, + "placeholder" TEXT, + "required" BOOLEAN NOT NULL DEFAULT true, + "style" INTEGER NOT NULL DEFAULT 2, + "type" TEXT NOT NULL DEFAULT 'TEXT', + "value" TEXT, + CONSTRAINT "questions_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "categories" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "questionAnswers" ( + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "ticketId" TEXT NOT NULL, + "questionId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "value" TEXT, + CONSTRAINT "questionAnswers_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "questionAnswers_questionId_fkey" FOREIGN KEY ("questionId") REFERENCES "questions" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "questionAnswers_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "tags" ( + "content" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "guildId" TEXT NOT NULL, + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT NOT NULL, + "regex" TEXT, + CONSTRAINT "tags_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "tickets" ( + "categoryId" INTEGER, + "claimedById" TEXT, + "closedAt" DATETIME, + "closedById" TEXT, + "closedReason" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdById" TEXT NOT NULL, + "deleted" BOOLEAN NOT NULL DEFAULT false, + "firstResponseAt" DATETIME, + "guildId" TEXT NOT NULL, + "id" TEXT NOT NULL PRIMARY KEY, + "lastMessageAt" DATETIME, + "messageCount" INTEGER, + "number" INTEGER NOT NULL, + "open" BOOLEAN NOT NULL DEFAULT true, + "openingMessageId" TEXT NOT NULL, + "pinnedMessageIds" TEXT NOT NULL DEFAULT '[]', + "priority" TEXT, + "referencesMessageId" TEXT, + "referencesTicketId" TEXT, + "topic" TEXT, + CONSTRAINT "tickets_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "categories" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "tickets_claimedById_fkey" FOREIGN KEY ("claimedById") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "tickets_closedById_fkey" FOREIGN KEY ("closedById") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "tickets_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "tickets_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "tickets_referencesTicketId_fkey" FOREIGN KEY ("referencesTicketId") REFERENCES "tickets" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "users" ( + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "id" TEXT NOT NULL PRIMARY KEY, + "messageCount" INTEGER NOT NULL DEFAULT 0 +); + +-- CreateIndex +CREATE UNIQUE INDEX "archivedChannels_ticketId_channelId_key" ON "archivedChannels"("ticketId", "channelId"); + +-- CreateIndex +CREATE UNIQUE INDEX "archivedRoles_ticketId_roleId_key" ON "archivedRoles"("ticketId", "roleId"); + +-- CreateIndex +CREATE UNIQUE INDEX "archivedUsers_ticketId_userId_key" ON "archivedUsers"("ticketId", "userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "tags_guildId_name_key" ON "tags"("guildId", "name"); + +-- CreateIndex +CREATE UNIQUE INDEX "tickets_guildId_number_key" ON "tickets"("guildId", "number"); diff --git a/db/sqlite/migrations/migration_lock.toml b/db/sqlite/migrations/migration_lock.toml new file mode 100644 index 0000000..e5e5c47 --- /dev/null +++ b/db/sqlite/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "sqlite" \ No newline at end of file diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 6e1c473..67d31c8 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -31,6 +31,6 @@ fs.copySync(`./db/${provider}`, './prisma'); // copy schema & migrations (async () => { await npx('prisma generate'); - // await npx('prisma migrate deploy'); + await npx('prisma migrate deploy'); })(); From 9e4f532ae870986287bcd3fbd7f1692cf51eeeff Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 31 Jan 2023 12:53:22 +0000 Subject: [PATCH 217/409] fix: listen on `0.0.0.0` --- src/http.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/http.js b/src/http.js index ab41ac6..1e19f38 100644 --- a/src/http.js +++ b/src/http.js @@ -177,7 +177,10 @@ module.exports = async client => { }); // start the fastify server - fastify.listen({ port: process.env.HTTP_BIND }, (err, addr) => { + fastify.listen({ + host: '0.0.0.0', + port: process.env.HTTP_BIND, + }, (err, addr) => { if (err) client.log.error.http(err); else client.log.success.http(`Listening at ${addr}`); }); From bcf474cc1c8a45f9a83f3a014ef628ed949919a7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 31 Jan 2023 15:18:40 +0000 Subject: [PATCH 218/409] fix: use environment variables --- src/commands/slash/tickets.js | 2 +- src/lib/tickets/archiver.js | 2 +- user/example.config.yml | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/commands/slash/tickets.js b/src/commands/slash/tickets.js index 7ed1f2b..95a0d91 100644 --- a/src/commands/slash/tickets.js +++ b/src/commands/slash/tickets.js @@ -122,7 +122,7 @@ module.exports = class TicketsSlashCommand extends SlashCommand { .setTitle(getMessage(`commands.slash.tickets.response.title.${ownOrOther}`, { displayName: member.displayName })) .setFields(fields); - if (settings.archive && !client.config.overrides.disableArchives) { + if (settings.archive && process.env.OVERRIDE_ARCHIVE !== 'false') { const transcriptCommand = client.application.commands.cache.find(c => c.name === 'transcript'); embed.setDescription(getMessage('commands.slash.tickets.response.description', { transcript: `` })); } diff --git a/src/lib/tickets/archiver.js b/src/lib/tickets/archiver.js index 04afac4..6687018 100644 --- a/src/lib/tickets/archiver.js +++ b/src/lib/tickets/archiver.js @@ -21,7 +21,7 @@ module.exports = class TicketArchiver { * @returns {import("@prisma/client").ArchivedMessage|boolean} */ async saveMessage(ticketId, message, external = false) { - if (this.client.config.overrides.disableArchives) return false; + if (process.env.OVERRIDE_ARCHIVE === 'false') return false; if (!message.member) { try { diff --git a/user/example.config.yml b/user/example.config.yml index 56e3b8c..c8a2297 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -20,8 +20,6 @@ logs: enabled: true keepFor: 30 level: info -overrides: - disableArchives: false presence: activities: - name: /new From 571c03a8733e63b54700cccbaf125ce6427cc96c Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 1 Feb 2023 20:55:01 +0000 Subject: [PATCH 219/409] build: include lockfile --- pnpm-lock.yaml | 2707 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2707 insertions(+) create mode 100644 pnpm-lock.yaml diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..f58c05b --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2707 @@ +lockfileVersion: 5.4 + +specifiers: + '@discord-tickets/settings': ^1.1.6 + '@eartharoid/dbf': ^0.3.3 + '@eartharoid/dtf': ^2.0.1 + '@eartharoid/i18n': ^1.2.1 + '@fastify/cookie': ^6.0.0 + '@fastify/cors': ^8.2.0 + '@fastify/http-proxy': ^8.4.0 + '@fastify/jwt': ^5.0.1 + '@fastify/oauth2': ^5.1.0 + '@prisma/client': ^4.9.0 + all-contributors-cli: ^6.24.0 + bufferutil: ^4.0.7 + cryptr: ^6.1.0 + discord.js: ^14.7.1 + dotenv: ^16.0.3 + erlpack: github:discord/erlpack + eslint: ^8.31.0 + eslint-plugin-unused-imports: ^2.0.0 + express: ^4.18.2 + fastify: ^4.11.0 + figlet: ^1.5.2 + fs-extra: ^10.1.0 + keyv: ^4.5.2 + leeks.js: ^0.2.4 + leekslazylogger: ^5.0.0 + ms: ^2.1.3 + mustache: ^4.2.0 + node-dir: ^0.1.17 + node-emoji: ^1.11.0 + nodemon: ^2.0.20 + object-diffy: ^1.0.4 + pad: ^3.2.0 + prisma: ^4.9.0 + semver: ^7.3.8 + terminal-link: ^2.1.1 + utf-8-validate: ^5.0.10 + yaml: ^1.10.2 + zlib-sync: ^0.1.7 + +dependencies: + '@discord-tickets/settings': 1.1.6 + '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq + '@eartharoid/dtf': 2.0.1 + '@eartharoid/i18n': 1.2.1 + '@fastify/cookie': 6.0.0 + '@fastify/cors': 8.2.0 + '@fastify/http-proxy': 8.4.0_3cxu5zja4e2r5wmvge7mdcljwq + '@fastify/jwt': 5.0.1 + '@fastify/oauth2': 5.1.0 + '@prisma/client': 4.9.0_prisma@4.9.0 + cryptr: 6.1.0 + discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq + dotenv: 16.0.3 + express: 4.18.2 + fastify: 4.11.0 + figlet: 1.5.2 + fs-extra: 10.1.0 + keyv: 4.5.2 + leeks.js: 0.2.4 + leekslazylogger: 5.0.0 + ms: 2.1.3 + mustache: 4.2.0 + node-dir: 0.1.17 + node-emoji: 1.11.0 + object-diffy: 1.0.4 + pad: 3.2.0 + prisma: 4.9.0 + semver: 7.3.8 + terminal-link: 2.1.1 + yaml: 1.10.2 + +optionalDependencies: + bufferutil: 4.0.7 + erlpack: github.com/discord/erlpack/cbe76be04c2210fc9cb6ff95910f0937c1011d04 + utf-8-validate: 5.0.10 + zlib-sync: 0.1.7 + +devDependencies: + all-contributors-cli: 6.24.0 + eslint: 8.31.0 + eslint-plugin-unused-imports: 2.0.0_eslint@8.31.0 + nodemon: 2.0.20 + +packages: + + /@babel/runtime/7.20.7: + resolution: {integrity: sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + dev: true + + /@discord-tickets/settings/1.1.6: + resolution: {integrity: sha512-NdU+M3j224yCB1fmPQTNWOIsgBQztQQY5uc+wugx5DqF8MJAS7LsQoLEymclx7h1wB/tdykLv2MNPNnjuFXwBg==} + dependencies: + '@fortawesome/fontawesome-free': 6.2.1 + cookie: 0.5.0 + emoji-name-map: 1.2.9 + marked: 4.2.5 + ms: 2.1.3 + postcss: 8.4.21 + sortablejs: 1.15.0 + svelte-modals: 1.2.0-beta.0 + dev: false + + /@discordjs/builders/1.4.0: + resolution: {integrity: sha512-nEeTCheTTDw5kO93faM1j8ZJPonAX86qpq/QVoznnSa8WWcCgJpjlu6GylfINTDW6o7zZY0my2SYdxx2mfNwGA==} + engines: {node: '>=16.9.0'} + dependencies: + '@discordjs/util': 0.1.0 + '@sapphire/shapeshift': 3.8.1 + discord-api-types: 0.37.28 + fast-deep-equal: 3.1.3 + ts-mixer: 6.0.2 + tslib: 2.4.1 + dev: false + + /@discordjs/collection/1.3.0: + resolution: {integrity: sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg==} + engines: {node: '>=16.9.0'} + dev: false + + /@discordjs/rest/1.5.0: + resolution: {integrity: sha512-lXgNFqHnbmzp5u81W0+frdXN6Etf4EUi8FAPcWpSykKd8hmlWh1xy6BmE0bsJypU1pxohaA8lQCgp70NUI3uzA==} + engines: {node: '>=16.9.0'} + dependencies: + '@discordjs/collection': 1.3.0 + '@discordjs/util': 0.1.0 + '@sapphire/async-queue': 1.5.0 + '@sapphire/snowflake': 3.4.0 + discord-api-types: 0.37.28 + file-type: 18.1.0 + tslib: 2.4.1 + undici: 5.15.0 + dev: false + + /@discordjs/util/0.1.0: + resolution: {integrity: sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ==} + engines: {node: '>=16.9.0'} + dev: false + + /@eartharoid/dbf/0.3.3_3cxu5zja4e2r5wmvge7mdcljwq: + resolution: {integrity: sha512-eVDdpFlDV5CAvqoV5g1iAvoYhPjnvcyJ0Nnepc1YihlE1KIYGhVIK/2RaDsltzxRuiweO3Y7dvDj/cUpJnnFPA==} + dependencies: + discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq + node-dir: 0.1.17 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /@eartharoid/deep-merge/0.0.2: + resolution: {integrity: sha512-t7kmNd6m7BOGxf25tE1YBhPZbgMEsXZT0tQyVV/Mlo+rcPEmiEEc6HV1DBnYm63MvMpgTk4o6yBkeAJYCMzvNg==} + dev: false + + /@eartharoid/dtf/2.0.1: + resolution: {integrity: sha512-H0oLRShkVqTskVRA8KMMIHjtwAu6Yc6GkxQm9cLPWMA7V0ZOxD5rg2PaZuQfDbt6UAk2sp7qb+k4ih5EHc/Jgg==} + dev: false + + /@eartharoid/i18n/1.2.1: + resolution: {integrity: sha512-nMQdHrGgpw+vNL5DbivELW2K3KAUGaMvTjjmFsEPf8mUW8+LAgRjvfFn2gkJq1mnlD6HoqUaHqEL4YpWr2T5MA==} + dev: false + + /@eslint/eslintrc/1.4.1: + resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.4.1 + globals: 13.19.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@fastify/ajv-compiler/3.5.0: + resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==} + dependencies: + ajv: 8.12.0 + ajv-formats: 2.1.1 + fast-uri: 2.2.0 + dev: false + + /@fastify/cookie/6.0.0: + resolution: {integrity: sha512-Luy3Po3dOJmqAuPCiPcWsX0tV5+C3AOnULSdlsGjNGOvyE7jqzysp8kT9ICfsUvove+TeUMgTWl1y9XS3ZPPMg==} + dependencies: + cookie-signature: 1.2.0 + fastify-plugin: 3.0.1 + dev: false + + /@fastify/cors/8.2.0: + resolution: {integrity: sha512-qDgwpmg6C4D0D3nh8MTMuRXWyEwPnDZDBODaJv90FP2o9ukbahJByW4FtrM5Bpod5KbTf1oIExBmpItbUTQmHg==} + dependencies: + fastify-plugin: 4.5.0 + mnemonist: 0.39.5 + dev: false + + /@fastify/deepmerge/1.3.0: + resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + dev: false + + /@fastify/error/3.2.0: + resolution: {integrity: sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==} + dev: false + + /@fastify/fast-json-stringify-compiler/4.2.0: + resolution: {integrity: sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg==} + dependencies: + fast-json-stringify: 5.5.0 + dev: false + + /@fastify/http-proxy/8.4.0_3cxu5zja4e2r5wmvge7mdcljwq: + resolution: {integrity: sha512-H8nwsmawFtKKRE6uhh1BtF1gQi/l147SmLsDGxB0HdYTHzjXz6uSQO3lEVmY7unKMzbArRjdoJQkEGpScszdSw==} + dependencies: + '@fastify/reply-from': 8.3.1 + ws: 8.12.0_3cxu5zja4e2r5wmvge7mdcljwq + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /@fastify/jwt/5.0.1: + resolution: {integrity: sha512-BF2JrhSRcMRwHim2pbFEgWLJE8DdRh0NaGeAgU+ZFICj2AHxGvrHSOaYRaXNUQixgmGjVaTrbRFl3bSBc6Pj+g==} + engines: {node: '>=10'} + dependencies: + '@lukeed/ms': 2.0.1 + fast-jwt: 1.7.2 + fastify-plugin: 3.0.1 + http-errors: 2.0.0 + steed: 1.1.3 + dev: false + + /@fastify/oauth2/5.1.0: + resolution: {integrity: sha512-Z/2ZR8v2BB+vwFm1t0DnavxAKymCGvJiGD/lTtNV/dTPHTHRQ1X68hFs/Nl2Rd0Gv9AlSlrpYsX9hfoNjTSiYQ==} + dependencies: + fastify-plugin: 4.5.0 + simple-oauth2: 3.4.0 + transitivePeerDependencies: + - supports-color + dev: false + + /@fastify/reply-from/8.3.1: + resolution: {integrity: sha512-fRByAvTMXuBuYIimcinukOB3YdmqtYPeoybXIBNY0aPVgetHkmCVffBo/M4pEOib9Pes8wuoYL4VawI65aHl4w==} + dependencies: + '@fastify/error': 3.2.0 + end-of-stream: 1.4.4 + fast-querystring: 1.1.0 + fastify-plugin: 4.5.0 + pump: 3.0.0 + tiny-lru: 10.0.1 + undici: 5.15.0 + dev: false + + /@fortawesome/fontawesome-free/6.2.1: + resolution: {integrity: sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A==} + engines: {node: '>=6'} + requiresBuild: true + dev: false + + /@hapi/address/2.1.4: + resolution: {integrity: sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==} + deprecated: Moved to 'npm install @sideway/address' + dev: false + + /@hapi/boom/7.4.11: + resolution: {integrity: sha512-VSU/Cnj1DXouukYxxkes4nNJonCnlogHvIff1v1RVoN4xzkKhMXX+GRmb3NyH1iar10I9WFPDv2JPwfH3GaV0A==} + deprecated: This version has been deprecated and is no longer supported or maintained + dependencies: + '@hapi/hoek': 8.5.1 + dev: false + + /@hapi/bourne/1.3.2: + resolution: {integrity: sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==} + deprecated: This version has been deprecated and is no longer supported or maintained + dev: false + + /@hapi/formula/1.2.0: + resolution: {integrity: sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==} + deprecated: Moved to 'npm install @sideway/formula' + dev: false + + /@hapi/hoek/8.5.1: + resolution: {integrity: sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==} + deprecated: This version has been deprecated and is no longer supported or maintained + dev: false + + /@hapi/joi/16.1.8: + resolution: {integrity: sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg==} + deprecated: Switch to 'npm install joi' + dependencies: + '@hapi/address': 2.1.4 + '@hapi/formula': 1.2.0 + '@hapi/hoek': 8.5.1 + '@hapi/pinpoint': 1.0.2 + '@hapi/topo': 3.1.6 + dev: false + + /@hapi/pinpoint/1.0.2: + resolution: {integrity: sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==} + deprecated: Moved to 'npm install @sideway/pinpoint' + dev: false + + /@hapi/topo/3.1.6: + resolution: {integrity: sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==} + deprecated: This version has been deprecated and is no longer supported or maintained + dependencies: + '@hapi/hoek': 8.5.1 + dev: false + + /@hapi/wreck/15.1.0: + resolution: {integrity: sha512-tQczYRTTeYBmvhsek/D49En/5khcShaBEmzrAaDjMrFXKJRuF8xA8+tlq1ETLBFwGd6Do6g2OC74rt11kzawzg==} + deprecated: This version has been deprecated and is no longer supported or maintained + dependencies: + '@hapi/boom': 7.4.11 + '@hapi/bourne': 1.3.2 + '@hapi/hoek': 8.5.1 + dev: false + + /@humanwhocodes/config-array/0.11.8: + resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer/1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema/1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true + + /@lukeed/ms/2.0.1: + resolution: {integrity: sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==} + engines: {node: '>=8'} + dev: false + + /@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@prisma/client/4.9.0_prisma@4.9.0: + resolution: {integrity: sha512-bz6QARw54sWcbyR1lLnF2QHvRW5R/Jxnbbmwh3u+969vUKXtBkXgSgjDA85nji31ZBlf7+FrHDy5x+5ydGyQDg==} + engines: {node: '>=14.17'} + requiresBuild: true + peerDependencies: + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + dependencies: + '@prisma/engines-version': 4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5 + prisma: 4.9.0 + dev: false + + /@prisma/engines-version/4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5: + resolution: {integrity: sha512-M16aibbxi/FhW7z1sJCX8u+0DriyQYY5AyeTH7plQm9MLnURoiyn3CZBqAyIoQ+Z1pS77usCIibYJWSgleBMBA==} + dev: false + + /@prisma/engines/4.9.0: + resolution: {integrity: sha512-t1pt0Gsp+HcgPJrHFc+d/ZSAaKKWar2G/iakrE07yeKPNavDP3iVKPpfXP22OTCHZUWf7OelwKJxQgKAm5hkgw==} + requiresBuild: true + dev: false + + /@sapphire/async-queue/1.5.0: + resolution: {integrity: sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dev: false + + /@sapphire/shapeshift/3.8.1: + resolution: {integrity: sha512-xG1oXXBhCjPKbxrRTlox9ddaZTvVpOhYLmKmApD/vIWOV1xEYXnpoFs68zHIZBGbqztq6FrUPNPerIrO1Hqeaw==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dependencies: + fast-deep-equal: 3.1.3 + lodash: 4.17.21 + dev: false + + /@sapphire/snowflake/3.4.0: + resolution: {integrity: sha512-zZxymtVO6zeXVMPds+6d7gv/OfnCc25M1Z+7ZLB0oPmeMTPeRWVPQSS16oDJy5ZsyCOLj7M6mbZml5gWXcVRNw==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + dev: false + + /@tokenizer/token/0.3.0: + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + dev: false + + /@types/node/18.11.18: + resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} + dev: false + + /@types/ws/8.5.4: + resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} + dependencies: + '@types/node': 18.11.18 + dev: false + + /abbrev/1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: true + + /abort-controller/3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: false + + /abstract-logging/2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + dev: false + + /accepts/1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + + /acorn-jsx/5.3.2_acorn@8.8.1: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.8.1 + dev: true + + /acorn/8.8.1: + resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv-formats/2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.12.0 + dev: false + + /ajv/6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ajv/8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + dev: false + + /all-contributors-cli/6.24.0: + resolution: {integrity: sha512-7oSKr2PnqxsOotuSwciltcFTS1eVRdjR0cn99hbElfff7gRQBShVhsf/XBprY41sLcgqTk0l0MKgKv6QNgZdMg==} + engines: {node: '>=4'} + hasBin: true + dependencies: + '@babel/runtime': 7.20.7 + async: 3.2.4 + chalk: 4.1.2 + didyoumean: 1.2.2 + inquirer: 7.3.3 + json-fixer: 1.6.15 + lodash: 4.17.21 + node-fetch: 2.6.8 + pify: 5.0.0 + yargs: 15.4.1 + transitivePeerDependencies: + - encoding + dev: true + + /ansi-escapes/4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + + /ansi-regex/5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /anymatch/3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /archy/1.0.0: + resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} + dev: false + + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-flatten/1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + dev: false + + /asn1.js/5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + dependencies: + bn.js: 4.12.0 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + safer-buffer: 2.1.2 + dev: false + + /async/3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + dev: true + + /atomic-sleep/1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + dev: false + + /avvio/8.2.0: + resolution: {integrity: sha512-bbCQdg7bpEv6kGH41RO/3B2/GMMmJSo2iBK+X8AWN9mujtfUipMDfIjsgHCfpnKqoGEQrrmCDKSa5OQ19+fDmg==} + dependencies: + archy: 1.0.0 + debug: 4.3.4 + fastq: 1.15.0 + transitivePeerDependencies: + - supports-color + dev: false + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js/1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: false + + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /bindings/1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: false + optional: true + + /bn.js/4.12.0: + resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + dev: false + + /body-parser/1.20.1: + resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.4 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /buffer/6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + + /bufferutil/4.0.7: + resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.0 + dev: false + + /busboy/1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: false + + /bytes/3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: false + + /call-bind/1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.1.3 + dev: false + + /callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /camelcase/5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + dev: true + + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chardet/0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true + + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /cli-cursor/3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + dependencies: + restore-cursor: 3.1.0 + dev: true + + /cli-width/3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + dev: true + + /cliui/6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: true + + /clone/1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: false + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + /content-disposition/0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type/1.0.4: + resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + engines: {node: '>= 0.6'} + dev: false + + /cookie-signature/1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + dev: false + + /cookie-signature/1.2.0: + resolution: {integrity: sha512-R0BOPfLGTitaKhgKROKZQN6iyq2iDQcH1DOF8nJoaWapguX5bC2w+Q/I9NmmM5lfcvEarnLZr+cCvmEYYSXvYA==} + engines: {node: '>=6.6.0'} + dev: false + + /cookie/0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: false + + /cross-spawn/7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /cryptr/6.1.0: + resolution: {integrity: sha512-vJBrDcKAZ/OpMW4EptbGTD2EfNEywh6QtsPDX5kd2u1MNBmTLGC3IlOoRh8Ov7dmZhFfRjUJ07u4eCoJ8+oM9A==} + dev: false + + /date-fns/2.29.3: + resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} + engines: {node: '>=0.11'} + dev: false + + /debug/2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: false + + /debug/3.2.7_supports-color@5.5.0: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + supports-color: 5.5.0 + dev: true + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /decamelize/1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + dev: true + + /deep-is/0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /defaults/1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + dependencies: + clone: 1.0.4 + dev: false + + /depd/2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /destroy/1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: false + + /didyoumean/1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dev: true + + /discord-api-types/0.37.28: + resolution: {integrity: sha512-K0fw7m7km9th3dCQ2AR90q/FwX3uAj+OLc+Zuo39VY9vCn0Ux/iObM4y1zJYIH3vTc+QlrksVErUvyeONjOKMQ==} + dev: false + + /discord.js/14.7.1_3cxu5zja4e2r5wmvge7mdcljwq: + resolution: {integrity: sha512-1FECvqJJjjeYcjSm0IGMnPxLqja/pmG1B0W2l3lUY2Gi4KXiyTeQmU1IxWcbXHn2k+ytP587mMWqva2IA87EbA==} + engines: {node: '>=16.9.0'} + dependencies: + '@discordjs/builders': 1.4.0 + '@discordjs/collection': 1.3.0 + '@discordjs/rest': 1.5.0 + '@discordjs/util': 0.1.0 + '@sapphire/snowflake': 3.4.0 + '@types/ws': 8.5.4 + discord-api-types: 0.37.28 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + tslib: 2.4.1 + undici: 5.15.0 + ws: 8.12.0_3cxu5zja4e2r5wmvge7mdcljwq + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /doctrine/3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /dotenv/16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + dev: false + + /ecdsa-sig-formatter/1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /ee-first/1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + dev: false + + /emoji-name-map/1.2.9: + resolution: {integrity: sha512-MSM8y6koSqh/2uEMI2VoKA+Ac0qL5RkgFGP/pzL6n5FOrOJ7FOZFxgs7+uNpqA+AT+WmdbMPXkd3HnFXXdz4AA==} + dependencies: + emojilib: 2.4.0 + iterate-object: 1.3.4 + map-o: 2.0.10 + dev: false + + /emoji-regex/8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /emojilib/2.4.0: + resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + dev: false + + /encodeurl/1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + dev: false + + /end-of-stream/1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: false + + /escape-html/1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + + /escape-string-regexp/1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + + /escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /eslint-plugin-unused-imports/2.0.0_eslint@8.31.0: + resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.0.0 + eslint: ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + dependencies: + eslint: 8.31.0 + eslint-rule-composer: 0.3.0 + dev: true + + /eslint-rule-composer/0.3.0: + resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} + engines: {node: '>=4.0.0'} + dev: true + + /eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-utils/3.0.0_eslint@8.31.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.31.0 + eslint-visitor-keys: 2.1.0 + dev: true + + /eslint-visitor-keys/2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + dev: true + + /eslint-visitor-keys/3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint/8.31.0: + resolution: {integrity: sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.4.1 + '@humanwhocodes/config-array': 0.11.8 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.31.0 + eslint-visitor-keys: 3.3.0 + espree: 9.4.1 + esquery: 1.4.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.19.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.4 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-sdsl: 4.2.0 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree/9.4.1: + resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.8.1 + acorn-jsx: 5.3.2_acorn@8.8.1 + eslint-visitor-keys: 3.3.0 + dev: true + + /esquery/1.4.0: + resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse/4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse/5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils/2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /etag/1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: false + + /event-target-shim/5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: false + + /events/3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + dev: false + + /express/4.18.2: + resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.1 + content-disposition: 0.5.4 + content-type: 1.0.4 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /external-editor/3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: true + + /fast-decode-uri-component/1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + dev: false + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + /fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-json-stringify/5.5.0: + resolution: {integrity: sha512-rmw2Z8/mLkND8zI+3KTYIkNPEoF5v6GqDP/o+g7H3vjdWjBwuKpgAYFHIzL6ORRB+iqDjjtJnLIW9Mzxn5szOA==} + dependencies: + '@fastify/deepmerge': 1.3.0 + ajv: 8.12.0 + ajv-formats: 2.1.1 + fast-deep-equal: 3.1.3 + fast-uri: 2.2.0 + rfdc: 1.3.0 + dev: false + + /fast-jwt/1.7.2: + resolution: {integrity: sha512-OEInypGXJhtURzq9GbFM5KaALUu9+4IV3kJEbWPuqOBN5JBe7A51Tx0CaQYHGC9GNfZnr5npA0lCIMaWiZmz/A==} + engines: {node: '>=14 <20'} + dependencies: + asn1.js: 5.4.1 + ecdsa-sig-formatter: 1.0.11 + mnemonist: 0.39.5 + dev: false + + /fast-levenshtein/2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true + + /fast-querystring/1.1.0: + resolution: {integrity: sha512-LWkjBCZlxjnSanuPpZ6mHswjy8hQv3VcPJsQB3ltUF2zjvrycr0leP3TSTEEfvQ1WEMSRl5YNsGqaft9bjLqEw==} + dependencies: + fast-decode-uri-component: 1.0.1 + dev: false + + /fast-redact/3.1.2: + resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} + engines: {node: '>=6'} + dev: false + + /fast-uri/2.2.0: + resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} + dev: false + + /fastfall/1.5.1: + resolution: {integrity: sha512-KH6p+Z8AKPXnmA7+Iz2Lh8ARCMr+8WNPVludm1LGkZoD2MjY6LVnRMtTKhkdzI+jr0RzQWXKzKyBJm1zoHEL4Q==} + engines: {node: '>=0.10.0'} + dependencies: + reusify: 1.0.4 + dev: false + + /fastify-plugin/3.0.1: + resolution: {integrity: sha512-qKcDXmuZadJqdTm6vlCqioEbyewF60b/0LOFCcYN1B6BIZGlYJumWWOYs70SFYLDAH4YqdE1cxH/RKMG7rFxgA==} + dev: false + + /fastify-plugin/4.5.0: + resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==} + dev: false + + /fastify/4.11.0: + resolution: {integrity: sha512-JteZ8pjEqd+6n+azQnQfSJV8MUMxAmxbvC2Dx/Mybj039Lf/u3kda9Kq84uy/huCpqCzZoyHIZS5JFGF3wLztw==} + dependencies: + '@fastify/ajv-compiler': 3.5.0 + '@fastify/error': 3.2.0 + '@fastify/fast-json-stringify-compiler': 4.2.0 + abstract-logging: 2.0.1 + avvio: 8.2.0 + content-type: 1.0.4 + find-my-way: 7.4.0 + light-my-request: 5.8.0 + pino: 8.8.0 + process-warning: 2.1.0 + proxy-addr: 2.0.7 + rfdc: 1.3.0 + secure-json-parse: 2.7.0 + semver: 7.3.8 + tiny-lru: 10.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /fastparallel/2.4.1: + resolution: {integrity: sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==} + dependencies: + reusify: 1.0.4 + xtend: 4.0.2 + dev: false + + /fastq/1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + + /fastseries/1.7.2: + resolution: {integrity: sha512-dTPFrPGS8SNSzAt7u/CbMKCJ3s01N04s4JFbORHcmyvVfVKmbhMD1VtRbh5enGHxkaQDqWyLefiKOGGmohGDDQ==} + dependencies: + reusify: 1.0.4 + xtend: 4.0.2 + dev: false + + /figlet/1.5.2: + resolution: {integrity: sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==} + engines: {node: '>= 0.4.0'} + dev: false + + /figures/3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + dependencies: + escape-string-regexp: 1.0.5 + dev: true + + /file-entry-cache/6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: true + + /file-type/18.1.0: + resolution: {integrity: sha512-FqjmVvHjX5C/EnibCENAsCMIg7HgUYO0vDypt5V8RmtKDk7eUa+/6mEWSrY4PStFhUt0K3CoE8stjLJCcMsJFQ==} + engines: {node: '>=14.16'} + dependencies: + readable-web-to-node-stream: 3.0.2 + strtok3: 7.0.0 + token-types: 5.0.1 + dev: false + + /file-uri-to-path/1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: false + optional: true + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /finalhandler/1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /find-my-way/7.4.0: + resolution: {integrity: sha512-JFT7eURLU5FumlZ3VBGnveId82cZz7UR7OUu+THQJOwdQXxmS/g8v0KLoFhv97HreycOrmAbqjXD/4VG2j0uMQ==} + engines: {node: '>=14'} + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.0 + safe-regex2: 2.0.0 + dev: false + + /find-up/4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + dev: true + + /find-up/5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: true + + /flat-cache/3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.7 + rimraf: 3.0.2 + dev: true + + /flatted/3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + dev: true + + /forwarded/0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: false + + /fresh/0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: false + + /fs-extra/10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: false + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: false + + /get-caller-file/2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + + /get-intrinsic/1.1.3: + resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + dev: false + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob/7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /globals/13.19.0: + resolution: {integrity: sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /graceful-fs/4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: false + + /grapheme-splitter/1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + dev: true + + /has-flag/3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: false + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: false + + /http-errors/2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + + /iconv-lite/0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + + /ieee754/1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: false + + /ignore-by-default/1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + dev: true + + /ignore/5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + + /import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /imurmurhash/0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /inquirer/7.3.3: + resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} + engines: {node: '>=8.0.0'} + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + run-async: 2.4.1 + rxjs: 6.6.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + dev: true + + /ipaddr.js/1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: false + + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-fullwidth-code-point/3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-path-inside/3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /isexe/2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /iterate-object/1.3.4: + resolution: {integrity: sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==} + dev: false + + /js-sdsl/4.2.0: + resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==} + dev: true + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /json-buffer/3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: false + + /json-fixer/1.6.15: + resolution: {integrity: sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==} + engines: {node: '>=10'} + dependencies: + '@babel/runtime': 7.20.7 + chalk: 4.1.2 + pegjs: 0.10.0 + dev: true + + /json-schema-traverse/0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-schema-traverse/1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + dev: false + + /json-stable-stringify-without-jsonify/1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /jsonfile/6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.10 + dev: false + + /keyv/4.5.2: + resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} + dependencies: + json-buffer: 3.0.1 + dev: false + + /leeks.js/0.2.4: + resolution: {integrity: sha512-yFR6BtcTA/5s2FJAVkPn2VEzqO76DqBIdkC+1vNyersz1Hw0DxhunRu7bI7/XKxwrNy8x0K4e1p4YQKIGRCBww==} + dev: false + + /leekslazylogger/5.0.0: + resolution: {integrity: sha512-tBWBTjam1hpLfLBp6L/dVMwPiwMSLUiaWbS05aeXf1r/PjR+YmktawitqzRYiiAtdauyuPvnNtnm+W9A9tkudg==} + engines: {node: '>=14'} + dependencies: + '@eartharoid/deep-merge': 0.0.2 + '@eartharoid/dtf': 2.0.1 + leeks.js: 0.2.4 + dev: false + + /levn/0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /light-my-request/5.8.0: + resolution: {integrity: sha512-4BtD5C+VmyTpzlDPCZbsatZMJVgUIciSOwYhJDCbLffPZ35KoDkDj4zubLeHDEb35b4kkPeEv5imbh+RJxK/Pg==} + dependencies: + cookie: 0.5.0 + process-warning: 2.1.0 + set-cookie-parser: 2.5.1 + dev: false + + /locate-path/5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 + dev: true + + /locate-path/6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /lodash.merge/4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /lodash.snakecase/4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + dev: false + + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + /lru-cache/6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: false + + /map-o/2.0.10: + resolution: {integrity: sha512-BxazE81fVByHWasyXhqKeo2m7bFKYu+ZbEfiuexMOnklXW+tzDvnlTi/JaklEeuuwqcqJzPaf9q+TWptSGXeLg==} + dependencies: + iterate-object: 1.3.4 + dev: false + + /marked/4.2.5: + resolution: {integrity: sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==} + engines: {node: '>= 12'} + hasBin: true + dev: false + + /media-typer/0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + dev: false + + /merge-descriptors/1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + dev: false + + /methods/1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false + + /mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types/2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /mime/1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: false + + /mimic-fn/2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true + + /minimalistic-assert/1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + dev: false + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + + /mnemonist/0.39.5: + resolution: {integrity: sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==} + dependencies: + obliterator: 2.0.4 + dev: false + + /ms/2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: false + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + /ms/2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + /mustache/4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + dev: false + + /mute-stream/0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + dev: true + + /nan/2.17.0: + resolution: {integrity: sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==} + dev: false + optional: true + + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: false + + /natural-compare/1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /negotiator/0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + + /node-dir/0.1.17: + resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} + engines: {node: '>= 0.10.5'} + dependencies: + minimatch: 3.1.2 + dev: false + + /node-emoji/1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + dependencies: + lodash: 4.17.21 + dev: false + + /node-fetch/2.6.8: + resolution: {integrity: sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: true + + /node-gyp-build/4.6.0: + resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} + hasBin: true + dev: false + + /nodemon/2.0.20: + resolution: {integrity: sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==} + engines: {node: '>=8.10.0'} + hasBin: true + requiresBuild: true + dependencies: + chokidar: 3.5.3 + debug: 3.2.7_supports-color@5.5.0 + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 5.7.1 + simple-update-notifier: 1.1.0 + supports-color: 5.5.0 + touch: 3.1.0 + undefsafe: 2.0.5 + dev: true + + /nopt/1.0.10: + resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: true + + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /object-diffy/1.0.4: + resolution: {integrity: sha512-zsgUWZhu9YtP7kp+PvCzUhYtOurBa7qIS2XUJFyVooq+I/ZlwFe0aHp1pyek/dpqd+EEYxM46j8czpW54JM2EA==} + dev: false + + /object-inspect/1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: false + + /obliterator/2.0.4: + resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} + dev: false + + /on-exit-leak-free/2.1.0: + resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} + dev: false + + /on-finished/2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + + /onetime/5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + dependencies: + mimic-fn: 2.1.0 + dev: true + + /optionator/0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.3 + dev: true + + /os-tmpdir/1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: true + + /p-limit/2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + dev: true + + /p-limit/3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: true + + /p-locate/4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + dependencies: + p-limit: 2.3.0 + dev: true + + /p-locate/5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /p-try/2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: true + + /pad/3.2.0: + resolution: {integrity: sha512-2u0TrjcGbOjBTJpyewEl4hBO3OeX5wWue7eIFPzQTg6wFSvoaHcBTTUY5m+n0hd04gmTCPuY0kCpVIVuw5etwg==} + engines: {node: '>= 4.0.0'} + dependencies: + wcwidth: 1.0.1 + dev: false + + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parseurl/1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: false + + /path-exists/4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key/3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-to-regexp/0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + dev: false + + /peek-readable/5.0.0: + resolution: {integrity: sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==} + engines: {node: '>=14.16'} + dev: false + + /pegjs/0.10.0: + resolution: {integrity: sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: false + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pify/5.0.0: + resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} + engines: {node: '>=10'} + dev: true + + /pino-abstract-transport/1.0.0: + resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==} + dependencies: + readable-stream: 4.3.0 + split2: 4.1.0 + dev: false + + /pino-std-serializers/6.1.0: + resolution: {integrity: sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==} + dev: false + + /pino/8.8.0: + resolution: {integrity: sha512-cF8iGYeu2ODg2gIwgAHcPrtR63ILJz3f7gkogaHC/TXVVXxZgInmNYiIpDYEwgEkxZti2Se6P2W2DxlBIZe6eQ==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.1.2 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 1.0.0 + pino-std-serializers: 6.1.0 + process-warning: 2.1.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.4.2 + sonic-boom: 3.2.1 + thread-stream: 2.3.0 + dev: false + + /postcss/8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: false + + /prelude-ls/1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /prisma/4.9.0: + resolution: {integrity: sha512-bS96oZ5oDFXYgoF2l7PJ3Mp1wWWfLOo8B/jAfbA2Pn0Wm5Z/owBHzaMQKS3i1CzVBDWWPVnOohmbJmjvkcHS5w==} + engines: {node: '>=14.17'} + hasBin: true + requiresBuild: true + dependencies: + '@prisma/engines': 4.9.0 + dev: false + + /process-warning/2.1.0: + resolution: {integrity: sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg==} + dev: false + + /process/0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + dev: false + + /proxy-addr/2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: false + + /pstree.remy/1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + dev: true + + /pump/3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: false + + /punycode/2.2.0: + resolution: {integrity: sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==} + engines: {node: '>=6'} + + /qs/6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /quick-format-unescaped/4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + dev: false + + /range-parser/1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: false + + /raw-body/2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + + /readable-stream/3.6.0: + resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: false + + /readable-stream/4.3.0: + resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + dev: false + + /readable-web-to-node-stream/3.0.2: + resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} + engines: {node: '>=8'} + dependencies: + readable-stream: 3.6.0 + dev: false + + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /real-require/0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + dev: false + + /regenerator-runtime/0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: true + + /regexpp/3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + dev: true + + /require-directory/2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + + /require-from-string/2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + dev: false + + /require-main-filename/2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + dev: true + + /resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /restore-cursor/3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + + /ret/0.2.2: + resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==} + engines: {node: '>=4'} + dev: false + + /reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + /rfdc/1.3.0: + resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} + dev: false + + /rimraf/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /run-async/2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + dev: true + + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /rxjs/6.6.7: + resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} + engines: {npm: '>=2.0.0'} + dependencies: + tslib: 1.14.1 + dev: true + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /safe-regex2/2.0.0: + resolution: {integrity: sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==} + dependencies: + ret: 0.2.2 + dev: false + + /safe-stable-stringify/2.4.2: + resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==} + engines: {node: '>=10'} + dev: false + + /safer-buffer/2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + /secure-json-parse/2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + dev: false + + /semver/5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + dev: true + + /semver/7.0.0: + resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} + hasBin: true + dev: true + + /semver/7.3.8: + resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: false + + /send/0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /serve-static/1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + dev: false + + /set-blocking/2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: true + + /set-cookie-parser/2.5.1: + resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==} + dev: false + + /setprototypeof/1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + + /shebang-command/2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex/3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.3 + object-inspect: 1.12.3 + dev: false + + /signal-exit/3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true + + /simple-oauth2/3.4.0: + resolution: {integrity: sha512-gDPCC/xjq82FJnzF7+XGUUMWWfHeibuGsp3OYOV7yHwIibxHkq4+WSxywY63/3BF9j8SfIDygGqBrPLynx/iuQ==} + deprecated: simple-oauth2 v3 is no longer supported. Use simple-oauth2 v4 or higher for continued support + dependencies: + '@hapi/hoek': 8.5.1 + '@hapi/joi': 16.1.8 + '@hapi/wreck': 15.1.0 + date-fns: 2.29.3 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + + /simple-update-notifier/1.1.0: + resolution: {integrity: sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==} + engines: {node: '>=8.10.0'} + dependencies: + semver: 7.0.0 + dev: true + + /sonic-boom/3.2.1: + resolution: {integrity: sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==} + dependencies: + atomic-sleep: 1.0.0 + dev: false + + /sortablejs/1.15.0: + resolution: {integrity: sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==} + dev: false + + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: false + + /split2/4.1.0: + resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} + engines: {node: '>= 10.x'} + dev: false + + /statuses/2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + + /steed/1.1.3: + resolution: {integrity: sha512-EUkci0FAUiE4IvGTSKcDJIQ/eRUP2JJb56+fvZ4sdnguLTqIdKjSxUe138poW8mkvKWXW2sFPrgTsxqoISnmoA==} + dependencies: + fastfall: 1.5.1 + fastparallel: 2.4.1 + fastq: 1.15.0 + fastseries: 1.7.2 + reusify: 1.0.4 + dev: false + + /streamsearch/1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: false + + /string-width/4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /string_decoder/1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /strip-ansi/6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /strtok3/7.0.0: + resolution: {integrity: sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==} + engines: {node: '>=14.16'} + dependencies: + '@tokenizer/token': 0.3.0 + peek-readable: 5.0.0 + dev: false + + /supports-color/5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + + /supports-hyperlinks/2.3.0: + resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + dev: false + + /svelte-modals/1.2.0-beta.0: + resolution: {integrity: sha512-c6bHI9iGryCTUixJ8hHeG6mlus5AJUEBiB2B+iRTergQE429S1vME55XBdc8HU71X8e9xWOcjWAKm3X1oLpg8Q==} + dev: false + + /terminal-link/2.1.1: + resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} + engines: {node: '>=8'} + dependencies: + ansi-escapes: 4.3.2 + supports-hyperlinks: 2.3.0 + dev: false + + /text-table/0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /thread-stream/2.3.0: + resolution: {integrity: sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==} + dependencies: + real-require: 0.2.0 + dev: false + + /through/2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true + + /tiny-lru/10.0.1: + resolution: {integrity: sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==} + engines: {node: '>=6'} + dev: false + + /tmp/0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + dependencies: + os-tmpdir: 1.0.2 + dev: true + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /toidentifier/1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + + /token-types/5.0.1: + resolution: {integrity: sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==} + engines: {node: '>=14.16'} + dependencies: + '@tokenizer/token': 0.3.0 + ieee754: 1.2.1 + dev: false + + /touch/3.1.0: + resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} + hasBin: true + dependencies: + nopt: 1.0.10 + dev: true + + /tr46/0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: true + + /ts-mixer/6.0.2: + resolution: {integrity: sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A==} + dev: false + + /tslib/1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + dev: true + + /tslib/2.4.1: + resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} + dev: false + + /type-check/0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-fest/0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /type-fest/0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + /type-is/1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + + /undefsafe/2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + dev: true + + /undici/5.15.0: + resolution: {integrity: sha512-wCAZJDyjw9Myv+Ay62LAoB+hZLPW9SmKbQkbHIhMw/acKSlpn7WohdMUc/Vd4j1iSMBO0hWwU8mjB7a5p5bl8g==} + engines: {node: '>=12.18'} + dependencies: + busboy: 1.6.0 + dev: false + + /universalify/2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + dev: false + + /unpipe/1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: false + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.2.0 + + /utf-8-validate/5.0.10: + resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.6.0 + dev: false + + /util-deprecate/1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false + + /utils-merge/1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + dev: false + + /vary/1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: false + + /wcwidth/1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + dependencies: + defaults: 1.0.4 + dev: false + + /webidl-conversions/3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: true + + /whatwg-url/5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: true + + /which-module/2.0.0: + resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} + dev: true + + /which/2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /word-wrap/1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + dev: true + + /wrap-ansi/6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + /ws/8.12.0_3cxu5zja4e2r5wmvge7mdcljwq: + resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dependencies: + bufferutil: 4.0.7 + utf-8-validate: 5.0.10 + dev: false + + /xtend/4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: false + + /y18n/4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + dev: true + + /yallist/4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: false + + /yaml/1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: false + + /yargs-parser/18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + dev: true + + /yargs/15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.0 + y18n: 4.0.3 + yargs-parser: 18.1.3 + dev: true + + /yocto-queue/0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true + + /zlib-sync/0.1.7: + resolution: {integrity: sha512-UmciU6ZrIwtwPC8noMzq+kGMdiWwNRZ3wC0SbED4Ew5Ikqx14MqDPRs/Pbk+3rZPh5SzsOgUBs1WRE0iieddpg==} + requiresBuild: true + dependencies: + nan: 2.17.0 + dev: false + optional: true + + github.com/discord/erlpack/cbe76be04c2210fc9cb6ff95910f0937c1011d04: + resolution: {tarball: https://codeload.github.com/discord/erlpack/tar.gz/cbe76be04c2210fc9cb6ff95910f0937c1011d04} + name: erlpack + version: 0.1.3 + requiresBuild: true + dependencies: + bindings: 1.5.0 + nan: 2.17.0 + dev: false + optional: true From ff37d63f2abaaf3e5cdc1f71b9fb29fe5e5fe18d Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 1 Feb 2023 21:14:01 +0000 Subject: [PATCH 220/409] =?UTF-8?q?the=20other=20half=20of=20the=20previou?= =?UTF-8?q?s=20commit=20=F0=9F=A4=A6=E2=80=8D=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index bf2418f..10457e7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ prisma/ *.env* *.db* *.log -*-lock.* user/config.yml user/**/*.* !user/**/.gitkeep From 5a2106caa47c04d99169dd21d7750ed15777bb30 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 1 Feb 2023 21:19:48 +0000 Subject: [PATCH 221/409] fix: http, improve env --- package.json | 8 +- pnpm-lock.yaml | 152 ++++++++++++++++++------------------ scripts/postinstall.js | 14 +++- scripts/preinstall.js | 26 ++++-- src/http.js | 14 ++-- src/routes/auth/callback.js | 1 + 6 files changed, 118 insertions(+), 97 deletions(-) diff --git a/package.json b/package.json index da125ba..d61b49b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.0", + "version": "4.0.0-beta.1", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.1.6", + "@discord-tickets/settings": "^1.1.7", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", @@ -48,7 +48,7 @@ "discord.js": "^14.7.1", "dotenv": "^16.0.3", "express": "^4.18.2", - "fastify": "^4.11.0", + "fastify": "^4.12.0", "figlet": "^1.5.2", "fs-extra": "^10.1.0", "keyv": "^4.5.2", @@ -67,7 +67,7 @@ }, "devDependencies": { "all-contributors-cli": "^6.24.0", - "eslint": "^8.31.0", + "eslint": "^8.33.0", "eslint-plugin-unused-imports": "^2.0.0", "nodemon": "^2.0.20" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f58c05b..71cb38b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7 +1,7 @@ lockfileVersion: 5.4 specifiers: - '@discord-tickets/settings': ^1.1.6 + '@discord-tickets/settings': ^1.1.7 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -17,10 +17,10 @@ specifiers: discord.js: ^14.7.1 dotenv: ^16.0.3 erlpack: github:discord/erlpack - eslint: ^8.31.0 + eslint: ^8.33.0 eslint-plugin-unused-imports: ^2.0.0 express: ^4.18.2 - fastify: ^4.11.0 + fastify: ^4.12.0 figlet: ^1.5.2 fs-extra: ^10.1.0 keyv: ^4.5.2 @@ -41,7 +41,7 @@ specifiers: zlib-sync: ^0.1.7 dependencies: - '@discord-tickets/settings': 1.1.6 + '@discord-tickets/settings': 1.1.7 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -55,7 +55,7 @@ dependencies: discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq dotenv: 16.0.3 express: 4.18.2 - fastify: 4.11.0 + fastify: 4.12.0 figlet: 1.5.2 fs-extra: 10.1.0 keyv: 4.5.2 @@ -80,26 +80,26 @@ optionalDependencies: devDependencies: all-contributors-cli: 6.24.0 - eslint: 8.31.0 - eslint-plugin-unused-imports: 2.0.0_eslint@8.31.0 + eslint: 8.33.0 + eslint-plugin-unused-imports: 2.0.0_eslint@8.33.0 nodemon: 2.0.20 packages: - /@babel/runtime/7.20.7: - resolution: {integrity: sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==} + /@babel/runtime/7.20.13: + resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 dev: true - /@discord-tickets/settings/1.1.6: - resolution: {integrity: sha512-NdU+M3j224yCB1fmPQTNWOIsgBQztQQY5uc+wugx5DqF8MJAS7LsQoLEymclx7h1wB/tdykLv2MNPNnjuFXwBg==} + /@discord-tickets/settings/1.1.7: + resolution: {integrity: sha512-Y10SHQafNUkIhfM3hf6MNgEWm7z/rAGkbfhAuuBkpcITAm9mt6Kf9jm3RH1FZibPeDH8Mvd5U1208xLejTxDHA==} dependencies: '@fortawesome/fontawesome-free': 6.2.1 cookie: 0.5.0 emoji-name-map: 1.2.9 - marked: 4.2.5 + marked: 4.2.12 ms: 2.1.3 postcss: 8.4.21 sortablejs: 1.15.0 @@ -112,10 +112,10 @@ packages: dependencies: '@discordjs/util': 0.1.0 '@sapphire/shapeshift': 3.8.1 - discord-api-types: 0.37.28 + discord-api-types: 0.37.31 fast-deep-equal: 3.1.3 ts-mixer: 6.0.2 - tslib: 2.4.1 + tslib: 2.5.0 dev: false /@discordjs/collection/1.3.0: @@ -131,10 +131,10 @@ packages: '@discordjs/util': 0.1.0 '@sapphire/async-queue': 1.5.0 '@sapphire/snowflake': 3.4.0 - discord-api-types: 0.37.28 - file-type: 18.1.0 - tslib: 2.4.1 - undici: 5.15.0 + discord-api-types: 0.37.31 + file-type: 18.2.0 + tslib: 2.5.0 + undici: 5.16.0 dev: false /@discordjs/util/0.1.0: @@ -171,7 +171,7 @@ packages: ajv: 6.12.6 debug: 4.3.4 espree: 9.4.1 - globals: 13.19.0 + globals: 13.20.0 ignore: 5.2.4 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -220,7 +220,7 @@ packages: /@fastify/http-proxy/8.4.0_3cxu5zja4e2r5wmvge7mdcljwq: resolution: {integrity: sha512-H8nwsmawFtKKRE6uhh1BtF1gQi/l147SmLsDGxB0HdYTHzjXz6uSQO3lEVmY7unKMzbArRjdoJQkEGpScszdSw==} dependencies: - '@fastify/reply-from': 8.3.1 + '@fastify/reply-from': 8.4.0 ws: 8.12.0_3cxu5zja4e2r5wmvge7mdcljwq transitivePeerDependencies: - bufferutil @@ -247,8 +247,8 @@ packages: - supports-color dev: false - /@fastify/reply-from/8.3.1: - resolution: {integrity: sha512-fRByAvTMXuBuYIimcinukOB3YdmqtYPeoybXIBNY0aPVgetHkmCVffBo/M4pEOib9Pes8wuoYL4VawI65aHl4w==} + /@fastify/reply-from/8.4.0: + resolution: {integrity: sha512-ju8h7k1aP3qHWwYOVZK+1jJJA6HnxXOHassU4mSRJ02ekDrCYD1t2gjCZWVHBRwJsAnMKnqfqWIzlSc+nRqyzA==} dependencies: '@fastify/error': 3.2.0 end-of-stream: 1.4.4 @@ -256,7 +256,7 @@ packages: fastify-plugin: 4.5.0 pump: 3.0.0 tiny-lru: 10.0.1 - undici: 5.15.0 + undici: 5.16.0 dev: false /@fortawesome/fontawesome-free/6.2.1: @@ -448,16 +448,16 @@ packages: negotiator: 0.6.3 dev: false - /acorn-jsx/5.3.2_acorn@8.8.1: + /acorn-jsx/5.3.2_acorn@8.8.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.8.1 + acorn: 8.8.2 dev: true - /acorn/8.8.1: - resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==} + /acorn/8.8.2: + resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -494,14 +494,14 @@ packages: engines: {node: '>=4'} hasBin: true dependencies: - '@babel/runtime': 7.20.7 + '@babel/runtime': 7.20.13 async: 3.2.4 chalk: 4.1.2 didyoumean: 1.2.2 inquirer: 7.3.3 json-fixer: 1.6.15 lodash: 4.17.21 - node-fetch: 2.6.8 + node-fetch: 2.6.9 pify: 5.0.0 yargs: 15.4.1 transitivePeerDependencies: @@ -602,7 +602,7 @@ packages: engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dependencies: bytes: 3.1.2 - content-type: 1.0.4 + content-type: 1.0.5 debug: 2.6.9 depd: 2.0.0 destroy: 1.2.0 @@ -661,7 +661,7 @@ packages: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: function-bind: 1.1.1 - get-intrinsic: 1.1.3 + get-intrinsic: 1.2.0 dev: false /callsites/3.1.0: @@ -747,8 +747,8 @@ packages: safe-buffer: 5.2.1 dev: false - /content-type/1.0.4: - resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + /content-type/1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} dev: false @@ -847,8 +847,8 @@ packages: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true - /discord-api-types/0.37.28: - resolution: {integrity: sha512-K0fw7m7km9th3dCQ2AR90q/FwX3uAj+OLc+Zuo39VY9vCn0Ux/iObM4y1zJYIH3vTc+QlrksVErUvyeONjOKMQ==} + /discord-api-types/0.37.31: + resolution: {integrity: sha512-k9DQQ7Wv+ehiF7901qk/FnP47k6O2MHm3meQFee4gUzi5dfGAVLf7SfLNtb4w7G2dmukJyWQtVJEDF9oMb9yuQ==} dev: false /discord.js/14.7.1_3cxu5zja4e2r5wmvge7mdcljwq: @@ -861,11 +861,11 @@ packages: '@discordjs/util': 0.1.0 '@sapphire/snowflake': 3.4.0 '@types/ws': 8.5.4 - discord-api-types: 0.37.28 + discord-api-types: 0.37.31 fast-deep-equal: 3.1.3 lodash.snakecase: 4.1.1 - tslib: 2.4.1 - undici: 5.15.0 + tslib: 2.5.0 + undici: 5.16.0 ws: 8.12.0_3cxu5zja4e2r5wmvge7mdcljwq transitivePeerDependencies: - bufferutil @@ -935,7 +935,7 @@ packages: engines: {node: '>=10'} dev: true - /eslint-plugin-unused-imports/2.0.0_eslint@8.31.0: + /eslint-plugin-unused-imports/2.0.0_eslint@8.33.0: resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -945,7 +945,7 @@ packages: '@typescript-eslint/eslint-plugin': optional: true dependencies: - eslint: 8.31.0 + eslint: 8.33.0 eslint-rule-composer: 0.3.0 dev: true @@ -962,13 +962,13 @@ packages: estraverse: 5.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.31.0: + /eslint-utils/3.0.0_eslint@8.33.0: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' dependencies: - eslint: 8.31.0 + eslint: 8.33.0 eslint-visitor-keys: 2.1.0 dev: true @@ -982,8 +982,8 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.31.0: - resolution: {integrity: sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==} + /eslint/8.33.0: + resolution: {integrity: sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: @@ -998,7 +998,7 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.31.0 + eslint-utils: 3.0.0_eslint@8.33.0 eslint-visitor-keys: 3.3.0 espree: 9.4.1 esquery: 1.4.0 @@ -1007,14 +1007,14 @@ packages: file-entry-cache: 6.0.1 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.19.0 + globals: 13.20.0 grapheme-splitter: 1.0.4 ignore: 5.2.4 import-fresh: 3.3.0 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-sdsl: 4.2.0 + js-sdsl: 4.3.0 js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 @@ -1034,8 +1034,8 @@ packages: resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.8.1 - acorn-jsx: 5.3.2_acorn@8.8.1 + acorn: 8.8.2 + acorn-jsx: 5.3.2_acorn@8.8.2 eslint-visitor-keys: 3.3.0 dev: true @@ -1086,7 +1086,7 @@ packages: array-flatten: 1.1.1 body-parser: 1.20.1 content-disposition: 0.5.4 - content-type: 1.0.4 + content-type: 1.0.5 cookie: 0.5.0 cookie-signature: 1.0.6 debug: 2.6.9 @@ -1126,6 +1126,10 @@ packages: tmp: 0.0.33 dev: true + /fast-content-type-parse/1.0.0: + resolution: {integrity: sha512-Xbc4XcysUXcsP5aHUU7Nq3OwvHq97C+WnbkeIefpeYLX+ryzFJlU6OStFJhs6Ol0LkUGpcK+wL0JwfM+FCU5IA==} + dev: false + /fast-decode-uri-component/1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} dev: false @@ -1191,15 +1195,15 @@ packages: resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==} dev: false - /fastify/4.11.0: - resolution: {integrity: sha512-JteZ8pjEqd+6n+azQnQfSJV8MUMxAmxbvC2Dx/Mybj039Lf/u3kda9Kq84uy/huCpqCzZoyHIZS5JFGF3wLztw==} + /fastify/4.12.0: + resolution: {integrity: sha512-Hh2GCsOCqnOuewWSvqXlpq5V/9VA+/JkVoooQWUhrU6gryO9+/UGOoF/dprGcKSDxkM/9TkMXSffYp8eA/YhYQ==} dependencies: '@fastify/ajv-compiler': 3.5.0 '@fastify/error': 3.2.0 '@fastify/fast-json-stringify-compiler': 4.2.0 abstract-logging: 2.0.1 avvio: 8.2.0 - content-type: 1.0.4 + fast-content-type-parse: 1.0.0 find-my-way: 7.4.0 light-my-request: 5.8.0 pino: 8.8.0 @@ -1251,8 +1255,8 @@ packages: flat-cache: 3.0.4 dev: true - /file-type/18.1.0: - resolution: {integrity: sha512-FqjmVvHjX5C/EnibCENAsCMIg7HgUYO0vDypt5V8RmtKDk7eUa+/6mEWSrY4PStFhUt0K3CoE8stjLJCcMsJFQ==} + /file-type/18.2.0: + resolution: {integrity: sha512-M3RQMWY3F2ykyWZ+IHwNCjpnUmukYhtdkGGC1ZVEUb0ve5REGF7NNJ4Q9ehCUabtQKtSVFOMbFTXgJlFb0DQIg==} engines: {node: '>=14.16'} dependencies: readable-web-to-node-stream: 3.0.2 @@ -1364,8 +1368,8 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dev: true - /get-intrinsic/1.1.3: - resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==} + /get-intrinsic/1.2.0: + resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} dependencies: function-bind: 1.1.1 has: 1.0.3 @@ -1397,8 +1401,8 @@ packages: path-is-absolute: 1.0.1 dev: true - /globals/13.19.0: - resolution: {integrity: sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==} + /globals/13.20.0: + resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} engines: {node: '>=8'} dependencies: type-fest: 0.20.2 @@ -1552,8 +1556,8 @@ packages: resolution: {integrity: sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==} dev: false - /js-sdsl/4.2.0: - resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==} + /js-sdsl/4.3.0: + resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} dev: true /js-yaml/4.1.0: @@ -1571,7 +1575,7 @@ packages: resolution: {integrity: sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==} engines: {node: '>=10'} dependencies: - '@babel/runtime': 7.20.7 + '@babel/runtime': 7.20.13 chalk: 4.1.2 pegjs: 0.10.0 dev: true @@ -1669,8 +1673,8 @@ packages: iterate-object: 1.3.4 dev: false - /marked/4.2.5: - resolution: {integrity: sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==} + /marked/4.2.12: + resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==} engines: {node: '>= 12'} hasBin: true dev: false @@ -1779,8 +1783,8 @@ packages: lodash: 4.17.21 dev: false - /node-fetch/2.6.8: - resolution: {integrity: sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==} + /node-fetch/2.6.9: + resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==} engines: {node: 4.x || >=6.0.0} peerDependencies: encoding: ^0.1.0 @@ -2053,8 +2057,8 @@ packages: once: 1.4.0 dev: false - /punycode/2.2.0: - resolution: {integrity: sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==} + /punycode/2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} /qs/6.11.0: @@ -2300,7 +2304,7 @@ packages: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: call-bind: 1.0.2 - get-intrinsic: 1.1.3 + get-intrinsic: 1.2.0 object-inspect: 1.12.3 dev: false @@ -2501,8 +2505,8 @@ packages: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - /tslib/2.4.1: - resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} + /tslib/2.5.0: + resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} dev: false /type-check/0.4.0: @@ -2533,8 +2537,8 @@ packages: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true - /undici/5.15.0: - resolution: {integrity: sha512-wCAZJDyjw9Myv+Ay62LAoB+hZLPW9SmKbQkbHIhMw/acKSlpn7WohdMUc/Vd4j1iSMBO0hWwU8mjB7a5p5bl8g==} + /undici/5.16.0: + resolution: {integrity: sha512-KWBOXNv6VX+oJQhchXieUznEmnJMqgXMbs0xxH2t8q/FUAWSJvOSr/rMaZKnX5RIVq7JDn0JbP4BOnKG2SGXLQ==} engines: {node: '>=12.18'} dependencies: busboy: 1.6.0 @@ -2553,7 +2557,7 @@ packages: /uri-js/4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: 2.2.0 + punycode: 2.3.0 /utf-8-validate/5.0.10: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 67d31c8..f36bd39 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -1,10 +1,16 @@ +/* eslint-disable no-console */ require('dotenv').config(); const fs = require('fs-extra'); const util = require('util'); const exec = util.promisify(require('child_process').exec); +const { short } = require('leeks.js'); + +function log(...strings) { + console.log(short('&9[postinstall]&r'), ...strings); +} async function npx(cmd) { - console.log(`[postinstall] > ${cmd}`); + log(`> ${cmd}`); const { stderr, stdout, @@ -17,14 +23,14 @@ const providers = ['mysql', 'postgresql', 'sqlite']; const provider = process.env.DB_PROVIDER; if (!provider) { - console.log('[postinstall] environment not set, exiting.'); + log('environment not set, exiting.'); process.exit(0); } if (!providers.includes(provider)) throw new Error(`DB_PROVIDER must be one of: ${providers}`); -console.log(`[postinstall] provider=${provider}`); -console.log(`[postinstall] copying ${provider} schema & migrations`); +log(`provider=${provider}`); +log(`copying ${provider} schema & migrations`); if (!fs.existsSync('./prisma')) fs.mkdirSync('./prisma'); fs.copySync(`./db/${provider}`, './prisma'); // copy schema & migrations diff --git a/scripts/preinstall.js b/scripts/preinstall.js index 06bca43..20261e5 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -1,5 +1,11 @@ +/* eslint-disable no-console */ const { randomBytes } = require('crypto'); const fs = require('fs'); +const { short } = require('leeks.js'); + +function log (...strings) { + console.log(short('&9[preinstall]&r'), ...strings); +} const env = { DB_CONNECTION_URL: '', @@ -7,18 +13,22 @@ const env = { DISCORD_SECRET: '', DISCORD_TOKEN: '', ENCRYPTION_KEY: randomBytes(24).toString('hex'), - HTTP_BIND: 8080, - HTTP_EXTERNAL: 'http://localhost:8080', + HTTP_EXTERNAL: 'http://127.0.0.1:8080', + HTTP_HOST: '127.0.0.1', + HTTP_PORT: 8080, + HTTP_TRUST_PROXY: false, + OVERRIDE_ARCHIVE: '', PUBLIC_BOT: false, - SETTINGS_BIND: 8888, + SETTINGS_HOST: '127.0.0.1', + SETTINGS_PORT: 8169, SUPER: '319467558166069248', }; -// check DISCORD_TOKEN because we don't want to force use of the .env file -if (!process.env.DISCORD_TOKEN && !fs.existsSync('./.env')) { - console.log('[preinstall] Generating ENCRYPTION_KEY'); +// check ENCRYPTION_KEY because we don't want to force use of the .env file +if (!process.env.ENCRYPTION_KEY && !fs.existsSync('./.env')) { + log('generating ENCRYPTION_KEY'); fs.writeFileSync('./.env', Object.entries(env).map(([k, v]) => `${k}=${v}`).join('\n')); - console.log('[preinstall] Created .env file'); + log('created .env file'); } else { - console.log('[preinstall] Nothing to do'); + log('nothing to do'); } \ No newline at end of file diff --git a/src/http.js b/src/http.js index 1e19f38..ef80113 100644 --- a/src/http.js +++ b/src/http.js @@ -1,4 +1,4 @@ -const fastify = require('fastify')({ trustProxy: true }); +const fastify = require('fastify')({ trustProxy: process.env.HTTP_TRUST_PROXY === 'true' }); const oauth = require('@fastify/oauth2'); const { domain } = require('./lib/http'); const { short } = require('leeks.js'); @@ -43,7 +43,7 @@ module.exports = async client => { secret: process.env.ENCRYPTION_KEY, }); - // proxy /settings to express + // proxy `/settings` to express fastify.register(require('@fastify/http-proxy'), { http2: false, prefix: '/settings', @@ -54,7 +54,7 @@ module.exports = async client => { }), }, rewritePrefix: '/settings', - upstream: `http://127.0.0.1:${process.env.SETTINGS_BIND}`, + upstream: `http://${process.env.SETTINGS_HOST}:${process.env.SETTINGS_PORT}`, }); // auth @@ -172,14 +172,14 @@ module.exports = async client => { client.log.verbose.http(short(`Express ${req.ip} ${req.method} ${req.route?.path ?? req.path}`)); }); express.use(handler); // let SvelteKit handle everything - express.listen(process.env.SETTINGS_BIND, () => { // start the express server - client.log.verbose.http(`Express listening on port ${process.env.SETTINGS_BIND}`); + express.listen(process.env.SETTINGS_PORT, process.env.SETTINGS_HOST, () => { // start the express server + client.log.verbose.http(`Express listening on port ${process.env.SETTINGS_PORT}`); }); // start the fastify server fastify.listen({ - host: '0.0.0.0', - port: process.env.HTTP_BIND, + host: process.env.HTTP_HOST, + port: process.env.HTTP_PORT, }, (err, addr) => { if (err) client.log.error.http(err); else client.log.success.http(`Listening at ${addr}`); diff --git a/src/routes/auth/callback.js b/src/routes/auth/callback.js index fccb57c..447773b 100644 --- a/src/routes/auth/callback.js +++ b/src/routes/auth/callback.js @@ -19,6 +19,7 @@ module.exports.get = () => ({ .setCookie('token', token, { domain: domain, httpOnly: true, + maxAge: 604800, // seconds, not milliseconds path: '/', sameSite: true, secure: false, From 12c741b2558570ccb4fbc883839c9e56ef873468 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 2 Feb 2023 16:11:10 +0000 Subject: [PATCH 222/409] fix: settings --- .npmrc | 1 + package.json | 6 +++--- pnpm-lock.yaml | 47 +++++++++++++++++++++++++++---------------- scripts/preinstall.js | 2 ++ src/http.js | 8 +++++++- 5 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..f87a044 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +auto-install-peers=true \ No newline at end of file diff --git a/package.json b/package.json index d61b49b..68c5102 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.1", + "version": "4.0.0-beta.2", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.1.7", + "@discord-tickets/settings": "^1.3.0", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", @@ -75,6 +75,6 @@ "bufferutil": "^4.0.7", "erlpack": "github:discord/erlpack", "utf-8-validate": "^5.0.10", - "zlib-sync": "^0.1.7" + "zlib-sync": "^0.1.8" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71cb38b..060b815 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7 +1,7 @@ lockfileVersion: 5.4 specifiers: - '@discord-tickets/settings': ^1.1.7 + '@discord-tickets/settings': ^1.3.0 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -38,10 +38,10 @@ specifiers: terminal-link: ^2.1.1 utf-8-validate: ^5.0.10 yaml: ^1.10.2 - zlib-sync: ^0.1.7 + zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 1.1.7 + '@discord-tickets/settings': 1.3.0_svelte@3.55.1 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -76,7 +76,7 @@ optionalDependencies: bufferutil: 4.0.7 erlpack: github.com/discord/erlpack/cbe76be04c2210fc9cb6ff95910f0937c1011d04 utf-8-validate: 5.0.10 - zlib-sync: 0.1.7 + zlib-sync: 0.1.8 devDependencies: all-contributors-cli: 6.24.0 @@ -93,8 +93,8 @@ packages: regenerator-runtime: 0.13.11 dev: true - /@discord-tickets/settings/1.1.7: - resolution: {integrity: sha512-Y10SHQafNUkIhfM3hf6MNgEWm7z/rAGkbfhAuuBkpcITAm9mt6Kf9jm3RH1FZibPeDH8Mvd5U1208xLejTxDHA==} + /@discord-tickets/settings/1.3.0_svelte@3.55.1: + resolution: {integrity: sha512-fp7JSKY1SdhoE3WuE1lEVG4+QjFe9iAh8rKZJ1+9qwMOhr5/5zqCNhNy/BDgQ7RYn1R2/8nhJJiBYIeJNQG4RA==} dependencies: '@fortawesome/fontawesome-free': 6.2.1 cookie: 0.5.0 @@ -103,7 +103,9 @@ packages: ms: 2.1.3 postcss: 8.4.21 sortablejs: 1.15.0 - svelte-modals: 1.2.0-beta.0 + svelte-modals: 1.2.1_svelte@3.55.1 + transitivePeerDependencies: + - svelte dev: false /@discordjs/builders/1.4.0: @@ -185,7 +187,7 @@ packages: resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==} dependencies: ajv: 8.12.0 - ajv-formats: 2.1.1 + ajv-formats: 2.1.1_ajv@8.12.0 fast-uri: 2.2.0 dev: false @@ -220,7 +222,7 @@ packages: /@fastify/http-proxy/8.4.0_3cxu5zja4e2r5wmvge7mdcljwq: resolution: {integrity: sha512-H8nwsmawFtKKRE6uhh1BtF1gQi/l147SmLsDGxB0HdYTHzjXz6uSQO3lEVmY7unKMzbArRjdoJQkEGpScszdSw==} dependencies: - '@fastify/reply-from': 8.4.0 + '@fastify/reply-from': 8.4.1 ws: 8.12.0_3cxu5zja4e2r5wmvge7mdcljwq transitivePeerDependencies: - bufferutil @@ -247,8 +249,8 @@ packages: - supports-color dev: false - /@fastify/reply-from/8.4.0: - resolution: {integrity: sha512-ju8h7k1aP3qHWwYOVZK+1jJJA6HnxXOHassU4mSRJ02ekDrCYD1t2gjCZWVHBRwJsAnMKnqfqWIzlSc+nRqyzA==} + /@fastify/reply-from/8.4.1: + resolution: {integrity: sha512-fGI4aRAUs7Mo/X8Fx4IAYBVnNBCqrz65syhwxnyXjBF8Yalxdnom4CQRnGERUx8Kh7GW4W+FGnjrdbqX0+fiVg==} dependencies: '@fastify/error': 3.2.0 end-of-stream: 1.4.4 @@ -462,8 +464,10 @@ packages: hasBin: true dev: true - /ajv-formats/2.1.1: + /ajv-formats/2.1.1_ajv@8.12.0: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 peerDependenciesMeta: ajv: optional: true @@ -1146,7 +1150,7 @@ packages: dependencies: '@fastify/deepmerge': 1.3.0 ajv: 8.12.0 - ajv-formats: 2.1.1 + ajv-formats: 2.1.1_ajv@8.12.0 fast-deep-equal: 3.1.3 fast-uri: 2.2.0 rfdc: 1.3.0 @@ -2428,8 +2432,17 @@ packages: supports-color: 7.2.0 dev: false - /svelte-modals/1.2.0-beta.0: - resolution: {integrity: sha512-c6bHI9iGryCTUixJ8hHeG6mlus5AJUEBiB2B+iRTergQE429S1vME55XBdc8HU71X8e9xWOcjWAKm3X1oLpg8Q==} + /svelte-modals/1.2.1_svelte@3.55.1: + resolution: {integrity: sha512-7MEKUx5wb5YppkXWFGeRlYM5FMGEnpix39u9Y6GtCNTMXRDZ7DB2Z50IYLMRTMW5lOsCdtJgFbB0E3iZMKmsAA==} + peerDependencies: + svelte: ^3.0.0 + dependencies: + svelte: 3.55.1 + dev: false + + /svelte/3.55.1: + resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==} + engines: {node: '>= 8'} dev: false /terminal-link/2.1.1: @@ -2691,8 +2704,8 @@ packages: engines: {node: '>=10'} dev: true - /zlib-sync/0.1.7: - resolution: {integrity: sha512-UmciU6ZrIwtwPC8noMzq+kGMdiWwNRZ3wC0SbED4Ew5Ikqx14MqDPRs/Pbk+3rZPh5SzsOgUBs1WRE0iieddpg==} + /zlib-sync/0.1.8: + resolution: {integrity: sha512-Xbu4odT5SbLsa1HFz8X/FvMgUbJYWxJYKB2+bqxJ6UOIIPaVGrqHEB3vyXDltSA6tTqBhSGYLgiVpzPQHYi3lA==} requiresBuild: true dependencies: nan: 2.17.0 diff --git a/scripts/preinstall.js b/scripts/preinstall.js index 20261e5..003bdf4 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -13,11 +13,13 @@ const env = { DISCORD_SECRET: '', DISCORD_TOKEN: '', ENCRYPTION_KEY: randomBytes(24).toString('hex'), + // HOST_HEADER: 'x-forwarded-host', HTTP_EXTERNAL: 'http://127.0.0.1:8080', HTTP_HOST: '127.0.0.1', HTTP_PORT: 8080, HTTP_TRUST_PROXY: false, OVERRIDE_ARCHIVE: '', + // PROTOCOL_HEADER: 'x-forwarded-proto', PUBLIC_BOT: false, SETTINGS_HOST: '127.0.0.1', SETTINGS_PORT: 8169, diff --git a/src/http.js b/src/http.js index ef80113..ad68d06 100644 --- a/src/http.js +++ b/src/http.js @@ -166,13 +166,19 @@ module.exports = async client => { // express server for settings const express = require('express')(); const { handler } = await import('@discord-tickets/settings/build/handler.js'); + process.on('sveltekit:error', ({ + error, + errorId, + }) => { + client.log.error.http(`Express ${errorId} ${error}`); + }); express.set('trust proxy', true); express.use((req, res, next) => { next(); client.log.verbose.http(short(`Express ${req.ip} ${req.method} ${req.route?.path ?? req.path}`)); }); express.use(handler); // let SvelteKit handle everything - express.listen(process.env.SETTINGS_PORT, process.env.SETTINGS_HOST, () => { // start the express server + express.listen(process.env.SETTINGS_PORT, process.env.SETTINGS_HOST, () => { // start the express server client.log.verbose.http(`Express listening on port ${process.env.SETTINGS_PORT}`); }); From 3a22ba535ba9ee712ddb8656bfdd0e49ed3fdce3 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 2 Feb 2023 16:20:39 +0000 Subject: [PATCH 223/409] chore: update dependencies --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 68c5102..1dadbce 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.3.0", + "@discord-tickets/settings": "^1.3.1", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 060b815..82567b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7 +1,7 @@ lockfileVersion: 5.4 specifiers: - '@discord-tickets/settings': ^1.3.0 + '@discord-tickets/settings': ^1.3.1 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -41,7 +41,7 @@ specifiers: zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 1.3.0_svelte@3.55.1 + '@discord-tickets/settings': 1.3.1_svelte@3.55.1 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -93,8 +93,8 @@ packages: regenerator-runtime: 0.13.11 dev: true - /@discord-tickets/settings/1.3.0_svelte@3.55.1: - resolution: {integrity: sha512-fp7JSKY1SdhoE3WuE1lEVG4+QjFe9iAh8rKZJ1+9qwMOhr5/5zqCNhNy/BDgQ7RYn1R2/8nhJJiBYIeJNQG4RA==} + /@discord-tickets/settings/1.3.1_svelte@3.55.1: + resolution: {integrity: sha512-Rv0uNjJy/gK411g1l54JbvrILYvvgeMbfglAcuCv3Q8FRJp1hEmrKq5W1/+JUTBSH7yqLbomasdBuTGkdT+2Jw==} dependencies: '@fortawesome/fontawesome-free': 6.2.1 cookie: 0.5.0 From c6597148ed9912c6cfdb4b84a9666dec84f590b9 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 2 Feb 2023 16:45:27 +0000 Subject: [PATCH 224/409] chore: update dependencies --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1dadbce..f31984d 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.3.1", + "@discord-tickets/settings": "^1.3.2", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82567b4..a24eb20 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7 +1,7 @@ lockfileVersion: 5.4 specifiers: - '@discord-tickets/settings': ^1.3.1 + '@discord-tickets/settings': ^1.3.2 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -41,7 +41,7 @@ specifiers: zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 1.3.1_svelte@3.55.1 + '@discord-tickets/settings': 1.3.2_svelte@3.55.1 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -93,8 +93,8 @@ packages: regenerator-runtime: 0.13.11 dev: true - /@discord-tickets/settings/1.3.1_svelte@3.55.1: - resolution: {integrity: sha512-Rv0uNjJy/gK411g1l54JbvrILYvvgeMbfglAcuCv3Q8FRJp1hEmrKq5W1/+JUTBSH7yqLbomasdBuTGkdT+2Jw==} + /@discord-tickets/settings/1.3.2_svelte@3.55.1: + resolution: {integrity: sha512-CjFlnI45FGNfSWMiFB5FEWuNgHs38NKhMG0j3rqHSj0PZLnSxABSNVDj8EiLpz73D+tRAnYu39YlWaGKgtxvWQ==} dependencies: '@fortawesome/fontawesome-free': 6.2.1 cookie: 0.5.0 From 5d86d9464d8f1a744776e351966fff456c83365b Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 2 Feb 2023 19:59:00 +0000 Subject: [PATCH 225/409] chore: update dependencies --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index f31984d..3b411be 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.3.2", + "@discord-tickets/settings": "^1.3.3", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a24eb20..99e4b42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7 +1,7 @@ lockfileVersion: 5.4 specifiers: - '@discord-tickets/settings': ^1.3.2 + '@discord-tickets/settings': ^1.3.3 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -41,7 +41,7 @@ specifiers: zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 1.3.2_svelte@3.55.1 + '@discord-tickets/settings': 1.3.3_svelte@3.55.1 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -93,8 +93,8 @@ packages: regenerator-runtime: 0.13.11 dev: true - /@discord-tickets/settings/1.3.2_svelte@3.55.1: - resolution: {integrity: sha512-CjFlnI45FGNfSWMiFB5FEWuNgHs38NKhMG0j3rqHSj0PZLnSxABSNVDj8EiLpz73D+tRAnYu39YlWaGKgtxvWQ==} + /@discord-tickets/settings/1.3.3_svelte@3.55.1: + resolution: {integrity: sha512-G6Z6DBsotbgY6CIhD+yVF1m1Lz/v699ONIEBKXLwMpAhXSXF+tCSzCcpRc5Do056YbVlEVxVUER4lbVtHn/ytQ==} dependencies: '@fortawesome/fontawesome-free': 6.2.1 cookie: 0.5.0 From 9fc1130c4c6e0ee6c31efaf4e4b2bc3b9a73170b Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 2 Feb 2023 20:45:18 +0000 Subject: [PATCH 226/409] fix: update cache when tags are updated --- package.json | 2 +- .../api/admin/guilds/[guild]/tags/[tag].js | 23 +++++++++++++++++++ .../api/admin/guilds/[guild]/tags/index.js | 19 +++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3b411be..455c6b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.2", + "version": "4.0.0-beta.3", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", diff --git a/src/routes/api/admin/guilds/[guild]/tags/[tag].js b/src/routes/api/admin/guilds/[guild]/tags/[tag].js index 4a954dd..0f06192 100644 --- a/src/routes/api/admin/guilds/[guild]/tags/[tag].js +++ b/src/routes/api/admin/guilds/[guild]/tags/[tag].js @@ -1,3 +1,4 @@ +const ms = require('ms'); const { logAdminEvent } = require('../../../../../../lib/logging'); module.exports.delete = fastify => ({ @@ -10,6 +11,17 @@ module.exports.delete = fastify => ({ if (original.guildId !== guildId) return res.status(404).send(new Error('Not Found')); const tag = await client.prisma.tag.delete({ where: { id: tagId } }); + const cacheKey = `cache/guild-tags:${guildId}`; + client.keyv.set(cacheKey, await client.prisma.tag.findMany({ + select: { + content: true, + id: true, + name: true, + regex: true, + }, + where: { guildId: guildId }, + }), ms('1h')); + logAdminEvent(client, { action: 'delete', guildId: req.params.guild, @@ -62,6 +74,17 @@ module.exports.patch = fastify => ({ where: { id: tagId }, }); + const cacheKey = `cache/guild-tags:${guildId}`; + client.keyv.set(cacheKey, await client.prisma.tag.findMany({ + select: { + content: true, + id: true, + name: true, + regex: true, + }, + where: { guildId: guildId }, + }), ms('1h')); + logAdminEvent(client, { action: 'update', diff: { diff --git a/src/routes/api/admin/guilds/[guild]/tags/index.js b/src/routes/api/admin/guilds/[guild]/tags/index.js index 73f31d4..35af225 100644 --- a/src/routes/api/admin/guilds/[guild]/tags/index.js +++ b/src/routes/api/admin/guilds/[guild]/tags/index.js @@ -1,3 +1,4 @@ +const ms = require('ms'); const { logAdminEvent } = require('../../../../../../lib/logging'); module.exports.get = fastify => ({ @@ -29,6 +30,24 @@ module.exports.post = fastify => ({ }, }); + const cacheKey = `cache/guild-tags:${guild.id}`; + let tags = await client.keyv.get(cacheKey); + if (!tags) { + tags = await client.prisma.tag.findMany({ + select: { + content: true, + id: true, + name: true, + regex: true, + }, + where: { guildId: guild.id }, + }); + client.keyv.set(cacheKey, tags, ms('1h')); + } else { + tags.push(tag); + client.keyv.set(cacheKey, tags, ms('1h')); + } + logAdminEvent(client, { action: 'create', guildId: guild.id, From e11956dfddbb1fb8986678222c218d3449e3e66a Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 2 Feb 2023 20:45:36 +0000 Subject: [PATCH 227/409] remove comments --- scripts/preinstall.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/preinstall.js b/scripts/preinstall.js index 003bdf4..20261e5 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -13,13 +13,11 @@ const env = { DISCORD_SECRET: '', DISCORD_TOKEN: '', ENCRYPTION_KEY: randomBytes(24).toString('hex'), - // HOST_HEADER: 'x-forwarded-host', HTTP_EXTERNAL: 'http://127.0.0.1:8080', HTTP_HOST: '127.0.0.1', HTTP_PORT: 8080, HTTP_TRUST_PROXY: false, OVERRIDE_ARCHIVE: '', - // PROTOCOL_HEADER: 'x-forwarded-proto', PUBLIC_BOT: false, SETTINGS_HOST: '127.0.0.1', SETTINGS_PORT: 8169, From fd2ab55a138fb047f0a7cfde9201b8dc211e5fae Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 3 Feb 2023 17:24:18 +0100 Subject: [PATCH 228/409] Added translation using Weblate (French) --- src/i18n/fr.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/fr.yml diff --git a/src/i18n/fr.yml b/src/i18n/fr.yml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/fr.yml @@ -0,0 +1 @@ +{} From 0eb715cb1f1e0a36c92de06e5cafc427798710eb Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 3 Feb 2023 16:41:19 +0000 Subject: [PATCH 229/409] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c12cd3..2b5e5ab 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Translation status](https://hosted.weblate.org/widgets/discord-tickets/-/bot/open-graph.png)](https://hosted.weblate.org/engage/discord-tickets/) + https://www.prisma.io/docs/reference/database-reference/supported-databases ![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create, slash/force-close, slash/claim, slash/release, and slash/move @@ -43,4 +45,4 @@ secret scan ## Contributors -[![Contributors](https://contrib.rocks/image?repo=discord-tickets/bot)](https://github.com/discord-tickets/bot/graphs/contributors) \ No newline at end of file +[![Contributors](https://contrib.rocks/image?repo=discord-tickets/bot)](https://github.com/discord-tickets/bot/graphs/contributors) From 7a40eec83fc70b6d34542073aa16d8f544b89907 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 3 Feb 2023 18:14:52 +0100 Subject: [PATCH 230/409] Added translation using Weblate (Hungarian) Co-authored-by: Isaac --- src/i18n/hu.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/hu.yml diff --git a/src/i18n/hu.yml b/src/i18n/hu.yml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/hu.yml @@ -0,0 +1 @@ +{} From 4f368bb5076c74df14306f4eb1e0f072d09d31e6 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 3 Feb 2023 18:18:59 +0100 Subject: [PATCH 231/409] Added translation using Weblate (German) Co-authored-by: Isaac --- src/i18n/de.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/de.yml diff --git a/src/i18n/de.yml b/src/i18n/de.yml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/de.yml @@ -0,0 +1 @@ +{} From 1225592227446d50d7aebda6b692d64e395ffeb3 Mon Sep 17 00:00:00 2001 From: Rabenherz112 Date: Fri, 3 Feb 2023 18:27:32 +0100 Subject: [PATCH 232/409] Translated using Weblate (German) Currently translated at 9.7% (23 of 235 strings) Co-authored-by: Rabenherz112 Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 0967ef4..b947f5a 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -1 +1,39 @@ -{} +buttons: + cancel: + emoji: ✖️ + text: Abbrechen + close: + emoji: ✖️ + text: Schließen + create: + text: Ticket erstellen + emoji: 🎫 + accept_close_request: + emoji: ✅ + text: Akzeptieren + claim: + emoji: 🙌 + confirm_open: + emoji: ✅ + text: Ticket erstellen + reject_close_request: + emoji: ✖️ + text: Ablehnen + edit: + emoji: ✏️ + text: Bearbeiten + unclaim: + text: Freigeben + emoji: ♻️ +commands: + message: + create: + name: Ein Ticket aus Nachricht erstellen + pin: + name: Nachricht anheften + not_pinnable: + description: "Diese Nachricht kann nicht angeheftet werden.\nBitten Sie einen\ + \ Administrator, die Berechtigungen des Bots zu überprüfen.\n" + title: ❌ Fehler + not_ticket: + description: Sie können nur Nachrichten in Tickets anheften. From 1243eac844b7ab657dec1bf8289917ce02d031e5 Mon Sep 17 00:00:00 2001 From: sgtJohnny Date: Fri, 3 Feb 2023 18:27:33 +0100 Subject: [PATCH 233/409] Translated using Weblate (German) Currently translated at 9.7% (23 of 235 strings) Co-authored-by: sgtJohnny Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index b947f5a..86154a2 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -13,6 +13,7 @@ buttons: text: Akzeptieren claim: emoji: 🙌 + text: Übernehmen confirm_open: emoji: ✅ text: Ticket erstellen From e96971f15850bed02aee4105508737fe1f504d81 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 3 Feb 2023 18:32:39 +0100 Subject: [PATCH 234/409] Added translation using Weblate (Finnish) Co-authored-by: Isaac --- src/i18n/fi.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/fi.yml diff --git a/src/i18n/fi.yml b/src/i18n/fi.yml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/fi.yml @@ -0,0 +1 @@ +{} From 01e658a3565936c7a3610f8060809659bdfe1ea4 Mon Sep 17 00:00:00 2001 From: Rabenherz112 Date: Fri, 3 Feb 2023 18:32:40 +0100 Subject: [PATCH 235/409] Translated using Weblate (German) Currently translated at 18.7% (44 of 235 strings) Co-authored-by: Rabenherz112 Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 86154a2..fdca5b0 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -38,3 +38,20 @@ commands: title: ❌ Fehler not_ticket: description: Sie können nur Nachrichten in Tickets anheften. + title: ❌ Dies ist kein Ticketkanal + pinned: + description: Die Nachricht wurde angeheftet. + title: ✅ Gepinnte Nachricht + slash: + add: + added: ➡️ {added} wurde von {by} hinzugefügt. + not_staff: + title: ❌ Fehler + options: + member: + name: Mitglied + ticket: + name: Ticket + success: + description: '{member} wurde zu {ticket} hinzugefügt.' + title: ✅ Hinzugefügt From de1c08afced8e32acbb3d7ff3ed3883de4d7ddfc Mon Sep 17 00:00:00 2001 From: sgtJohnny Date: Fri, 3 Feb 2023 18:32:40 +0100 Subject: [PATCH 236/409] Translated using Weblate (German) Currently translated at 18.7% (44 of 235 strings) Co-authored-by: sgtJohnny Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index fdca5b0..33c33d8 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -55,3 +55,26 @@ commands: success: description: '{member} wurde zu {ticket} hinzugefügt.' title: ✅ Hinzugefügt + description: Benutzer zu einem Ticket hinzufügen + help: + response: + links: + support: Unterstützung + links: Nützliche Links + docs: Dokumentation + commands: komplette Befehlsliste + commands: Befehle + description: '**Verwende {command} um ein Ticket zu erstellen und Hilfe zu + erhalten.**' + settings: Bot Einstellungen + name: Hilfe + force-close: + options: + reason: + name: Grund + close: + invalid_time: + title: ❌ Ungültig + options: + reason: + name: Grund From e1fa465c9b4a3f04fb19d61aeef78f687b368a09 Mon Sep 17 00:00:00 2001 From: sgtJohnny Date: Fri, 3 Feb 2023 18:40:49 +0100 Subject: [PATCH 237/409] Translated using Weblate (German) Currently translated at 23.4% (55 of 235 strings) Co-authored-by: sgtJohnny Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 33c33d8..d86cf74 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -47,15 +47,18 @@ commands: added: ➡️ {added} wurde von {by} hinzugefügt. not_staff: title: ❌ Fehler + description: Nur Teammitglieder können Benutzer zu Tickets anderer hinzufügen. options: member: name: Mitglied + description: Der Benutzer welcher zum Ticket hinzugefügt werden soll ticket: name: Ticket success: description: '{member} wurde zu {ticket} hinzugefügt.' title: ✅ Hinzugefügt description: Benutzer zu einem Ticket hinzufügen + name: hinzufügen help: response: links: @@ -68,6 +71,7 @@ commands: erhalten.**' settings: Bot Einstellungen name: Hilfe + title: Hilfe force-close: options: reason: @@ -78,3 +82,14 @@ commands: options: reason: name: Grund + move: + name: verschieben + description: Verschiebe ein Ticket in eine andere Kategorie + moved: 🗃️ {by} hat dieses Ticket von **{from}** nach **{to}** verschoben. + options: + category: + description: Die Kategorie in welche das Ticket verschoben werden soll + name: Kategorie + new: + name: neu + description: Neues Ticket erstellen From 316de401602d0789e45d614ad39108ff660979b1 Mon Sep 17 00:00:00 2001 From: Aapo Katila Date: Fri, 3 Feb 2023 18:40:50 +0100 Subject: [PATCH 238/409] Translated using Weblate (Finnish) Currently translated at 5.5% (13 of 235 strings) Co-authored-by: Aapo Katila Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/fi/ Translation: Discord Tickets/Bot --- src/i18n/fi.yml | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/i18n/fi.yml b/src/i18n/fi.yml index 0967ef4..afaef9f 100644 --- a/src/i18n/fi.yml +++ b/src/i18n/fi.yml @@ -1 +1,32 @@ -{} +commands: + message: + pin: + not_ticket: + title: ❌ Tämä ei ole tiketti kanava + slash: + add: + description: Lisää jäsen tikettiin + name: lisää + options: + ticket: + name: tiketti + member: + name: jäsen + description: Jäsen joka lisätään tikettiin + not_staff: + description: Vain henkilökunta voi lisätä toisia tiketteihin. +buttons: + close: + text: Sulje + edit: + text: Muokkaa + reject_close_request: + text: Hylkää + unclaim: + text: Julkaise + accept_close_request: + text: Hyväksy + cancel: + text: Peruuta + confirm_open: + text: Luo tiketti From 4464db07bac90bd4a9ea2cc8c824ba59af1ffd5d Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 3 Feb 2023 23:56:23 +0100 Subject: [PATCH 239/409] Added translation using Weblate (Czech) Co-authored-by: Isaac --- src/i18n/cs.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/cs.yml diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/cs.yml @@ -0,0 +1 @@ +{} From 596a59599674ff2bfb3350defd4a375bc4433ff0 Mon Sep 17 00:00:00 2001 From: Blutwurst Date: Fri, 3 Feb 2023 23:56:23 +0100 Subject: [PATCH 240/409] Translated using Weblate (German) Currently translated at 34.4% (81 of 235 strings) Co-authored-by: Blutwurst Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index d86cf74..e719554 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -51,9 +51,10 @@ commands: options: member: name: Mitglied - description: Der Benutzer welcher zum Ticket hinzugefügt werden soll + description: Das Mitglied, das dem Ticket hinzugefügt werden soll ticket: name: Ticket + description: Das Ticket, zu dem das Mitglied hinzugefügt werden soll success: description: '{member} wurde zu {ticket} hinzugefügt.' title: ✅ Hinzugefügt @@ -93,3 +94,6 @@ commands: new: name: neu description: Neues Ticket erstellen + claim: + description: Ticket übernehmen + name: Übernehmen From e55a1b37abadd9c15d3c20c0a8aa97de4ca04258 Mon Sep 17 00:00:00 2001 From: sgtJohnny Date: Fri, 3 Feb 2023 23:56:24 +0100 Subject: [PATCH 241/409] Translated using Weblate (German) Currently translated at 34.4% (81 of 235 strings) Co-authored-by: sgtJohnny Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index e719554..0f062bc 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -77,12 +77,25 @@ commands: options: reason: name: Grund + description: Der Grund für das Schließen des/der Tickets + category: + name: Kategorie + time: + name: Zeit + no_tickets: + description: Es gibt keine offenen Tickets, die länger als `{time}` inaktiv + waren. + title: Keine Tickets + not_staff: + title: ❌ Fehler close: invalid_time: title: ❌ Ungültig + description: '`{input}` ist kein gültiges Zeitformat.' options: reason: name: Grund + description: Der Grund für das Schließen des/der Tickets move: name: verschieben description: Verschiebe ein Ticket in eine andere Kategorie @@ -94,6 +107,37 @@ commands: new: name: neu description: Neues Ticket erstellen + options: + references: + name: Verweise claim: description: Ticket übernehmen name: Übernehmen + priority: + options: + priority: + description: Die Priorität des Tickets + name: Priorität + choices: + HIGH: 🔴 Hoch + LOW: 🟢 Niedrig + MEDIUM: 🟠 Mittel + success: + title: ✅ Priorität gesetzt + name: Priorität + tickets: + not_staff: + title: ❌ Fehler + options: + member: + description: Das Mitglied, dessen Tickets aufgelistet werden sollen + name: Mitglied + remove: + not_staff: + description: Nur Mitarbeiter können Mitglieder aus den Tickets anderer entfernen. + title: ❌ Fehler + options: + member: + name: Mitglied + success: + title: ✅ Entfernt From 46b539c28b007242b874e22704f9d4a9a4e4759f Mon Sep 17 00:00:00 2001 From: Aapo Katila Date: Fri, 3 Feb 2023 23:56:24 +0100 Subject: [PATCH 242/409] Translated using Weblate (Finnish) Currently translated at 36.1% (85 of 235 strings) Translated using Weblate (Finnish) Currently translated at 32.7% (77 of 235 strings) Co-authored-by: Aapo Katila Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/fi/ Translation: Discord Tickets/Bot --- src/i18n/fi.yml | 131 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/src/i18n/fi.yml b/src/i18n/fi.yml index afaef9f..2f118f9 100644 --- a/src/i18n/fi.yml +++ b/src/i18n/fi.yml @@ -3,6 +3,14 @@ commands: pin: not_ticket: title: ❌ Tämä ei ole tiketti kanava + description: Voit kiinnittää viestejä vain tiketeissä. + name: Kiinnitä viesti + not_pinnable: + title: ❌ Virhe + pinned: + description: Viesti on kiinnitetty. + create: + name: Luo tiketti viestistä slash: add: description: Lisää jäsen tikettiin @@ -10,23 +18,146 @@ commands: options: ticket: name: tiketti + description: Tiketti johon jäsen lisätään member: name: jäsen description: Jäsen joka lisätään tikettiin not_staff: description: Vain henkilökunta voi lisätä toisia tiketteihin. + title: ❌ Virhe + success: + title: ✅ Lisätty + description: '{member} lisättiin {ticket}' + force-close: + not_staff: + title: ❌ Virhe + no_tickets: + title: ❌ Ei tikettejä + options: + category: + name: kategoria + reason: + name: syy + ticket: + name: tiketti + time: + name: aika + confirm_multiple: + title: ❓ Oletko varma? + help: + description: Näyttää apua valikon + response: + links: + feedback: Palaute + links: Hyödyllisiä linkkejä + support: Tuki + commands: Koko komento lista + settings: Botin asetukset + commands: Komennot + title: Apua + name: apua + move: + name: siirrä + options: + category: + name: kategoria + description: Siirrä tiketti toiseen kategoriaan + remove: + options: + member: + description: Jäsen joka poistetaan tiketistä + name: jäsen + ticket: + name: tiketti + name: poista + not_staff: + title: ❌ Virhe + success: + title: ✅ Poistettu + description: Poista jäsen tiketistä + close: + invalid_time: + title: ❌ Virheellinen + name: sulje + options: + reason: + name: syy + description: Syy tiketin(t) sulkemiseen + description: Pyydä tiketin sulkemista + claim: + description: Varaa tiketti + name: varaa + new: + description: Luo uusi tiketti + name: uusi + priority: + options: + priority: + choices: + LOW: 🟢 Hidas + HIGH: 🔴 Kiireellinen + MEDIUM: 🟠 Normaali + topic: + name: aihe + tickets: + not_staff: + title: ❌ Virhe + response: + title: + other: '{displayName} tiketit' + own: Sinun tiketit + fields: + closed: + name: Suljetut tiketit + name: tiketit + options: + member: + name: jäsen + transfer: + options: + member: + name: jäsen + name: siirrä + transcript: + options: + ticket: + name: tiketti + release: + name: julkaise buttons: close: text: Sulje + emoji: ✖️ edit: text: Muokkaa + emoji: ✏️ reject_close_request: text: Hylkää + emoji: ✖️ unclaim: text: Julkaise + emoji: ♻️ accept_close_request: text: Hyväksy + emoji: ✅ cancel: text: Peruuta + emoji: ✖️ confirm_open: text: Luo tiketti + emoji: ✅ + create: + emoji: 🎫 + text: Luo tiketti + claim: + text: Varaa + emoji: 🙌 +ticket: + opening_message: + fields: + topic: Aihe + references_ticket: + fields: + topic: Aihe + number: Numero + feedback: Kiitos palautteestasi. From 8edcf90000eb4b8d8f654e2d3d0d80313ae671a7 Mon Sep 17 00:00:00 2001 From: Noel Horvath Date: Fri, 3 Feb 2023 23:56:24 +0100 Subject: [PATCH 243/409] Translated using Weblate (Hungarian) Currently translated at 100.0% (235 of 235 strings) Translated using Weblate (Hungarian) Currently translated at 74.4% (175 of 235 strings) Co-authored-by: Noel Horvath Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/hu/ Translation: Discord Tickets/Bot --- src/i18n/hu.yml | 381 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 380 insertions(+), 1 deletion(-) diff --git a/src/i18n/hu.yml b/src/i18n/hu.yml index 0967ef4..8961142 100644 --- a/src/i18n/hu.yml +++ b/src/i18n/hu.yml @@ -1 +1,380 @@ -{} +log: + admin: + description: + target: + panel: panel + question: kérdés + category: kategória + settings: beállítás + tag: címke + joined: '{user} {verb} {targetType}' + title: + target: + category: Kategória + settings: Beállítások + panel: Panel + question: Kérdés + tag: Címke + joined: '{targetType} {verb}' + verb: + create: létrehozva + delete: törölve + update: frissítve + changes: Változások + ticket: + added: Hozzáadott felhasználók + description: '{user} {verb} hibajegy' + removed: Eltávolított felhasználók + title: Hibjaegy {verb} + verb: + create: létrehozva + claim: begyűjtve + close: bezárva + unclaim: felszabadítva + update: frissítve + ticket: Hibajegy + message: + message: Üzenet + verb: + update: frissítve + delete: törölve + description: '{user} {verb} a(z) üzenet' + title: Üzenet {verb} +buttons: + accept_close_request: + emoji: ✅ + text: Elfogadás + cancel: + emoji: ✖️ + text: Mégse + claim: + emoji: 🙌 + text: Begyűjtés + close: + emoji: ✖️ + text: Bezárás + confirm_open: + text: Hibajegy létrehozása + emoji: ✅ + edit: + text: Szerkesztés + emoji: ✏️ + reject_close_request: + text: Elutasítás + emoji: ✖️ + create: + emoji: 🎫 + text: Hibajegy létrehozása + unclaim: + emoji: ♻️ + text: Felszabadítás +commands: + message: + pin: + not_ticket: + title: ❌ Nem hibajegy csatorna + description: Csak a hibajegyekben tudod kitűzni az üzeneteket. + name: Üzenet kitűzése + not_pinnable: + title: ❌ Hiba + description: "Ezt az üzenetet nem tudod kitűzni.\nKérlek vedd fel a kapcsolatot\ + \ egy adminsztrátorral, aki le tudja ellenőrizni a bot jogosultságait.\n" + pinned: + description: Az üzenet kitűzésre került. + title: ✅ Üzenet kitűzve + create: + name: Hibajegy létrehozása üzenetből + slash: + force-close: + options: + time: + name: idő + description: Összes hibajegy bezására, amely inaktív volt a megadott ideig + (használható a(z) `kategóriával` is) + category: + description: Összes hibajegy bezására a megadott kategóriában (használható + a(z) `idővel` is) + name: kategória + reason: + description: Indok a hibajegy(ek) bezárására + name: indok + ticket: + description: A hibajegy, amit be szeretnél zárni + name: hibajegy + no_tickets: + description: Nincs olyan nyitott hibajegy, mely inaktív lenne `{time}` ideje. + title: ❌ Nincs hibajegy + confirm_multiple: + description: "Bezására kerül **{count}** darab hibajegy, mely inaktív `{time}`\ + \ ideje:\n{tickets}\n" + title: ❓ Biztos vagy benne? + description: Hibajegy erőltetett bezárása + name: erőltetett-bezárás + not_staff: + description: Csak a személyzeti tagok erőltethetik a hibajegy bezárását. + title: ❌ Hiba + close: + invalid_time: + description: '`{input}` nem megfelelő formátumú.' + title: ❌ Érvénytelen + description: Kérelmezd a hibajegy bezárását + options: + reason: + description: Indok a hibajegy(ek) bezárásához + name: indok + name: bezárás + help: + name: segítség + response: + commands: Parancsok + links: + support: Segítség + commands: Teljes parancs lista + docs: Dokumentáció + feedback: Visszajelzés + links: Hasznos linkek + settings: Bot beállítások + description: '**Használd a(z) {command} parancsot a hibajegy létrehozásához..**' + description: Segítség menü megjelenítése + title: Segítség + tickets: + response: + title: + own: Hibajegyeid + other: '{displayName} hibajegyei' + description: Használd a(z) {transcript}, hogy letölthesd a hibajegy átiratát. + fields: + closed: + name: Bezárt hibajegyek + none: + other: '{user} még nem hozott létre hibajegyet.' + own: "Nem hoztál még létre hibajegyet.\nHasználd a(z) {new} a létrehozáshoz.\n" + open: + name: Nyitott hibajegyek + name: hibajegyek + not_staff: + description: Csak a személyzet tagjai tekinthetik meg mások hibajegyeit. + title: ❌ Hiba + description: Sorolja fel saját vagy valaki más hibajegyeit + options: + member: + description: A felhasználó, akinek a hibajegyeit szeretnéd listázni + name: felhasználó + new: + options: + references: + description: Kapcsolódó hibajegyek száma + name: hivatkozások + name: új + description: Hibajegy létrehozása + priority: + options: + priority: + description: A hibajegy prioritása + choices: + HIGH: 🔴 Magas + LOW: 🟢 Alacsony + MEDIUM: 🟠 Közepes + name: prioritás + name: prioritás + success: + title: ✅ Prioritás beállítva + description: 'A hibajegy prioritása beállításra került: `{priority}`.' + description: Hibajegy prioritásának beállítása + move: + description: Hibajegy áthelyezése másik kategóriába + moved: '🗃️ {by} áthelyezte a hibajegyet innen: **{from}** ide: **{to}**.' + options: + category: + description: Kategória, amibe át szeretnéd helyezni a hibajegyet + name: kategória + name: áthelyezés + remove: + options: + ticket: + name: hibajegy + description: A hibajegy, amiből el szeretnéd távolítani a felhasználót + member: + name: felhasználó + description: A felhasználó, akit el szeretnél távolítani a hibajegyből + name: eltávolítás + not_staff: + description: Csak a személyzeti tagok tudnak eltávolítani felhasználókat a + felhasználók hibajegyéből. + title: ❌ Hiba + removed: ⬅️ {removed} eltávolításra került {by} által. + success: + description: '{member} eltávolításra került a(z) {ticket} hibajegyből.' + title: ✅ Eltávolítva + description: Felhasználó eltávolítása a hibajegyből + release: + name: felszabadítás + description: Hibajegy felszabadítása + tag: + options: + tag: + name: címke + description: A használandó címke neve + for: + name: részére + description: A felhasználó, akihez a címkét szeretnéd kötni + description: Címke használata + name: címke + transfer: + name: átruházás + description: A hibajegy tulajdonjogának átruházása egy másik felhasználóra + options: + member: + description: A felhasználó, akire át szeretnéd ruházni a tulajdonjogot + name: felhasználó + transferred: 📨 {user} átruházta a hibajegy tulajdonjogát {to} számára. + transferred_from: '📨 {user} áthelyezte ezt a hibahegyet innen: {from} ide: {to}.' + add: + description: Felhasználó hozzáadása a hibajegyhez + success: + title: ✅ Hozzáadva + description: '{member} hozzáadásra került a(z) {ticket} hibajegyhez.' + name: hozzáadás + added: ➡️ {added} hozzáadásra került {by} által. + options: + member: + description: Felhasználó, akit hozzá szeretnél adni a hibajegyhez + name: felhasználó + ticket: + description: A hibajegy, amihez hozzá szeretnéd adni a felhasználót + name: hibajegy + not_staff: + description: Csak a személyzet tagiai tudnak hozzáadni felhasználót más felhasználó + hibajegyéhez. + title: ❌ Hiba + claim: + description: Hibajegy begyűjtése + name: begyűjtés + topic: + description: Hibajegy témájának megváltoztatása + name: téma + transcript: + options: + ticket: + description: A hibajegy száma, melynek az átiratát szeretnéd lekérni + name: hibajegy + description: Hibajegy átiratának lekérése + name: hibajegy + user: + create: + name: Hibajegy létrehozása a felhasználó számára +dm: + closed: + title: A hibajegyed bezárásra került + archived: Használd a(z) `/transcript` a(z)**{guild}** szerveren, hogy megtekintsd + az archivált üzeneteket. + confirm_open: + title: Létre szeretnél hozni egy hibajegyet a megadott témával? +misc: + blocked: + title: ❌ Letiltva + description: Nem tudsz hibajegyet létrehozni. + category_full: + description: "A kategória elérte a maximális kapacitást.\nPróbáld újra később.\n" + title: ❌ Kategória megtelt + cooldown: + description: Kérlek várj {time}, mielőtt új hibajegyet hoznál létre a kategóriában. + title: ❌ Kérlek várj + ratelimited: + description: Próbáld újra néhány másodperc múlva. + title: 🐢 Kérlek lassíts + expires_in: Lejár {time} + error: + description: "Sajnálom váratlan hiba lépett fel.\nKérlek vedd fel a kapcsolatot\ + \ az adminisztrátorral.\n" + fields: + code: Hibakód + identifier: Azonosító + title: ⚠️ Hiba lépett fel + invalid_ticket: + description: Kérlek add meg egy létező hibajegyet. + title: ❌ Érvénytelen hibajegy + expired: + description: Nem válaszoltál időben. Kérlek próbáld újra. + title: ⏰ Lejárt + member_limit: + description: + - Kérlek használd a meglévő hibajegyed, vagy zárd be mielőtt újat hoznál létre. + - "Kérlek zárd be a hibajegyet, mielőtt másikat hoznál létre.\nHasználd a(z) `/tickets`\ + \ parancsot a meglévő hibajegyeid megtekintéséhez.\n" + title: + - ❌ Már van hibajegyed + - ❌ Már van %d megnyitott hibajegyed + missing_roles: + title: ❌ Nem megfelelő rangok + description: Nincs megfelelő rangod ahhoz, hogy létrehozhass hibajegyet ebbe a + kategóriába. + no_categories: + description: Nem került konfigurálásra egyetlen hibajegy kategória sem. + title: ❌ Nincsenek hibajegy kategóriák + not_ticket: + description: Csak hibajegyekben használhatod ezt a parancsot. + title: ❌ Nem hibajegy csatorna + unknown_category: + description: Kérlek próbálj egy másik kategóriát. + title: ❌ Nem létezik ilyen hibajegy kategória +menus: + category: + placeholder: Kategória választása + guild: + placeholder: Szerver választása +ticket: + edited: + description: A változtatások mentésre kerültek. + title: ✅ Hibajegy frissítve + references_ticket: + title: ℹ️ Referencia + fields: + topic: Téma + number: Szám + date: Létrehozva + description: 'Ez a hibajegy egy korábbi hibajegyhez kapcsolódik:' + close: + wait_for_staff: ✋ Kérlek várj a személyzetre, ahhoz hogy bezárhasd ezt a hibajegyet. + closed: + description: A csatorna törlésre kerül néhány másodperc múlva... + title: ✅ Hibajegy bezárva + forbidden: + description: Nincs jogosultságod a hibajegy bezárásához. + title: ❌ Hiba + rejected: ✋ {user} elutasította a kérést a hibajegy bezárásáról. + staff_request: + archived: "\nAz üzenetek ebben a csatornában archiválásra kerülnek a későbbiekre.\n" + description: "{requestedBy} be szeretné zárni ezt a hibajegyet.\nKattints \"\ + Elfogadás\" a bezáráshoz, vagy \"Elutasíts\" ha további segítsére van szükséged.\n" + title: ❓ Bezárásra kerülhet a hibajegy? + user_request: + title: ❓ {requestedBy} be szeretné zárni ezt a hibajegyet + wait_for_user: ✋ Kérlek várj a felhasználó válaszára. + released: ♻️ {user} felszabadította a hibajegyet. + created: + description: 'A hibajegyed létrehozásra került: {channel}.' + title: ✅ Hibajegy létrehozva + claimed: 🙌 {user} begyűjtötte a hibajegyet. + answers: + no_value: '*Nem válaszolt*' + feedback: Köszönjük a visszajelzést. + opening_message: + content: "{staff}\n{creator} létrehozott egy új hibajegyet\n" + fields: + topic: Téma + references_message: + title: ℹ️ Referencia + description: Referencia [üzenet]({url}) elküldve {timestamp} {author} által. +modals: + feedback: + rating: + placeholder: 1-5 + label: Értékelés + comment: + label: Megjegyzés + placeholder: Van további visszajelzése? + title: Mit gondolsz, hogy teljesítettünk? + topic: + label: Téma + placeholder: Miről szól a hibajegy? From 41e3d768d9608cfada8a73c3a00c01ca4aa14a64 Mon Sep 17 00:00:00 2001 From: Bruno Babica Date: Fri, 3 Feb 2023 23:56:25 +0100 Subject: [PATCH 244/409] Translated using Weblate (Czech) Currently translated at 18.2% (43 of 235 strings) Co-authored-by: Bruno Babica Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/cs/ Translation: Discord Tickets/Bot --- src/i18n/cs.yml | 72 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index 0967ef4..77eae91 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -1 +1,71 @@ -{} +commands: + message: + pin: + name: Připnout zprávu + pinned: + description: Tato zpráva nemůže být připnutá. + title: ✅ Zpráva připnuta + not_pinnable: + description: "Tato zpráva nemůže být připnuta.\nProsím zeptejte se admininistrátora,\ + \ jestli má bot dostatečné oprávnění.\n" + title: ❌ Chyba + not_ticket: + description: Zprávy můžeš připnout jen v ticketech. + title: ❌ Tento kanál není ticket + create: + name: Vytvořit ticket ze zprávy + slash: + add: + name: přidat + options: + ticket: + name: ticket + description: Ticket na přidání člena + member: + name: člen + description: Člen na přidání do ticketu + added: ➡️{by} přidal {added}. + description: Přidat člena do ticketu + not_staff: + description: Pouze členové staff teamu můžou přidávat členy do ticketů ostatních. + title: ❌ Chyba + success: + description: '{member} byl přidán do {ticket}.' + title: ✅ Přidán + claim: + name: převzít + description: Převzít ticket + close: + invalid_time: + title: ❌ Neplatný + description: '`{input}` není platný formát času.' + description: Zažádat o uzavření ticketu + name: uzavřít +buttons: + accept_close_request: + emoji: ✅ + text: Přijmout + create: + text: Vytvořit ticket + emoji: 🎫 + cancel: + emoji: ✖️ + text: Zrušit + claim: + emoji: 🙌 + text: Převzít + edit: + emoji: ✏️ + text: Upravit + close: + emoji: ✖️ + text: Zavřít + confirm_open: + emoji: ✅ + text: Vytvořit ticket + reject_close_request: + emoji: ✖️ + text: Odmítnut + unclaim: + emoji: ♻️ + text: Vydat From 9fb08a3ff4098c5688c09bcfc5626d386e7b884d Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 3 Feb 2023 23:56:25 +0100 Subject: [PATCH 245/409] Added translation using Weblate (Turkish) Translated using Weblate (Czech) Currently translated at 18.2% (43 of 235 strings) Co-authored-by: Isaac Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/cs/ Translation: Discord Tickets/Bot --- src/i18n/cs.yml | 2 ++ src/i18n/tr.yml | 1 + 2 files changed, 3 insertions(+) create mode 100644 src/i18n/tr.yml diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index 77eae91..fe0de8b 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -41,6 +41,8 @@ commands: description: '`{input}` není platný formát času.' description: Zažádat o uzavření ticketu name: uzavřít + move: + moved: '' buttons: accept_close_request: emoji: ✅ diff --git a/src/i18n/tr.yml b/src/i18n/tr.yml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/tr.yml @@ -0,0 +1 @@ +{} From d4d69cc4ef33a08630d384487db6f148e89a7cfc Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 3 Feb 2023 23:56:26 +0100 Subject: [PATCH 246/409] Update translation files Updated by "Remove blank strings" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/ Translation: Discord Tickets/Bot --- src/i18n/cs.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index fe0de8b..77eae91 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -41,8 +41,6 @@ commands: description: '`{input}` není platný formát času.' description: Zažádat o uzavření ticketu name: uzavřít - move: - moved: '' buttons: accept_close_request: emoji: ✅ From 4043706efa22e0035e3d9661daddbebdd66f2372 Mon Sep 17 00:00:00 2001 From: sgtJohnny Date: Sat, 4 Feb 2023 15:12:02 +0000 Subject: [PATCH 247/409] Translated using Weblate (German) Currently translated at 36.1% (85 of 235 strings) Translation: Discord Tickets/Bot Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ --- src/i18n/de.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 0f062bc..dad7648 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -82,6 +82,8 @@ commands: name: Kategorie time: name: Zeit + description: Alle Tickets schließen, die für die angegebene Zeit inaktiv + waren (kann mit `Kategorie` verwendet werden) no_tickets: description: Es gibt keine offenen Tickets, die länger als `{time}` inaktiv waren. @@ -124,6 +126,7 @@ commands: MEDIUM: 🟠 Mittel success: title: ✅ Priorität gesetzt + description: Die Priorität dieses Tickets wurde auf `{priority}` gesetzt. name: Priorität tickets: not_staff: @@ -132,6 +135,7 @@ commands: member: description: Das Mitglied, dessen Tickets aufgelistet werden sollen name: Mitglied + name: Tickets remove: not_staff: description: Nur Mitarbeiter können Mitglieder aus den Tickets anderer entfernen. @@ -139,5 +143,7 @@ commands: options: member: name: Mitglied + ticket: + name: Ticket success: title: ✅ Entfernt From 2411aaa5859af92cb71335ff1b4105ee3e010717 Mon Sep 17 00:00:00 2001 From: Bruno Babica Date: Sat, 4 Feb 2023 09:57:07 +0000 Subject: [PATCH 248/409] Translated using Weblate (Czech) Currently translated at 18.7% (44 of 235 strings) Translation: Discord Tickets/Bot Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/cs/ --- src/i18n/cs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index 77eae91..6db6e79 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -41,6 +41,9 @@ commands: description: '`{input}` není platný formát času.' description: Zažádat o uzavření ticketu name: uzavřít + options: + reason: + description: Důvod pro uzavření ticketu buttons: accept_close_request: emoji: ✅ From 308d38beb960e3a430b44870a0778f77a7a3a1cd Mon Sep 17 00:00:00 2001 From: sgtJohnny Date: Sat, 4 Feb 2023 21:53:05 +0000 Subject: [PATCH 249/409] Translated using Weblate (German) Currently translated at 74.8% (176 of 235 strings) Translation: Discord Tickets/Bot Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ --- src/i18n/de.yml | 165 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index dad7648..c92d2b0 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -98,6 +98,8 @@ commands: reason: name: Grund description: Der Grund für das Schließen des/der Tickets + description: Beantragen, dass ein Ticket geschlossen wird + name: Schließen move: name: verschieben description: Verschiebe ein Ticket in eine andere Kategorie @@ -136,6 +138,10 @@ commands: description: Das Mitglied, dessen Tickets aufgelistet werden sollen name: Mitglied name: Tickets + response: + title: + own: Deine Tickets + other: '{displayName}s Tickets' remove: not_staff: description: Nur Mitarbeiter können Mitglieder aus den Tickets anderer entfernen. @@ -147,3 +153,162 @@ commands: name: Ticket success: title: ✅ Entfernt + topic: + description: Thema eines Tickets ändern + name: Thema + transcript: + name: Abschrift + options: + ticket: + name: Ticket + transfer: + name: Übertragen + transferred_from: 📨 {user} hat dieses Ticket von {from} auf {to} übertragen. + options: + member: + name: Mitglied + transferred: 📨 {user} hat dieses Ticket an {to} übertragen. + user: + create: + name: Ein Ticket für einen Benutzer erstellen +log: + admin: + changes: Änderungen + description: + target: + question: eine Frage + settings: Einstellungen + category: eine Kategorie + panel: ein Panel + title: + target: + category: Kategorie + panel: Panel + settings: Einstellungen + question: Frage + verb: + delete: gelöscht + create: erstellt + message: + description: '{user} {verb} eine Nachricht' + verb: + delete: gelöscht + message: Nachricht + title: Nachricht {verb} + ticket: + title: Ticket {verb} + description: '{user} {verb} ein Ticket' + ticket: Ticket +menus: + category: + placeholder: Wähle eine Ticketkategorie + guild: + placeholder: Wähle einen Server +misc: + blocked: + title: ❌ Gesperrt + description: Du darfst keine Tickets erstellen. + cooldown: + description: Bitte warte {time}, bevor du ein weiteres Ticket in dieser Kategorie + erstellst. + title: ❌Bitte warten + error: + description: "Entschuldigung, ein unerwarteter Fehler ist aufgetreten.\nBitte\ + \ gib diese Informationen an einen Administrator weiter.\n" + fields: + code: Fehlercode + identifier: Kennung + title: ⚠️ Etwas ist schief gelaufen + expired: + description: Du hast nicht rechtzeitig geantwortet. Bitte versuche es erneut. + title: ⏰ Abgelaufen + expires_in: Läuft in {time} ab + invalid_ticket: + description: Bitte gib ein gültiges Ticket an. + title: ❌ Ungültiges Ticket + member_limit: + description: + - Bitte verwende dein vorhandenes Ticket oder schließe es ab, bevor du ein neues + erstellst. + - "Bitte schließe ein Ticket, bevor du ein neues erstellst.\nVerwende `/Tickets`,\ + \ um deine bestehenden Tickets anzuzeigen.\n" + title: + - ❌ Du hast bereits ein Ticket + - ❌ Du hast bereits %d offene Tickets + not_ticket: + title: ❌ Dies ist kein Ticketkanal + description: Du kannst diesen Befehl nur in Tickets verwenden. + ratelimited: + description: Versuche es in ein paar Sekunden erneut. + unknown_category: + description: Bitte versuche es mit einer anderen Kategorie. + title: ❌ Diese Ticketkategorie existiert nicht + no_categories: + title: ❌ Es gibt keine Ticketkategorien + description: Es wurden keine Ticketkategorien konfiguriert. + missing_roles: + description: Du hast nicht die erforderlichen Rollen, um ein Ticket in dieser + Kategorie erstellen zu können. + title: ❌ Unzureichende Rollen + category_full: + description: "Die Kategorie hat ihre maximale Kapazität erreicht.\nBitte versuche\ + \ es später erneut.\n" + title: ❌ Kategorie voll +modals: + feedback: + comment: + placeholder: Hast du zusätzliches Feedback? + label: Kommentar + rating: + label: Bewertung + placeholder: 1-5 + topic: + label: Thema + placeholder: Worum geht es in diesem Ticket? +ticket: + answers: + no_value: '*Keine Antwort*' + close: + closed: + description: Dieser Kanal wird in wenigen Sekunden gelöscht... + title: ✅ Ticket geschlossen + forbidden: + title: ❌ Fehler + description: Du bist nicht berechtigt, dieses Ticket zu schließen. + rejected: ✋ {user} hat eine Anfrage zum Schließen dieses Tickets abgelehnt. + staff_request: + title: ❓ Kann dieses Ticket geschlossen werden? + archived: "\nDie Nachrichten in diesem Kanal werden zum späteren Nachschlagen\ + \ archiviert.\n" + description: "{requestedBy} möchte dieses Ticket schließen.\nKlicke auf \"Akzeptieren\"\ + , um es jetzt zu schließen, oder auf \"Ablehnen\", wenn du noch Hilfe benötigst.\n" + user_request: + title: ❓ {requestedBy} möchte dieses Ticket schließen + wait_for_user: ✋ Bitte warte bis der Benutzer antwortet. + created: + title: ✅ Ticket erstellt + description: 'Dein Ticketkanal wurde erstellt: {channel}.' + edited: + description: Deine Änderungen wurden gespeichert. + title: ✅ Ticket aktualisiert + feedback: Danke für deine Rückmeldung. + opening_message: + content: "{staff}\n{creator} hat ein neues Ticket erstellt\n" + fields: + topic: Thema + references_message: + title: ℹ️ Referenz + description: Verweist auf [eine Nachricht]({url}), die {timestamp} von {author} + gesendet wurde. + references_ticket: + description: 'Dieses Ticket bezieht sich auf ein vorheriges Ticket:' + fields: + date: Erstellt am + number: Nummer + topic: Thema + title: ℹ️ Referenz +dm: + closed: + title: Dein Ticket wurde geschlossen + confirm_open: + title: Möchtest du ein Ticket mit dem folgendem Thema eröffnen? From b02a7cf4f37247a9086010525ac4d2f9ce96583e Mon Sep 17 00:00:00 2001 From: Nathan Date: Mon, 6 Feb 2023 08:55:00 +0000 Subject: [PATCH 250/409] Translated using Weblate (French) Currently translated at 3.8% (9 of 235 strings) Translation: Discord Tickets/Bot Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/fr/ --- src/i18n/fr.yml | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/i18n/fr.yml b/src/i18n/fr.yml index 0967ef4..28a5f12 100644 --- a/src/i18n/fr.yml +++ b/src/i18n/fr.yml @@ -1 +1,34 @@ -{} +buttons: + accept_close_request: + text: Accepter + cancel: + emoji: ✖️ + text: Annuler + claim: + emoji: 🙌 + close: + text: Fermer + reject_close_request: + emoji: ✖️ + text: Rejeter +commands: + message: + pin: + name: Épingler le message + not_pinnable: + title: ❌ Erreur + pinned: + title: ✅ Message épinglé + slash: + add: + not_staff: + title: ❌ Erreur + success: + title: ✅ Ajouté + close: + invalid_time: + title: ❌ Invalide + force-close: + options: + ticket: + name: billet From bbd91c01101a0ede650f28d4374aa6a09825d974 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 8 Feb 2023 13:29:12 +0100 Subject: [PATCH 251/409] Added translation using Weblate (Russian) --- src/i18n/ru.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/ru.yml diff --git a/src/i18n/ru.yml b/src/i18n/ru.yml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/ru.yml @@ -0,0 +1 @@ +{} From bb710ea4423d983a1ebb2cf51bb9f3b19a94d74c Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 8 Feb 2023 15:19:01 +0100 Subject: [PATCH 252/409] Translated using Weblate (German) Currently translated at 73.6% (173 of 235 strings) Co-authored-by: Isaac Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index c92d2b0..fc0c131 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -29,7 +29,7 @@ buttons: commands: message: create: - name: Ein Ticket aus Nachricht erstellen + name: '' pin: name: Nachricht anheften not_pinnable: From 965b6f861d147d77da8bef35ffbbb0de7710bfff Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 8 Feb 2023 15:19:02 +0100 Subject: [PATCH 253/409] Update translation files Updated by "Remove blank strings" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index fc0c131..6ed977a 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -28,8 +28,6 @@ buttons: emoji: ♻️ commands: message: - create: - name: '' pin: name: Nachricht anheften not_pinnable: From 21fe8381029a31eef3cbea929f24fcb95d3778a3 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 8 Feb 2023 14:20:31 +0000 Subject: [PATCH 254/409] Update Weblate card use project rather than component image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2b5e5ab..fb7b786 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Translation status](https://hosted.weblate.org/widgets/discord-tickets/-/bot/open-graph.png)](https://hosted.weblate.org/engage/discord-tickets/) +[![Translation status](https://hosted.weblate.org/widgets/discord-tickets/-/open-graph.png)](https://hosted.weblate.org/engage/discord-tickets/) https://www.prisma.io/docs/reference/database-reference/supported-databases From 0f98a98316f772ae91074f1af28dbdb1b9428fcf Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 8 Feb 2023 14:33:24 +0000 Subject: [PATCH 255/409] Update strings --- src/i18n/en-GB.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index a6aeea8..02064f7 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -72,7 +72,7 @@ commands: name: close options: reason: - description: The reason for closing the ticket(s) + description: The reason for closing the ticket name: reason force-close: confirm_multiple: From 9ab2a37c03af316197b4fa47af0f413cf33eaab1 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 8 Feb 2023 14:34:38 +0000 Subject: [PATCH 256/409] Update strings --- src/i18n/en-GB.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 02064f7..341f1f1 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -353,7 +353,7 @@ ticket: claimed: 🙌 {user} has claimed this ticket. close: closed: - description: This channel will be deleted in a few seconds... + description: This channel will be deleted in a few seconds… title: ✅ Ticket closed forbidden: description: You don't have permission to close this ticket. From 629a687c1965f9d257ccaf741ed2c3076dfab9a7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 8 Feb 2023 15:33:39 +0100 Subject: [PATCH 257/409] Translated using Weblate (German) Currently translated at 72.7% (171 of 235 strings) Co-authored-by: Isaac Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 6ed977a..4317c5b 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -80,8 +80,7 @@ commands: name: Kategorie time: name: Zeit - description: Alle Tickets schließen, die für die angegebene Zeit inaktiv - waren (kann mit `Kategorie` verwendet werden) + description: '' no_tickets: description: Es gibt keine offenen Tickets, die länger als `{time}` inaktiv waren. @@ -168,7 +167,7 @@ commands: transferred: 📨 {user} hat dieses Ticket an {to} übertragen. user: create: - name: Ein Ticket für einen Benutzer erstellen + name: '' log: admin: changes: Änderungen From 8158c3926b7909793169259708cc62d276be0176 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 8 Feb 2023 15:33:39 +0100 Subject: [PATCH 258/409] Update translation files Updated by "Remove blank strings" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 4317c5b..dfb6486 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -80,7 +80,6 @@ commands: name: Kategorie time: name: Zeit - description: '' no_tickets: description: Es gibt keine offenen Tickets, die länger als `{time}` inaktiv waren. @@ -165,9 +164,7 @@ commands: member: name: Mitglied transferred: 📨 {user} hat dieses Ticket an {to} übertragen. - user: - create: - name: '' + user: {} log: admin: changes: Änderungen From 2caf4d3716996b388b98f9666695e8f7df2c3075 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 8 Feb 2023 15:41:17 +0100 Subject: [PATCH 259/409] Translated using Weblate (German) Currently translated at 74.4% (175 of 235 strings) Translated using Weblate (Hungarian) Currently translated at 99.1% (233 of 235 strings) Co-authored-by: Isaac Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/hu/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 2 +- src/i18n/hu.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index dfb6486..74995f3 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -264,7 +264,7 @@ ticket: no_value: '*Keine Antwort*' close: closed: - description: Dieser Kanal wird in wenigen Sekunden gelöscht... + description: Dieser Kanal wird in wenigen Sekunden gelöscht… title: ✅ Ticket geschlossen forbidden: title: ❌ Fehler diff --git a/src/i18n/hu.yml b/src/i18n/hu.yml index 8961142..597cb52 100644 --- a/src/i18n/hu.yml +++ b/src/i18n/hu.yml @@ -262,7 +262,7 @@ commands: name: hibajegy user: create: - name: Hibajegy létrehozása a felhasználó számára + name: '' dm: closed: title: A hibajegyed bezárásra került @@ -337,7 +337,7 @@ ticket: close: wait_for_staff: ✋ Kérlek várj a személyzetre, ahhoz hogy bezárhasd ezt a hibajegyet. closed: - description: A csatorna törlésre kerül néhány másodperc múlva... + description: A csatorna törlésre kerül néhány másodperc múlva… title: ✅ Hibajegy bezárva forbidden: description: Nincs jogosultságod a hibajegy bezárásához. From 7ef56cd2fca0c63ee71603af0ce453f344edbd67 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 8 Feb 2023 15:41:18 +0100 Subject: [PATCH 260/409] Update translation files Updated by "Remove blank strings" hook in Weblate. Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/ Translation: Discord Tickets/Bot --- src/i18n/hu.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/i18n/hu.yml b/src/i18n/hu.yml index 597cb52..d63a917 100644 --- a/src/i18n/hu.yml +++ b/src/i18n/hu.yml @@ -260,9 +260,7 @@ commands: name: hibajegy description: Hibajegy átiratának lekérése name: hibajegy - user: - create: - name: '' + user: {} dm: closed: title: A hibajegyed bezárásra került From 0b444650243c87ae1550a7c8abc68e4cba3cfef7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 17:15:49 +0100 Subject: [PATCH 261/409] Added translation using Weblate (Italian) Co-authored-by: Isaac --- src/i18n/it.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/it.yml diff --git a/src/i18n/it.yml b/src/i18n/it.yml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/it.yml @@ -0,0 +1 @@ +{} From a73cc2bdd45dd3629ed714b3c3e4aea4df3c0054 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 16:52:29 +0000 Subject: [PATCH 262/409] fix: don't close tickets twice --- src/listeners/client/channelDelete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/listeners/client/channelDelete.js b/src/listeners/client/channelDelete.js index b371997..f8c20ce 100644 --- a/src/listeners/client/channelDelete.js +++ b/src/listeners/client/channelDelete.js @@ -18,7 +18,7 @@ module.exports = class extends Listener { where: { id: channel.id }, }); - if (!ticket?.open) return; + if (!ticket || ticket.open) return; await client.tickets.finallyClose(ticket.id, { reason: 'channel deleted' }); this.client.log.info(`Closed ticket ${ticket.id} because the channel was deleted`); From df725185eb91720dee08f1df528c7d1cd981de60 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 16:52:29 +0000 Subject: [PATCH 263/409] Revert "fix: don't close tickets twice" This reverts commit a73cc2bdd45dd3629ed714b3c3e4aea4df3c0054. --- src/listeners/client/channelDelete.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/listeners/client/channelDelete.js b/src/listeners/client/channelDelete.js index f8c20ce..b371997 100644 --- a/src/listeners/client/channelDelete.js +++ b/src/listeners/client/channelDelete.js @@ -18,7 +18,7 @@ module.exports = class extends Listener { where: { id: channel.id }, }); - if (!ticket || ticket.open) return; + if (!ticket?.open) return; await client.tickets.finallyClose(ticket.id, { reason: 'channel deleted' }); this.client.log.info(`Closed ticket ${ticket.id} because the channel was deleted`); From 62ab9eb6c731edd8b3374c6a973a4ba2a429902d Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 17:34:17 +0000 Subject: [PATCH 264/409] fix: don't throw errors on every message if a guild isn't configured --- src/listeners/client/messageCreate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index 46386e4..1cee0ea 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -181,6 +181,7 @@ module.exports = class extends Listener { } } else { const settings = await client.prisma.guild.findUnique({ where: { id: message.guild.id } }); + if (!settings) return; let ticket = await client.prisma.ticket.findUnique({ where: { id: message.channel.id } }); if (ticket) { From 630d3ff4bbac7fb58a18e1f6997ed0fed1b54580 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 17:38:46 +0000 Subject: [PATCH 265/409] fix: set tickets as closed (fixes #382) --- src/lib/tickets/manager.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 0c58817..cea8979 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1067,6 +1067,7 @@ module.exports = class TicketManager { } || undefined, // Prisma wants undefined not null because it is a relation closedReason: reason && encrypt(reason), messageCount: archivedMessages, + open: false, }; /** @type {import("discord.js").TextChannel} */ From 0d1cb90e7ea36685015712011835f45745c80613 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 17:39:22 +0000 Subject: [PATCH 266/409] fix: convert pinned messages `Map Iterator` to array --- src/lib/tickets/manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index cea8979..2c1e319 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1074,7 +1074,7 @@ module.exports = class TicketManager { const channel = this.client.channels.cache.get(ticketId); if (channel) { const pinned = await channel.messages.fetchPinned(); - data.pinnedMessageIds = pinned.keys(); + data.pinnedMessageIds = [...pinned.keys()]; } await this.client.prisma.ticket.update({ From d7ae5c0c9e7d01e27ec298ef4ba79b7e4f83173b Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 17:39:53 +0000 Subject: [PATCH 267/409] fix: don't give useless `#unknown-channel` in ticket close logs --- src/lib/tickets/manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 2c1e319..08595ca 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1092,7 +1092,7 @@ module.exports = class TicketManager { action: 'close', target: { id: ticket.id, - name: channel.toString(), + name: `[${ticket.number}]`, }, userId: closedBy, }); From c5f092ce2f58b155807fec14f7b3c49fbf8ff555 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 17:40:25 +0000 Subject: [PATCH 268/409] refactor --- src/listeners/client/channelDelete.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/listeners/client/channelDelete.js b/src/listeners/client/channelDelete.js index b371997..712cd92 100644 --- a/src/listeners/client/channelDelete.js +++ b/src/listeners/client/channelDelete.js @@ -18,9 +18,9 @@ module.exports = class extends Listener { where: { id: channel.id }, }); - if (!ticket?.open) return; - - await client.tickets.finallyClose(ticket.id, { reason: 'channel deleted' }); - this.client.log.info(`Closed ticket ${ticket.id} because the channel was deleted`); + if (ticket?.open) { + await client.tickets.finallyClose(ticket.id, { reason: 'channel deleted' }); + this.client.log.info.tickets(`Closed ticket ${ticket.id} because the channel was deleted`); + } } }; From 34c3ed1b6a034145b4336000cba5ca4b545c4d66 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 21:50:50 +0000 Subject: [PATCH 269/409] fix: `/topic` command when there was previously no topic --- src/commands/slash/topic.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/commands/slash/topic.js b/src/commands/slash/topic.js index 07e7617..eed8da8 100644 --- a/src/commands/slash/topic.js +++ b/src/commands/slash/topic.js @@ -57,6 +57,17 @@ module.exports = class TopicSlashCommand extends SlashCommand { const getMessage = client.i18n.getLocale(ticket.guild.locale); + const field = new TextInputBuilder() + .setCustomId('topic') + .setLabel(getMessage('modals.topic.label')) + .setStyle(TextInputStyle.Paragraph) + .setMaxLength(1000) + .setMinLength(5) + .setPlaceholder(getMessage('modals.topic.placeholder')) + .setRequired(true); + + if (ticket.topic) field.setValue(decrypt(ticket.topic)); // why can't discord.js accept null or undefined :( + await interaction.showModal( new ModalBuilder() .setCustomId(JSON.stringify({ @@ -66,17 +77,7 @@ module.exports = class TopicSlashCommand extends SlashCommand { .setTitle(ticket.category.name) .setComponents( new ActionRowBuilder() - .setComponents( - new TextInputBuilder() - .setCustomId('topic') - .setLabel(getMessage('modals.topic.label')) - .setStyle(TextInputStyle.Paragraph) - .setMaxLength(1000) - .setMinLength(5) - .setPlaceholder(getMessage('modals.topic.placeholder')) - .setRequired(true) - .setValue(ticket.topic ? decrypt(ticket.topic) : ''), - ), + .setComponents(field), ), ); } From 5982754813e8e42a9914f06684667020842be75d Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 21:51:08 +0000 Subject: [PATCH 270/409] fix: decrypt referenced ticket's topic --- src/lib/tickets/manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 08595ca..c87f58a 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -584,7 +584,7 @@ module.exports = class TicketManager { embed.addFields({ inline: false, name: getMessage('ticket.references_ticket.fields.topic'), - value: ticket.topic, + value: decrypt(ticket.topic), }); } await channel.send({ embeds: [embed] }); From a0ffbae36d4775afa8e3ec1133523bcc2f531e7d Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 21:51:28 +0000 Subject: [PATCH 271/409] feat: make closed ticket DM more useful --- src/i18n/en-GB.yml | 13 +++++- src/lib/tickets/manager.js | 86 +++++++++++++++++++++++++++++++++++--- 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 341f1f1..cf7df34 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -231,7 +231,18 @@ commands: name: Create a ticket for user dm: closed: - archived: Type `/transcript` in **{guild}** to view the archived messages. + archived: Use the `/transcript` command in **{guild}** to view the archived messages. + fields: + closed: + name: Closed at + value: "{timestamp} (after {duration})" + closed_by: Closed by + created: Created at + feedback: Your feedback + reason: Closed because + response: Response time + ticket: Ticket + topic: Topic title: Your ticket has been closed confirm_open: title: Do you want to open a ticket with the following topic? diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index c87f58a..6b2a74b 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -19,7 +19,10 @@ const { logTicketEvent } = require('../logging'); const { isStaff } = require('../users'); const { Collection } = require('discord.js'); const Cryptr = require('cryptr'); -const { encrypt } = new Cryptr(process.env.ENCRYPTION_KEY); +const { + decrypt, + encrypt, +} = new Cryptr(process.env.ENCRYPTION_KEY); /** * @typedef {import('@prisma/client').Category & @@ -1047,7 +1050,8 @@ module.exports = class TicketManager { closedBy = null, reason = null, }) { - const ticket = await this.getTicket(ticketId); + let ticket = await this.getTicket(ticketId); + const getMessage = this.client.i18n.getLocale(ticket.guild.locale); this.$count.categories[ticket.categoryId].total -= 1; this.$count.categories[ticket.categoryId][ticket.createdById] -= 1; @@ -1077,8 +1081,13 @@ module.exports = class TicketManager { data.pinnedMessageIds = [...pinned.keys()]; } - await this.client.prisma.ticket.update({ + ticket = await this.client.prisma.ticket.update({ data, + include: { + category: true, + feedback: true, + guild: true, + }, where: { id: ticket.id }, }); @@ -1092,7 +1101,7 @@ module.exports = class TicketManager { action: 'close', target: { id: ticket.id, - name: `[${ticket.number}]`, + name: `${ticket.category.name} **#${ticket.number}**`, }, userId: closedBy, }); @@ -1101,14 +1110,79 @@ module.exports = class TicketManager { try { const creator = await channel?.guild.members.fetch(ticket.createdById); if (creator) { - const getMessage = this.client.i18n.getLocale(ticket.guild.locale); const embed = new ExtendedEmbedBuilder({ iconURL: channel.guild.iconURL(), text: ticket.guild.footer, }) .setColor(ticket.guild.primaryColour) - .setTitle(getMessage('dm.closed.title')); + .setTitle(getMessage('dm.closed.title')) + .addFields([ + { + inline: true, + name: getMessage('dm.closed.fields.ticket'), + value: `${ticket.category.name} **#${ticket.number}**`, + }, + + ]); + if (ticket.topic) { + embed.addFields({ + inline: true, + name: getMessage('dm.closed.fields.topic'), + value: decrypt(ticket.topic), + }); + } + + embed.addFields([ + { + inline: true, + name: getMessage('dm.closed.fields.created'), + value: ``, + }, + { + inline: true, + name: getMessage('dm.closed.fields.closed.name'), + value: getMessage('dm.closed.fields.closed.value', { + duration: ms(ticket.closedAt - ticket.createdAt, { long: true }), + timestamp: ``, + }), + }, + ]); + + if (ticket.firstResponseAt) { + embed.addFields({ + inline: true, + name: getMessage('dm.closed.fields.response'), + value: ms(ticket.firstResponseAt - ticket.createdAt, { long: true }), + }); + } + + if (ticket.feedback) { + embed.addFields({ + inline: true, + name: getMessage('dm.closed.fields.feedback'), + value: Array(ticket.feedback.rating).fill('⭐').join(' ') + ` (${ticket.feedback.rating}/5)`, + }); + } + + if (ticket.closedById) { + embed.addFields({ + inline: true, + name: getMessage('dm.closed.fields.closed_by'), + value: `<@${ticket.closedById}>`, + }); + } + + + if (reason) { + embed.addFields({ + inline: true, + name: getMessage('dm.closed.fields.reason'), + value: reason, + }); + } + if (ticket.guild.archive) embed.setDescription(getMessage('dm.closed.archived', { guild: channel.guild.name })); + await creator.send({ embeds: [embed] }); } } catch (error) { From 42ad5216f6da80fb8b751bc2746eee15d91b87ba Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 22:22:47 +0000 Subject: [PATCH 272/409] fix: update reject button correctly --- src/buttons/close.js | 58 +++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/src/buttons/close.js b/src/buttons/close.js index ff1a61e..3289047 100644 --- a/src/buttons/close.js +++ b/src/buttons/close.js @@ -58,27 +58,45 @@ module.exports = class CloseButton extends Button { } } else { // TODO: reply - if (client.tickets.$stale.has(ticket.id)) { - try { - await interaction.channel.messages.edit( - client.tickets.$stale.get(ticket.id).message.id, - { - components: [], - embeds: [ - new ExtendedEmbedBuilder({ - iconURL: interaction.guild.iconURL(), - text: ticket.guild.footer, - }) - .setColor(ticket.guild.errorColour) - .setDescription(getMessage('ticket.close.rejected', { user: interaction.user.toString() })) - .setFooter({ text: null }), - ], - }, - ); - } finally { // this should run regardless of whatever happens above - client.tickets.$stale.delete(ticket.id); - } + try { + await interaction.update({ + components: [], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setDescription(getMessage('ticket.close.rejected', { user: interaction.user.toString() })) + .setFooter({ text: null }), + ], + }); + + } finally { // this should run regardless of whatever happens above + client.tickets.$stale.delete(ticket.id); } + + // if (client.tickets.$stale.has(ticket.id)) { + // try { + // await interaction.channel.messages.edit( + // client.tickets.$stale.get(ticket.id).message.id, + // { + // components: [], + // embeds: [ + // new ExtendedEmbedBuilder({ + // iconURL: interaction.guild.iconURL(), + // text: ticket.guild.footer, + // }) + // .setColor(ticket.guild.errorColour) + // .setDescription(getMessage('ticket.close.rejected', { user: interaction.user.toString() })) + // .setFooter({ text: null }), + // ], + // }, + // ); + // } finally { // this should run regardless of whatever happens above + // client.tickets.$stale.delete(ticket.id); + // } + // } } } } From b4521e8657978b99c4fb7336eb30ca04fef1d6db Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 9 Feb 2023 22:51:35 +0100 Subject: [PATCH 273/409] Translated using Weblate (Italian) Currently translated at 0.8% (2 of 235 strings) Co-authored-by: Phil Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/it/ Translation: Discord Tickets/Bot --- src/i18n/it.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/it.yml b/src/i18n/it.yml index 0967ef4..177cbcb 100644 --- a/src/i18n/it.yml +++ b/src/i18n/it.yml @@ -1 +1,5 @@ -{} +buttons: + accept_close_request: + text: Conferma + cancel: + text: Annulla From be58072e5cf03e28507926ef5f8502c49222e1e8 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 22:23:08 +0000 Subject: [PATCH 274/409] =?UTF-8?q?=F0=9F=98=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/buttons/close.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/buttons/close.js b/src/buttons/close.js index 3289047..292ae43 100644 --- a/src/buttons/close.js +++ b/src/buttons/close.js @@ -57,7 +57,6 @@ module.exports = class CloseButton extends Button { await client.tickets.acceptClose(interaction); } } else { - // TODO: reply try { await interaction.update({ components: [], @@ -75,28 +74,6 @@ module.exports = class CloseButton extends Button { } finally { // this should run regardless of whatever happens above client.tickets.$stale.delete(ticket.id); } - - // if (client.tickets.$stale.has(ticket.id)) { - // try { - // await interaction.channel.messages.edit( - // client.tickets.$stale.get(ticket.id).message.id, - // { - // components: [], - // embeds: [ - // new ExtendedEmbedBuilder({ - // iconURL: interaction.guild.iconURL(), - // text: ticket.guild.footer, - // }) - // .setColor(ticket.guild.errorColour) - // .setDescription(getMessage('ticket.close.rejected', { user: interaction.user.toString() })) - // .setFooter({ text: null }), - // ], - // }, - // ); - // } finally { // this should run regardless of whatever happens above - // client.tickets.$stale.delete(ticket.id); - // } - // } } } } From 3b50135cc0305e7b7e05903bb743d5ab5f84a517 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Feb 2023 22:31:39 +0000 Subject: [PATCH 275/409] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 455c6b1..5355ed2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.3", + "version": "4.0.0-beta.4", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", From a95330853203b1b035bd9cb66aaa48b3efa11b9f Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Feb 2023 14:47:13 +0000 Subject: [PATCH 276/409] fix: make `/move` edit the channel name and permission overwrites --- src/commands/slash/move.js | 43 ++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/commands/slash/move.js b/src/commands/slash/move.js index b5437a2..4083ff6 100644 --- a/src/commands/slash/move.js +++ b/src/commands/slash/move.js @@ -62,6 +62,7 @@ module.exports = class MoveSlashCommand extends SlashCommand { }); } + const creator = await interaction.guild.members.fetch(ticket.createdById); const newCategory = await client.prisma.category.findUnique({ where: { id: interaction.options.getInteger('category', true) } }); const discordCategory = await interaction.guild.channels.fetch(newCategory.discordCategory); const getMessage = client.i18n.getLocale(ticket.guild.locale); @@ -80,6 +81,7 @@ module.exports = class MoveSlashCommand extends SlashCommand { ephemeral: true, }); } else { + // don't reassign `ticket`, the previous value is used below await client.prisma.ticket.update({ data: { category: { connect: { id: newCategory.id } } }, where: { id: ticket.id }, @@ -97,10 +99,43 @@ module.exports = class MoveSlashCommand extends SlashCommand { $newCategory[ticket.createdById] ||= 0; $newCategory[ticket.createdById]++; - await interaction.channel.setParent(discordCategory, { - lockPermissions: false, - reason: `Moved by ${interaction.user.tag}`, - }); + // these 3 could be done separately, + // but using `setParent`, `setName` etc instead of a single `edit` call increases the number of API requests + if ( + newCategory.staffRoles !== ticket.category.staffRoles || + newCategory.channelName !== ticket.category.channelName || + newCategory.discordCategory !== ticket.category.discordCategory + ) { + const allow = ['ViewChannel', 'ReadMessageHistory', 'SendMessages', 'EmbedLinks', 'AttachFiles']; + const channelName = newCategory.channelName + .replace(/{+\s?(user)?name\s?}+/gi, creator.user.username) + .replace(/{+\s?(nick|display)(name)?\s?}+/gi, creator.displayName) + .replace(/{+\s?num(ber)?\s?}+/gi, ticket.number === 1488 ? '1487b' : ticket.number); + await interaction.channel.edit({ + lockPermissions: false, + name: channelName, + parent: discordCategory, + permissionOverwrites: [ + { + deny: ['ViewChannel'], + id: interaction.guild.roles.everyone, + }, + { + allow, + id: this.client.user.id, + }, + { + allow, + id: creator.id, + }, + ...newCategory.staffRoles.map(id => ({ + allow, + id, + })), + ], + reason: `Moved by ${interaction.user.tag}`, + }); + } await interaction.editReply({ embeds: [ From ced14ce36a37a7ecb9fd66e93650f3579955516a Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Feb 2023 23:32:44 +0000 Subject: [PATCH 277/409] fix: response & resolution time stats --- src/lib/tickets/manager.js | 14 +++++++++++--- src/listeners/client/ready.js | 3 ++- .../api/admin/guilds/[guild]/categories/index.js | 2 +- src/routes/api/admin/guilds/[guild]/index.js | 5 +++-- src/routes/api/client.js | 3 ++- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 6b2a74b..910839c 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -406,9 +406,17 @@ module.exports = class TicketManager { const statsCacheKey = `cache/category-stats/${categoryId}`; let stats = await this.client.keyv.get(statsCacheKey); if (!stats) { - const { tickets } = await this.client.prisma.category.findUnique({ - select: { tickets: { where: { open: false } } }, - where: { id: categoryId }, + const tickets = await this.client.prisma.ticket.findMany({ + select: { + closedAt: true, + createdAt: true, + firstResponseAt: true, + }, + where: { + categoryId: category.id, + firstResponseAt: { not: null }, + open: false, + }, }); stats = { avgResolutionTime: ms(tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / tickets.length), diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index f19baba..97b0a97 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -33,11 +33,12 @@ module.exports = class extends Listener { if (!cached) { const tickets = await client.prisma.ticket.findMany({ select: { + closedAt: true, createdAt: true, firstResponseAt: true, }, }); - const closedTickets = tickets.filter(t => t.closedAt); + const closedTickets = tickets.filter(t => t.firstResponseAt && t.closedAt); cached = { avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index e7d1c38..318914f 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -29,7 +29,7 @@ module.exports.get = fastify => ({ where: { id: req.params.guild }, }); categories = categories.map(c => { - const closedTickets = c.tickets.filter(t => t.closedAt); + const closedTickets = c.tickets.filter(t => t.firstResponseAt && t.closedAt); c = { ...c, stats: { diff --git a/src/routes/api/admin/guilds/[guild]/index.js b/src/routes/api/admin/guilds/[guild]/index.js index c3b22a0..913d6ce 100644 --- a/src/routes/api/admin/guilds/[guild]/index.js +++ b/src/routes/api/admin/guilds/[guild]/index.js @@ -23,12 +23,13 @@ module.exports.get = fastify => ({ }); const tickets = await client.prisma.ticket.findMany({ select: { + closedAt: true, createdAt: true, firstResponseAt: true, }, where: { guildId: id }, }); - const closedTickets = tickets.filter(t => t.closedAt); + const closedTickets = tickets.filter(t => t.firstResponseAt && t.closedAt); cached = { createdAt: settings.createdAt, id: guild.id, @@ -46,7 +47,7 @@ module.exports.get = fastify => ({ tickets: tickets.length, }, }; - await client.keyv.set(cacheKey, cached, ms('5m')); + // await client.keyv.set(cacheKey, cached, ms('5m')); } return cached; diff --git a/src/routes/api/client.js b/src/routes/api/client.js index 8d89eec..4c793fb 100644 --- a/src/routes/api/client.js +++ b/src/routes/api/client.js @@ -10,11 +10,12 @@ module.exports.get = () => ({ if (!cached) { const tickets = await client.prisma.ticket.findMany({ select: { + closedAt: true, createdAt: true, firstResponseAt: true, }, }); - const closedTickets = tickets.filter(t => t.closedAt); + const closedTickets = tickets.filter(t => t.firstResponseAt && t.closedAt); const users = await client.prisma.user.findMany({ select: { messageCount: true } }); cached = { avatar: client.user.avatarURL(), From b4ab7524e2710987ab206e67d84c7756e8cb21f5 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Feb 2023 23:34:00 +0000 Subject: [PATCH 278/409] fix: uncomment --- src/routes/api/admin/guilds/[guild]/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/api/admin/guilds/[guild]/index.js b/src/routes/api/admin/guilds/[guild]/index.js index 913d6ce..38e9b62 100644 --- a/src/routes/api/admin/guilds/[guild]/index.js +++ b/src/routes/api/admin/guilds/[guild]/index.js @@ -47,7 +47,7 @@ module.exports.get = fastify => ({ tickets: tickets.length, }, }; - // await client.keyv.set(cacheKey, cached, ms('5m')); + await client.keyv.set(cacheKey, cached, ms('5m')); } return cached; From 1614e6bceb8459644e74dd29b823a1800c860fe0 Mon Sep 17 00:00:00 2001 From: sgtJohnny Date: Sun, 12 Feb 2023 01:59:43 +0100 Subject: [PATCH 279/409] Translated using Weblate (German) Currently translated at 78.2% (191 of 244 strings) Co-authored-by: sgtJohnny Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 74995f3..3904ae6 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -129,15 +129,27 @@ commands: tickets: not_staff: title: ❌ Fehler + description: Nur Teammitglieder können Tickets anderer einsehen. options: member: description: Das Mitglied, dessen Tickets aufgelistet werden sollen name: Mitglied - name: Tickets + name: tickets response: title: own: Deine Tickets other: '{displayName}s Tickets' + fields: + closed: + none: + own: "Du hast noch keine Tickets erstellt\nBenutze {new} um ein Ticket\ + \ zu öffnen.\n" + other: '{user} hat noch keine Tickets erstellt.' + name: Geschlossene Tickets + open: + name: Offene Tickets + description: Verwende {transcript}, um die Abschrift eines Tickets herunterzuladen. + description: Eigene oder fremde Tickets auflisten remove: not_staff: description: Nur Mitarbeiter können Mitglieder aus den Tickets anderer entfernen. @@ -149,21 +161,36 @@ commands: name: Ticket success: title: ✅ Entfernt + description: '{member} wurde von {ticket} entfernt.' topic: description: Thema eines Tickets ändern - name: Thema + name: thema transcript: - name: Abschrift + name: abschrift options: ticket: - name: Ticket + name: ticket + description: Die Nummer des Tickets von welchem die Abschrift erstellt werden + soll + description: Abschrift eines Tickets erhalten transfer: - name: Übertragen + name: übertragen transferred_from: 📨 {user} hat dieses Ticket von {from} auf {to} übertragen. options: member: name: Mitglied + description: Das Mitglied an welches das Ticket transferiert werden soll. transferred: 📨 {user} hat dieses Ticket an {to} übertragen. + description: Ein Ticket an ein anderes Mitglied übertragen + tag: + options: + for: + name: für + tag: + name: Tag + description: Der Name des zu verwendenden Tags + name: tag + description: Verwende einen Tag user: {} log: admin: From a79141dd792fa8f9052ecf51fa668c777a7eca2b Mon Sep 17 00:00:00 2001 From: sgtJohnny Date: Sun, 12 Feb 2023 22:39:22 +0100 Subject: [PATCH 280/409] Translated using Weblate (German) Currently translated at 79.5% (194 of 244 strings) Co-authored-by: sgtJohnny Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 3904ae6..cbbcc57 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -40,6 +40,8 @@ commands: pinned: description: Die Nachricht wurde angeheftet. title: ✅ Gepinnte Nachricht + create: + name: Ein Ticket aus einer Nachricht erstellen slash: add: added: ➡️ {added} wurde von {by} hinzugefügt. @@ -86,6 +88,10 @@ commands: title: Keine Tickets not_staff: title: ❌ Fehler + confirm_multiple: + description: "Du bist dabei **{count}** Tickets zu schließen die länger als\ + \ `{time}`:\n{tickets} inaktiv waren.\n" + title: ❓Bist du dir sicher? close: invalid_time: title: ❌ Ungültig @@ -93,7 +99,7 @@ commands: options: reason: name: Grund - description: Der Grund für das Schließen des/der Tickets + description: Der Grund für das Schließen des Tickets description: Beantragen, dass ein Ticket geschlossen wird name: Schließen move: From c03eafd32950bf7b7e7a14740c28ee6d6b179467 Mon Sep 17 00:00:00 2001 From: sgtJohnny Date: Mon, 13 Feb 2023 05:37:55 +0100 Subject: [PATCH 281/409] Translated using Weblate (German) Currently translated at 98.3% (240 of 244 strings) Co-authored-by: sgtJohnny Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 81 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index cbbcc57..51e3319 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -31,15 +31,15 @@ commands: pin: name: Nachricht anheften not_pinnable: - description: "Diese Nachricht kann nicht angeheftet werden.\nBitten Sie einen\ - \ Administrator, die Berechtigungen des Bots zu überprüfen.\n" + description: "Diese Nachricht kann nicht angeheftet werden.\nBitte einen Administrator,\ + \ die Berechtigungen des Bots zu überprüfen.\n" title: ❌ Fehler not_ticket: - description: Sie können nur Nachrichten in Tickets anheften. + description: Du kannst Nachrichten nur in Tickets anheften. title: ❌ Dies ist kein Ticketkanal pinned: description: Die Nachricht wurde angeheftet. - title: ✅ Gepinnte Nachricht + title: ✅ Nachricht angeheftet create: name: Ein Ticket aus einer Nachricht erstellen slash: @@ -66,13 +66,15 @@ commands: support: Unterstützung links: Nützliche Links docs: Dokumentation - commands: komplette Befehlsliste + commands: Vollständige Befehlsliste + feedback: Feedback commands: Befehle description: '**Verwende {command} um ein Ticket zu erstellen und Hilfe zu erhalten.**' - settings: Bot Einstellungen - name: Hilfe - title: Hilfe + settings: Bot-Einstellungen + name: hilfe + title: hilfe + description: Zeigt das Hilfemenü an force-close: options: reason: @@ -80,18 +82,28 @@ commands: description: Der Grund für das Schließen des/der Tickets category: name: Kategorie + description: Schließe alle Tickets einer Kategorie (kann mit `time` benutzt + werden) time: name: Zeit + description: Alle Tickets schließen, die für die angegebene Zeit inaktiv + waren (mit `Kategorie` verwendbar) + ticket: + description: Das Ticket welches geschlossen werden soll + name: ticket no_tickets: description: Es gibt keine offenen Tickets, die länger als `{time}` inaktiv waren. - title: Keine Tickets + title: ❌ Keine Tickets not_staff: title: ❌ Fehler + description: Nur Teammitglieder können Tickets zwangsweise abschliessen. confirm_multiple: description: "Du bist dabei **{count}** Tickets zu schließen die länger als\ \ `{time}`:\n{tickets} inaktiv waren.\n" title: ❓Bist du dir sicher? + description: Ticket zwangsweise schließen + name: force-close close: invalid_time: title: ❌ Ungültig @@ -101,7 +113,7 @@ commands: name: Grund description: Der Grund für das Schließen des Tickets description: Beantragen, dass ein Ticket geschlossen wird - name: Schließen + name: schließen move: name: verschieben description: Verschiebe ein Ticket in eine andere Kategorie @@ -116,9 +128,10 @@ commands: options: references: name: Verweise + description: Die Nummer eines zugehörigen Tickets claim: description: Ticket übernehmen - name: Übernehmen + name: übernehmen priority: options: priority: @@ -132,6 +145,7 @@ commands: title: ✅ Priorität gesetzt description: Die Priorität dieses Tickets wurde auf `{priority}` gesetzt. name: Priorität + description: Legt die Priorität eines Tickets fest tickets: not_staff: title: ❌ Fehler @@ -163,11 +177,16 @@ commands: options: member: name: Mitglied + description: Das Mitglied, das aus dem Ticket entfernt werden soll ticket: name: Ticket + description: Das Ticket, aus dem das Mitglied entfernt werden soll success: title: ✅ Entfernt description: '{member} wurde von {ticket} entfernt.' + description: Entfernt ein Mitglied aus einem Ticket + name: entfernen + removed: ⬅️ {removed} wurde von {by} entfernt. topic: description: Thema eines Tickets ändern name: thema @@ -185,7 +204,8 @@ commands: options: member: name: Mitglied - description: Das Mitglied an welches das Ticket transferiert werden soll. + description: Das Mitglied, an das die Eigentümerschaft übertragen werden + soll transferred: 📨 {user} hat dieses Ticket an {to} übertragen. description: Ein Ticket an ein anderes Mitglied übertragen tag: @@ -197,7 +217,12 @@ commands: description: Der Name des zu verwendenden Tags name: tag description: Verwende einen Tag - user: {} + release: + description: Ein Ticket freigeben (unclaim) + name: freigeben + user: + create: + name: Erstellt ein Ticket für einen Benutzer log: admin: changes: Änderungen @@ -207,25 +232,39 @@ log: settings: Einstellungen category: eine Kategorie panel: ein Panel + tag: ein Tag + joined: '{user} {verb} {targetType}' title: target: category: Kategorie panel: Panel settings: Einstellungen question: Frage + tag: Tag + joined: '{targetType} {verb}' verb: delete: gelöscht create: erstellt + update: Aktualisiert message: description: '{user} {verb} eine Nachricht' verb: delete: gelöscht + update: Aktualisiert message: Nachricht title: Nachricht {verb} ticket: title: Ticket {verb} description: '{user} {verb} ein Ticket' ticket: Ticket + verb: + claim: übernommen + close: Geschlossen + update: Aktualisiert + create: erstellt + unclaim: freigegeben + added: Mitglieder hinzugefügt + removed: Entfernte Mitglieder menus: category: placeholder: Wähle eine Ticketkategorie @@ -289,6 +328,7 @@ modals: rating: label: Bewertung placeholder: 1-5 + title: Wie haben wir das gemacht? topic: label: Thema placeholder: Worum geht es in diesem Ticket? @@ -312,6 +352,7 @@ ticket: user_request: title: ❓ {requestedBy} möchte dieses Ticket schließen wait_for_user: ✋ Bitte warte bis der Benutzer antwortet. + wait_for_staff: ✋ Bitte wartee, bis das Personal dieses Ticket schließt. created: title: ✅ Ticket erstellt description: 'Dein Ticketkanal wurde erstellt: {channel}.' @@ -334,8 +375,22 @@ ticket: number: Nummer topic: Thema title: ℹ️ Referenz + claimed: 🙌 {user} hat dieses Ticket übernommen. + released: ♻️ {user} hat das ticket freigegeben. dm: closed: title: Dein Ticket wurde geschlossen + fields: + closed: + name: Geschlossen um + value: '{timestamp} (nach {duration})' + closed_by: Geschlossen durch + created: Erstellt um + feedback: Deine Rückmeldung + reason: Grund des Schließens + response: Reaktionszeit + ticket: Ticket + topic: Thema + archived: Benutze `/transcript` in **{guild}** um archivierte Nachrichten einzusehen. confirm_open: title: Möchtest du ein Ticket mit dem folgendem Thema eröffnen? From 63c3cc729a5ba188b87c88639cc048f3bf6f2ed0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 13 Feb 2023 13:14:51 +0000 Subject: [PATCH 282/409] chore: update scripts --- scripts/keygen.js | 4 ++-- scripts/preinstall.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/keygen.js b/scripts/keygen.js index 4a6964a..08ec5d0 100644 --- a/scripts/keygen.js +++ b/scripts/keygen.js @@ -2,7 +2,7 @@ const { randomBytes } = require('crypto'); const { short } = require('leeks.js'); console.log(short( - 'Set the "ENCRYPTION_KEY" environment variable to: \n&!b' + + 'Set the "ENCRYPTION_KEY" environment variable to: \n&!b ' + randomBytes(24).toString('hex') + - '&r\n\n&0&!e WARNING &r &e&lIf you lose the encryption key, most of the data in the database will become unreadable, requiring a new key and a full reset.', + ' &r\n\n&0&!e WARNING &r &e&lIf you lose the encryption key, most of the data in the database will become unreadable, requiring a new key and a full reset.', )); \ No newline at end of file diff --git a/scripts/preinstall.js b/scripts/preinstall.js index 20261e5..2e51c02 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -29,6 +29,7 @@ if (!process.env.ENCRYPTION_KEY && !fs.existsSync('./.env')) { log('generating ENCRYPTION_KEY'); fs.writeFileSync('./.env', Object.entries(env).map(([k, v]) => `${k}=${v}`).join('\n')); log('created .env file'); + log(short('&r&0&!e WARNING &r &e&lkeep your environment variables safe, don\'t lose your encryption key or you will lose data')); } else { log('nothing to do'); } \ No newline at end of file From 380a84617a079a907193245a7d06cfd3cc730ed6 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 13 Feb 2023 14:29:01 +0000 Subject: [PATCH 283/409] chore: add commitlint --- .commitlintrc.js | 1 + .husky/commit-msg | 4 + package.json | 3 + pnpm-lock.yaml | 923 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 914 insertions(+), 17 deletions(-) create mode 100644 .commitlintrc.js create mode 100644 .husky/commit-msg diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 0000000..3d88028 --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1 @@ +module.exports = { extends: ['@commitlint/config-conventional'] }; \ No newline at end of file diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..c160a77 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx --no -- commitlint --edit ${1} diff --git a/package.json b/package.json index 5355ed2..e68991a 100644 --- a/package.json +++ b/package.json @@ -66,9 +66,12 @@ "yaml": "^1.10.2" }, "devDependencies": { + "@commitlint/cli": "^17.4.3", + "@commitlint/config-conventional": "^17.4.3", "all-contributors-cli": "^6.24.0", "eslint": "^8.33.0", "eslint-plugin-unused-imports": "^2.0.0", + "husky": "^8.0.3", "nodemon": "^2.0.20" }, "optionalDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99e4b42..87667a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,6 +1,8 @@ lockfileVersion: 5.4 specifiers: + '@commitlint/cli': ^17.4.3 + '@commitlint/config-conventional': ^17.4.3 '@discord-tickets/settings': ^1.3.3 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 @@ -23,6 +25,7 @@ specifiers: fastify: ^4.12.0 figlet: ^1.5.2 fs-extra: ^10.1.0 + husky: ^8.0.3 keyv: ^4.5.2 leeks.js: ^0.2.4 leekslazylogger: ^5.0.0 @@ -79,13 +82,37 @@ optionalDependencies: zlib-sync: 0.1.8 devDependencies: + '@commitlint/cli': 17.4.3 + '@commitlint/config-conventional': 17.4.3 all-contributors-cli: 6.24.0 eslint: 8.33.0 eslint-plugin-unused-imports: 2.0.0_eslint@8.33.0 + husky: 8.0.3 nodemon: 2.0.20 packages: + /@babel/code-frame/7.18.6: + resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.18.6 + dev: true + + /@babel/helper-validator-identifier/7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/highlight/7.18.6: + resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.19.1 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: true + /@babel/runtime/7.20.13: resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==} engines: {node: '>=6.9.0'} @@ -93,6 +120,181 @@ packages: regenerator-runtime: 0.13.11 dev: true + /@commitlint/cli/17.4.3: + resolution: {integrity: sha512-IPTS7AZuBHgD0gl24El8HwuDM9zJN9JLa5KmZUQoFD1BQeGGdzAYJOnAr85CeJWpTDok0BGHDL0+4odnH0iTyA==} + engines: {node: '>=v14'} + hasBin: true + dependencies: + '@commitlint/format': 17.4.0 + '@commitlint/lint': 17.4.3 + '@commitlint/load': 17.4.2 + '@commitlint/read': 17.4.2 + '@commitlint/types': 17.4.0 + execa: 5.1.1 + lodash.isfunction: 3.0.9 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + yargs: 17.6.2 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + dev: true + + /@commitlint/config-conventional/17.4.3: + resolution: {integrity: sha512-8EsY2iDw74hCk3hIQSg7/E0R8/KtPjnFPZVwmmHxcjhZjkSykmxysefICPDnbI3xgxfov0zwL1WKDHM8zglJdw==} + engines: {node: '>=v14'} + dependencies: + conventional-changelog-conventionalcommits: 5.0.0 + dev: true + + /@commitlint/config-validator/17.4.0: + resolution: {integrity: sha512-Sa/+8KNpDXz4zT4bVbz2fpFjvgkPO6u2V2fP4TKgt6FjmOw2z3eEX859vtfeaTav/ukBw0/0jr+5ZTZp9zCBhA==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.4.0 + ajv: 8.12.0 + dev: true + + /@commitlint/ensure/17.4.0: + resolution: {integrity: sha512-7oAxt25je0jeQ/E0O/M8L3ADb1Cvweu/5lc/kYF8g/kXatI0wxGE5La52onnAUAWeWlsuvBNar15WcrmDmr5Mw==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.4.0 + lodash.camelcase: 4.3.0 + lodash.kebabcase: 4.1.1 + lodash.snakecase: 4.1.1 + lodash.startcase: 4.4.0 + lodash.upperfirst: 4.3.1 + dev: true + + /@commitlint/execute-rule/17.4.0: + resolution: {integrity: sha512-LIgYXuCSO5Gvtc0t9bebAMSwd68ewzmqLypqI2Kke1rqOqqDbMpYcYfoPfFlv9eyLIh4jocHWwCK5FS7z9icUA==} + engines: {node: '>=v14'} + dev: true + + /@commitlint/format/17.4.0: + resolution: {integrity: sha512-Z2bWAU5+f1YZh9W76c84J8iLIWIvvm+mzqogTz0Nsc1x6EHW0Z2gI38g5HAjB0r0I3ZjR15IDEJKhsxyblcyhA==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.4.0 + chalk: 4.1.2 + dev: true + + /@commitlint/is-ignored/17.4.2: + resolution: {integrity: sha512-1b2Y2qJ6n7bHG9K6h8S4lBGUl6kc7mMhJN9gy1SQfUZqe92ToDjUTtgNWb6LbzR1X8Cq4SEus4VU8Z/riEa94Q==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.4.0 + semver: 7.3.8 + dev: true + + /@commitlint/lint/17.4.3: + resolution: {integrity: sha512-GnPsqEYmXIB/MaBhRMzkiDJWyjuLrKad4xoxKO4N6Kc19iqjR4DPc/bl2dxeW9kUmtrAtefOzIEzJAevpA5y2w==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/is-ignored': 17.4.2 + '@commitlint/parse': 17.4.2 + '@commitlint/rules': 17.4.3 + '@commitlint/types': 17.4.0 + dev: true + + /@commitlint/load/17.4.2: + resolution: {integrity: sha512-Si++F85rJ9t4hw6JcOw1i2h0fdpdFQt0YKwjuK4bk9KhFjyFkRxvR3SB2dPaMs+EwWlDrDBGL+ygip1QD6gmPw==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/config-validator': 17.4.0 + '@commitlint/execute-rule': 17.4.0 + '@commitlint/resolve-extends': 17.4.0 + '@commitlint/types': 17.4.0 + '@types/node': 18.11.18 + chalk: 4.1.2 + cosmiconfig: 8.0.0 + cosmiconfig-typescript-loader: 4.3.0_nww3gwtks3ghjaektoxywfmuuy + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + lodash.uniq: 4.5.0 + resolve-from: 5.0.0 + ts-node: 10.9.1_bdgp3l2zgaopogaavxusmetvge + typescript: 4.9.5 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + dev: true + + /@commitlint/message/17.4.2: + resolution: {integrity: sha512-3XMNbzB+3bhKA1hSAWPCQA3lNxR4zaeQAQcHj0Hx5sVdO6ryXtgUBGGv+1ZCLMgAPRixuc6en+iNAzZ4NzAa8Q==} + engines: {node: '>=v14'} + dev: true + + /@commitlint/parse/17.4.2: + resolution: {integrity: sha512-DK4EwqhxfXpyCA+UH8TBRIAXAfmmX4q9QRBz/2h9F9sI91yt6mltTrL6TKURMcjUVmgaB80wgS9QybNIyVBIJA==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/types': 17.4.0 + conventional-changelog-angular: 5.0.13 + conventional-commits-parser: 3.2.4 + dev: true + + /@commitlint/read/17.4.2: + resolution: {integrity: sha512-hasYOdbhEg+W4hi0InmXHxtD/1favB4WdwyFxs1eOy/DvMw6+2IZBmATgGOlqhahsypk4kChhxjAFJAZ2F+JBg==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/top-level': 17.4.0 + '@commitlint/types': 17.4.0 + fs-extra: 11.1.0 + git-raw-commits: 2.0.11 + minimist: 1.2.8 + dev: true + + /@commitlint/resolve-extends/17.4.0: + resolution: {integrity: sha512-3JsmwkrCzoK8sO22AzLBvNEvC1Pmdn/65RKXzEtQMy6oYMl0Snrq97a5bQQEFETF0VsvbtUuKttLqqgn99OXRQ==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/config-validator': 17.4.0 + '@commitlint/types': 17.4.0 + import-fresh: 3.3.0 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 + resolve-global: 1.0.0 + dev: true + + /@commitlint/rules/17.4.3: + resolution: {integrity: sha512-xHReDfE3Z+O9p1sXeEhPRSk4FifBsC4EbXzvQ4aa0ykQe+n/iZDd4CrFC/Oiv2K9BU4ZnFHak30IbMLa4ks1Rw==} + engines: {node: '>=v14'} + dependencies: + '@commitlint/ensure': 17.4.0 + '@commitlint/message': 17.4.2 + '@commitlint/to-lines': 17.4.0 + '@commitlint/types': 17.4.0 + execa: 5.1.1 + dev: true + + /@commitlint/to-lines/17.4.0: + resolution: {integrity: sha512-LcIy/6ZZolsfwDUWfN1mJ+co09soSuNASfKEU5sCmgFCvX5iHwRYLiIuoqXzOVDYOy7E7IcHilr/KS0e5T+0Hg==} + engines: {node: '>=v14'} + dev: true + + /@commitlint/top-level/17.4.0: + resolution: {integrity: sha512-/1loE/g+dTTQgHnjoCy0AexKAEFyHsR2zRB4NWrZ6lZSMIxAhBJnmCqwao7b4H8888PsfoTBCLBYIw8vGnej8g==} + engines: {node: '>=v14'} + dependencies: + find-up: 5.0.0 + dev: true + + /@commitlint/types/17.4.0: + resolution: {integrity: sha512-2NjAnq5IcxY9kXtUeO2Ac0aPpvkuOmwbH/BxIm36XXK5LtWFObWJWjXOA+kcaABMrthjWu6la+FUpyYFMHRvbA==} + engines: {node: '>=v14'} + dependencies: + chalk: 4.1.2 + dev: true + + /@cspotcode/source-map-support/0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + dev: true + /@discord-tickets/settings/1.3.3_svelte@3.55.1: resolution: {integrity: sha512-G6Z6DBsotbgY6CIhD+yVF1m1Lz/v699ONIEBKXLwMpAhXSXF+tCSzCcpRc5Do056YbVlEVxVUER4lbVtHn/ytQ==} dependencies: @@ -346,6 +548,22 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@jridgewell/resolve-uri/3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec/1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + dev: true + + /@jridgewell/trace-mapping/0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + /@lukeed/ms/2.0.1: resolution: {integrity: sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==} engines: {node: '>=8'} @@ -417,9 +635,32 @@ packages: resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} dev: false + /@tsconfig/node10/1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12/1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14/1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16/1.0.3: + resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} + dev: true + + /@types/minimist/1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true + /@types/node/18.11.18: resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} - dev: false + + /@types/normalize-package-data/2.4.1: + resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + dev: true /@types/ws/8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} @@ -427,6 +668,14 @@ packages: '@types/node': 18.11.18 dev: false + /JSONStream/1.3.5: + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + dev: true + /abbrev/1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} dev: true @@ -458,6 +707,11 @@ packages: acorn: 8.8.2 dev: true + /acorn-walk/8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + /acorn/8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} @@ -491,7 +745,6 @@ packages: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 uri-js: 4.4.1 - dev: false /all-contributors-cli/6.24.0: resolution: {integrity: sha512-7oSKr2PnqxsOotuSwciltcFTS1eVRdjR0cn99hbElfff7gRQBShVhsf/XBprY41sLcgqTk0l0MKgKv6QNgZdMg==} @@ -523,6 +776,13 @@ packages: engines: {node: '>=8'} dev: true + /ansi-styles/3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + /ansi-styles/4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -542,6 +802,10 @@ packages: resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} dev: false + /arg/4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + /argparse/2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true @@ -550,6 +814,15 @@ packages: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} dev: false + /array-ify/1.0.0: + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + dev: true + + /arrify/1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + /asn1.js/5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} dependencies: @@ -673,11 +946,29 @@ packages: engines: {node: '>=6'} dev: true + /camelcase-keys/6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + dev: true + /camelcase/5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} dev: true + /chalk/2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + /chalk/4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -725,11 +1016,26 @@ packages: wrap-ansi: 6.2.0 dev: true + /cliui/8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + /clone/1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} dev: false + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + /color-convert/2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -737,10 +1043,21 @@ packages: color-name: 1.1.4 dev: true + /color-name/1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true + /color-name/1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /compare-func/2.0.0: + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + dev: true + /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -756,6 +1073,36 @@ packages: engines: {node: '>= 0.6'} dev: false + /conventional-changelog-angular/5.0.13: + resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} + engines: {node: '>=10'} + dependencies: + compare-func: 2.0.0 + q: 1.5.1 + dev: true + + /conventional-changelog-conventionalcommits/5.0.0: + resolution: {integrity: sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw==} + engines: {node: '>=10'} + dependencies: + compare-func: 2.0.0 + lodash: 4.17.21 + q: 1.5.1 + dev: true + + /conventional-commits-parser/3.2.4: + resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} + engines: {node: '>=10'} + hasBin: true + dependencies: + JSONStream: 1.3.5 + is-text-path: 1.0.1 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + dev: true + /cookie-signature/1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} dev: false @@ -770,6 +1117,35 @@ packages: engines: {node: '>= 0.6'} dev: false + /cosmiconfig-typescript-loader/4.3.0_nww3gwtks3ghjaektoxywfmuuy: + resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=7' + ts-node: '>=10' + typescript: '>=3' + dependencies: + '@types/node': 18.11.18 + cosmiconfig: 8.0.0 + ts-node: 10.9.1_bdgp3l2zgaopogaavxusmetvge + typescript: 4.9.5 + dev: true + + /cosmiconfig/8.0.0: + resolution: {integrity: sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==} + engines: {node: '>=14'} + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + dev: true + + /create-require/1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + /cross-spawn/7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -783,6 +1159,11 @@ packages: resolution: {integrity: sha512-vJBrDcKAZ/OpMW4EptbGTD2EfNEywh6QtsPDX5kd2u1MNBmTLGC3IlOoRh8Ov7dmZhFfRjUJ07u4eCoJ8+oM9A==} dev: false + /dargs/7.0.0: + resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} + engines: {node: '>=8'} + dev: true + /date-fns/2.29.3: resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} engines: {node: '>=0.11'} @@ -822,6 +1203,14 @@ packages: dependencies: ms: 2.1.2 + /decamelize-keys/1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + /decamelize/1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -851,6 +1240,11 @@ packages: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true + /diff/4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + /discord-api-types/0.37.31: resolution: {integrity: sha512-k9DQQ7Wv+ehiF7901qk/FnP47k6O2MHm3meQFee4gUzi5dfGAVLf7SfLNtb4w7G2dmukJyWQtVJEDF9oMb9yuQ==} dev: false @@ -883,6 +1277,13 @@ packages: esutils: 2.0.3 dev: true + /dot-prop/5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + dependencies: + is-obj: 2.0.0 + dev: true + /dotenv/16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} @@ -925,6 +1326,17 @@ packages: once: 1.4.0 dev: false + /error-ex/1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: true + + /escalade/3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + /escape-html/1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} dev: false @@ -1082,6 +1494,21 @@ packages: engines: {node: '>=0.8.x'} dev: false + /execa/5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true + /express/4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -1351,6 +1778,15 @@ packages: universalify: 2.0.0 dev: false + /fs-extra/11.1.0: + resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} + engines: {node: '>=14.14'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + /fs.realpath/1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true @@ -1365,7 +1801,6 @@ packages: /function-bind/1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: false /get-caller-file/2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} @@ -1380,6 +1815,23 @@ packages: has-symbols: 1.0.3 dev: false + /get-stream/6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: true + + /git-raw-commits/2.0.11: + resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} + engines: {node: '>=10'} + hasBin: true + dependencies: + dargs: 7.0.0 + lodash: 4.17.21 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + dev: true + /glob-parent/5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1405,6 +1857,13 @@ packages: path-is-absolute: 1.0.1 dev: true + /global-dirs/0.1.1: + resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} + engines: {node: '>=4'} + dependencies: + ini: 1.3.8 + dev: true + /globals/13.20.0: resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} engines: {node: '>=8'} @@ -1414,12 +1873,16 @@ packages: /graceful-fs/4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - dev: false /grapheme-splitter/1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /hard-rejection/2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + dev: true + /has-flag/3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -1439,7 +1902,17 @@ packages: engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 - dev: false + + /hosted-git-info/2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true + + /hosted-git-info/4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + dependencies: + lru-cache: 6.0.0 + dev: true /http-errors/2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} @@ -1452,6 +1925,17 @@ packages: toidentifier: 1.0.1 dev: false + /human-signals/2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: true + + /husky/8.0.3: + resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} + engines: {node: '>=14'} + hasBin: true + dev: true + /iconv-lite/0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -1484,6 +1968,11 @@ packages: engines: {node: '>=0.8.19'} dev: true + /indent-string/4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + /inflight/1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -1494,6 +1983,10 @@ packages: /inherits/2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + /ini/1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: true + /inquirer/7.3.3: resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} engines: {node: '>=8.0.0'} @@ -1518,6 +2011,10 @@ packages: engines: {node: '>= 0.10'} dev: false + /is-arrayish/0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true + /is-binary-path/2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1525,6 +2022,12 @@ packages: binary-extensions: 2.2.0 dev: true + /is-core-module/2.11.0: + resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + dependencies: + has: 1.0.3 + dev: true + /is-extglob/2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1547,11 +2050,33 @@ packages: engines: {node: '>=0.12.0'} dev: true + /is-obj/2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + dev: true + /is-path-inside/3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} dev: true + /is-plain-obj/1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: true + + /is-stream/2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true + + /is-text-path/1.0.1: + resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} + engines: {node: '>=0.10.0'} + dependencies: + text-extensions: 1.9.0 + dev: true + /isexe/2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -1564,6 +2089,10 @@ packages: resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} dev: true + /js-tokens/4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true + /js-yaml/4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1584,13 +2113,16 @@ packages: pegjs: 0.10.0 dev: true + /json-parse-even-better-errors/2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true + /json-schema-traverse/0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true /json-schema-traverse/1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: false /json-stable-stringify-without-jsonify/1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -1602,7 +2134,11 @@ packages: universalify: 2.0.0 optionalDependencies: graceful-fs: 4.2.10 - dev: false + + /jsonparse/1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + dev: true /keyv/4.5.2: resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} @@ -1610,6 +2146,11 @@ packages: json-buffer: 3.0.1 dev: false + /kind-of/6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + dev: true + /leeks.js/0.2.4: resolution: {integrity: sha512-yFR6BtcTA/5s2FJAVkPn2VEzqO76DqBIdkC+1vNyersz1Hw0DxhunRu7bI7/XKxwrNy8x0K4e1p4YQKIGRCBww==} dev: false @@ -1639,6 +2180,10 @@ packages: set-cookie-parser: 2.5.1 dev: false + /lines-and-columns/1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true + /locate-path/5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -1653,13 +2198,44 @@ packages: p-locate: 5.0.0 dev: true + /lodash.camelcase/4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + dev: true + + /lodash.isfunction/3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + dev: true + + /lodash.isplainobject/4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: true + + /lodash.kebabcase/4.1.1: + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} + dev: true + /lodash.merge/4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash.mergewith/4.6.2: + resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + dev: true + /lodash.snakecase/4.1.1: resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} - dev: false + + /lodash.startcase/4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + dev: true + + /lodash.uniq/4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + dev: true + + /lodash.upperfirst/4.3.1: + resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + dev: true /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -1669,7 +2245,10 @@ packages: engines: {node: '>=10'} dependencies: yallist: 4.0.0 - dev: false + + /make-error/1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true /map-o/2.0.10: resolution: {integrity: sha512-BxazE81fVByHWasyXhqKeo2m7bFKYu+ZbEfiuexMOnklXW+tzDvnlTi/JaklEeuuwqcqJzPaf9q+TWptSGXeLg==} @@ -1677,6 +2256,16 @@ packages: iterate-object: 1.3.4 dev: false + /map-obj/1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + dev: true + + /map-obj/4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + dev: true + /marked/4.2.12: resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==} engines: {node: '>= 12'} @@ -1688,10 +2277,31 @@ packages: engines: {node: '>= 0.6'} dev: false + /meow/8.1.2: + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + dev: true + /merge-descriptors/1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} dev: false + /merge-stream/2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + /methods/1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -1720,6 +2330,11 @@ packages: engines: {node: '>=6'} dev: true + /min-indent/1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + /minimalistic-assert/1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} dev: false @@ -1729,6 +2344,19 @@ packages: dependencies: brace-expansion: 1.1.11 + /minimist-options/4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + dev: true + + /minimist/1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + /mnemonist/0.39.5: resolution: {integrity: sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==} dependencies: @@ -1829,11 +2457,37 @@ packages: abbrev: 1.1.1 dev: true + /normalize-package-data/2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.1 + semver: 5.7.1 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-package-data/3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.11.0 + semver: 7.3.8 + validate-npm-package-license: 3.0.4 + dev: true + /normalize-path/3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} dev: true + /npm-run-path/4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + dev: true + /object-diffy/1.0.4: resolution: {integrity: sha512-zsgUWZhu9YtP7kp+PvCzUhYtOurBa7qIS2XUJFyVooq+I/ZlwFe0aHp1pyek/dpqd+EEYxM46j8czpW54JM2EA==} dev: false @@ -1933,6 +2587,16 @@ packages: callsites: 3.1.0 dev: true + /parse-json/5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.18.6 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: true + /parseurl/1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -1953,10 +2617,19 @@ packages: engines: {node: '>=8'} dev: true + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + /path-to-regexp/0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} dev: false + /path-type/4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true + /peek-readable/5.0.0: resolution: {integrity: sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==} engines: {node: '>=14.16'} @@ -2065,6 +2738,11 @@ packages: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} + /q/1.5.1: + resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + dev: true + /qs/6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} @@ -2080,6 +2758,11 @@ packages: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} dev: false + /quick-lru/4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + dev: true + /range-parser/1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -2095,6 +2778,25 @@ packages: unpipe: 1.0.0 dev: false + /read-pkg-up/7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + dev: true + + /read-pkg/5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + dev: true + /readable-stream/3.6.0: resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} engines: {node: '>= 6'} @@ -2102,7 +2804,6 @@ packages: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - dev: false /readable-stream/4.3.0: resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==} @@ -2133,6 +2834,14 @@ packages: engines: {node: '>= 12.13.0'} dev: false + /redent/3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + dev: true + /regenerator-runtime/0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} dev: true @@ -2150,7 +2859,6 @@ packages: /require-from-string/2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - dev: false /require-main-filename/2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} @@ -2161,6 +2869,27 @@ packages: engines: {node: '>=4'} dev: true + /resolve-from/5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true + + /resolve-global/1.0.0: + resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} + engines: {node: '>=8'} + dependencies: + global-dirs: 0.1.1 + dev: true + + /resolve/1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + /restore-cursor/3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -2209,7 +2938,6 @@ packages: /safe-buffer/5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: false /safe-regex2/2.0.0: resolution: {integrity: sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==} @@ -2245,7 +2973,6 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 - dev: false /send/0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} @@ -2351,6 +3078,34 @@ packages: engines: {node: '>=0.10.0'} dev: false + /spdx-correct/3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-exceptions/2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse/3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-license-ids/3.0.12: + resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + dev: true + + /split2/3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + dependencies: + readable-stream: 3.6.0 + dev: true + /split2/4.1.0: resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} engines: {node: '>= 10.x'} @@ -2389,7 +3144,6 @@ packages: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 - dev: false /strip-ansi/6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} @@ -2398,6 +3152,18 @@ packages: ansi-regex: 5.0.1 dev: true + /strip-final-newline/2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: true + + /strip-indent/3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + /strip-json-comments/3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -2432,6 +3198,11 @@ packages: supports-color: 7.2.0 dev: false + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + /svelte-modals/1.2.1_svelte@3.55.1: resolution: {integrity: sha512-7MEKUx5wb5YppkXWFGeRlYM5FMGEnpix39u9Y6GtCNTMXRDZ7DB2Z50IYLMRTMW5lOsCdtJgFbB0E3iZMKmsAA==} peerDependencies: @@ -2453,6 +3224,11 @@ packages: supports-hyperlinks: 2.3.0 dev: false + /text-extensions/1.9.0: + resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} + engines: {node: '>=0.10'} + dev: true + /text-table/0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true @@ -2467,6 +3243,12 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true + /through2/4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + dependencies: + readable-stream: 3.6.0 + dev: true + /tiny-lru/10.0.1: resolution: {integrity: sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==} engines: {node: '>=6'} @@ -2510,10 +3292,46 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: true + /trim-newlines/3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + dev: true + /ts-mixer/6.0.2: resolution: {integrity: sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A==} dev: false + /ts-node/10.9.1_bdgp3l2zgaopogaavxusmetvge: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.3 + '@types/node': 18.11.18 + acorn: 8.8.2 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /tslib/1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true @@ -2529,6 +3347,11 @@ packages: prelude-ls: 1.2.1 dev: true + /type-fest/0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} + dev: true + /type-fest/0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -2538,6 +3361,16 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + /type-fest/0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + dev: true + + /type-fest/0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + dev: true + /type-is/1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -2546,6 +3379,12 @@ packages: mime-types: 2.1.35 dev: false + /typescript/4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + /undefsafe/2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true @@ -2560,7 +3399,6 @@ packages: /universalify/2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} - dev: false /unpipe/1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} @@ -2582,13 +3420,23 @@ packages: /util-deprecate/1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: false /utils-merge/1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} dev: false + /v8-compile-cache-lib/3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + + /validate-npm-package-license/3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 + dev: true + /vary/1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -2637,6 +3485,15 @@ packages: strip-ansi: 6.0.1 dev: true + /wrap-ansi/7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -2665,9 +3522,13 @@ packages: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} dev: true + /y18n/5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + /yallist/4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: false /yaml/1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} @@ -2682,6 +3543,16 @@ packages: decamelize: 1.2.0 dev: true + /yargs-parser/20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: true + + /yargs-parser/21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + /yargs/15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} @@ -2699,6 +3570,24 @@ packages: yargs-parser: 18.1.3 dev: true + /yargs/17.6.2: + resolution: {integrity: sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + + /yn/3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true + /yocto-queue/0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} From f428fac4e57254439e1651eb96ed158bf62725e4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 13 Feb 2023 14:33:27 +0000 Subject: [PATCH 284/409] ci: add commitlint, update eslint --- .github/workflows/lint.yml | 48 ++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 10bae39..7c0e78c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,30 +1,32 @@ -name: eslint -on: - push: - branches: - - main +name: Lint +on: [push, pull_request] + # push: + # branches: + # - main # pull_request: - # branches: - # - main + # branches: + # - main jobs: - lint: + commitlint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - - name: Cache pnpm modules - uses: actions/cache@v2 - env: - cache-name: cache-pnpm-modules + - uses: actions/checkout@v3 with: - path: ~/.pnpm-store - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}-${{ hashFiles('**/package.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}- - - uses: pnpm/action-setup@v2.0.1 + fetch-depth: 0 + - uses: wagoid/commitlint-github-action@v5 with: - version: 6.3.0 - run_install: false - - run: pnpm install eslint - - run: pnpm run lint + configFile: .commitlintrc.js + eslint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: pnpm/action-setup@v2 + with: + version: 7 + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'pnpm' + - run: pnpm install + - run: pnpm run lint \ No newline at end of file From b8dd1900e950ccd140c8d9a32c235c6025d52fe0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 13 Feb 2023 14:36:15 +0000 Subject: [PATCH 285/409] fix: lint command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e68991a..2c97a96 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "contributors:add": "all-contributors add", "contributors:generate": "all-contributors generate", "keygen": "node scripts/keygen", - "lint": "eslint src scripts --ext mjs --fix", + "lint": "eslint src scripts --fix", "preinstall": "node scripts/preinstall", "postinstall": "node scripts/postinstall", "start": "node .", From c3d6d703162c651975d60264a0cc9213c24a31ce Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 13 Feb 2023 15:07:05 +0000 Subject: [PATCH 286/409] style: lint --- scripts/keygen.js | 1 + src/commands/user/create.js | 2 +- src/listeners/client/ready.js | 6 +++--- src/schemas/settings.js | 2 ++ src/stdin/eval.js | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/keygen.js b/scripts/keygen.js index 08ec5d0..6b19700 100644 --- a/scripts/keygen.js +++ b/scripts/keygen.js @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ const { randomBytes } = require('crypto'); const { short } = require('leeks.js'); diff --git a/src/commands/user/create.js b/src/commands/user/create.js index 5f0470a..4cf06a0 100644 --- a/src/commands/user/create.js +++ b/src/commands/user/create.js @@ -13,7 +13,7 @@ module.exports = class CreateUserCommand extends UserCommand { }); } - async run(interaction) { + async run(/* interaction */) { // TODO: isStaff? // TODO: user->create // select category diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 97b0a97..a736f20 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -121,9 +121,9 @@ module.exports = class extends Listener { // staleSince: Date.now(), // }); - for (const [ticketId, $] of client.tickets.$stale) { - // ⌛ - } + // for (const [ticketId, $] of client.tickets.$stale) { + // // ⌛ + // } }, ms('5m')); } }; diff --git a/src/schemas/settings.js b/src/schemas/settings.js index e1081b7..9b7694f 100644 --- a/src/schemas/settings.js +++ b/src/schemas/settings.js @@ -1,3 +1,5 @@ +// TODO +/* eslint-disable no-undef */ module.exports = joi.object({ archive: joi.boolean().optional(), autoClose: joi.number().min(3_600_000).optional(), diff --git a/src/stdin/eval.js b/src/stdin/eval.js index e9787b6..6a5de5c 100644 --- a/src/stdin/eval.js +++ b/src/stdin/eval.js @@ -12,7 +12,7 @@ module.exports = class extends StdinCommand { const toEval = input.join(' '); try { const res = await eval(toEval); - console.log(res); + console.log(res); // eslint-disable-line no-console return true; } catch (error) { this.client.log.error(error); From 1daab7fda9f33b04db0342ab42458940b857cd44 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 13 Feb 2023 15:07:35 +0000 Subject: [PATCH 287/409] chore: lint before commit --- .husky/pre-commit | 4 + package.json | 7 +- pnpm-lock.yaml | 261 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 268 insertions(+), 4 deletions(-) create mode 100644 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..d24fdfc --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx lint-staged diff --git a/package.json b/package.json index 2c97a96..c3cc084 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,15 @@ "keygen": "node scripts/keygen", "lint": "eslint src scripts --fix", "preinstall": "node scripts/preinstall", + "prepare": "husky install", "postinstall": "node scripts/postinstall", "start": "node .", "studio": "npx prisma studio", "test": "echo \"There's nothing to test\" && exit 1" }, + "lint-staged": { + "**/*.js": ["npm run lint --"] + }, "repository": { "type": "git", "url": "git+https://github.com/discord-tickets/bot.git" @@ -72,6 +76,7 @@ "eslint": "^8.33.0", "eslint-plugin-unused-imports": "^2.0.0", "husky": "^8.0.3", + "lint-staged": "^13.1.1", "nodemon": "^2.0.20" }, "optionalDependencies": { @@ -80,4 +85,4 @@ "utf-8-validate": "^5.0.10", "zlib-sync": "^0.1.8" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87667a4..99036aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,7 @@ specifiers: keyv: ^4.5.2 leeks.js: ^0.2.4 leekslazylogger: ^5.0.0 + lint-staged: ^13.1.1 ms: ^2.1.3 mustache: ^4.2.0 node-dir: ^0.1.17 @@ -88,6 +89,7 @@ devDependencies: eslint: 8.33.0 eslint-plugin-unused-imports: 2.0.0_eslint@8.33.0 husky: 8.0.3 + lint-staged: 13.1.1 nodemon: 2.0.20 packages: @@ -718,6 +720,14 @@ packages: hasBin: true dev: true + /aggregate-error/3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + /ajv-formats/2.1.1_ajv@8.12.0: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -776,6 +786,11 @@ packages: engines: {node: '>=8'} dev: true + /ansi-regex/6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + /ansi-styles/3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -790,6 +805,11 @@ packages: color-convert: 2.0.1 dev: true + /ansi-styles/6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: true + /anymatch/3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -832,6 +852,11 @@ packages: safer-buffer: 2.1.2 dev: false + /astral-regex/2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + dev: true + /async/3.2.4: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: true @@ -996,6 +1021,11 @@ packages: fsevents: 2.3.2 dev: true + /clean-stack/2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: true + /cli-cursor/3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -1003,6 +1033,22 @@ packages: restore-cursor: 3.1.0 dev: true + /cli-truncate/2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + dev: true + + /cli-truncate/3.1.0: + resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + slice-ansi: 5.0.0 + string-width: 5.1.2 + dev: true + /cli-width/3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} @@ -1051,6 +1097,15 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /colorette/2.0.19: + resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} + dev: true + + /commander/9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + dev: true + /compare-func/2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} dependencies: @@ -1289,6 +1344,10 @@ packages: engines: {node: '>=12'} dev: false + /eastasianwidth/0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + /ecdsa-sig-formatter/1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} dependencies: @@ -1311,6 +1370,10 @@ packages: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true + /emoji-regex/9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + /emojilib/2.4.0: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} dev: false @@ -1509,6 +1572,21 @@ packages: strip-final-newline: 2.0.0 dev: true + /execa/6.1.0: + resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 3.0.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + dev: true + /express/4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -1930,6 +2008,11 @@ packages: engines: {node: '>=10.17.0'} dev: true + /human-signals/3.0.1: + resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} + engines: {node: '>=12.20.0'} + dev: true + /husky/8.0.3: resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} engines: {node: '>=14'} @@ -2038,6 +2121,11 @@ packages: engines: {node: '>=8'} dev: true + /is-fullwidth-code-point/4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + dev: true + /is-glob/4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2070,6 +2158,11 @@ packages: engines: {node: '>=8'} dev: true + /is-stream/3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /is-text-path/1.0.1: resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} engines: {node: '>=0.10.0'} @@ -2180,10 +2273,57 @@ packages: set-cookie-parser: 2.5.1 dev: false + /lilconfig/2.0.6: + resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} + engines: {node: '>=10'} + dev: true + /lines-and-columns/1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true + /lint-staged/13.1.1: + resolution: {integrity: sha512-LLJLO0Kdbcv2a+CvSF4p1M7jBZOajKSMpBUvyR8+bXccsqPER0/NxTFQSpNHjqwV9kM3tkHczYerTB5wI+bksQ==} + engines: {node: ^14.13.1 || >=16.0.0} + hasBin: true + dependencies: + cli-truncate: 3.1.0 + colorette: 2.0.19 + commander: 9.5.0 + debug: 4.3.4 + execa: 6.1.0 + lilconfig: 2.0.6 + listr2: 5.0.7 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-inspect: 1.12.3 + pidtree: 0.6.0 + string-argv: 0.3.1 + yaml: 2.2.1 + transitivePeerDependencies: + - enquirer + - supports-color + dev: true + + /listr2/5.0.7: + resolution: {integrity: sha512-MD+qXHPmtivrHIDRwPYdfNkrzqDiuaKU/rfBcec3WMyMF3xylQj3jMq344OtvQxz7zaCFViRAeqlr2AFhPvXHw==} + engines: {node: ^14.13.1 || >=16.0.0} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true + dependencies: + cli-truncate: 2.1.0 + colorette: 2.0.19 + log-update: 4.0.0 + p-map: 4.0.0 + rfdc: 1.3.0 + rxjs: 7.8.0 + through: 2.3.8 + wrap-ansi: 7.0.0 + dev: true + /locate-path/5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -2240,6 +2380,16 @@ packages: /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + /log-update/4.0.0: + resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} + engines: {node: '>=10'} + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 + dev: true + /lru-cache/6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -2307,6 +2457,14 @@ packages: engines: {node: '>= 0.6'} dev: false + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + /mime-db/1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -2330,6 +2488,11 @@ packages: engines: {node: '>=6'} dev: true + /mimic-fn/4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + /min-indent/1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -2488,13 +2651,19 @@ packages: path-key: 3.1.1 dev: true + /npm-run-path/5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + /object-diffy/1.0.4: resolution: {integrity: sha512-zsgUWZhu9YtP7kp+PvCzUhYtOurBa7qIS2XUJFyVooq+I/ZlwFe0aHp1pyek/dpqd+EEYxM46j8czpW54JM2EA==} dev: false /object-inspect/1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - dev: false /obliterator/2.0.4: resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} @@ -2523,6 +2692,13 @@ packages: mimic-fn: 2.1.0 dev: true + /onetime/6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + /optionator/0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} @@ -2568,6 +2744,13 @@ packages: p-limit: 3.1.0 dev: true + /p-map/4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + dependencies: + aggregate-error: 3.1.0 + dev: true + /p-try/2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -2617,6 +2800,11 @@ packages: engines: {node: '>=8'} dev: true + /path-key/4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true @@ -2650,6 +2838,12 @@ packages: engines: {node: '>=8.6'} dev: true + /pidtree/0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + /pify/5.0.0: resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} engines: {node: '>=10'} @@ -2909,7 +3103,6 @@ packages: /rfdc/1.3.0: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} - dev: false /rimraf/3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} @@ -2936,6 +3129,12 @@ packages: tslib: 1.14.1 dev: true + /rxjs/7.8.0: + resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} + dependencies: + tslib: 2.5.0 + dev: true + /safe-buffer/5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -3063,6 +3262,32 @@ packages: semver: 7.0.0 dev: true + /slice-ansi/3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + dev: true + + /slice-ansi/4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + dev: true + + /slice-ansi/5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + dev: true + /sonic-boom/3.2.1: resolution: {integrity: sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==} dependencies: @@ -3131,6 +3356,11 @@ packages: engines: {node: '>=10.0.0'} dev: false + /string-argv/0.3.1: + resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} + engines: {node: '>=0.6.19'} + dev: true + /string-width/4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -3140,6 +3370,15 @@ packages: strip-ansi: 6.0.1 dev: true + /string-width/5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.0.1 + dev: true + /string_decoder/1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: @@ -3152,11 +3391,23 @@ packages: ansi-regex: 5.0.1 dev: true + /strip-ansi/7.0.1: + resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + /strip-final-newline/2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} dev: true + /strip-final-newline/3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + /strip-indent/3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -3338,7 +3589,6 @@ packages: /tslib/2.5.0: resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} - dev: false /type-check/0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} @@ -3535,6 +3785,11 @@ packages: engines: {node: '>= 6'} dev: false + /yaml/2.2.1: + resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} + engines: {node: '>= 14'} + dev: true + /yargs-parser/18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} From 50f3102ec9e08426272b9bc76c732ebca0d40f8e Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 13 Feb 2023 16:41:56 +0000 Subject: [PATCH 288/409] chore: remove husky from npm prepare --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c3cc084..88dffc0 100644 --- a/package.json +++ b/package.json @@ -10,14 +10,15 @@ "keygen": "node scripts/keygen", "lint": "eslint src scripts --fix", "preinstall": "node scripts/preinstall", - "prepare": "husky install", "postinstall": "node scripts/postinstall", "start": "node .", "studio": "npx prisma studio", "test": "echo \"There's nothing to test\" && exit 1" }, "lint-staged": { - "**/*.js": ["npm run lint --"] + "**/*.js": [ + "npm run lint --" + ] }, "repository": { "type": "git", From 9fe300dc075744544768aea4c2e50985ef5233b3 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 13 Feb 2023 16:42:35 +0000 Subject: [PATCH 289/409] chore: add changelog generator --- package.json | 2 + pnpm-lock.yaml | 426 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 427 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 88dffc0..5505577 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "description": "An open-source Discord bot for ticket management", "main": "src/", "scripts": { + "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", "contributors:add": "all-contributors add", "contributors:generate": "all-contributors generate", "keygen": "node scripts/keygen", @@ -74,6 +75,7 @@ "@commitlint/cli": "^17.4.3", "@commitlint/config-conventional": "^17.4.3", "all-contributors-cli": "^6.24.0", + "conventional-changelog-cli": "^2.2.2", "eslint": "^8.33.0", "eslint-plugin-unused-imports": "^2.0.0", "husky": "^8.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99036aa..147ea13 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,7 @@ specifiers: '@prisma/client': ^4.9.0 all-contributors-cli: ^6.24.0 bufferutil: ^4.0.7 + conventional-changelog-cli: ^2.2.2 cryptr: ^6.1.0 discord.js: ^14.7.1 dotenv: ^16.0.3 @@ -86,6 +87,7 @@ devDependencies: '@commitlint/cli': 17.4.3 '@commitlint/config-conventional': 17.4.3 all-contributors-cli: 6.24.0 + conventional-changelog-cli: 2.2.2 eslint: 8.33.0 eslint-plugin-unused-imports: 2.0.0_eslint@8.33.0 husky: 8.0.3 @@ -550,6 +552,11 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@hutson/parse-repository-url/3.0.2: + resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} + engines: {node: '>=6.9.0'} + dev: true + /@jridgewell/resolve-uri/3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} @@ -720,6 +727,10 @@ packages: hasBin: true dev: true + /add-stream/1.0.0: + resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} + dev: true + /aggregate-error/3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -1062,6 +1073,14 @@ packages: wrap-ansi: 6.2.0 dev: true + /cliui/7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + /cliui/8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1136,6 +1155,41 @@ packages: q: 1.5.1 dev: true + /conventional-changelog-atom/2.0.8: + resolution: {integrity: sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-cli/2.2.2: + resolution: {integrity: sha512-8grMV5Jo8S0kP3yoMeJxV2P5R6VJOqK72IiSV9t/4H5r/HiRqEBQ83bYGuz4Yzfdj4bjaAEhZN/FFbsFXr5bOA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + add-stream: 1.0.0 + conventional-changelog: 3.1.25 + lodash: 4.17.21 + meow: 8.1.2 + tempfile: 3.0.0 + dev: true + + /conventional-changelog-codemirror/2.0.8: + resolution: {integrity: sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-conventionalcommits/4.6.3: + resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==} + engines: {node: '>=10'} + dependencies: + compare-func: 2.0.0 + lodash: 4.17.21 + q: 1.5.1 + dev: true + /conventional-changelog-conventionalcommits/5.0.0: resolution: {integrity: sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw==} engines: {node: '>=10'} @@ -1145,6 +1199,108 @@ packages: q: 1.5.1 dev: true + /conventional-changelog-core/4.2.4: + resolution: {integrity: sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==} + engines: {node: '>=10'} + dependencies: + add-stream: 1.0.0 + conventional-changelog-writer: 5.0.1 + conventional-commits-parser: 3.2.4 + dateformat: 3.0.3 + get-pkg-repo: 4.2.1 + git-raw-commits: 2.0.11 + git-remote-origin-url: 2.0.0 + git-semver-tags: 4.1.1 + lodash: 4.17.21 + normalize-package-data: 3.0.3 + q: 1.5.1 + read-pkg: 3.0.0 + read-pkg-up: 3.0.0 + through2: 4.0.2 + dev: true + + /conventional-changelog-ember/2.0.9: + resolution: {integrity: sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-eslint/3.0.9: + resolution: {integrity: sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-express/2.0.6: + resolution: {integrity: sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-jquery/3.0.11: + resolution: {integrity: sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==} + engines: {node: '>=10'} + dependencies: + q: 1.5.1 + dev: true + + /conventional-changelog-jshint/2.0.9: + resolution: {integrity: sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==} + engines: {node: '>=10'} + dependencies: + compare-func: 2.0.0 + q: 1.5.1 + dev: true + + /conventional-changelog-preset-loader/2.3.4: + resolution: {integrity: sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==} + engines: {node: '>=10'} + dev: true + + /conventional-changelog-writer/5.0.1: + resolution: {integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==} + engines: {node: '>=10'} + hasBin: true + dependencies: + conventional-commits-filter: 2.0.7 + dateformat: 3.0.3 + handlebars: 4.7.7 + json-stringify-safe: 5.0.1 + lodash: 4.17.21 + meow: 8.1.2 + semver: 6.3.0 + split: 1.0.1 + through2: 4.0.2 + dev: true + + /conventional-changelog/3.1.25: + resolution: {integrity: sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==} + engines: {node: '>=10'} + dependencies: + conventional-changelog-angular: 5.0.13 + conventional-changelog-atom: 2.0.8 + conventional-changelog-codemirror: 2.0.8 + conventional-changelog-conventionalcommits: 4.6.3 + conventional-changelog-core: 4.2.4 + conventional-changelog-ember: 2.0.9 + conventional-changelog-eslint: 3.0.9 + conventional-changelog-express: 2.0.6 + conventional-changelog-jquery: 3.0.11 + conventional-changelog-jshint: 2.0.9 + conventional-changelog-preset-loader: 2.3.4 + dev: true + + /conventional-commits-filter/2.0.7: + resolution: {integrity: sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==} + engines: {node: '>=10'} + dependencies: + lodash.ismatch: 4.4.0 + modify-values: 1.0.1 + dev: true + /conventional-commits-parser/3.2.4: resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} engines: {node: '>=10'} @@ -1172,6 +1328,10 @@ packages: engines: {node: '>= 0.6'} dev: false + /core-util-is/1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: true + /cosmiconfig-typescript-loader/4.3.0_nww3gwtks3ghjaektoxywfmuuy: resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} @@ -1224,6 +1384,10 @@ packages: engines: {node: '>=0.11'} dev: false + /dateformat/3.0.3: + resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} + dev: true + /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -1809,6 +1973,13 @@ packages: safe-regex2: 2.0.0 dev: false + /find-up/2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + dependencies: + locate-path: 2.0.0 + dev: true + /find-up/4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -1893,6 +2064,17 @@ packages: has-symbols: 1.0.3 dev: false + /get-pkg-repo/4.2.1: + resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} + engines: {node: '>=6.9.0'} + hasBin: true + dependencies: + '@hutson/parse-repository-url': 3.0.2 + hosted-git-info: 4.1.0 + through2: 2.0.5 + yargs: 16.2.0 + dev: true + /get-stream/6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -1910,6 +2092,29 @@ packages: through2: 4.0.2 dev: true + /git-remote-origin-url/2.0.0: + resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} + engines: {node: '>=4'} + dependencies: + gitconfiglocal: 1.0.0 + pify: 2.3.0 + dev: true + + /git-semver-tags/4.1.1: + resolution: {integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + meow: 8.1.2 + semver: 6.3.0 + dev: true + + /gitconfiglocal/1.0.0: + resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} + dependencies: + ini: 1.3.8 + dev: true + /glob-parent/5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1956,6 +2161,19 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /handlebars/4.7.7: + resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} + engines: {node: '>=0.4.7'} + hasBin: true + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.17.4 + dev: true + /hard-rejection/2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} @@ -2170,6 +2388,10 @@ packages: text-extensions: 1.9.0 dev: true + /isarray/1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: true + /isexe/2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -2206,6 +2428,10 @@ packages: pegjs: 0.10.0 dev: true + /json-parse-better-errors/1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + dev: true + /json-parse-even-better-errors/2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true @@ -2221,6 +2447,10 @@ packages: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true + /json-stringify-safe/5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + dev: true + /jsonfile/6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: @@ -2324,6 +2554,24 @@ packages: wrap-ansi: 7.0.0 dev: true + /load-json-file/4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + dependencies: + graceful-fs: 4.2.10 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + dev: true + + /locate-path/2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + dev: true + /locate-path/5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -2346,6 +2594,10 @@ packages: resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} dev: true + /lodash.ismatch/4.4.0: + resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} + dev: true + /lodash.isplainobject/4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} dev: true @@ -2526,6 +2778,11 @@ packages: obliterator: 2.0.4 dev: false + /modify-values/1.0.1: + resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} + engines: {node: '>=0.10.0'} + dev: true + /ms/2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: false @@ -2565,6 +2822,10 @@ packages: engines: {node: '>= 0.6'} dev: false + /neo-async/2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + dev: true + /node-dir/0.1.17: resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} engines: {node: '>= 0.10.5'} @@ -2716,6 +2977,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /p-limit/1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + dependencies: + p-try: 1.0.0 + dev: true + /p-limit/2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -2730,6 +2998,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-locate/2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + dependencies: + p-limit: 1.3.0 + dev: true + /p-locate/4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -2751,6 +3026,11 @@ packages: aggregate-error: 3.1.0 dev: true + /p-try/1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + dev: true + /p-try/2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -2770,6 +3050,14 @@ packages: callsites: 3.1.0 dev: true + /parse-json/4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + dev: true + /parse-json/5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -2785,6 +3073,11 @@ packages: engines: {node: '>= 0.8'} dev: false + /path-exists/3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + dev: true + /path-exists/4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2813,6 +3106,13 @@ packages: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} dev: false + /path-type/3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + dependencies: + pify: 3.0.0 + dev: true + /path-type/4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -2844,6 +3144,16 @@ packages: hasBin: true dev: true + /pify/2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + dev: true + + /pify/3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + dev: true + /pify/5.0.0: resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} engines: {node: '>=10'} @@ -2900,6 +3210,10 @@ packages: '@prisma/engines': 4.9.0 dev: false + /process-nextick-args/2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: true + /process-warning/2.1.0: resolution: {integrity: sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg==} dev: false @@ -2972,6 +3286,14 @@ packages: unpipe: 1.0.0 dev: false + /read-pkg-up/3.0.0: + resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} + engines: {node: '>=4'} + dependencies: + find-up: 2.1.0 + read-pkg: 3.0.0 + dev: true + /read-pkg-up/7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -2981,6 +3303,15 @@ packages: type-fest: 0.8.1 dev: true + /read-pkg/3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + dev: true + /read-pkg/5.2.0: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} @@ -2991,6 +3322,18 @@ packages: type-fest: 0.6.0 dev: true + /readable-stream/2.3.7: + resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: true + /readable-stream/3.6.0: resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} engines: {node: '>= 6'} @@ -3135,6 +3478,10 @@ packages: tslib: 2.5.0 dev: true + /safe-buffer/5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: true + /safe-buffer/5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -3161,6 +3508,11 @@ packages: hasBin: true dev: true + /semver/6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true + dev: true + /semver/7.0.0: resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} hasBin: true @@ -3303,6 +3655,11 @@ packages: engines: {node: '>=0.10.0'} dev: false + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + /spdx-correct/3.1.1: resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} dependencies: @@ -3325,6 +3682,12 @@ packages: resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} dev: true + /split/1.0.1: + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + dependencies: + through: 2.3.8 + dev: true + /split2/3.2.2: resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} dependencies: @@ -3379,6 +3742,12 @@ packages: strip-ansi: 7.0.1 dev: true + /string_decoder/1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: true + /string_decoder/1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: @@ -3398,6 +3767,11 @@ packages: ansi-regex: 6.0.1 dev: true + /strip-bom/3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + /strip-final-newline/2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} @@ -3467,6 +3841,19 @@ packages: engines: {node: '>= 8'} dev: false + /temp-dir/2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} + dev: true + + /tempfile/3.0.0: + resolution: {integrity: sha512-uNFCg478XovRi85iD42egu+eSFUmmka750Jy7L5tfHI5hQKKtbPnxaSaXAbBqCDYrw3wx4tXjKwci4/QmsZJxw==} + engines: {node: '>=8'} + dependencies: + temp-dir: 2.0.0 + uuid: 3.4.0 + dev: true + /terminal-link/2.1.1: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} @@ -3494,6 +3881,13 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true + /through2/2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + dependencies: + readable-stream: 2.3.7 + xtend: 4.0.2 + dev: true + /through2/4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} dependencies: @@ -3635,6 +4029,14 @@ packages: hasBin: true dev: true + /uglify-js/3.17.4: + resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + engines: {node: '>=0.8.0'} + hasBin: true + requiresBuild: true + dev: true + optional: true + /undefsafe/2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true @@ -3676,6 +4078,12 @@ packages: engines: {node: '>= 0.4.0'} dev: false + /uuid/3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + dev: true + /v8-compile-cache-lib/3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true @@ -3726,6 +4134,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /wordwrap/1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: true + /wrap-ansi/6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -3766,7 +4178,6 @@ packages: /xtend/4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - dev: false /y18n/4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} @@ -3825,6 +4236,19 @@ packages: yargs-parser: 18.1.3 dev: true + /yargs/16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + dev: true + /yargs/17.6.2: resolution: {integrity: sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==} engines: {node: '>=12'} From ecf95100c0724883feb987e80a1881f9b28859d6 Mon Sep 17 00:00:00 2001 From: sgtJohnny Date: Tue, 14 Feb 2023 03:41:06 +0100 Subject: [PATCH 290/409] feat(i18n): update German translations [skip ci] Currently translated at 98.7% (241 of 244 strings) Co-authored-by: sgtJohnny Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 51e3319..106cb78 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -144,7 +144,7 @@ commands: success: title: ✅ Priorität gesetzt description: Die Priorität dieses Tickets wurde auf `{priority}` gesetzt. - name: Priorität + name: priorität description: Legt die Priorität eines Tickets fest tickets: not_staff: @@ -306,6 +306,7 @@ misc: description: Du kannst diesen Befehl nur in Tickets verwenden. ratelimited: description: Versuche es in ein paar Sekunden erneut. + title: 🐢 Bitte langsamer unknown_category: description: Bitte versuche es mit einer anderen Kategorie. title: ❌ Diese Ticketkategorie existiert nicht From 6f97e4adc8a7d201ccc9f03eeab5688287217478 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 14 Feb 2023 23:36:20 +0100 Subject: [PATCH 291/409] feat(i18n): add Dutch translations [skip ci] Co-authored-by: Isaac --- src/i18n/nl.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/nl.yml diff --git a/src/i18n/nl.yml b/src/i18n/nl.yml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/nl.yml @@ -0,0 +1 @@ +{} From e6bce8b331c440f39575dab7d2cab3428ada96a6 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 14 Feb 2023 23:21:36 +0000 Subject: [PATCH 292/409] feat(docker): add new Dockerfile Co-authored-by: Uzurka <101745008+Uzurka@users.noreply.github.com> --- .dockerignore | 20 +++++++++++++++++++- Dockerfile | 32 ++++++++++++++++++++++++++++++++ scripts/preinstall.js | 5 +++++ scripts/start.sh | 10 ++++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 Dockerfile create mode 100644 scripts/start.sh diff --git a/.dockerignore b/.dockerignore index b512c09..cbeb323 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,19 @@ -node_modules \ No newline at end of file +# only db, scripts, src, and user directories are needed +.github +.husky +.vscode +logs +node_modules +# prisma will generated by postinstall script +prisma +.all-contributorsrc +.commitlintrc.js +.dockerignore +.env +.eslintrc.json +.gitattributes +.gitignore +CONTRIBUTORS.md +Dockerfile +jsconfig.json +README.md \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6e676e8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +# syntax=docker/dockerfile:1 + +FROM node:18-alpine + +ENV NODE_ENV=production \ + HTTP_HOST=0.0.0.0 \ + HTTP_PORT=80 \ + SETTINGS_HOST=127.0.0.1 \ + SETTINGS_PORT=8169 + +WORKDIR /usr/bot + +COPY package.json pnpm-lock.yaml ./ + +COPY --link scripts scripts + + # install python etc so node-gyp works for the optional dependencies +RUN apk add --no-cache --virtual .build-deps make gcc g++ python3 \ + # install pnpm to make dependency installation faster (because it has a lockfile) + && npm install -g pnpm \ + # install dependencies, CI=true to skip pre/postinstall scripts + && CI=true pnpm install --prod --frozen-lockfile \ + # pnpm isn't needed now + && npm uninstall pnpm --global \ + # remove node-gyp stuff + && apk del .build-deps + +COPY --link . . + +EXPOSE ${HTTP_PORT} + +ENTRYPOINT [ "/bin/sh", "/usr/bot/scripts/start.sh" ] \ No newline at end of file diff --git a/scripts/preinstall.js b/scripts/preinstall.js index 2e51c02..cdc23a7 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -7,6 +7,11 @@ function log (...strings) { console.log(short('&9[preinstall]&r'), ...strings); } +if (process.env.CI) { + log('CI detected, skipping'); + process.exit(0); +} + const env = { DB_CONNECTION_URL: '', DB_PROVIDER: '', // don't default to sqlite, postinstall checks if empty diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100644 index 0000000..89fede7 --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +echo "Checking environment..." +node scripts/preinstall + +echo "Preparing the database..." +node scripts/postinstall + +echo "Starting..." +node src/ \ No newline at end of file From d79701ea2c8c8bbfbeab03d4aeab69ac6674d04f Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 14 Feb 2023 23:22:52 +0000 Subject: [PATCH 293/409] perf(docker): decrease image size by 35% by using multi-stage builds --- Dockerfile | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6e676e8..46c8cf0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,24 @@ # syntax=docker/dockerfile:1 -FROM node:18-alpine +FROM node:18-alpine AS build +WORKDIR /build +COPY package.json pnpm-lock.yaml ./ +COPY --link scripts scripts +# install python etc so node-gyp works for the optional dependencies +RUN apk add --no-cache make gcc g++ python3 +# install pnpm to make dependency installation faster (because it has a lockfile) +RUN npm install -g pnpm +# install dependencies, CI=true to skip pre/postinstall scripts +RUN CI=true pnpm install --prod --frozen-lockfile +COPY --link . . +FROM node:18-alpine AS final ENV NODE_ENV=production \ HTTP_HOST=0.0.0.0 \ HTTP_PORT=80 \ SETTINGS_HOST=127.0.0.1 \ SETTINGS_PORT=8169 - WORKDIR /usr/bot - -COPY package.json pnpm-lock.yaml ./ - -COPY --link scripts scripts - - # install python etc so node-gyp works for the optional dependencies -RUN apk add --no-cache --virtual .build-deps make gcc g++ python3 \ - # install pnpm to make dependency installation faster (because it has a lockfile) - && npm install -g pnpm \ - # install dependencies, CI=true to skip pre/postinstall scripts - && CI=true pnpm install --prod --frozen-lockfile \ - # pnpm isn't needed now - && npm uninstall pnpm --global \ - # remove node-gyp stuff - && apk del .build-deps - -COPY --link . . - +COPY --from=build /build ./ EXPOSE ${HTTP_PORT} - ENTRYPOINT [ "/bin/sh", "/usr/bot/scripts/start.sh" ] \ No newline at end of file From b2de472329aa6ae6f861729a0099ec23593adde9 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 15 Feb 2023 01:07:29 +0000 Subject: [PATCH 294/409] ci: add docker workflow --- .github/workflows/docker.yml | 58 ++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..2244b3b --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,58 @@ +name: Publish Docker image + +on: + push: + branches: + - 'main' + tags: + - 'v*' + pull_request: + branches: + - 'main' + +jobs: + push_to_registries: + name: Push Docker image to multiple registries + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Check out the repo + uses: actions/checkout@v3 + + - name: Log in to Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Log in to the Container registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: | + eartharoid/discord-tickets + ghcr.io/${{ github.repository }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + + - name: Build and push Docker images + uses: docker/build-push-action@v4 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file From f5eec3b4694adab5beb2a5027351e2632bb8c832 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 15 Feb 2023 01:13:35 +0000 Subject: [PATCH 295/409] style: fix weird space --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 46c8cf0..940246e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /build COPY package.json pnpm-lock.yaml ./ COPY --link scripts scripts # install python etc so node-gyp works for the optional dependencies -RUN apk add --no-cache make gcc g++ python3 +RUN apk add --no-cache make gcc g++ python3 # install pnpm to make dependency installation faster (because it has a lockfile) RUN npm install -g pnpm # install dependencies, CI=true to skip pre/postinstall scripts From 699b612e675b1dfaff2bdd838aa0548d3099bee7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 15 Feb 2023 01:16:04 +0000 Subject: [PATCH 296/409] feat(docker): rename build stages --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 940246e..df9db8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM node:18-alpine AS build +FROM node:18-alpine AS builder WORKDIR /build COPY package.json pnpm-lock.yaml ./ COPY --link scripts scripts @@ -12,7 +12,7 @@ RUN npm install -g pnpm RUN CI=true pnpm install --prod --frozen-lockfile COPY --link . . -FROM node:18-alpine AS final +FROM node:18-alpine AS runner ENV NODE_ENV=production \ HTTP_HOST=0.0.0.0 \ HTTP_PORT=80 \ From ec8589393b0b2c0ba741286e058fb238b1c7235a Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 15 Feb 2023 01:17:17 +0000 Subject: [PATCH 297/409] fix(docker): rename --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index df9db8b..ffec55a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,6 @@ ENV NODE_ENV=production \ SETTINGS_HOST=127.0.0.1 \ SETTINGS_PORT=8169 WORKDIR /usr/bot -COPY --from=build /build ./ +COPY --from=builder /build ./ EXPOSE ${HTTP_PORT} ENTRYPOINT [ "/bin/sh", "/usr/bot/scripts/start.sh" ] \ No newline at end of file From 64da241a7649e51c02adad064bbeb4a264793089 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 15 Feb 2023 01:22:09 +0000 Subject: [PATCH 298/409] perf(docker): reorder commands to improve caching (maybe?) --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ffec55a..b2aec35 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,12 @@ FROM node:18-alpine AS builder WORKDIR /build -COPY package.json pnpm-lock.yaml ./ -COPY --link scripts scripts # install python etc so node-gyp works for the optional dependencies RUN apk add --no-cache make gcc g++ python3 # install pnpm to make dependency installation faster (because it has a lockfile) RUN npm install -g pnpm +COPY package.json pnpm-lock.yaml ./ +COPY --link scripts scripts # install dependencies, CI=true to skip pre/postinstall scripts RUN CI=true pnpm install --prod --frozen-lockfile COPY --link . . From cb4e253dda202cd3d3c5d475fc55e102e17b2c0c Mon Sep 17 00:00:00 2001 From: Uzurka <101745008+Uzurka@users.noreply.github.com> Date: Wed, 15 Feb 2023 22:04:43 +0100 Subject: [PATCH 299/409] feat(docker): add docker-compose.yml (#387) * Add Docker-compose * refactor(docker): improve compose file --------- Co-authored-by: Isaac --- Dockerfile | 2 +- docker-compose.yml | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile index b2aec35..562705e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,4 +21,4 @@ ENV NODE_ENV=production \ WORKDIR /usr/bot COPY --from=builder /build ./ EXPOSE ${HTTP_PORT} -ENTRYPOINT [ "/bin/sh", "/usr/bot/scripts/start.sh" ] \ No newline at end of file +ENTRYPOINT [ "/usr/bot/scripts/start.sh" ] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e88f153 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,53 @@ +version: "3.9" + +services: + mysql: + image: mysql:8 + restart: unless-stopped + hostname: mysql + networks: + - discord-tickets + volumes: + - tickets-mysql:/var/lib/mysql + environment: + MYSQL_DATABASE: tickets + MYSQL_PASSWORD: insecure # change this to a secure password + MYSQL_ROOT_PASSWORD: insecure # change this to a (different) secure password + MYSQL_USER: tickets + + bot: + image: discord-tickets:4.0 + depends_on: + - mysql + restart: unless-stopped + hostname: bot + networks: + - discord-tickets + ports: + - 8080:80 + volumes: + - tickets-bot:/usr/bot/user + # Please refer to the documentation: + # https://discordtickets.app/self-hosting/configuration/#environment-variables + environment: + DB_CONNECTION_URL: mysql://tickets:insecure@mysql/tickets # change `insecure` to the MYSQL_PASSWORD you set above + DISCORD_SECRET: # required + DISCORD_TOKEN: # required + ENCRYPTION_KEY: # required + DB_PROVIDER: mysql + HTTP_EXTERNAL: http://127.0.0.1:8080 # change this to your server's external IP (or domain) + HTTP_HOST: 0.0.0.0 + HTTP_PORT: 80 + HTTP_TRUST_PROXY: false # set to true if you're using a reverse proxy + PUBLIC_BOT: false + OVERRIDE_ARCHIVE: null + SETTINGS_PORT: 8169 + SETTINGS_HOST: 127.0.0.1 + SUPER: !!str 319467558166069248 # optionally add `,youruseridhere` + +networks: + discord-tickets: + +volumes: + tickets-mysql: + tickets-bot: From 7d4a44c77686a793245e567c98957103f588a0c2 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 16 Feb 2023 16:42:50 +0000 Subject: [PATCH 300/409] chore: update dependencies --- package.json | 4 ++-- pnpm-lock.yaml | 53 +++++++++++++++++++++----------------------------- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index 5505577..78a3bea 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@fastify/http-proxy": "^8.4.0", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", - "@prisma/client": "^4.9.0", + "@prisma/client": "^4.10.1", "cryptr": "^6.1.0", "discord.js": "^14.7.1", "dotenv": "^16.0.3", @@ -66,7 +66,7 @@ "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", "pad": "^3.2.0", - "prisma": "^4.9.0", + "prisma": "^4.10.1", "semver": "^7.3.8", "terminal-link": "^2.1.1", "yaml": "^1.10.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 147ea13..628043e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ specifiers: '@fastify/http-proxy': ^8.4.0 '@fastify/jwt': ^5.0.1 '@fastify/oauth2': ^5.1.0 - '@prisma/client': ^4.9.0 + '@prisma/client': ^4.10.1 all-contributors-cli: ^6.24.0 bufferutil: ^4.0.7 conventional-changelog-cli: ^2.2.2 @@ -38,7 +38,7 @@ specifiers: nodemon: ^2.0.20 object-diffy: ^1.0.4 pad: ^3.2.0 - prisma: ^4.9.0 + prisma: ^4.10.1 semver: ^7.3.8 terminal-link: ^2.1.1 utf-8-validate: ^5.0.10 @@ -46,7 +46,7 @@ specifiers: zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 1.3.3_svelte@3.55.1 + '@discord-tickets/settings': 1.3.3 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -55,7 +55,7 @@ dependencies: '@fastify/http-proxy': 8.4.0_3cxu5zja4e2r5wmvge7mdcljwq '@fastify/jwt': 5.0.1 '@fastify/oauth2': 5.1.0 - '@prisma/client': 4.9.0_prisma@4.9.0 + '@prisma/client': 4.10.1_prisma@4.10.1 cryptr: 6.1.0 discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq dotenv: 16.0.3 @@ -72,7 +72,7 @@ dependencies: node-emoji: 1.11.0 object-diffy: 1.0.4 pad: 3.2.0 - prisma: 4.9.0 + prisma: 4.10.1 semver: 7.3.8 terminal-link: 2.1.1 yaml: 1.10.2 @@ -299,7 +299,7 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@discord-tickets/settings/1.3.3_svelte@3.55.1: + /@discord-tickets/settings/1.3.3: resolution: {integrity: sha512-G6Z6DBsotbgY6CIhD+yVF1m1Lz/v699ONIEBKXLwMpAhXSXF+tCSzCcpRc5Do056YbVlEVxVUER4lbVtHn/ytQ==} dependencies: '@fortawesome/fontawesome-free': 6.2.1 @@ -309,7 +309,7 @@ packages: ms: 2.1.3 postcss: 8.4.21 sortablejs: 1.15.0 - svelte-modals: 1.2.1_svelte@3.55.1 + svelte-modals: 1.2.1 transitivePeerDependencies: - svelte dev: false @@ -393,7 +393,7 @@ packages: resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==} dependencies: ajv: 8.12.0 - ajv-formats: 2.1.1_ajv@8.12.0 + ajv-formats: 2.1.1 fast-uri: 2.2.0 dev: false @@ -599,8 +599,8 @@ packages: fastq: 1.15.0 dev: true - /@prisma/client/4.9.0_prisma@4.9.0: - resolution: {integrity: sha512-bz6QARw54sWcbyR1lLnF2QHvRW5R/Jxnbbmwh3u+969vUKXtBkXgSgjDA85nji31ZBlf7+FrHDy5x+5ydGyQDg==} + /@prisma/client/4.10.1_prisma@4.10.1: + resolution: {integrity: sha512-VonXLJZybdt8e5XZH5vnIGCRNnIh6OMX1FS3H/yzMGLT3STj5TJ/OkMcednrvELgk8PK89Vo3aSh51MWNO0axA==} engines: {node: '>=14.17'} requiresBuild: true peerDependencies: @@ -609,16 +609,16 @@ packages: prisma: optional: true dependencies: - '@prisma/engines-version': 4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5 - prisma: 4.9.0 + '@prisma/engines-version': 4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19 + prisma: 4.10.1 dev: false - /@prisma/engines-version/4.9.0-42.ceb5c99003b99c9ee2c1d2e618e359c14aef2ea5: - resolution: {integrity: sha512-M16aibbxi/FhW7z1sJCX8u+0DriyQYY5AyeTH7plQm9MLnURoiyn3CZBqAyIoQ+Z1pS77usCIibYJWSgleBMBA==} + /@prisma/engines-version/4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19: + resolution: {integrity: sha512-tsjTho7laDhf9EJ9EnDxAPEf7yrigSMDhniXeU4YoWc7azHAs4GPxRi2P9LTFonmHkJLMOLjR77J1oIP8Ife1w==} dev: false - /@prisma/engines/4.9.0: - resolution: {integrity: sha512-t1pt0Gsp+HcgPJrHFc+d/ZSAaKKWar2G/iakrE07yeKPNavDP3iVKPpfXP22OTCHZUWf7OelwKJxQgKAm5hkgw==} + /@prisma/engines/4.10.1: + resolution: {integrity: sha512-B3tcTxjx196nuAu1GOTKO9cGPUgTFHYRdkPkTS4m5ptb2cejyBlH9X7GOfSt3xlI7p4zAJDshJP4JJivCg9ouA==} requiresBuild: true dev: false @@ -739,10 +739,8 @@ packages: indent-string: 4.0.0 dev: true - /ajv-formats/2.1.1_ajv@8.12.0: + /ajv-formats/2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 peerDependenciesMeta: ajv: optional: true @@ -1819,7 +1817,7 @@ packages: dependencies: '@fastify/deepmerge': 1.3.0 ajv: 8.12.0 - ajv-formats: 2.1.1_ajv@8.12.0 + ajv-formats: 2.1.1 fast-deep-equal: 3.1.3 fast-uri: 2.2.0 rfdc: 1.3.0 @@ -3201,13 +3199,13 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prisma/4.9.0: - resolution: {integrity: sha512-bS96oZ5oDFXYgoF2l7PJ3Mp1wWWfLOo8B/jAfbA2Pn0Wm5Z/owBHzaMQKS3i1CzVBDWWPVnOohmbJmjvkcHS5w==} + /prisma/4.10.1: + resolution: {integrity: sha512-0jDxgg+DruB1kHVNlcspXQB9au62IFfVg9drkhzXudszHNUAQn0lVuu+T8np0uC2z1nKD5S3qPeCyR8u5YFLnA==} engines: {node: '>=14.17'} hasBin: true requiresBuild: true dependencies: - '@prisma/engines': 4.9.0 + '@prisma/engines': 4.10.1 dev: false /process-nextick-args/2.0.1: @@ -3828,17 +3826,10 @@ packages: engines: {node: '>= 0.4'} dev: true - /svelte-modals/1.2.1_svelte@3.55.1: + /svelte-modals/1.2.1: resolution: {integrity: sha512-7MEKUx5wb5YppkXWFGeRlYM5FMGEnpix39u9Y6GtCNTMXRDZ7DB2Z50IYLMRTMW5lOsCdtJgFbB0E3iZMKmsAA==} peerDependencies: svelte: ^3.0.0 - dependencies: - svelte: 3.55.1 - dev: false - - /svelte/3.55.1: - resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==} - engines: {node: '>= 8'} dev: false /temp-dir/2.0.0: From c59343168c36b0be59b0a4103c9a7e82cd23d4b9 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 16 Feb 2023 16:43:10 +0000 Subject: [PATCH 301/409] chore: update npm config --- .npmrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.npmrc b/.npmrc index f87a044..18ae03c 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ -auto-install-peers=true \ No newline at end of file +auto-install-peers=true +engine-strict=true \ No newline at end of file From 65eb2a3e86fed254a2e85ab2e0c2a715b56e2600 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 16 Feb 2023 21:12:37 +0000 Subject: [PATCH 302/409] feat: use long format for category time stats --- src/lib/tickets/manager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 910839c..4ab35d2 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -419,8 +419,8 @@ module.exports = class TicketManager { }, }); stats = { - avgResolutionTime: ms(tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / tickets.length), - avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length), + avgResolutionTime: ms(tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / tickets.length, { long: true }), + avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length, { long: true }), }; this.client.keyv.set(statsCacheKey, stats, ms('1h')); } From 725fdf5ac57f485930eadbcea367c8dbc1be1b05 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 16 Feb 2023 21:36:40 +0000 Subject: [PATCH 303/409] chore: update dependencies --- package.json | 8 +- pnpm-lock.yaml | 194 +++++++++++++++++++++++++++++-------------------- 2 files changed, 118 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index 78a3bea..326142b 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.3.3", + "@discord-tickets/settings": "^1.4.0", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", @@ -54,7 +54,7 @@ "discord.js": "^14.7.1", "dotenv": "^16.0.3", "express": "^4.18.2", - "fastify": "^4.12.0", + "fastify": "^4.13.0", "figlet": "^1.5.2", "fs-extra": "^10.1.0", "keyv": "^4.5.2", @@ -76,10 +76,10 @@ "@commitlint/config-conventional": "^17.4.3", "all-contributors-cli": "^6.24.0", "conventional-changelog-cli": "^2.2.2", - "eslint": "^8.33.0", + "eslint": "^8.34.0", "eslint-plugin-unused-imports": "^2.0.0", "husky": "^8.0.3", - "lint-staged": "^13.1.1", + "lint-staged": "^13.1.2", "nodemon": "^2.0.20" }, "optionalDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 628043e..38360c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,7 +3,7 @@ lockfileVersion: 5.4 specifiers: '@commitlint/cli': ^17.4.3 '@commitlint/config-conventional': ^17.4.3 - '@discord-tickets/settings': ^1.3.3 + '@discord-tickets/settings': ^1.4.0 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -20,17 +20,17 @@ specifiers: discord.js: ^14.7.1 dotenv: ^16.0.3 erlpack: github:discord/erlpack - eslint: ^8.33.0 + eslint: ^8.34.0 eslint-plugin-unused-imports: ^2.0.0 express: ^4.18.2 - fastify: ^4.12.0 + fastify: ^4.13.0 figlet: ^1.5.2 fs-extra: ^10.1.0 husky: ^8.0.3 keyv: ^4.5.2 leeks.js: ^0.2.4 leekslazylogger: ^5.0.0 - lint-staged: ^13.1.1 + lint-staged: ^13.1.2 ms: ^2.1.3 mustache: ^4.2.0 node-dir: ^0.1.17 @@ -46,7 +46,7 @@ specifiers: zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 1.3.3 + '@discord-tickets/settings': 1.4.0_svelte@3.55.1 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -60,7 +60,7 @@ dependencies: discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq dotenv: 16.0.3 express: 4.18.2 - fastify: 4.12.0 + fastify: 4.13.0 figlet: 1.5.2 fs-extra: 10.1.0 keyv: 4.5.2 @@ -88,10 +88,10 @@ devDependencies: '@commitlint/config-conventional': 17.4.3 all-contributors-cli: 6.24.0 conventional-changelog-cli: 2.2.2 - eslint: 8.33.0 - eslint-plugin-unused-imports: 2.0.0_eslint@8.33.0 + eslint: 8.34.0 + eslint-plugin-unused-imports: 2.0.0_eslint@8.34.0 husky: 8.0.3 - lint-staged: 13.1.1 + lint-staged: 13.1.2 nodemon: 2.0.20 packages: @@ -138,7 +138,7 @@ packages: lodash.isfunction: 3.0.9 resolve-from: 5.0.0 resolve-global: 1.0.0 - yargs: 17.6.2 + yargs: 17.7.0 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' @@ -210,15 +210,15 @@ packages: '@commitlint/execute-rule': 17.4.0 '@commitlint/resolve-extends': 17.4.0 '@commitlint/types': 17.4.0 - '@types/node': 18.11.18 + '@types/node': 18.13.0 chalk: 4.1.2 cosmiconfig: 8.0.0 - cosmiconfig-typescript-loader: 4.3.0_nww3gwtks3ghjaektoxywfmuuy + cosmiconfig-typescript-loader: 4.3.0_p7cp6dsfhdrlk7mvuxd3wodbsu lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1_bdgp3l2zgaopogaavxusmetvge + ts-node: 10.9.1_4bewfcp2iebiwuold25d6rgcsy typescript: 4.9.5 transitivePeerDependencies: - '@swc/core' @@ -299,17 +299,18 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@discord-tickets/settings/1.3.3: - resolution: {integrity: sha512-G6Z6DBsotbgY6CIhD+yVF1m1Lz/v699ONIEBKXLwMpAhXSXF+tCSzCcpRc5Do056YbVlEVxVUER4lbVtHn/ytQ==} + /@discord-tickets/settings/1.4.0_svelte@3.55.1: + resolution: {integrity: sha512-bpdsKZQscFlOAduNbQkTN4vU3NQzaWaquh69xDECh4hgsfz1ILEPz86WNzA+B79qAaAYNrxsIq/W9ga7NhoUuQ==} dependencies: - '@fortawesome/fontawesome-free': 6.2.1 + '@fortawesome/fontawesome-free': 6.3.0 + '@skyra/discord-components-core': 3.6.0 cookie: 0.5.0 emoji-name-map: 1.2.9 marked: 4.2.12 ms: 2.1.3 postcss: 8.4.21 sortablejs: 1.15.0 - svelte-modals: 1.2.1 + svelte-modals: 1.2.1_svelte@3.55.1 transitivePeerDependencies: - svelte dev: false @@ -320,9 +321,9 @@ packages: dependencies: '@discordjs/util': 0.1.0 '@sapphire/shapeshift': 3.8.1 - discord-api-types: 0.37.31 + discord-api-types: 0.37.34 fast-deep-equal: 3.1.3 - ts-mixer: 6.0.2 + ts-mixer: 6.0.3 tslib: 2.5.0 dev: false @@ -339,10 +340,10 @@ packages: '@discordjs/util': 0.1.0 '@sapphire/async-queue': 1.5.0 '@sapphire/snowflake': 3.4.0 - discord-api-types: 0.37.31 + discord-api-types: 0.37.34 file-type: 18.2.0 tslib: 2.5.0 - undici: 5.16.0 + undici: 5.19.1 dev: false /@discordjs/util/0.1.0: @@ -393,7 +394,7 @@ packages: resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==} dependencies: ajv: 8.12.0 - ajv-formats: 2.1.1 + ajv-formats: 2.1.1_ajv@8.12.0 fast-uri: 2.2.0 dev: false @@ -428,8 +429,8 @@ packages: /@fastify/http-proxy/8.4.0_3cxu5zja4e2r5wmvge7mdcljwq: resolution: {integrity: sha512-H8nwsmawFtKKRE6uhh1BtF1gQi/l147SmLsDGxB0HdYTHzjXz6uSQO3lEVmY7unKMzbArRjdoJQkEGpScszdSw==} dependencies: - '@fastify/reply-from': 8.4.1 - ws: 8.12.0_3cxu5zja4e2r5wmvge7mdcljwq + '@fastify/reply-from': 8.4.2 + ws: 8.12.1_3cxu5zja4e2r5wmvge7mdcljwq transitivePeerDependencies: - bufferutil - utf-8-validate @@ -455,20 +456,20 @@ packages: - supports-color dev: false - /@fastify/reply-from/8.4.1: - resolution: {integrity: sha512-fGI4aRAUs7Mo/X8Fx4IAYBVnNBCqrz65syhwxnyXjBF8Yalxdnom4CQRnGERUx8Kh7GW4W+FGnjrdbqX0+fiVg==} + /@fastify/reply-from/8.4.2: + resolution: {integrity: sha512-xFa/5CYQWgZo+L8f97n+JBk9H4Dy/SXyTW/H0Puavat6akCWPD6tlNucZipGuwvjNFYrVbMtV9MtP1O+Z3+EbQ==} dependencies: '@fastify/error': 3.2.0 end-of-stream: 1.4.4 - fast-querystring: 1.1.0 + fast-querystring: 1.1.1 fastify-plugin: 4.5.0 pump: 3.0.0 tiny-lru: 10.0.1 - undici: 5.16.0 + undici: 5.19.1 dev: false - /@fortawesome/fontawesome-free/6.2.1: - resolution: {integrity: sha512-viouXhegu/TjkvYQoiRZK3aax69dGXxgEjpvZW81wIJdxm5Fnvp3VVIP4VHKqX4SvFw6qpmkILkD4RJWAdrt7A==} + /@fortawesome/fontawesome-free/6.3.0: + resolution: {integrity: sha512-qVtd5i1Cc7cdrqnTWqTObKQHjPWAiRwjUPaXObaeNPcy7+WKxJumGBx66rfSFgK6LNpIasVKkEgW8oyf0tmPLA==} engines: {node: '>=6'} requiresBuild: true dev: false @@ -640,6 +641,21 @@ packages: engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dev: false + /@skyra/discord-components-core/3.6.0: + resolution: {integrity: sha512-13aVNoleHyAMqd4lcfcfjEduV4QavlHn9P2TUlTUPQC8m8cd8n8wSUTDUxKhQbYpzJ0hn7AHnlmLixfiqQT4FQ==} + engines: {node: '>=v14.0.0'} + dependencies: + '@stencil/core': 2.22.2 + clsx: 1.2.1 + hex-to-rgba: 2.0.1 + dev: false + + /@stencil/core/2.22.2: + resolution: {integrity: sha512-r+vbxsGNcBaV1VDOYW25lv4QfXTlNoIb5GpUX7rZ+cr59yqYCZC5tlV+IzX6YgHKW62ulCc9M3RYtTfHtNbNNw==} + engines: {node: '>=12.10.0', npm: '>=6.0.0'} + hasBin: true + dev: false + /@tokenizer/token/0.3.0: resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} dev: false @@ -664,8 +680,8 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - /@types/node/18.11.18: - resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} + /@types/node/18.13.0: + resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==} /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -674,7 +690,7 @@ packages: /@types/ws/8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: - '@types/node': 18.11.18 + '@types/node': 18.13.0 dev: false /JSONStream/1.3.5: @@ -739,8 +755,10 @@ packages: indent-string: 4.0.0 dev: true - /ajv-formats/2.1.1: + /ajv-formats/2.1.1_ajv@8.12.0: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 peerDependenciesMeta: ajv: optional: true @@ -875,8 +893,8 @@ packages: engines: {node: '>=8.0.0'} dev: false - /avvio/8.2.0: - resolution: {integrity: sha512-bbCQdg7bpEv6kGH41RO/3B2/GMMmJSo2iBK+X8AWN9mujtfUipMDfIjsgHCfpnKqoGEQrrmCDKSa5OQ19+fDmg==} + /avvio/8.2.1: + resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==} dependencies: archy: 1.0.0 debug: 4.3.4 @@ -1093,6 +1111,11 @@ packages: engines: {node: '>=0.8'} dev: false + /clsx/1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + dev: false + /color-convert/1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -1330,7 +1353,7 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /cosmiconfig-typescript-loader/4.3.0_nww3gwtks3ghjaektoxywfmuuy: + /cosmiconfig-typescript-loader/4.3.0_p7cp6dsfhdrlk7mvuxd3wodbsu: resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -1339,9 +1362,9 @@ packages: ts-node: '>=10' typescript: '>=3' dependencies: - '@types/node': 18.11.18 + '@types/node': 18.13.0 cosmiconfig: 8.0.0 - ts-node: 10.9.1_bdgp3l2zgaopogaavxusmetvge + ts-node: 10.9.1_4bewfcp2iebiwuold25d6rgcsy typescript: 4.9.5 dev: true @@ -1462,8 +1485,8 @@ packages: engines: {node: '>=0.3.1'} dev: true - /discord-api-types/0.37.31: - resolution: {integrity: sha512-k9DQQ7Wv+ehiF7901qk/FnP47k6O2MHm3meQFee4gUzi5dfGAVLf7SfLNtb4w7G2dmukJyWQtVJEDF9oMb9yuQ==} + /discord-api-types/0.37.34: + resolution: {integrity: sha512-3sX2QMX2vLjT93n6+c1rN6lM6qWHycjYol6x/ls6U5hbZPdGH00dRMjg2csF58ISMf7DGV/6inDKgY1u5niWhA==} dev: false /discord.js/14.7.1_3cxu5zja4e2r5wmvge7mdcljwq: @@ -1476,12 +1499,12 @@ packages: '@discordjs/util': 0.1.0 '@sapphire/snowflake': 3.4.0 '@types/ws': 8.5.4 - discord-api-types: 0.37.31 + discord-api-types: 0.37.34 fast-deep-equal: 3.1.3 lodash.snakecase: 4.1.1 tslib: 2.5.0 - undici: 5.16.0 - ws: 8.12.0_3cxu5zja4e2r5wmvge7mdcljwq + undici: 5.19.1 + ws: 8.12.1_3cxu5zja4e2r5wmvge7mdcljwq transitivePeerDependencies: - bufferutil - utf-8-validate @@ -1576,7 +1599,7 @@ packages: engines: {node: '>=10'} dev: true - /eslint-plugin-unused-imports/2.0.0_eslint@8.33.0: + /eslint-plugin-unused-imports/2.0.0_eslint@8.34.0: resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1586,7 +1609,7 @@ packages: '@typescript-eslint/eslint-plugin': optional: true dependencies: - eslint: 8.33.0 + eslint: 8.34.0 eslint-rule-composer: 0.3.0 dev: true @@ -1603,13 +1626,13 @@ packages: estraverse: 5.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.33.0: + /eslint-utils/3.0.0_eslint@8.34.0: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' dependencies: - eslint: 8.33.0 + eslint: 8.34.0 eslint-visitor-keys: 2.1.0 dev: true @@ -1623,8 +1646,8 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.33.0: - resolution: {integrity: sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==} + /eslint/8.34.0: + resolution: {integrity: sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: @@ -1639,10 +1662,10 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.33.0 + eslint-utils: 3.0.0_eslint@8.34.0 eslint-visitor-keys: 3.3.0 espree: 9.4.1 - esquery: 1.4.0 + esquery: 1.4.2 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 @@ -1680,8 +1703,8 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /esquery/1.4.0: - resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} + /esquery/1.4.2: + resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==} engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 @@ -1817,7 +1840,7 @@ packages: dependencies: '@fastify/deepmerge': 1.3.0 ajv: 8.12.0 - ajv-formats: 2.1.1 + ajv-formats: 2.1.1_ajv@8.12.0 fast-deep-equal: 3.1.3 fast-uri: 2.2.0 rfdc: 1.3.0 @@ -1836,8 +1859,8 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /fast-querystring/1.1.0: - resolution: {integrity: sha512-LWkjBCZlxjnSanuPpZ6mHswjy8hQv3VcPJsQB3ltUF2zjvrycr0leP3TSTEEfvQ1WEMSRl5YNsGqaft9bjLqEw==} + /fast-querystring/1.1.1: + resolution: {integrity: sha512-qR2r+e3HvhEFmpdHMv//U8FnFlnYjaC6QKDuaXALDkw2kvHO8WDjxH+f/rHGR4Me4pnk8p9JAkRNTjYHAKRn2Q==} dependencies: fast-decode-uri-component: 1.0.1 dev: false @@ -1866,18 +1889,18 @@ packages: resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==} dev: false - /fastify/4.12.0: - resolution: {integrity: sha512-Hh2GCsOCqnOuewWSvqXlpq5V/9VA+/JkVoooQWUhrU6gryO9+/UGOoF/dprGcKSDxkM/9TkMXSffYp8eA/YhYQ==} + /fastify/4.13.0: + resolution: {integrity: sha512-p9ibdFWH3pZ7KPgmfHPKGUy2W4EWU2TEpwlcu58w4CwGyU3ARFfh2kwq6zpZ5W2ZGVbufi4tZbqHIHAlX/9Z/A==} dependencies: '@fastify/ajv-compiler': 3.5.0 '@fastify/error': 3.2.0 '@fastify/fast-json-stringify-compiler': 4.2.0 abstract-logging: 2.0.1 - avvio: 8.2.0 + avvio: 8.2.1 fast-content-type-parse: 1.0.0 find-my-way: 7.4.0 - light-my-request: 5.8.0 - pino: 8.8.0 + light-my-request: 5.9.1 + pino: 8.10.0 process-warning: 2.1.0 proxy-addr: 2.0.7 rfdc: 1.3.0 @@ -1967,7 +1990,7 @@ packages: engines: {node: '>=14'} dependencies: fast-deep-equal: 3.1.3 - fast-querystring: 1.1.0 + fast-querystring: 1.1.1 safe-regex2: 2.0.0 dev: false @@ -2197,6 +2220,10 @@ packages: dependencies: function-bind: 1.1.1 + /hex-to-rgba/2.0.1: + resolution: {integrity: sha512-5XqPJBpsEUMsseJUi2w2Hl7cHFFi3+OO10M2pzAvKB1zL6fc+koGMhmBqoDOCB4GemiRM/zvDMRIhVw6EkB8dQ==} + dev: false + /hosted-git-info/2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true @@ -2493,8 +2520,8 @@ packages: type-check: 0.4.0 dev: true - /light-my-request/5.8.0: - resolution: {integrity: sha512-4BtD5C+VmyTpzlDPCZbsatZMJVgUIciSOwYhJDCbLffPZ35KoDkDj4zubLeHDEb35b4kkPeEv5imbh+RJxK/Pg==} + /light-my-request/5.9.1: + resolution: {integrity: sha512-UT7pUk8jNCR1wR7w3iWfIjx32DiB2f3hFdQSOwy3/EPQ3n3VocyipUxcyRZR0ahoev+fky69uA+GejPa9KuHKg==} dependencies: cookie: 0.5.0 process-warning: 2.1.0 @@ -2510,8 +2537,8 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - /lint-staged/13.1.1: - resolution: {integrity: sha512-LLJLO0Kdbcv2a+CvSF4p1M7jBZOajKSMpBUvyR8+bXccsqPER0/NxTFQSpNHjqwV9kM3tkHczYerTB5wI+bksQ==} + /lint-staged/13.1.2: + resolution: {integrity: sha512-K9b4FPbWkpnupvK3WXZLbgu9pchUJ6N7TtVZjbaPsoizkqFUDkUReUL25xdrCljJs7uLUF3tZ7nVPeo/6lp+6w==} engines: {node: ^14.13.1 || >=16.0.0} hasBin: true dependencies: @@ -3168,8 +3195,8 @@ packages: resolution: {integrity: sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==} dev: false - /pino/8.8.0: - resolution: {integrity: sha512-cF8iGYeu2ODg2gIwgAHcPrtR63ILJz3f7gkogaHC/TXVVXxZgInmNYiIpDYEwgEkxZti2Se6P2W2DxlBIZe6eQ==} + /pino/8.10.0: + resolution: {integrity: sha512-ODfIe+giJtQGsvNAEj5/sHHpL3TFBg161JBH4W62Hc0l0PJjsDFD1R7meLI4PZ2aoHDJznxFNShkJcaG/qJToQ==} hasBin: true dependencies: atomic-sleep: 1.0.0 @@ -3826,10 +3853,17 @@ packages: engines: {node: '>= 0.4'} dev: true - /svelte-modals/1.2.1: + /svelte-modals/1.2.1_svelte@3.55.1: resolution: {integrity: sha512-7MEKUx5wb5YppkXWFGeRlYM5FMGEnpix39u9Y6GtCNTMXRDZ7DB2Z50IYLMRTMW5lOsCdtJgFbB0E3iZMKmsAA==} peerDependencies: svelte: ^3.0.0 + dependencies: + svelte: 3.55.1 + dev: false + + /svelte/3.55.1: + resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==} + engines: {node: '>= 8'} dev: false /temp-dir/2.0.0: @@ -3933,11 +3967,11 @@ packages: engines: {node: '>=8'} dev: true - /ts-mixer/6.0.2: - resolution: {integrity: sha512-zvHx3VM83m2WYCE8XL99uaM7mFwYSkjR2OZti98fabHrwkjsCvgwChda5xctein3xGOyaQhtTeDq/1H/GNvF3A==} + /ts-mixer/6.0.3: + resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==} dev: false - /ts-node/10.9.1_bdgp3l2zgaopogaavxusmetvge: + /ts-node/10.9.1_4bewfcp2iebiwuold25d6rgcsy: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -3956,7 +3990,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 18.11.18 + '@types/node': 18.13.0 acorn: 8.8.2 acorn-walk: 8.2.0 arg: 4.1.3 @@ -4032,8 +4066,8 @@ packages: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true - /undici/5.16.0: - resolution: {integrity: sha512-KWBOXNv6VX+oJQhchXieUznEmnJMqgXMbs0xxH2t8q/FUAWSJvOSr/rMaZKnX5RIVq7JDn0JbP4BOnKG2SGXLQ==} + /undici/5.19.1: + resolution: {integrity: sha512-YiZ61LPIgY73E7syxCDxxa3LV2yl3sN8spnIuTct60boiiRaE1J8mNWHO8Im2Zi/sFrPusjLlmRPrsyraSqX6A==} engines: {node: '>=12.18'} dependencies: busboy: 1.6.0 @@ -4150,8 +4184,8 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - /ws/8.12.0_3cxu5zja4e2r5wmvge7mdcljwq: - resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==} + /ws/8.12.1_3cxu5zja4e2r5wmvge7mdcljwq: + resolution: {integrity: sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -4240,8 +4274,8 @@ packages: yargs-parser: 20.2.9 dev: true - /yargs/17.6.2: - resolution: {integrity: sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==} + /yargs/17.7.0: + resolution: {integrity: sha512-dwqOPg5trmrre9+v8SUo2q/hAwyKoVfu8OC1xPHKJGNdxAvPl4sKxL4vBnh3bQz/ZvvGAFeA5H3ou2kcOY8sQQ==} engines: {node: '>=12'} dependencies: cliui: 8.0.1 From ab470f948bafd46136e76879ddee252ec8d163a1 Mon Sep 17 00:00:00 2001 From: Phil Date: Fri, 17 Feb 2023 04:39:06 +0100 Subject: [PATCH 304/409] feat(i18n): update Italian translations [skip ci] Currently translated at 4.0% (10 of 244 strings) Co-authored-by: Phil Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/it/ Translation: Discord Tickets/Bot --- src/i18n/it.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/i18n/it.yml b/src/i18n/it.yml index 177cbcb..b32bf55 100644 --- a/src/i18n/it.yml +++ b/src/i18n/it.yml @@ -3,3 +3,22 @@ buttons: text: Conferma cancel: text: Annulla + close: + text: Chiudi + edit: + text: Modifica +commands: + message: + pin: + not_pinnable: + description: "Questo messaggio non può essere fissato.\nPer favore chiedi\ + \ ad un amministratore di verificare i permessi del bot.\n" + pinned: + description: Il messaggio è stato fissato + slash: + add: + description: Aggiungi un utente ad un ticket + name: Aggiungi + not_staff: + description: Solo i membri dello staff possono aggiungere utenti ai ticket + altrui. From 46225f215d1dbbb508e85d82dfca56d401f62134 Mon Sep 17 00:00:00 2001 From: Alessio Aquilano Date: Fri, 17 Feb 2023 04:39:06 +0100 Subject: [PATCH 305/409] feat(i18n): update Italian translations [skip ci] Currently translated at 4.0% (10 of 244 strings) Co-authored-by: Alessio Aquilano Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/it/ Translation: Discord Tickets/Bot --- src/i18n/it.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/i18n/it.yml b/src/i18n/it.yml index b32bf55..710cf5b 100644 --- a/src/i18n/it.yml +++ b/src/i18n/it.yml @@ -15,6 +15,8 @@ commands: \ ad un amministratore di verificare i permessi del bot.\n" pinned: description: Il messaggio è stato fissato + create: + name: Crea un ticket dal messaggio slash: add: description: Aggiungi un utente ad un ticket @@ -22,3 +24,4 @@ commands: not_staff: description: Solo i membri dello staff possono aggiungere utenti ai ticket altrui. + added: ➡️ {added} è stato aggiunto da {by}. From 7b632ec54b969fe49ab2704e71c5f04702587283 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 17 Feb 2023 14:23:51 +0000 Subject: [PATCH 306/409] refactor: remove `pad` dependency because it can be done in vanilla JS just as easily --- package.json | 1 - pnpm-lock.yaml | 26 -------------------------- src/commands/slash/transcript.js | 3 +-- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/package.json b/package.json index 326142b..107992d 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "node-dir": "^0.1.17", "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", - "pad": "^3.2.0", "prisma": "^4.10.1", "semver": "^7.3.8", "terminal-link": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38360c3..406d746 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,7 +37,6 @@ specifiers: node-emoji: ^1.11.0 nodemon: ^2.0.20 object-diffy: ^1.0.4 - pad: ^3.2.0 prisma: ^4.10.1 semver: ^7.3.8 terminal-link: ^2.1.1 @@ -71,7 +70,6 @@ dependencies: node-dir: 0.1.17 node-emoji: 1.11.0 object-diffy: 1.0.4 - pad: 3.2.0 prisma: 4.10.1 semver: 7.3.8 terminal-link: 2.1.1 @@ -1106,11 +1104,6 @@ packages: wrap-ansi: 7.0.0 dev: true - /clone/1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - dev: false - /clsx/1.2.1: resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} engines: {node: '>=6'} @@ -1460,12 +1453,6 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /defaults/1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - dependencies: - clone: 1.0.4 - dev: false - /depd/2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -3061,13 +3048,6 @@ packages: engines: {node: '>=6'} dev: true - /pad/3.2.0: - resolution: {integrity: sha512-2u0TrjcGbOjBTJpyewEl4hBO3OeX5wWue7eIFPzQTg6wFSvoaHcBTTUY5m+n0hd04gmTCPuY0kCpVIVuw5etwg==} - engines: {node: '>= 4.0.0'} - dependencies: - wcwidth: 1.0.1 - dev: false - /parent-module/1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -4125,12 +4105,6 @@ packages: engines: {node: '>= 0.8'} dev: false - /wcwidth/1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - dependencies: - defaults: 1.0.4 - dev: false - /webidl-conversions/3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: true diff --git a/src/commands/slash/transcript.js b/src/commands/slash/transcript.js index 6acf636..b889f4d 100644 --- a/src/commands/slash/transcript.js +++ b/src/commands/slash/transcript.js @@ -6,7 +6,6 @@ const Mustache = require('mustache'); const { AttachmentBuilder } = require('discord.js'); const Cryptr = require('cryptr'); const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); -const pad = require('pad'); module.exports = class TranscriptSlashCommand extends SlashCommand { constructor(client, options) { @@ -84,7 +83,7 @@ module.exports = class TranscriptSlashCommand extends SlashCommand { message.text = message.content.content?.replace(/\n/g, '\n\t') ?? ''; message.content.attachments?.forEach(a => (message.text += '\n\t' + a.url)); message.content.embeds?.forEach(() => (message.text += '\n\t[embedded content]')); - message.number = 'M' + pad(String(ticket.archivedMessages.length).length, i + 1, '0'); + message.number = 'M' + String(i + 1).padStart(ticket.archivedMessages.length.toString().length, '0'); ticket.archivedMessages[i] = message; }); From 67f674ed17ac4f691c8d6824183667309a3c4829 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 17 Feb 2023 15:00:58 +0000 Subject: [PATCH 307/409] chore: add `.nvmrc` --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..e4f1473 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +lts/* \ No newline at end of file From 259a033752583cf78113253d4c76a71e9db6e817 Mon Sep 17 00:00:00 2001 From: Phil Date: Fri, 17 Feb 2023 22:38:41 +0100 Subject: [PATCH 308/409] feat(i18n): update Italian translations [skip ci] Currently translated at 10.6% (26 of 244 strings) Co-authored-by: Phil Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/it/ Translation: Discord Tickets/Bot --- src/i18n/it.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/i18n/it.yml b/src/i18n/it.yml index 710cf5b..449c269 100644 --- a/src/i18n/it.yml +++ b/src/i18n/it.yml @@ -1,12 +1,24 @@ buttons: accept_close_request: text: Conferma + emoji: ✅ cancel: text: Annulla + emoji: ✖️ close: text: Chiudi edit: text: Modifica + claim: + text: Rivendica + confirm_open: + text: Crea un ticket + create: + text: Crea un ticket + reject_close_request: + text: Rifiuta + unclaim: + text: Rilascia commands: message: pin: @@ -15,6 +27,9 @@ commands: \ ad un amministratore di verificare i permessi del bot.\n" pinned: description: Il messaggio è stato fissato + name: Fissa il messaggio + not_ticket: + description: Puoi fissare solo i messaggi nei ticket create: name: Crea un ticket dal messaggio slash: From 68765e530b71d13a88c80e1a501f77a367f383c1 Mon Sep 17 00:00:00 2001 From: G R Date: Fri, 17 Feb 2023 22:38:41 +0100 Subject: [PATCH 309/409] feat(i18n): update Italian translations [skip ci] Currently translated at 10.6% (26 of 244 strings) Co-authored-by: G R Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/it/ Translation: Discord Tickets/Bot --- src/i18n/it.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/i18n/it.yml b/src/i18n/it.yml index 449c269..89e7d25 100644 --- a/src/i18n/it.yml +++ b/src/i18n/it.yml @@ -7,24 +7,32 @@ buttons: emoji: ✖️ close: text: Chiudi + emoji: ✖️ edit: text: Modifica + emoji: ✏️ claim: text: Rivendica + emoji: 🙌 confirm_open: text: Crea un ticket + emoji: ✅ create: text: Crea un ticket + emoji: 🎫 reject_close_request: text: Rifiuta + emoji: ✖️ unclaim: text: Rilascia + emoji: ♻️ commands: message: pin: not_pinnable: description: "Questo messaggio non può essere fissato.\nPer favore chiedi\ \ ad un amministratore di verificare i permessi del bot.\n" + title: ❌ Errore pinned: description: Il messaggio è stato fissato name: Fissa il messaggio From 4049202ab21f01a2d3de6dfa87d49981d664b1e2 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 18 Feb 2023 02:32:09 +0000 Subject: [PATCH 310/409] docs: write an awesome README --- README.md | 198 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 163 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index fb7b786..204924f 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,176 @@ -[![Translation status](https://hosted.weblate.org/widgets/discord-tickets/-/open-graph.png)](https://hosted.weblate.org/engage/discord-tickets/) - -https://www.prisma.io/docs/reference/database-reference/supported-databases - -![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create, slash/force-close, slash/claim, slash/release, and slash/move - -menu question max length cannot be higher than question options - -ports <1024 require root - -- TODO: update notifications +> **Warning** +> +> This is the development branch, it may be unstable and may not work as expected. +> +> *dev notes:* +> - https://static.eartharoid.me/k/22/08/02185801.png - for user/create, slash/force-close, slash/claim, slash/release, and slash/move +> - menu question max length cannot be higher than question options +> - ports <1024 require root +> - TODO: update notifications +> - TODO: user:create, force-close +> - **likely to be rate limited by discord**: +> +> [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/eB6TkX?referralCode=Z3aYd2) -creation requires an interaction: -- [x] /new -> category? -> topic or questions -> create -- [ ] user:create(self) -> category? -> topic or questions -> create -- [ ] user:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create -- [x] message:create(self) -> category? -> topic or questions -> create -- [ ] ~~message:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create~~ -- [x] DM -> guild? -> category? -> topic or questions -> create -- [x] panel(interaction) -> topic or questions -> create -- [ ] ~~panel(message) -> DM (channel fallback) button -> topic or questions -> create~~ + +
+ +![bots](https://img.shields.io/badge/dynamic/json?color=5865F2&label=bots&query=clients.total&url=https%3A%2F%2Fstats.discordtickets.app%2Fapi%2Fv3%2Fcurrent&logo=discord&logoColor=white&style=flat-square) +![tickets](https://img.shields.io/badge/dynamic/json?color=5865F2&label=tickets&query=tickets&url=https%3A%2F%2Fstats.discordtickets.app%2Fapi%2Fv3%2Fcurrent&logo=discord&logoColor=white&style=flat-square) +[![GitHub stars](https://img.shields.io/github/stars/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/stargazers) +[![Codacy](https://img.shields.io/codacy/grade/b974eb5f984c40868e07d82c968bd02d?logo=codacy&style=flat-square)](https://www.codacy.com/gh/discord-tickets/bot/dashboard) +[![All Contributors](https://img.shields.io/github/all-contributors/discord-tickets/bot?color=ee8449&style=flat-square)](https://github.com/discord-tickets/bot/blob/main/CONTRIBUTORS.md) +[![Discord](https://img.shields.io/discord/451745464480432129?label=discord&color=7289DA&style=flat-square)](https://lnk.earth/discord) + +
+ +![Discord Tickets](https://static.eartharoid.me/discord-tickets/logo/wordmark/gradient-by-eartharoid.png) + +**A free alternative to the premium and white-label plans of other ticket management bots.** + +
+ + +Discord Tickets - A free ticketing solution | Product Hunt + + +
+ +--- + +[![PebbleHost](https://img.eartharoid.me/insecure/rs:auto:180/plain/s3://eartharoid/sharex/21/10/pebblehost.webp)](https://pebble.host/discordtickets) + +
+ +**[Partnered with PebbleHost](https://pebble.host/discordtickets)** + +for affordable bot hosting + +--- + +
+
+ +## 📖 Contents + +- [📖 Contents](#-contents) +- [✨ Features](#-features) + - [Want to learn more?](#want-to-learn-more) +- [⚡ Getting started](#-getting-started) +- [🤑 Sponsors](#-sponsors) +- [🎖️ Contributors](#️-contributors) + - [🧰 Contributing](#-contributing) + - [🌎 Translating](#-translating) +- [😕 Support](#-support) +- [⭐ Star History](#-star-history) +- [🥱 License](#-license) + + +## ✨ Features + +- 📖 [**Documentation**](https://discordtickets.app/getting-started/) - comprehensive documentation and guides to help you get started +- ⚙️ **Simple settings** - configure your bot with the included and easy-to-use dashboard +- 🎨 **Highly customisable** - tweak features, colours, messages, and more to your liking +- 🛸 **Modern features** - including slash commands, buttons, select menus, and modals +- 🤖 **Automation** - ease your staff team's workload with configurable automation + - 🏷️ [**Tags**](https://v4--discordtickets.netlify.app/features/#tags) - resolve members' problems without escalating to tickets + - 🎫 **Tickets** - close inactive tickets automatically +- ❓ **Context** - ask for a topic or up to 5 custom questions before creating a ticket, and see references to a message or previous ticket at a glance +- 🗃️ **Organisation** - claim, release, move and transfer tickets between members and categories +- 🌎 [**Internationalisation**](#-translating) - available in more than 10 languages +- ⏱️ **Statistics** - analyse your staff members' performance +- 🪓 **Battle-tested** - trusted by thousands of servers, with over [half a million tickets](https://stats.discordtickets.app/) created since 2019 +- 🐳 [**Docker**](https://discordtickets.app/self-hosting/installation/docker/) - reliable and quick deployment with Docker + +[*...and more!*](https://discordtickets.app/features/) + +### Want to learn more? + +**Visit [the website](https://discordtickets.app/) for more features, details, and screenshots**, +or skip to the [full feature tour](https://discordtickets.app/features/). + +## ⚡ Getting started + +> *🙏 Please read the [documentation](https://discordtickets.app/self-hosting/installation/) before you start.* + +There are 3 ways to get started with Discord Tickets: + +- ☁️ [Add the public bot](https://discordtickets.app/public/) - instant setup, but branded *(great for testing)* +- 😴 [Get a managed bot](https://discordtickets.app/managed/) - your own bot, hosted by me *(easy and cheap)* +- 🧑‍💻 [Install your own bot](https://discordtickets.app/self-hosting/) - host it yourself *(⚠️ experience recommended)* + +**[Read the documentation](https://discordtickets.app/getting-started/)** for more information. + +> **Warning** +> This button is here because it looks nice, but I will cry if you click it before reading the [documentation](https://discordtickets.app/getting-started/). :) + +[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/eB6TkX?referralCode=Z3aYd2) + + + +## 🤑 Sponsors + +Discord Tickets is made possible by its sponsors: + +![Sponsors](https://cdn.jsdelivr.net/gh/eartharoid/sponsors/sponsorkit/sponsors.svg) + +Please consider sponsoring the project if it adds value to your business/community. + +**[Sponsor on GitHub](https://github.com/discord-tickets/bot/?sponsor=1)**, or + +[![Donate at ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/eartharoid) > **Note** > -> test +> Your logo will only appear here if you sponsor through GitHub Sponsors. +> [Create an organisation](https://github.com/account/organizations/new?plan=free) if you want to use your business/community logo. -> **Warning** +## 🎖️ Contributors + + + +Discord Tickets is made possible by all of the people listed in [CONTRIBUTORS.md](https://github.com/discord-tickets/bot/blob/main/CONTRIBUTORS.md). + + +### 🧰 Contributing + +If you want to help translate, suggest a feature, submit a bug report, +or contribute in any other way, please read the [contributing guidelines](https://github.com/discord-tickets/.github/blob/main//CONTRIBUTING.md). + +> **Note** > -> test +> You can add yourself to the list of contributors [here](https://github.com/discord-tickets/bot/issues/new/choose). - +### 🌎 Translating -## Contributing +[![Translation status](https://hosted.weblate.org/widgets/discord-tickets/-/open-graph.png)](https://hosted.weblate.org/engage/discord-tickets/) -https://commitlint.js.org/#/ +## 😕 Support -huksy? -eslint -secret scan +[![Discord](https://discordapp.com/api/guilds/451745464480432129/widget.png?style=banner4)](https://lnk.earth/discord) -## Contributors +## ⭐ Star History -[![Contributors](https://contrib.rocks/image?repo=discord-tickets/bot)](https://github.com/discord-tickets/bot/graphs/contributors) +
+ Show graph + + [![Star History Chart](https://api.star-history.com/svg?repos=discord-tickets/bot&type=Date)](https://star-history.com/#discord-tickets/bot&Date) + +
+ +## 🥱 License + +Discord Tickets by eartharoid™️ is licensed under the [GPLv3 license](https://github.com/discord-tickets/bot/blob/main/LICENSE). + +This is not an official Discord product. It is not affiliated with nor endorsed by Discord Inc. + +© 2023 Isaac Saunders \ No newline at end of file From 239e9e9f819ddea533f758a17d4dd1cd87890343 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 18 Feb 2023 02:38:54 +0000 Subject: [PATCH 311/409] docs: improve README --- README.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 204924f..5443c87 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,12 @@
-![bots](https://img.shields.io/badge/dynamic/json?color=5865F2&label=bots&query=clients.total&url=https%3A%2F%2Fstats.discordtickets.app%2Fapi%2Fv3%2Fcurrent&logo=discord&logoColor=white&style=flat-square) -![tickets](https://img.shields.io/badge/dynamic/json?color=5865F2&label=tickets&query=tickets&url=https%3A%2F%2Fstats.discordtickets.app%2Fapi%2Fv3%2Fcurrent&logo=discord&logoColor=white&style=flat-square) -[![GitHub stars](https://img.shields.io/github/stars/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/stargazers) -[![Codacy](https://img.shields.io/codacy/grade/b974eb5f984c40868e07d82c968bd02d?logo=codacy&style=flat-square)](https://www.codacy.com/gh/discord-tickets/bot/dashboard) -[![All Contributors](https://img.shields.io/github/all-contributors/discord-tickets/bot?color=ee8449&style=flat-square)](https://github.com/discord-tickets/bot/blob/main/CONTRIBUTORS.md) -[![Discord](https://img.shields.io/discord/451745464480432129?label=discord&color=7289DA&style=flat-square)](https://lnk.earth/discord) +![bots](https://img.shields.io/badge/dynamic/json?color=5865F2&label=bots&query=clients.total&url=https%3A%2F%2Fstats.discordtickets.app%2Fapi%2Fv3%2Fcurrent&logo=discord&logoColor=white&style=for-the-badge) +![tickets](https://img.shields.io/badge/dynamic/json?color=5865F2&label=tickets&query=tickets&url=https%3A%2F%2Fstats.discordtickets.app%2Fapi%2Fv3%2Fcurrent&logo=discord&logoColor=white&style=for-the-badge) +[![GitHub stars](https://img.shields.io/github/stars/discord-tickets/bot?style=for-the-badge)](https://github.com/discord-tickets/bot/stargazers) +[![Codacy](https://img.shields.io/codacy/grade/b974eb5f984c40868e07d82c968bd02d?logo=codacy&style=for-the-badge)](https://www.codacy.com/gh/discord-tickets/bot/dashboard) +[![All Contributors](https://img.shields.io/github/all-contributors/discord-tickets/bot?color=ee8449&style=for-the-badge)](https://github.com/discord-tickets/bot/blob/main/CONTRIBUTORS.md) +[![Discord](https://img.shields.io/discord/451745464480432129?label=discord&color=7289DA&style=for-the-badge)](https://lnk.earth/discord)
@@ -30,7 +30,6 @@ **A free alternative to the premium and white-label plans of other ticket management bots.**
- @@ -50,9 +49,8 @@ [![PebbleHost](https://img.eartharoid.me/insecure/rs:auto:180/plain/s3://eartharoid/sharex/21/10/pebblehost.webp)](https://pebble.host/discordtickets)
- -**[Partnered with PebbleHost](https://pebble.host/discordtickets)** - +
Partnered with PebbleHost +
for affordable bot hosting --- @@ -119,7 +117,7 @@ There are 3 ways to get started with Discord Tickets: ## 🤑 Sponsors -Discord Tickets is made possible by its sponsors: +Discord Tickets is made possible by these awesome people and organisations: ![Sponsors](https://cdn.jsdelivr.net/gh/eartharoid/sponsors/sponsorkit/sponsors.svg) @@ -132,6 +130,7 @@ Please consider sponsoring the project if it adds value to your business/communi > **Note** > > Your logo will only appear here if you sponsor through GitHub Sponsors. +> > [Create an organisation](https://github.com/account/organizations/new?plan=free) if you want to use your business/community logo. ## 🎖️ Contributors From 3122633695aa653d1cdeb82f106abbecef4208d0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 18 Feb 2023 02:42:00 +0000 Subject: [PATCH 312/409] docs: improve README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 5443c87..1574aa1 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ [![PebbleHost](https://img.eartharoid.me/insecure/rs:auto:180/plain/s3://eartharoid/sharex/21/10/pebblehost.webp)](https://pebble.host/discordtickets) -
Partnered with PebbleHost
for affordable bot hosting From 62ab89ad5c7b14b1ab3368968e4909af317584cb Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 18 Feb 2023 02:46:51 +0000 Subject: [PATCH 313/409] docs: improve README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 1574aa1..b5d43a7 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,6 @@ If you want to help translate, suggest a feature, submit a bug report, or contribute in any other way, please read the [contributing guidelines](https://github.com/discord-tickets/.github/blob/main//CONTRIBUTING.md). > **Note** -> > You can add yourself to the list of contributors [here](https://github.com/discord-tickets/bot/issues/new/choose). ### 🌎 Translating From b228229b742a917e33f17f7dc4901ac3f8d3515b Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 18 Feb 2023 02:51:42 +0000 Subject: [PATCH 314/409] docs: update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b5d43a7..9a83ffa 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ - 🤖 **Automation** - ease your staff team's workload with configurable automation - 🏷️ [**Tags**](https://v4--discordtickets.netlify.app/features/#tags) - resolve members' problems without escalating to tickets - 🎫 **Tickets** - close inactive tickets automatically +- 📜 **Archiving** - store messages in the database and view transcripts later - ❓ **Context** - ask for a topic or up to 5 custom questions before creating a ticket, and see references to a message or previous ticket at a glance - 🗃️ **Organisation** - claim, release, move and transfer tickets between members and categories - 🌎 [**Internationalisation**](#-translating) - available in more than 10 languages From 2ba5415705ed3d34eba811ea258b32627c9f9323 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 18 Feb 2023 20:08:04 +0000 Subject: [PATCH 315/409] docs: update README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9a83ffa..ea97fac 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,8 @@ - [⚡ Getting started](#-getting-started) - [🤑 Sponsors](#-sponsors) - [🎖️ Contributors](#️-contributors) - - [🧰 Contributing](#-contributing) - - [🌎 Translating](#-translating) + - [💻 Contributing](#-contributing) + - [🌎 Translating](#-translating) - [😕 Support](#-support) - [⭐ Star History](#-star-history) - [🥱 License](#-license) @@ -104,7 +104,7 @@ There are 3 ways to get started with Discord Tickets: - ☁️ [Add the public bot](https://discordtickets.app/public/) - instant setup, but branded *(great for testing)* - 😴 [Get a managed bot](https://discordtickets.app/managed/) - your own bot, hosted by me *(easy and cheap)* -- 🧑‍💻 [Install your own bot](https://discordtickets.app/self-hosting/) - host it yourself *(⚠️ experience recommended)* +- 🧑‍💻 [Install your own bot](https://discordtickets.app/self-hosting/) - host it yourself *(experience recommended)* **[Read the documentation](https://discordtickets.app/getting-started/)** for more information. @@ -140,7 +140,7 @@ Please consider sponsoring the project if it adds value to your business/communi Discord Tickets is made possible by all of the people listed in [CONTRIBUTORS.md](https://github.com/discord-tickets/bot/blob/main/CONTRIBUTORS.md). -### 🧰 Contributing +### 💻 Contributing If you want to help translate, suggest a feature, submit a bug report, or contribute in any other way, please read the [contributing guidelines](https://github.com/discord-tickets/.github/blob/main//CONTRIBUTING.md). @@ -148,7 +148,7 @@ or contribute in any other way, please read the [contributing guidelines](https: > **Note** > You can add yourself to the list of contributors [here](https://github.com/discord-tickets/bot/issues/new/choose). -### 🌎 Translating +#### 🌎 Translating [![Translation status](https://hosted.weblate.org/widgets/discord-tickets/-/open-graph.png)](https://hosted.weblate.org/engage/discord-tickets/) From a43801edf1ffe810be2e27f3afb1ebf43e2dd663 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 18 Feb 2023 20:08:14 +0000 Subject: [PATCH 316/409] chore: update default env --- scripts/preinstall.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/preinstall.js b/scripts/preinstall.js index cdc23a7..517be0e 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -19,7 +19,7 @@ const env = { DISCORD_TOKEN: '', ENCRYPTION_KEY: randomBytes(24).toString('hex'), HTTP_EXTERNAL: 'http://127.0.0.1:8080', - HTTP_HOST: '127.0.0.1', + HTTP_HOST: '0.0.0.0', HTTP_PORT: 8080, HTTP_TRUST_PROXY: false, OVERRIDE_ARCHIVE: '', From bd1bc195489cdcccb73c7fe61741fe603d0b6b67 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 18 Feb 2023 20:48:42 +0000 Subject: [PATCH 317/409] fix: delete tickets when their category is deleted (closes #384) --- .../migration.sql | 2 +- db/mysql/schema.prisma | 2 +- .../migration.sql | 2 +- db/postgresql/schema.prisma | 2 +- .../migration.sql | 2 +- db/sqlite/schema.prisma | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename db/mysql/migrations/{20230130211315_4_0_0 => 20230218204428_4_0_0}/migration.sql (99%) rename db/postgresql/migrations/{20230130210021_4_0_0 => 20230218204642_4_0_0}/migration.sql (99%) rename db/sqlite/migrations/{20230130211842_4_0_0 => 20230218204317_4_0_0}/migration.sql (99%) diff --git a/db/mysql/migrations/20230130211315_4_0_0/migration.sql b/db/mysql/migrations/20230218204428_4_0_0/migration.sql similarity index 99% rename from db/mysql/migrations/20230130211315_4_0_0/migration.sql rename to db/mysql/migrations/20230218204428_4_0_0/migration.sql index 32b3b86..29bd31e 100644 --- a/db/mysql/migrations/20230130211315_4_0_0/migration.sql +++ b/db/mysql/migrations/20230218204428_4_0_0/migration.sql @@ -239,7 +239,7 @@ ALTER TABLE `questionAnswers` ADD CONSTRAINT `questionAnswers_userId_fkey` FOREI ALTER TABLE `tags` ADD CONSTRAINT `tags_guildId_fkey` FOREIGN KEY (`guildId`) REFERENCES `guilds`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE `tickets` ADD CONSTRAINT `tickets_categoryId_fkey` FOREIGN KEY (`categoryId`) REFERENCES `categories`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE `tickets` ADD CONSTRAINT `tickets_categoryId_fkey` FOREIGN KEY (`categoryId`) REFERENCES `categories`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE `tickets` ADD CONSTRAINT `tickets_claimedById_fkey` FOREIGN KEY (`claimedById`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index aceb90d..9dcfee6 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -188,7 +188,7 @@ model Ticket { archivedMessages ArchivedMessage[] archivedRoles ArchivedRole[] archivedUsers ArchivedUser[] - category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) + category Category? @relation(fields: [categoryId], references: [id], onDelete: Cascade) categoryId Int? claimedBy User? @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) claimedById String? @db.VarChar(19) diff --git a/db/postgresql/migrations/20230130210021_4_0_0/migration.sql b/db/postgresql/migrations/20230218204642_4_0_0/migration.sql similarity index 99% rename from db/postgresql/migrations/20230130210021_4_0_0/migration.sql rename to db/postgresql/migrations/20230218204642_4_0_0/migration.sql index 7519ba4..5abd9a7 100644 --- a/db/postgresql/migrations/20230130210021_4_0_0/migration.sql +++ b/db/postgresql/migrations/20230218204642_4_0_0/migration.sql @@ -255,7 +255,7 @@ ALTER TABLE "questionAnswers" ADD CONSTRAINT "questionAnswers_userId_fkey" FOREI ALTER TABLE "tags" ADD CONSTRAINT "tags_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "tickets" ADD CONSTRAINT "tickets_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "categories"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "tickets" ADD CONSTRAINT "tickets_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "categories"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "tickets" ADD CONSTRAINT "tickets_claimedById_fkey" FOREIGN KEY ("claimedById") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index d06520c..b20b0ac 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -187,7 +187,7 @@ model Ticket { archivedMessages ArchivedMessage[] archivedRoles ArchivedRole[] archivedUsers ArchivedUser[] - category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) + category Category? @relation(fields: [categoryId], references: [id], onDelete: Cascade) categoryId Int? claimedBy User? @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) claimedById String? @db.VarChar(19) diff --git a/db/sqlite/migrations/20230130211842_4_0_0/migration.sql b/db/sqlite/migrations/20230218204317_4_0_0/migration.sql similarity index 99% rename from db/sqlite/migrations/20230130211842_4_0_0/migration.sql rename to db/sqlite/migrations/20230218204317_4_0_0/migration.sql index 1ee0b48..308773b 100644 --- a/db/sqlite/migrations/20230130211842_4_0_0/migration.sql +++ b/db/sqlite/migrations/20230218204317_4_0_0/migration.sql @@ -176,7 +176,7 @@ CREATE TABLE "tickets" ( "referencesMessageId" TEXT, "referencesTicketId" TEXT, "topic" TEXT, - CONSTRAINT "tickets_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "categories" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "tickets_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "categories" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "tickets_claimedById_fkey" FOREIGN KEY ("claimedById") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT "tickets_closedById_fkey" FOREIGN KEY ("closedById") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT "tickets_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "users" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma index 8581650..ebad7ce 100644 --- a/db/sqlite/schema.prisma +++ b/db/sqlite/schema.prisma @@ -187,7 +187,7 @@ model Ticket { archivedMessages ArchivedMessage[] archivedRoles ArchivedRole[] archivedUsers ArchivedUser[] - category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) + category Category? @relation(fields: [categoryId], references: [id], onDelete: Cascade) categoryId Int? claimedBy User? @relation(name: "TicketsClaimedByUser", fields: [claimedById], references: [id]) claimedById String? From c76c6fd62d4457c8aecc2174e8c2d34183f310d0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 18 Feb 2023 20:48:54 +0000 Subject: [PATCH 318/409] chore: bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 107992d..7aa04cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.4", + "version": "4.0.0-beta.5", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", From 61571f80076d0ff02f952a4cc39df5c9ee0dd039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dow=C3=ADk=20Bot=C3=ADk?= Date: Tue, 21 Feb 2023 20:31:34 +0100 Subject: [PATCH 319/409] feat(i18n): update Czech translations [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 42.2% (103 of 244 strings) Co-authored-by: Dowík Botík Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/cs/ Translation: Discord Tickets/Bot --- src/i18n/cs.yml | 99 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index 6db6e79..100c401 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -3,11 +3,11 @@ commands: pin: name: Připnout zprávu pinned: - description: Tato zpráva nemůže být připnutá. + description: Zpráva byla připnuta. title: ✅ Zpráva připnuta not_pinnable: description: "Tato zpráva nemůže být připnuta.\nProsím zeptejte se admininistrátora,\ - \ jestli má bot dostatečné oprávnění.\n" + \ jestli má robot dostatečné oprávnění.\n" title: ❌ Chyba not_ticket: description: Zprávy můžeš připnout jen v ticketech. @@ -23,7 +23,7 @@ commands: description: Ticket na přidání člena member: name: člen - description: Člen na přidání do ticketu + description: Člen, kterého chcete přidat do ticketu. added: ➡️{by} přidal {added}. description: Přidat člena do ticketu not_staff: @@ -44,6 +44,99 @@ commands: options: reason: description: Důvod pro uzavření ticketu + name: důvod + remove: + options: + ticket: + name: tiket + description: Tiket pro odebrání člena + member: + description: Člen, který má být odstraněn z tiketu + name: člen + not_staff: + description: Pouze členové staff teamu mohou odstranit členy z ostatních tiketů. + title: ❌ Chyba + success: + description: '{member} byl odstraněn z {ticket}.' + title: ✅ Odstraněn + description: Odstranit člena z tiketu + name: odstranit + removed: ⬅️ {removed} byl odstraněn uživatelem {by}. + help: + response: + links: + feedback: Zpětná vazba + commands: Celý list příkazů + docs: Dokumentace + links: Užitečné odkazy + support: Podpora + commands: Příkazy + settings: Nastavení bota + description: '**Použijte {command} pro vytvoření ticketu a získání podpory.**' + description: Zobrazit pomocné menu + name: pomoc + title: Pomoc + force-close: + options: + category: + name: kategorie + reason: + description: Důvod pro uzavření ticketu(ů) + name: důvod + ticket: + description: Ticket k uzavření + name: ticket + time: + name: čas + confirm_multiple: + title: ❓Jste si jist? + name: uzavřít silou + description: Násilně uzavřít ticket + no_tickets: + title: ❌Žádné tickety + not_staff: + title: ❌Chyba + move: + description: Přesunout ticket do jiné kategorie + name: přesunout + options: + category: + description: Kategorie, do které se má ticket přesunout + name: kategorie + moved: 🗃️ {by} přesunul tento ticket z **{from}** do **{to}**. + new: + description: Vytvořit nový ticket + name: nový + options: + references: + description: Číslo souvisejícího tiketu + name: reference + priority: + description: Nastavit důležitost ticketu + name: důležitost + options: + priority: + choices: + HIGH: 🔴 Vysoká + LOW: 🟢 Malá + MEDIUM: 🟠 Střední + description: Priorita tiketu + name: priorita + success: + description: Priorita tiketu byla nastavena na `{priority}`. + title: ✅ Priorita nastavena + release: + name: uvolnit + tag: + description: Použijte tag + name: tag + options: + for: + description: Uživatel, na kterého má cílit tag + name: pro + tag: + description: Jméno tagu pro použití + name: tag buttons: accept_close_request: emoji: ✅ From f87d7e38bce2c76cff6f82b745f6b29decc48ef7 Mon Sep 17 00:00:00 2001 From: Gdany Date: Tue, 21 Feb 2023 20:31:35 +0100 Subject: [PATCH 320/409] feat(i18n): update Czech translations [skip ci] Currently translated at 42.2% (103 of 244 strings) Co-authored-by: Gdany Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/cs/ Translation: Discord Tickets/Bot --- src/i18n/cs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index 100c401..d083cb1 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -96,6 +96,7 @@ commands: title: ❌Žádné tickety not_staff: title: ❌Chyba + description: Tickety mohou násilně uzavírat pouze zaměstnanci. move: description: Přesunout ticket do jiné kategorie name: přesunout From 7cc75b8f35923f2f18ed19e772eaf3095df95e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dow=C3=ADk=20Bot=C3=ADk?= Date: Wed, 22 Feb 2023 02:47:40 +0100 Subject: [PATCH 321/409] feat(i18n): update Czech translations [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 42.6% (104 of 244 strings) Co-authored-by: Dowík Botík Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/cs/ Translation: Discord Tickets/Bot --- src/i18n/cs.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index d083cb1..c2210a2 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -19,11 +19,11 @@ commands: name: přidat options: ticket: - name: ticket + name: tiket description: Ticket na přidání člena member: name: člen - description: Člen, kterého chcete přidat do ticketu. + description: Člen, kterého chcete přidat do ticketu added: ➡️{by} přidal {added}. description: Přidat člena do ticketu not_staff: @@ -85,15 +85,15 @@ commands: name: důvod ticket: description: Ticket k uzavření - name: ticket + name: tiket time: name: čas confirm_multiple: title: ❓Jste si jist? name: uzavřít silou - description: Násilně uzavřít ticket + description: Násilně uzavřít tiketu no_tickets: - title: ❌Žádné tickety + title: ❌Žádné tikety not_staff: title: ❌Chyba description: Tickety mohou násilně uzavírat pouze zaměstnanci. From 3b18b3bef15733ab2248f2e4ed3e73bc4e106e8e Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 22 Feb 2023 22:39:44 +0000 Subject: [PATCH 322/409] docs: update README [skip ci] --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index ea97fac..395352d 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,8 @@ > *dev notes:* > - https://static.eartharoid.me/k/22/08/02185801.png - for user/create, slash/force-close, slash/claim, slash/release, and slash/move > - menu question max length cannot be higher than question options -> - ports <1024 require root > - TODO: update notifications > - TODO: user:create, force-close -> - **likely to be rate limited by discord**: -> -> [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/eB6TkX?referralCode=Z3aYd2) -
From 55985e95f079bf00226a81d8a2a18e79e846e0ac Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 23 Feb 2023 20:25:29 +0000 Subject: [PATCH 323/409] chore: update dependencies --- package.json | 8 +- pnpm-lock.yaml | 222 ++++++++++++++++++++++++------------------------- 2 files changed, 115 insertions(+), 115 deletions(-) diff --git a/package.json b/package.json index 7aa04cb..4d6bf18 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.4.0", + "@discord-tickets/settings": "^1.4.1", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", @@ -59,7 +59,7 @@ "fs-extra": "^10.1.0", "keyv": "^4.5.2", "leeks.js": "^0.2.4", - "leekslazylogger": "^5.0.0", + "leekslazylogger": "^5.0.1", "ms": "^2.1.3", "mustache": "^4.2.0", "node-dir": "^0.1.17", @@ -71,8 +71,8 @@ "yaml": "^1.10.2" }, "devDependencies": { - "@commitlint/cli": "^17.4.3", - "@commitlint/config-conventional": "^17.4.3", + "@commitlint/cli": "^17.4.4", + "@commitlint/config-conventional": "^17.4.4", "all-contributors-cli": "^6.24.0", "conventional-changelog-cli": "^2.2.2", "eslint": "^8.34.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 406d746..33685e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,9 @@ lockfileVersion: 5.4 specifiers: - '@commitlint/cli': ^17.4.3 - '@commitlint/config-conventional': ^17.4.3 - '@discord-tickets/settings': ^1.4.0 + '@commitlint/cli': ^17.4.4 + '@commitlint/config-conventional': ^17.4.4 + '@discord-tickets/settings': ^1.4.1 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -29,7 +29,7 @@ specifiers: husky: ^8.0.3 keyv: ^4.5.2 leeks.js: ^0.2.4 - leekslazylogger: ^5.0.0 + leekslazylogger: ^5.0.1 lint-staged: ^13.1.2 ms: ^2.1.3 mustache: ^4.2.0 @@ -45,7 +45,7 @@ specifiers: zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 1.4.0_svelte@3.55.1 + '@discord-tickets/settings': 1.4.1_svelte@3.55.1 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -64,7 +64,7 @@ dependencies: fs-extra: 10.1.0 keyv: 4.5.2 leeks.js: 0.2.4 - leekslazylogger: 5.0.0 + leekslazylogger: 5.0.1 ms: 2.1.3 mustache: 4.2.0 node-dir: 0.1.17 @@ -82,8 +82,8 @@ optionalDependencies: zlib-sync: 0.1.8 devDependencies: - '@commitlint/cli': 17.4.3 - '@commitlint/config-conventional': 17.4.3 + '@commitlint/cli': 17.4.4 + '@commitlint/config-conventional': 17.4.4 all-contributors-cli: 6.24.0 conventional-changelog-cli: 2.2.2 eslint: 8.34.0 @@ -115,53 +115,53 @@ packages: js-tokens: 4.0.0 dev: true - /@babel/runtime/7.20.13: - resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==} + /@babel/runtime/7.21.0: + resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 dev: true - /@commitlint/cli/17.4.3: - resolution: {integrity: sha512-IPTS7AZuBHgD0gl24El8HwuDM9zJN9JLa5KmZUQoFD1BQeGGdzAYJOnAr85CeJWpTDok0BGHDL0+4odnH0iTyA==} + /@commitlint/cli/17.4.4: + resolution: {integrity: sha512-HwKlD7CPVMVGTAeFZylVNy14Vm5POVY0WxPkZr7EXLC/os0LH/obs6z4HRvJtH/nHCMYBvUBQhGwnufKfTjd5g==} engines: {node: '>=v14'} hasBin: true dependencies: - '@commitlint/format': 17.4.0 - '@commitlint/lint': 17.4.3 - '@commitlint/load': 17.4.2 - '@commitlint/read': 17.4.2 - '@commitlint/types': 17.4.0 + '@commitlint/format': 17.4.4 + '@commitlint/lint': 17.4.4 + '@commitlint/load': 17.4.4 + '@commitlint/read': 17.4.4 + '@commitlint/types': 17.4.4 execa: 5.1.1 lodash.isfunction: 3.0.9 resolve-from: 5.0.0 resolve-global: 1.0.0 - yargs: 17.7.0 + yargs: 17.7.1 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' dev: true - /@commitlint/config-conventional/17.4.3: - resolution: {integrity: sha512-8EsY2iDw74hCk3hIQSg7/E0R8/KtPjnFPZVwmmHxcjhZjkSykmxysefICPDnbI3xgxfov0zwL1WKDHM8zglJdw==} + /@commitlint/config-conventional/17.4.4: + resolution: {integrity: sha512-u6ztvxqzi6NuhrcEDR7a+z0yrh11elY66nRrQIpqsqW6sZmpxYkDLtpRH8jRML+mmxYQ8s4qqF06Q/IQx5aJeQ==} engines: {node: '>=v14'} dependencies: conventional-changelog-conventionalcommits: 5.0.0 dev: true - /@commitlint/config-validator/17.4.0: - resolution: {integrity: sha512-Sa/+8KNpDXz4zT4bVbz2fpFjvgkPO6u2V2fP4TKgt6FjmOw2z3eEX859vtfeaTav/ukBw0/0jr+5ZTZp9zCBhA==} + /@commitlint/config-validator/17.4.4: + resolution: {integrity: sha512-bi0+TstqMiqoBAQDvdEP4AFh0GaKyLFlPPEObgI29utoKEYoPQTvF0EYqIwYYLEoJYhj5GfMIhPHJkTJhagfeg==} engines: {node: '>=v14'} dependencies: - '@commitlint/types': 17.4.0 + '@commitlint/types': 17.4.4 ajv: 8.12.0 dev: true - /@commitlint/ensure/17.4.0: - resolution: {integrity: sha512-7oAxt25je0jeQ/E0O/M8L3ADb1Cvweu/5lc/kYF8g/kXatI0wxGE5La52onnAUAWeWlsuvBNar15WcrmDmr5Mw==} + /@commitlint/ensure/17.4.4: + resolution: {integrity: sha512-AHsFCNh8hbhJiuZ2qHv/m59W/GRE9UeOXbkOqxYMNNg9pJ7qELnFcwj5oYpa6vzTSHtPGKf3C2yUFNy1GGHq6g==} engines: {node: '>=v14'} dependencies: - '@commitlint/types': 17.4.0 + '@commitlint/types': 17.4.4 lodash.camelcase: 4.3.0 lodash.kebabcase: 4.1.1 lodash.snakecase: 4.1.1 @@ -174,49 +174,49 @@ packages: engines: {node: '>=v14'} dev: true - /@commitlint/format/17.4.0: - resolution: {integrity: sha512-Z2bWAU5+f1YZh9W76c84J8iLIWIvvm+mzqogTz0Nsc1x6EHW0Z2gI38g5HAjB0r0I3ZjR15IDEJKhsxyblcyhA==} + /@commitlint/format/17.4.4: + resolution: {integrity: sha512-+IS7vpC4Gd/x+uyQPTAt3hXs5NxnkqAZ3aqrHd5Bx/R9skyCAWusNlNbw3InDbAK6j166D9asQM8fnmYIa+CXQ==} engines: {node: '>=v14'} dependencies: - '@commitlint/types': 17.4.0 + '@commitlint/types': 17.4.4 chalk: 4.1.2 dev: true - /@commitlint/is-ignored/17.4.2: - resolution: {integrity: sha512-1b2Y2qJ6n7bHG9K6h8S4lBGUl6kc7mMhJN9gy1SQfUZqe92ToDjUTtgNWb6LbzR1X8Cq4SEus4VU8Z/riEa94Q==} + /@commitlint/is-ignored/17.4.4: + resolution: {integrity: sha512-Y3eo1SFJ2JQDik4rWkBC4tlRIxlXEFrRWxcyrzb1PUT2k3kZ/XGNuCDfk/u0bU2/yS0tOA/mTjFsV+C4qyACHw==} engines: {node: '>=v14'} dependencies: - '@commitlint/types': 17.4.0 + '@commitlint/types': 17.4.4 semver: 7.3.8 dev: true - /@commitlint/lint/17.4.3: - resolution: {integrity: sha512-GnPsqEYmXIB/MaBhRMzkiDJWyjuLrKad4xoxKO4N6Kc19iqjR4DPc/bl2dxeW9kUmtrAtefOzIEzJAevpA5y2w==} + /@commitlint/lint/17.4.4: + resolution: {integrity: sha512-qgkCRRFjyhbMDWsti/5jRYVJkgYZj4r+ZmweZObnbYqPUl5UKLWMf9a/ZZisOI4JfiPmRktYRZ2JmqlSvg+ccw==} engines: {node: '>=v14'} dependencies: - '@commitlint/is-ignored': 17.4.2 - '@commitlint/parse': 17.4.2 - '@commitlint/rules': 17.4.3 - '@commitlint/types': 17.4.0 + '@commitlint/is-ignored': 17.4.4 + '@commitlint/parse': 17.4.4 + '@commitlint/rules': 17.4.4 + '@commitlint/types': 17.4.4 dev: true - /@commitlint/load/17.4.2: - resolution: {integrity: sha512-Si++F85rJ9t4hw6JcOw1i2h0fdpdFQt0YKwjuK4bk9KhFjyFkRxvR3SB2dPaMs+EwWlDrDBGL+ygip1QD6gmPw==} + /@commitlint/load/17.4.4: + resolution: {integrity: sha512-z6uFIQ7wfKX5FGBe1AkOF4l/ShOQsaa1ml/nLMkbW7R/xF8galGS7Zh0yHvzVp/srtfS0brC+0bUfQfmpMPFVQ==} engines: {node: '>=v14'} dependencies: - '@commitlint/config-validator': 17.4.0 + '@commitlint/config-validator': 17.4.4 '@commitlint/execute-rule': 17.4.0 - '@commitlint/resolve-extends': 17.4.0 - '@commitlint/types': 17.4.0 - '@types/node': 18.13.0 + '@commitlint/resolve-extends': 17.4.4 + '@commitlint/types': 17.4.4 + '@types/node': 18.14.1 chalk: 4.1.2 cosmiconfig: 8.0.0 - cosmiconfig-typescript-loader: 4.3.0_p7cp6dsfhdrlk7mvuxd3wodbsu + cosmiconfig-typescript-loader: 4.3.0_s4dpre5ezutgdzsn47klmddvia lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1_4bewfcp2iebiwuold25d6rgcsy + ts-node: 10.9.1_uayvamxqnl5yeiojjysxwopmsy typescript: 4.9.5 transitivePeerDependencies: - '@swc/core' @@ -228,46 +228,46 @@ packages: engines: {node: '>=v14'} dev: true - /@commitlint/parse/17.4.2: - resolution: {integrity: sha512-DK4EwqhxfXpyCA+UH8TBRIAXAfmmX4q9QRBz/2h9F9sI91yt6mltTrL6TKURMcjUVmgaB80wgS9QybNIyVBIJA==} + /@commitlint/parse/17.4.4: + resolution: {integrity: sha512-EKzz4f49d3/OU0Fplog7nwz/lAfXMaDxtriidyGF9PtR+SRbgv4FhsfF310tKxs6EPj8Y+aWWuX3beN5s+yqGg==} engines: {node: '>=v14'} dependencies: - '@commitlint/types': 17.4.0 + '@commitlint/types': 17.4.4 conventional-changelog-angular: 5.0.13 conventional-commits-parser: 3.2.4 dev: true - /@commitlint/read/17.4.2: - resolution: {integrity: sha512-hasYOdbhEg+W4hi0InmXHxtD/1favB4WdwyFxs1eOy/DvMw6+2IZBmATgGOlqhahsypk4kChhxjAFJAZ2F+JBg==} + /@commitlint/read/17.4.4: + resolution: {integrity: sha512-B2TvUMJKK+Svzs6eji23WXsRJ8PAD+orI44lVuVNsm5zmI7O8RSGJMvdEZEikiA4Vohfb+HevaPoWZ7PiFZ3zA==} engines: {node: '>=v14'} dependencies: '@commitlint/top-level': 17.4.0 - '@commitlint/types': 17.4.0 + '@commitlint/types': 17.4.4 fs-extra: 11.1.0 git-raw-commits: 2.0.11 minimist: 1.2.8 dev: true - /@commitlint/resolve-extends/17.4.0: - resolution: {integrity: sha512-3JsmwkrCzoK8sO22AzLBvNEvC1Pmdn/65RKXzEtQMy6oYMl0Snrq97a5bQQEFETF0VsvbtUuKttLqqgn99OXRQ==} + /@commitlint/resolve-extends/17.4.4: + resolution: {integrity: sha512-znXr1S0Rr8adInptHw0JeLgumS11lWbk5xAWFVno+HUFVN45875kUtqjrI6AppmD3JI+4s0uZlqqlkepjJd99A==} engines: {node: '>=v14'} dependencies: - '@commitlint/config-validator': 17.4.0 - '@commitlint/types': 17.4.0 + '@commitlint/config-validator': 17.4.4 + '@commitlint/types': 17.4.4 import-fresh: 3.3.0 lodash.mergewith: 4.6.2 resolve-from: 5.0.0 resolve-global: 1.0.0 dev: true - /@commitlint/rules/17.4.3: - resolution: {integrity: sha512-xHReDfE3Z+O9p1sXeEhPRSk4FifBsC4EbXzvQ4aa0ykQe+n/iZDd4CrFC/Oiv2K9BU4ZnFHak30IbMLa4ks1Rw==} + /@commitlint/rules/17.4.4: + resolution: {integrity: sha512-0tgvXnHi/mVcyR8Y8mjTFZIa/FEQXA4uEutXS/imH2v1UNkYDSEMsK/68wiXRpfW1euSgEdwRkvE1z23+yhNrQ==} engines: {node: '>=v14'} dependencies: - '@commitlint/ensure': 17.4.0 + '@commitlint/ensure': 17.4.4 '@commitlint/message': 17.4.2 '@commitlint/to-lines': 17.4.0 - '@commitlint/types': 17.4.0 + '@commitlint/types': 17.4.4 execa: 5.1.1 dev: true @@ -283,8 +283,8 @@ packages: find-up: 5.0.0 dev: true - /@commitlint/types/17.4.0: - resolution: {integrity: sha512-2NjAnq5IcxY9kXtUeO2Ac0aPpvkuOmwbH/BxIm36XXK5LtWFObWJWjXOA+kcaABMrthjWu6la+FUpyYFMHRvbA==} + /@commitlint/types/17.4.4: + resolution: {integrity: sha512-amRN8tRLYOsxRr6mTnGGGvB5EmW/4DDjLMgiwK3CCVEmN6Sr/6xePGEpWaspKkckILuUORCwe6VfDBw6uj4axQ==} engines: {node: '>=v14'} dependencies: chalk: 4.1.2 @@ -297,8 +297,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@discord-tickets/settings/1.4.0_svelte@3.55.1: - resolution: {integrity: sha512-bpdsKZQscFlOAduNbQkTN4vU3NQzaWaquh69xDECh4hgsfz1ILEPz86WNzA+B79qAaAYNrxsIq/W9ga7NhoUuQ==} + /@discord-tickets/settings/1.4.1_svelte@3.55.1: + resolution: {integrity: sha512-Gi/DbZY9VdtXiSn3ricZCemDStkDVW1NA7TBfY4VUXuZ7gxhV6UlIs2H7LP3mFV1bMJB6R0Zk5WbL/H95lANQg==} dependencies: '@fortawesome/fontawesome-free': 6.3.0 '@skyra/discord-components-core': 3.6.0 @@ -319,7 +319,7 @@ packages: dependencies: '@discordjs/util': 0.1.0 '@sapphire/shapeshift': 3.8.1 - discord-api-types: 0.37.34 + discord-api-types: 0.37.35 fast-deep-equal: 3.1.3 ts-mixer: 6.0.3 tslib: 2.5.0 @@ -338,10 +338,10 @@ packages: '@discordjs/util': 0.1.0 '@sapphire/async-queue': 1.5.0 '@sapphire/snowflake': 3.4.0 - discord-api-types: 0.37.34 - file-type: 18.2.0 + discord-api-types: 0.37.35 + file-type: 18.2.1 tslib: 2.5.0 - undici: 5.19.1 + undici: 5.20.0 dev: false /@discordjs/util/0.1.0: @@ -421,13 +421,13 @@ packages: /@fastify/fast-json-stringify-compiler/4.2.0: resolution: {integrity: sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg==} dependencies: - fast-json-stringify: 5.5.0 + fast-json-stringify: 5.6.2 dev: false /@fastify/http-proxy/8.4.0_3cxu5zja4e2r5wmvge7mdcljwq: resolution: {integrity: sha512-H8nwsmawFtKKRE6uhh1BtF1gQi/l147SmLsDGxB0HdYTHzjXz6uSQO3lEVmY7unKMzbArRjdoJQkEGpScszdSw==} dependencies: - '@fastify/reply-from': 8.4.2 + '@fastify/reply-from': 8.4.3 ws: 8.12.1_3cxu5zja4e2r5wmvge7mdcljwq transitivePeerDependencies: - bufferutil @@ -454,8 +454,8 @@ packages: - supports-color dev: false - /@fastify/reply-from/8.4.2: - resolution: {integrity: sha512-xFa/5CYQWgZo+L8f97n+JBk9H4Dy/SXyTW/H0Puavat6akCWPD6tlNucZipGuwvjNFYrVbMtV9MtP1O+Z3+EbQ==} + /@fastify/reply-from/8.4.3: + resolution: {integrity: sha512-8QDytjgFfxEbPGBpNj3nj0/QZt+SEyqAPeJ/2/zxxufWdQJ8fz77pEoTO7v/f2rHOJ0rr2ail3Tf6zFIs+78dg==} dependencies: '@fastify/error': 3.2.0 end-of-stream: 1.4.4 @@ -463,7 +463,7 @@ packages: fastify-plugin: 4.5.0 pump: 3.0.0 tiny-lru: 10.0.1 - undici: 5.19.1 + undici: 5.20.0 dev: false /@fortawesome/fontawesome-free/6.3.0: @@ -678,8 +678,8 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - /@types/node/18.13.0: - resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==} + /@types/node/18.14.1: + resolution: {integrity: sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ==} /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -688,7 +688,7 @@ packages: /@types/ws/8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: - '@types/node': 18.13.0 + '@types/node': 18.14.1 dev: false /JSONStream/1.3.5: @@ -786,7 +786,7 @@ packages: engines: {node: '>=4'} hasBin: true dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 async: 3.2.4 chalk: 4.1.2 didyoumean: 1.2.2 @@ -1346,7 +1346,7 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /cosmiconfig-typescript-loader/4.3.0_p7cp6dsfhdrlk7mvuxd3wodbsu: + /cosmiconfig-typescript-loader/4.3.0_s4dpre5ezutgdzsn47klmddvia: resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -1355,9 +1355,9 @@ packages: ts-node: '>=10' typescript: '>=3' dependencies: - '@types/node': 18.13.0 + '@types/node': 18.14.1 cosmiconfig: 8.0.0 - ts-node: 10.9.1_4bewfcp2iebiwuold25d6rgcsy + ts-node: 10.9.1_uayvamxqnl5yeiojjysxwopmsy typescript: 4.9.5 dev: true @@ -1472,8 +1472,8 @@ packages: engines: {node: '>=0.3.1'} dev: true - /discord-api-types/0.37.34: - resolution: {integrity: sha512-3sX2QMX2vLjT93n6+c1rN6lM6qWHycjYol6x/ls6U5hbZPdGH00dRMjg2csF58ISMf7DGV/6inDKgY1u5niWhA==} + /discord-api-types/0.37.35: + resolution: {integrity: sha512-iyKZ/82k7FX3lcmHiAvvWu5TmyfVo78RtghBV/YsehK6CID83k5SI03DKKopBcln+TiEIYw5MGgq7SJXSpNzMg==} dev: false /discord.js/14.7.1_3cxu5zja4e2r5wmvge7mdcljwq: @@ -1486,11 +1486,11 @@ packages: '@discordjs/util': 0.1.0 '@sapphire/snowflake': 3.4.0 '@types/ws': 8.5.4 - discord-api-types: 0.37.34 + discord-api-types: 0.37.35 fast-deep-equal: 3.1.3 lodash.snakecase: 4.1.1 tslib: 2.5.0 - undici: 5.19.1 + undici: 5.20.0 ws: 8.12.1_3cxu5zja4e2r5wmvge7mdcljwq transitivePeerDependencies: - bufferutil @@ -1822,8 +1822,8 @@ packages: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true - /fast-json-stringify/5.5.0: - resolution: {integrity: sha512-rmw2Z8/mLkND8zI+3KTYIkNPEoF5v6GqDP/o+g7H3vjdWjBwuKpgAYFHIzL6ORRB+iqDjjtJnLIW9Mzxn5szOA==} + /fast-json-stringify/5.6.2: + resolution: {integrity: sha512-F6xkRrXvtGbAiDSEI5Rk7qk2P63Y9kc8bO6Dnsd3Rt6sBNr2QxNFWs0JbKftgiyOfGxnJaRoHe4SizCTqeAyrA==} dependencies: '@fastify/deepmerge': 1.3.0 ajv: 8.12.0 @@ -1885,9 +1885,9 @@ packages: abstract-logging: 2.0.1 avvio: 8.2.1 fast-content-type-parse: 1.0.0 - find-my-way: 7.4.0 + find-my-way: 7.5.0 light-my-request: 5.9.1 - pino: 8.10.0 + pino: 8.11.0 process-warning: 2.1.0 proxy-addr: 2.0.7 rfdc: 1.3.0 @@ -1936,8 +1936,8 @@ packages: flat-cache: 3.0.4 dev: true - /file-type/18.2.0: - resolution: {integrity: sha512-M3RQMWY3F2ykyWZ+IHwNCjpnUmukYhtdkGGC1ZVEUb0ve5REGF7NNJ4Q9ehCUabtQKtSVFOMbFTXgJlFb0DQIg==} + /file-type/18.2.1: + resolution: {integrity: sha512-Yw5MtnMv7vgD2/6Bjmmuegc8bQEVA9GmAyaR18bMYWKqsWDG9wgYZ1j4I6gNMF5Y5JBDcUcjRQqNQx7Y8uotcg==} engines: {node: '>=14.16'} dependencies: readable-web-to-node-stream: 3.0.2 @@ -1972,8 +1972,8 @@ packages: - supports-color dev: false - /find-my-way/7.4.0: - resolution: {integrity: sha512-JFT7eURLU5FumlZ3VBGnveId82cZz7UR7OUu+THQJOwdQXxmS/g8v0KLoFhv97HreycOrmAbqjXD/4VG2j0uMQ==} + /find-my-way/7.5.0: + resolution: {integrity: sha512-3ehydSBhGcS0TtMA/BYEyMAKi9Sv0MqF8aqiMO5oGBXyCcSlyEJyfGWsbNxAx7BekTNWUwD1ttLJLURni2vmJg==} engines: {node: '>=14'} dependencies: fast-deep-equal: 3.1.3 @@ -2435,7 +2435,7 @@ packages: resolution: {integrity: sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==} engines: {node: '>=10'} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.21.0 chalk: 4.1.2 pegjs: 0.10.0 dev: true @@ -2490,8 +2490,8 @@ packages: resolution: {integrity: sha512-yFR6BtcTA/5s2FJAVkPn2VEzqO76DqBIdkC+1vNyersz1Hw0DxhunRu7bI7/XKxwrNy8x0K4e1p4YQKIGRCBww==} dev: false - /leekslazylogger/5.0.0: - resolution: {integrity: sha512-tBWBTjam1hpLfLBp6L/dVMwPiwMSLUiaWbS05aeXf1r/PjR+YmktawitqzRYiiAtdauyuPvnNtnm+W9A9tkudg==} + /leekslazylogger/5.0.1: + resolution: {integrity: sha512-JPA1o6psxrovivF/X84VbBHeMfwnAqxHwAv4S7LlnSX5aSHikDL1P77jGy7fxW0NKj3RA4F7G8lM0Eb813wqvg==} engines: {node: '>=14'} dependencies: '@eartharoid/deep-merge': 0.0.2 @@ -3175,8 +3175,8 @@ packages: resolution: {integrity: sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==} dev: false - /pino/8.10.0: - resolution: {integrity: sha512-ODfIe+giJtQGsvNAEj5/sHHpL3TFBg161JBH4W62Hc0l0PJjsDFD1R7meLI4PZ2aoHDJznxFNShkJcaG/qJToQ==} + /pino/8.11.0: + resolution: {integrity: sha512-Z2eKSvlrl2rH8p5eveNUnTdd4AjJk8tAsLkHYZQKGHP4WTh2Gi1cOSOs3eWPqaj+niS3gj4UkoreoaWgF3ZWYg==} hasBin: true dependencies: atomic-sleep: 1.0.0 @@ -3327,8 +3327,8 @@ packages: type-fest: 0.6.0 dev: true - /readable-stream/2.3.7: - resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} + /readable-stream/2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -3339,8 +3339,8 @@ packages: util-deprecate: 1.0.2 dev: true - /readable-stream/3.6.0: - resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + /readable-stream/3.6.1: + resolution: {integrity: sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==} engines: {node: '>= 6'} dependencies: inherits: 2.0.4 @@ -3361,7 +3361,7 @@ packages: resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} engines: {node: '>=8'} dependencies: - readable-stream: 3.6.0 + readable-stream: 3.6.1 dev: false /readdirp/3.6.0: @@ -3696,7 +3696,7 @@ packages: /split2/3.2.2: resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} dependencies: - readable-stream: 3.6.0 + readable-stream: 3.6.1 dev: true /split2/4.1.0: @@ -3889,14 +3889,14 @@ packages: /through2/2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} dependencies: - readable-stream: 2.3.7 + readable-stream: 2.3.8 xtend: 4.0.2 dev: true /through2/4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} dependencies: - readable-stream: 3.6.0 + readable-stream: 3.6.1 dev: true /tiny-lru/10.0.1: @@ -3951,7 +3951,7 @@ packages: resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==} dev: false - /ts-node/10.9.1_4bewfcp2iebiwuold25d6rgcsy: + /ts-node/10.9.1_uayvamxqnl5yeiojjysxwopmsy: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -3970,7 +3970,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 18.13.0 + '@types/node': 18.14.1 acorn: 8.8.2 acorn-walk: 8.2.0 arg: 4.1.3 @@ -4046,8 +4046,8 @@ packages: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true - /undici/5.19.1: - resolution: {integrity: sha512-YiZ61LPIgY73E7syxCDxxa3LV2yl3sN8spnIuTct60boiiRaE1J8mNWHO8Im2Zi/sFrPusjLlmRPrsyraSqX6A==} + /undici/5.20.0: + resolution: {integrity: sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==} engines: {node: '>=12.18'} dependencies: busboy: 1.6.0 @@ -4248,8 +4248,8 @@ packages: yargs-parser: 20.2.9 dev: true - /yargs/17.7.0: - resolution: {integrity: sha512-dwqOPg5trmrre9+v8SUo2q/hAwyKoVfu8OC1xPHKJGNdxAvPl4sKxL4vBnh3bQz/ZvvGAFeA5H3ou2kcOY8sQQ==} + /yargs/17.7.1: + resolution: {integrity: sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==} engines: {node: '>=12'} dependencies: cliui: 8.0.1 From ea3413d8cba0e68172a6c757e4d354515c0f2709 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 23 Feb 2023 21:53:18 +0000 Subject: [PATCH 324/409] feat: validate environment variables at startup --- src/env.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 21 ++++++++---------- 2 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 src/env.js diff --git a/src/env.js b/src/env.js new file mode 100644 index 0000000..99824a5 --- /dev/null +++ b/src/env.js @@ -0,0 +1,61 @@ +/* eslint-disable no-console */ + +const dotenv = require('dotenv'); +const { colours } = require('leeks.js'); + +const providers = ['mysql', 'postgresql', 'sqlite']; + +// ideally the defaults would be set here too, but the pre-install script may run when `src/` is not available +const env = { + DB_CONNECTION_URL: v => + !!v || + (process.env.DB_PROVIDER === 'sqlite') || + new Error('must be set when "DB_PROVIDER" is not "sqlite"'), + DB_PROVIDER: v => + (!!v && providers.includes(v)) || + new Error(`must be one of: ${providers.map(v => `"${v}"`).join(', ')}`), + DISCORD_SECRET: v => + !!v || + new Error('is required'), + DISCORD_TOKEN: v => + !!v || + new Error('is required'), + ENCRYPTION_KEY: v => + (!!v && v.length >= 48) || + new Error('is required and must be at least 48 characters long; run "npm run keygen" to generate a key'), + HTTP_EXTERNAL: v => + (!!v && v.startsWith('http') && !v.endsWith('/')) || + new Error('must be a valid URL without a trailing slash'), + HTTP_HOST: v => + (!!v && !v.startsWith('http')) || + new Error('is required and must be an address, not a URL'), + HTTP_PORT: v => + !!v || + new Error('is required'), + HTTP_TRUST_PROXY: () => true, // optional + OVERRIDE_ARCHIVE: () => true, // optional + PUBLIC_BOT: () => true, // optional + SETTINGS_HOST: v => + (!!v && !v.startsWith('http')) || + new Error('is required and must be an address, not a URL'), + SETTINGS_PORT: v => + !!v || + new Error('is required'), + SUPER: () => true, // optional +}; + +const load = options => { + dotenv.config(options); + Object.entries(env).forEach(([name, validate]) => { + const result = validate(process.env[name]); // `true` for pass, or `Error` for fail + if (result instanceof Error) { + console.log('\x07' + colours.redBright(`Error: The "${name}" environment variable ${result.message}.`)); + process.exit(1); + } + }); +}; + +module.exports = { + env, + load, +}; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 2181c22..79a8a45 100644 --- a/src/index.js +++ b/src/index.js @@ -23,20 +23,12 @@ /* eslint-disable no-console */ -process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set -require('dotenv').config(); // load env file - const pkg = require('../package.json'); const banner = require('./lib/banner'); console.log(banner(pkg.version)); // print big title -const fs = require('fs'); const semver = require('semver'); const { colours } = require('leeks.js'); -const logger = require('./lib/logger'); -const YAML = require('yaml'); -const Client = require('./client'); -const http = require('./http'); // check node version if (!semver.satisfies(process.versions.node, pkg.engines.node)) { @@ -44,10 +36,15 @@ if (!semver.satisfies(process.versions.node, pkg.engines.node)) { process.exit(1); } -if (process.env.ENCRYPTION_KEY === undefined) { - console.log('\x07' + colours.redBright('Error: The "ENCRYPTION_KEY" environment variable is not set.\nRun "npm run keygen" to generate a key.')); - process.exit(1); -} +// this could be done first, but then there would be no banner :( +process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set +require('./env').load(); // load and check environment variables + +const fs = require('fs'); +const YAML = require('yaml'); +const logger = require('./lib/logger'); +const Client = require('./client'); +const http = require('./http'); if (!fs.existsSync('./user/config.yml')) { const examplePath = './user/example.config.yml'; From ff626d2871620dc6ace99608d506f923344cbaf0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 23 Feb 2023 22:37:48 +0000 Subject: [PATCH 325/409] fix: image name in docker-compose.yml --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e88f153..287fdeb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,7 @@ services: MYSQL_USER: tickets bot: - image: discord-tickets:4.0 + image: eartharoid/discord-tickets:4.0 depends_on: - mysql restart: unless-stopped From adab3831fac1175af12a2b4015d3c115183e3ee5 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 23 Feb 2023 22:40:14 +0000 Subject: [PATCH 326/409] feat: add `PUBLISH_COMMANDS` environment variable --- docker-compose.yml | 5 +++-- scripts/preinstall.js | 1 + src/env.js | 1 + src/listeners/client/ready.js | 8 ++++++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 287fdeb..a865563 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,11 +39,12 @@ services: HTTP_HOST: 0.0.0.0 HTTP_PORT: 80 HTTP_TRUST_PROXY: false # set to true if you're using a reverse proxy - PUBLIC_BOT: false OVERRIDE_ARCHIVE: null + PUBLIC_BOT: false + PUBLISH_COMMANDS: false SETTINGS_PORT: 8169 SETTINGS_HOST: 127.0.0.1 - SUPER: !!str 319467558166069248 # optionally add `,youruseridhere` + SUPER: 319467558166069248 # optionally add `,youruseridhere` networks: discord-tickets: diff --git a/scripts/preinstall.js b/scripts/preinstall.js index 517be0e..783c47d 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -24,6 +24,7 @@ const env = { HTTP_TRUST_PROXY: false, OVERRIDE_ARCHIVE: '', PUBLIC_BOT: false, + PUBLISH_COMMANDS: false, SETTINGS_HOST: '127.0.0.1', SETTINGS_PORT: 8169, SUPER: '319467558166069248', diff --git a/src/env.js b/src/env.js index 99824a5..787e975 100644 --- a/src/env.js +++ b/src/env.js @@ -35,6 +35,7 @@ const env = { HTTP_TRUST_PROXY: () => true, // optional OVERRIDE_ARCHIVE: () => true, // optional PUBLIC_BOT: () => true, // optional + PUBLISH_COMMANDS: () => true, // optional SETTINGS_HOST: v => (!!v && !v.startsWith('http')) || new Error('is required and must be an address, not a URL'), diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index a736f20..880ee6a 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -23,8 +23,16 @@ module.exports = class extends Listener { process.title = 'tickets'; client.log.success('Connected to Discord as "%s"', client.user.tag); + // fill cache await sync(client); + if (process.env.PUBLISH_COMMANDS === 'true') { + client.log.info('Automatically publishing commands...'); + client.commands.publish() + .then(commands => client.log.success('Published %d commands', commands?.size)) + .catch(client.log.error); + } + // presence/activity let next = 0; const setPresence = async () => { From f97a7ab61c68d39b3deb8155cd46a04f2e862703 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 24 Feb 2023 00:18:38 +0000 Subject: [PATCH 327/409] fix: default to `production` --- scripts/preinstall.js | 1 + src/index.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/preinstall.js b/scripts/preinstall.js index 783c47d..97931c4 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -22,6 +22,7 @@ const env = { HTTP_HOST: '0.0.0.0', HTTP_PORT: 8080, HTTP_TRUST_PROXY: false, + NODE_ENV: 'production', // not bot-specific OVERRIDE_ARCHIVE: '', PUBLIC_BOT: false, PUBLISH_COMMANDS: false, diff --git a/src/index.js b/src/index.js index 79a8a45..25d507f 100644 --- a/src/index.js +++ b/src/index.js @@ -37,7 +37,7 @@ if (!semver.satisfies(process.versions.node, pkg.engines.node)) { } // this could be done first, but then there would be no banner :( -process.env.NODE_ENV ??= 'development'; // make sure NODE_ENV is set +process.env.NODE_ENV ??= 'production'; // make sure NODE_ENV is set require('./env').load(); // load and check environment variables const fs = require('fs'); From 4d8495df77fc72ae13c6a515ce7e24fb33b518fd Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 24 Feb 2023 00:18:51 +0000 Subject: [PATCH 328/409] chore: update dependencies --- package.json | 2 +- pnpm-lock.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4d6bf18..43cadee 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "node": ">=18.0" }, "dependencies": { - "@discord-tickets/settings": "^1.4.1", + "@discord-tickets/settings": "^1.4.2", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33685e5..fb47375 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,7 +3,7 @@ lockfileVersion: 5.4 specifiers: '@commitlint/cli': ^17.4.4 '@commitlint/config-conventional': ^17.4.4 - '@discord-tickets/settings': ^1.4.1 + '@discord-tickets/settings': ^1.4.2 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -45,7 +45,7 @@ specifiers: zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 1.4.1_svelte@3.55.1 + '@discord-tickets/settings': 1.4.2_svelte@3.55.1 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -297,8 +297,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@discord-tickets/settings/1.4.1_svelte@3.55.1: - resolution: {integrity: sha512-Gi/DbZY9VdtXiSn3ricZCemDStkDVW1NA7TBfY4VUXuZ7gxhV6UlIs2H7LP3mFV1bMJB6R0Zk5WbL/H95lANQg==} + /@discord-tickets/settings/1.4.2_svelte@3.55.1: + resolution: {integrity: sha512-C8/aS9XTDUcnaX8wbFFUxH32M9SELcZ/BbsARTnXwFS3fGFs04mMI7L05Br0VHMYbKGmIykBMNZAGGco6Ka48A==} dependencies: '@fortawesome/fontawesome-free': 6.3.0 '@skyra/discord-components-core': 3.6.0 From db265537241c659a88f0e5a8745f6c5e4455a0e7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 24 Feb 2023 00:19:11 +0000 Subject: [PATCH 329/409] fix(docker): use the same port --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a865563..a1b09da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: networks: - discord-tickets ports: - - 8080:80 + - 8080:8080 volumes: - tickets-bot:/usr/bot/user # Please refer to the documentation: @@ -37,7 +37,7 @@ services: DB_PROVIDER: mysql HTTP_EXTERNAL: http://127.0.0.1:8080 # change this to your server's external IP (or domain) HTTP_HOST: 0.0.0.0 - HTTP_PORT: 80 + HTTP_PORT: 8080 HTTP_TRUST_PROXY: false # set to true if you're using a reverse proxy OVERRIDE_ARCHIVE: null PUBLIC_BOT: false From 6844828d33273a091754e7f32518c2e8500ea505 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 24 Feb 2023 00:32:48 +0000 Subject: [PATCH 330/409] fix(docker): add `tty` and `stdin_open` --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index a1b09da..dbb27fb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,6 +27,8 @@ services: - 8080:8080 volumes: - tickets-bot:/usr/bot/user + tty: true + stdin_open: true # Please refer to the documentation: # https://discordtickets.app/self-hosting/configuration/#environment-variables environment: From 229cfe11e506e985b9b2af2cc88e91d64d7116af Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 24 Feb 2023 16:32:52 +0000 Subject: [PATCH 331/409] chore: update dependencies --- package.json | 2 +- pnpm-lock.yaml | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 43cadee..3fd9856 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "figlet": "^1.5.2", "fs-extra": "^10.1.0", "keyv": "^4.5.2", - "leeks.js": "^0.2.4", + "leeks.js": "^0.3.0", "leekslazylogger": "^5.0.1", "ms": "^2.1.3", "mustache": "^4.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb47375..e536402 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ specifiers: fs-extra: ^10.1.0 husky: ^8.0.3 keyv: ^4.5.2 - leeks.js: ^0.2.4 + leeks.js: ^0.3.0 leekslazylogger: ^5.0.1 lint-staged: ^13.1.2 ms: ^2.1.3 @@ -63,7 +63,7 @@ dependencies: figlet: 1.5.2 fs-extra: 10.1.0 keyv: 4.5.2 - leeks.js: 0.2.4 + leeks.js: 0.3.0 leekslazylogger: 5.0.1 ms: 2.1.3 mustache: 4.2.0 @@ -2490,6 +2490,10 @@ packages: resolution: {integrity: sha512-yFR6BtcTA/5s2FJAVkPn2VEzqO76DqBIdkC+1vNyersz1Hw0DxhunRu7bI7/XKxwrNy8x0K4e1p4YQKIGRCBww==} dev: false + /leeks.js/0.3.0: + resolution: {integrity: sha512-6viaW7OYf8ijUe3G+rQ6CvbCLkN32vmeaEnVg82M474MbEeB++FcA80Qya1lv3cCgwkuxnfzDgnmWxol1bfhfA==} + dev: false + /leekslazylogger/5.0.1: resolution: {integrity: sha512-JPA1o6psxrovivF/X84VbBHeMfwnAqxHwAv4S7LlnSX5aSHikDL1P77jGy7fxW0NKj3RA4F7G8lM0Eb813wqvg==} engines: {node: '>=14'} From 42bfb17b5b5c787e954376cfe9c162407c0bbbad Mon Sep 17 00:00:00 2001 From: Hawexx Date: Sat, 25 Feb 2023 20:03:58 +0100 Subject: [PATCH 332/409] feat(i18n): update Turkish translations [skip ci] Currently translated at 2.0% (5 of 244 strings) Co-authored-by: Hawexx Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/tr/ Translation: Discord Tickets/Bot --- src/i18n/tr.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/i18n/tr.yml b/src/i18n/tr.yml index 0967ef4..5a469fb 100644 --- a/src/i18n/tr.yml +++ b/src/i18n/tr.yml @@ -1 +1,11 @@ -{} +buttons: + cancel: + emoji: ✖️ + close: + emoji: ✖️ + accept_close_request: + emoji: ✅ + create: + emoji: 🎫 + edit: + emoji: ✏️ From 12d97ee816bf4b1fb1a9e46113ae0458e251a93d Mon Sep 17 00:00:00 2001 From: sgtJohnny Date: Sun, 26 Feb 2023 02:43:15 +0100 Subject: [PATCH 333/409] feat(i18n): update German translations [skip ci] Currently translated at 99.5% (243 of 244 strings) Co-authored-by: sgtJohnny Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 106cb78..adcbe91 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -41,7 +41,7 @@ commands: description: Die Nachricht wurde angeheftet. title: ✅ Nachricht angeheftet create: - name: Ein Ticket aus einer Nachricht erstellen + name: Erstelle Ticket aus Nachricht slash: add: added: ➡️ {added} wurde von {by} hinzugefügt. @@ -222,7 +222,7 @@ commands: name: freigeben user: create: - name: Erstellt ein Ticket für einen Benutzer + name: Erstelle Ticket für Benutzer log: admin: changes: Änderungen From 325fe1d1c03bab17ea41a66f31251707ed0f44c8 Mon Sep 17 00:00:00 2001 From: Hawexx Date: Sun, 26 Feb 2023 02:43:16 +0100 Subject: [PATCH 334/409] feat(i18n): update Turkish translations [skip ci] Currently translated at 2.8% (7 of 244 strings) Co-authored-by: Hawexx Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/tr/ Translation: Discord Tickets/Bot --- src/i18n/tr.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/i18n/tr.yml b/src/i18n/tr.yml index 5a469fb..314d07b 100644 --- a/src/i18n/tr.yml +++ b/src/i18n/tr.yml @@ -9,3 +9,7 @@ buttons: emoji: 🎫 edit: emoji: ✏️ + claim: + emoji: 🙌 + confirm_open: + emoji: ✅ From 8843f306a7e3b1b9c79652cbcdf590a7302e6f7a Mon Sep 17 00:00:00 2001 From: Gdany Date: Mon, 27 Feb 2023 22:37:00 +0100 Subject: [PATCH 335/409] feat(i18n): update Czech translations [skip ci] Currently translated at 44.6% (109 of 244 strings) Co-authored-by: Gdany Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/cs/ Translation: Discord Tickets/Bot --- src/i18n/cs.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index c2210a2..7d0959f 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -138,6 +138,14 @@ commands: tag: description: Jméno tagu pro použití name: tag + tickets: + name: tickety + response: + fields: + closed: + name: Uzavřené tickety + open: + name: Otevřít tickety buttons: accept_close_request: emoji: ✅ From d6897e26542d8e751bde407ebe8987c43cad0c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondr=C3=A1=C5=A1?= Date: Mon, 27 Feb 2023 22:37:01 +0100 Subject: [PATCH 336/409] feat(i18n): update Czech translations [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 44.6% (109 of 244 strings) Co-authored-by: Ondráš Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/cs/ Translation: Discord Tickets/Bot --- src/i18n/cs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index 7d0959f..7f7f3af 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -146,6 +146,10 @@ commands: name: Uzavřené tickety open: name: Otevřít tickety + not_staff: + title: ❌ Chyba + topic: + name: téma buttons: accept_close_request: emoji: ✅ From ebcf81f8d32882ed401d4a8d33e74165b058c7e2 Mon Sep 17 00:00:00 2001 From: i3sey Date: Thu, 2 Mar 2023 18:37:17 +0100 Subject: [PATCH 337/409] feat(i18n): update Russian translations [skip ci] Currently translated at 0.8% (2 of 244 strings) Co-authored-by: i3sey Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/ru/ Translation: Discord Tickets/Bot --- src/i18n/ru.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/ru.yml b/src/i18n/ru.yml index 0967ef4..a6c9fca 100644 --- a/src/i18n/ru.yml +++ b/src/i18n/ru.yml @@ -1 +1,5 @@ -{} +buttons: + accept_close_request: + emoji: ✅ + cancel: + emoji: ✖️ From ad3abe109477204b0492e69851d7615c91e56b09 Mon Sep 17 00:00:00 2001 From: Gdany Date: Fri, 3 Mar 2023 23:36:49 +0100 Subject: [PATCH 338/409] feat(i18n): update Czech translations [skip ci] Currently translated at 57.3% (140 of 244 strings) Co-authored-by: Gdany Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/cs/ Translation: Discord Tickets/Bot --- src/i18n/cs.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index 7f7f3af..622907e 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -80,6 +80,7 @@ commands: options: category: name: kategorie + description: Uzavřít všechny tickety v zadané kategorii (lze použít s `time`). reason: description: Důvod pro uzavření ticketu(ů) name: důvod @@ -88,12 +89,18 @@ commands: name: tiket time: name: čas + description: Zavřít všechny tickety, které byly po zadanou dobu neaktivní + (lze použít s `category`). confirm_multiple: title: ❓Jste si jist? + description: "Chystáte se uzavřít **{count}** ticketů, které jsou neaktivní\ + \ déle než `{time}`:\n{tickets}\n" name: uzavřít silou description: Násilně uzavřít tiketu no_tickets: title: ❌Žádné tikety + description: Neexistují žádné otevřené tickety, které by byly neaktivní déle + než `{time}`. not_staff: title: ❌Chyba description: Tickety mohou násilně uzavírat pouze zaměstnanci. @@ -128,6 +135,7 @@ commands: title: ✅ Priorita nastavena release: name: uvolnit + description: Uvolnit (vrátit) ticket tag: description: Použijte tag name: tag @@ -146,6 +154,8 @@ commands: name: Uzavřené tickety open: name: Otevřít tickety + title: + other: Tickety uživatele {displayName} not_staff: title: ❌ Chyba topic: @@ -178,3 +188,19 @@ buttons: unclaim: emoji: ♻️ text: Vydat +dm: + closed: + archived: Chcete-li zobrazit archivované zprávy, zadejte `/transcript` do **{guild}**. + title: Váš ticket byl uzavřen + fields: + closed_by: Zavřeno uživatelem + ticket: Ticket + confirm_open: + title: Chcete otevřít ticket s následujícím tématem? +log: + admin: + description: + target: + tag: značka + verb: + create: vytvořeno From de825a5d81b86e0f7350047dd61ac883ae534da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondr=C3=A1=C5=A1?= Date: Fri, 3 Mar 2023 23:36:49 +0100 Subject: [PATCH 339/409] feat(i18n): update Czech translations [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 57.3% (140 of 244 strings) Co-authored-by: Ondráš Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/cs/ Translation: Discord Tickets/Bot --- src/i18n/cs.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index 622907e..4e3dd15 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -195,6 +195,14 @@ dm: fields: closed_by: Zavřeno uživatelem ticket: Ticket + closed: + name: Zavřeno v + value: '{timestamp} (po {duration})' + created: Vytvořeno v + feedback: Vaše zpětná vazba + reason: Zavřeno, protože + response: Doba odezvy + topic: Téma confirm_open: title: Chcete otevřít ticket s následujícím tématem? log: @@ -202,5 +210,22 @@ log: description: target: tag: značka + settings: nastavení + category: kategorie + panel: panel + question: otázka + joined: '{user} {verb} {targetType}' verb: create: vytvořeno + title: + target: + panel: Panel + category: Kategorie + question: Otázka + settings: Nastavení + tag: Štítek + joined: '{targetType} {verb}' + changes: Změny +misc: + error: + title: ⚠️ Něco se pokazilo From 955feda708f99bd407582210bf6cda3dfa77db8f Mon Sep 17 00:00:00 2001 From: Noel Horvath Date: Mon, 6 Mar 2023 04:40:24 +0100 Subject: [PATCH 340/409] feat(i18n): update Hungarian translations [skip ci] Currently translated at 99.5% (243 of 244 strings) Co-authored-by: Noel Horvath Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/hu/ Translation: Discord Tickets/Bot --- src/i18n/hu.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/i18n/hu.yml b/src/i18n/hu.yml index d63a917..d7de3c4 100644 --- a/src/i18n/hu.yml +++ b/src/i18n/hu.yml @@ -260,12 +260,25 @@ commands: name: hibajegy description: Hibajegy átiratának lekérése name: hibajegy - user: {} + user: + create: + name: Hibajegy létrehozása a felhasználó számára dm: closed: title: A hibajegyed bezárásra került archived: Használd a(z) `/transcript` a(z)**{guild}** szerveren, hogy megtekintsd az archivált üzeneteket. + fields: + closed: + name: Bezárva ekkor + value: '{timestamp} ({duration} alatt)' + closed_by: Bezárva általa + created: Létrehozva ekkor + feedback: Visszajelzésed + reason: Bezárva ezért + response: Válaszidő + ticket: Hibajegy + topic: Téma confirm_open: title: Létre szeretnél hozni egy hibajegyet a megadott témával? misc: From 6133a3d59f47dfe1f2ce3ed3181017930ec5ebd0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 6 Mar 2023 22:09:05 +0000 Subject: [PATCH 341/409] feat: update checker --- package.json | 3 +- pnpm-lock.yaml | 69 ++++++++++++++++++++++----- src/i18n/en-GB.yml | 7 ++- src/index.js | 2 +- src/lib/logger.js | 9 +++- src/lib/updates.js | 89 +++++++++++++++++++++++++++++++++++ src/listeners/client/ready.js | 6 +++ user/example.config.yml | 1 + 8 files changed, 171 insertions(+), 15 deletions(-) create mode 100644 src/lib/updates.js diff --git a/package.json b/package.json index 3fd9856..aeb15dd 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ }, "homepage": "https://discordtickets.app", "engines": { - "node": ">=18.0" + "node": ">=18" }, "dependencies": { "@discord-tickets/settings": "^1.4.2", @@ -50,6 +50,7 @@ "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", "@prisma/client": "^4.10.1", + "boxen": "^7.0.2", "cryptr": "^6.1.0", "discord.js": "^14.7.1", "dotenv": "^16.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e536402..bb6f6bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,7 @@ specifiers: '@fastify/oauth2': ^5.1.0 '@prisma/client': ^4.10.1 all-contributors-cli: ^6.24.0 + boxen: ^7.0.2 bufferutil: ^4.0.7 conventional-changelog-cli: ^2.2.2 cryptr: ^6.1.0 @@ -55,6 +56,7 @@ dependencies: '@fastify/jwt': 5.0.1 '@fastify/oauth2': 5.1.0 '@prisma/client': 4.10.1_prisma@4.10.1 + boxen: 7.0.2 cryptr: 6.1.0 discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq dotenv: 16.0.3 @@ -800,6 +802,12 @@ packages: - encoding dev: true + /ansi-align/3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + dependencies: + string-width: 4.2.3 + dev: false + /ansi-escapes/4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -809,12 +817,10 @@ packages: /ansi-regex/5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-regex/6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true /ansi-styles/3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -833,7 +839,6 @@ packages: /ansi-styles/6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true /anymatch/3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} @@ -944,6 +949,20 @@ packages: - supports-color dev: false + /boxen/7.0.2: + resolution: {integrity: sha512-1Z4UJabXUP1/R9rLpoU3O2lEMnG3pPLAs/ZD2lF3t2q7qD5lM8rqbtnvtvm4N0wEyNlE+9yZVTVAGmd1V5jabg==} + engines: {node: '>=14.16'} + dependencies: + ansi-align: 3.0.1 + camelcase: 7.0.1 + chalk: 5.2.0 + cli-boxes: 3.0.0 + string-width: 5.1.2 + type-fest: 2.19.0 + widest-line: 4.0.1 + wrap-ansi: 8.1.0 + dev: false + /brace-expansion/1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -1010,6 +1029,11 @@ packages: engines: {node: '>=6'} dev: true + /camelcase/7.0.1: + resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} + engines: {node: '>=14.16'} + dev: false + /chalk/2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -1027,6 +1051,11 @@ packages: supports-color: 7.2.0 dev: true + /chalk/5.2.0: + resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: false + /chardet/0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true @@ -1051,6 +1080,11 @@ packages: engines: {node: '>=6'} dev: true + /cli-boxes/3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + dev: false + /cli-cursor/3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -1518,7 +1552,6 @@ packages: /eastasianwidth/0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true /ecdsa-sig-formatter/1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} @@ -1540,11 +1573,9 @@ packages: /emoji-regex/8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /emoji-regex/9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true /emojilib/2.4.0: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} @@ -2349,7 +2380,6 @@ packages: /is-fullwidth-code-point/3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true /is-fullwidth-code-point/4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} @@ -3740,7 +3770,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true /string-width/5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -3749,7 +3778,6 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.0.1 - dev: true /string_decoder/1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -3767,14 +3795,12 @@ packages: engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - dev: true /strip-ansi/7.0.1: resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: true /strip-bom/3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} @@ -4024,6 +4050,11 @@ packages: engines: {node: '>=8'} dev: true + /type-fest/2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + dev: false + /type-is/1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -4132,6 +4163,13 @@ packages: isexe: 2.0.0 dev: true + /widest-line/4.0.1: + resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + dev: false + /word-wrap/1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} @@ -4159,6 +4197,15 @@ packages: strip-ansi: 6.0.1 dev: true + /wrap-ansi/8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.0.1 + dev: false + /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index cf7df34..22322be 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -183,7 +183,6 @@ commands: name: tag tickets: description: List your own or someone else's tickets - fields: name: tickets not_staff: description: Only staff members can view others' tickets. @@ -346,6 +345,12 @@ misc: unknown_category: description: Please try a different category. title: ❌ That ticket category doesn't exist + update: + description: | + > [View `{version}` on GitHub]({github}) + > [Changelog]({changelog}) + > [Update guide]({guide}) + title: An update is available modals: feedback: comment: diff --git a/src/index.js b/src/index.js index 25d507f..65f5ba5 100644 --- a/src/index.js +++ b/src/index.js @@ -32,7 +32,7 @@ const { colours } = require('leeks.js'); // check node version if (!semver.satisfies(process.versions.node, pkg.engines.node)) { - console.log('\x07' + colours.redBright(`Error: Your current Node.js version, ${process.versions.node}, does not meet the requirement "${pkg.engines.node}".`)); + console.log('\x07' + colours.redBright(`Error: Your current Node.js version, ${process.versions.node}, does not meet the requirement "${pkg.engines.node}". Please update to version ${semver.minVersion(pkg.engines.node).version} or higher.`)); process.exit(1); } diff --git a/src/lib/logger.js b/src/lib/logger.js index 701aa41..f0a0ab4 100644 --- a/src/lib/logger.js +++ b/src/lib/logger.js @@ -5,6 +5,7 @@ const { } = require('leekslazylogger'); const DTF = require('@eartharoid/dtf'); const { short } = require('leeks.js'); +const { format } = require('util'); const dtf = new DTF('en-GB'); const colours = { @@ -24,7 +25,13 @@ module.exports = config => { format: log => { const timestamp = dtf.fill('DD/MM/YY HH:mm:ss', log.timestamp); const colour = colours[log.level.name]; - return short(`&f&!7 ${timestamp} &r ${colour[0]}[${log.level.name.toUpperCase()}]&r ${log.namespace ? `&d(${log.namespace.toUpperCase()})&r ` : ''}${colour[1]}${log.content}`); + return format( + short(`&f&!7 %s &r ${colour[0]}[%s]&r %s${colour[1]}%s&r`), + timestamp, + log.level.name.toUpperCase(), + log.namespace ? short(`&d(${log.namespace.toUpperCase()})&r `) : '', + log.content, + ); }, level: config.logs.level, }), diff --git a/src/lib/updates.js b/src/lib/updates.js new file mode 100644 index 0000000..4daec76 --- /dev/null +++ b/src/lib/updates.js @@ -0,0 +1,89 @@ +const semver = require('semver'); +const { short } = require('leeks.js'); +const ExtendedEmbedBuilder = require('./embed'); +const { version: currentVersion } = require('../../package.json'); + +/** @param {import("client")} client */ +module.exports = client => { + client.log.info('Checking for updates...'); + fetch('https://api.github.com/repos/discord-tickets/bot/releases') + .then(res => res.json()) + .then(async json => { + // releases are ordered by date, so a patch for an old version could be before the latest version + const releases = json + .filter(release => !release.prerelease) + .sort((a, b) => semver.compare(semver.coerce(b.tag_name)?.version, semver.coerce(a.tag_name)?.version)); + const latestRelease = releases[0]; + const latestVersion = semver.coerce(latestRelease.tag_name)?.version; + const compared = semver.compare(latestVersion, currentVersion); + + switch (compared) { + case -1: { + client.log.notice('You are running a pre-release version of Discord Tickets'); + break; + } + case 0: { + client.log.info('No updates available'); + break; + } + case 1: { + let currentRelease = releases.findIndex(release => semver.coerce(release.tag_name)?.version === currentVersion); + if (currentRelease === -1) return client.log.warn('Failed to find current release'); + const behind = currentRelease; + currentRelease = releases[currentRelease]; + const changelog = `https://discordtickets.app/changelogs/v${latestVersion.replaceAll('.', '') }/`; + const guide = 'https://discordtickets.app/self-hosting/updating/'; + const { default: boxen } = await import('boxen'); + + client.log.notice( + short('&r&6A new version of Discord Tickets is available (&c%s&6 -> &a%s&6)&r\n'), + currentVersion, + latestVersion, + boxen( + short([ // uses template literals to ensure boxen adds the correct padding + `&6You are &f${behind}&6 version${behind === 1 ? '' : 's'} behind the latest version, &a${latestVersion}&6.&r`, + `&6Changelog: &e${changelog}&r`, + `&6Update guide: &e${guide}&r`, + ].join('\n')), + { + align: 'center', + borderColor: 'yellow', + borderStyle: 'round', + margin: 1, + padding: 1, + title: 'Update available', + }), + ); + + if (process.env.PUBLIC_BOT !== 'true') { + const guilds = await client.prisma.guild.findMany({ where: { logChannel: { not: null } } }); + for (const guild of guilds) { + const getMessage = client.i18n.getLocale(guild.locale); + await client.channels.cache.get(guild.logChannel).send({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor('Blurple') + .setAuthor({ + iconURL: latestRelease.author.avatar_url, + name: latestRelease.author.login, + }) + .setTitle(getMessage('misc.update.title')) + .setDescription(getMessage('misc.update.fields.links.value', { + changelog, + github: latestRelease.html_url, + guide, + version: latestRelease.tag_name, + })), + ], + }); + } + } + break; + } + } + }) + .catch(error => { + client.log.warn('Failed to check for updates'); + client.log.error(error); + }); +}; \ No newline at end of file diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 880ee6a..e892210 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -4,6 +4,7 @@ const ms = require('ms'); const { version } = require('../../../package.json'); const { msToMins } = require('../../lib/misc'); const sync = require('../../lib/sync'); +const checkForUpdates = require('../../lib/updates'); module.exports = class extends Listener { constructor(client, options) { @@ -118,6 +119,11 @@ module.exports = class extends Listener { setInterval(() => send(), ms('12h')); } + if (client.config.updates) { + checkForUpdates(client); + setInterval(() => checkForUpdates(client), ms('1w')); + } + setInterval(() => { // TODO: check lastMessageAt and set stale // this.$stale.set(ticket.id, { diff --git a/user/example.config.yml b/user/example.config.yml index c8a2297..d60910f 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -33,3 +33,4 @@ presence: stats: true templates: transcript: transcript.md +updates: true From 7e4039dcce41a9705ca84f184adc14768568bbd8 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Mar 2023 14:45:26 +0000 Subject: [PATCH 342/409] fix(schema): feedback cascading deletion --- .../migration.sql | 4 +-- db/mysql/schema.prisma | 4 +-- .../migration.sql | 4 +-- db/postgresql/schema.prisma | 4 +-- .../migration.sql | 4 +-- db/sqlite/schema.prisma | 4 +-- package.json | 6 ++-- pnpm-lock.yaml | 30 +++++++++---------- 8 files changed, 30 insertions(+), 30 deletions(-) rename db/mysql/migrations/{20230218204428_4_0_0 => 20230309144248_4_0_0}/migration.sql (98%) rename db/postgresql/migrations/{20230218204642_4_0_0 => 20230309132703_4_0_0}/migration.sql (98%) rename db/sqlite/migrations/{20230218204317_4_0_0 => 20230309142817_4_0_0}/migration.sql (98%) diff --git a/db/mysql/migrations/20230218204428_4_0_0/migration.sql b/db/mysql/migrations/20230309144248_4_0_0/migration.sql similarity index 98% rename from db/mysql/migrations/20230218204428_4_0_0/migration.sql rename to db/mysql/migrations/20230309144248_4_0_0/migration.sql index 29bd31e..498761e 100644 --- a/db/mysql/migrations/20230218204428_4_0_0/migration.sql +++ b/db/mysql/migrations/20230309144248_4_0_0/migration.sql @@ -218,10 +218,10 @@ ALTER TABLE `categories` ADD CONSTRAINT `categories_guildId_fkey` FOREIGN KEY (` ALTER TABLE `feedback` ADD CONSTRAINT `feedback_guildId_fkey` FOREIGN KEY (`guildId`) REFERENCES `guilds`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE `feedback` ADD CONSTRAINT `feedback_ticketId_fkey` FOREIGN KEY (`ticketId`) REFERENCES `tickets`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE `feedback` ADD CONSTRAINT `feedback_ticketId_fkey` FOREIGN KEY (`ticketId`) REFERENCES `tickets`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE `feedback` ADD CONSTRAINT `feedback_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE `feedback` ADD CONSTRAINT `feedback_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE `questions` ADD CONSTRAINT `questions_categoryId_fkey` FOREIGN KEY (`categoryId`) REFERENCES `categories`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/db/mysql/schema.prisma b/db/mysql/schema.prisma index 9dcfee6..305dffb 100644 --- a/db/mysql/schema.prisma +++ b/db/mysql/schema.prisma @@ -103,9 +103,9 @@ model Feedback { guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) rating Int - ticket Ticket @relation(fields: [ticketId], references: [id]) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @id @db.VarChar(19) - user User? @relation(fields: [userId], references: [id]) + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) userId String? @db.VarChar(19) @@map("feedback") diff --git a/db/postgresql/migrations/20230218204642_4_0_0/migration.sql b/db/postgresql/migrations/20230309132703_4_0_0/migration.sql similarity index 98% rename from db/postgresql/migrations/20230218204642_4_0_0/migration.sql rename to db/postgresql/migrations/20230309132703_4_0_0/migration.sql index 5abd9a7..062c17a 100644 --- a/db/postgresql/migrations/20230218204642_4_0_0/migration.sql +++ b/db/postgresql/migrations/20230309132703_4_0_0/migration.sql @@ -234,10 +234,10 @@ ALTER TABLE "categories" ADD CONSTRAINT "categories_guildId_fkey" FOREIGN KEY (" ALTER TABLE "feedback" ADD CONSTRAINT "feedback_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "feedback" ADD CONSTRAINT "feedback_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets"("id") ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "feedback" ADD CONSTRAINT "feedback_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "feedback" ADD CONSTRAINT "feedback_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "questions" ADD CONSTRAINT "questions_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "categories"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma index b20b0ac..7e7841b 100644 --- a/db/postgresql/schema.prisma +++ b/db/postgresql/schema.prisma @@ -102,9 +102,9 @@ model Feedback { guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String @db.VarChar(19) rating Int - ticket Ticket @relation(fields: [ticketId], references: [id]) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @id @db.VarChar(19) - user User? @relation(fields: [userId], references: [id]) + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) userId String? @db.VarChar(19) @@map("feedback") diff --git a/db/sqlite/migrations/20230218204317_4_0_0/migration.sql b/db/sqlite/migrations/20230309142817_4_0_0/migration.sql similarity index 98% rename from db/sqlite/migrations/20230218204317_4_0_0/migration.sql rename to db/sqlite/migrations/20230309142817_4_0_0/migration.sql index 308773b..29d423a 100644 --- a/db/sqlite/migrations/20230218204317_4_0_0/migration.sql +++ b/db/sqlite/migrations/20230309142817_4_0_0/migration.sql @@ -87,8 +87,8 @@ CREATE TABLE "feedback" ( "ticketId" TEXT NOT NULL PRIMARY KEY, "userId" TEXT, CONSTRAINT "feedback_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "feedback_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, - CONSTRAINT "feedback_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE + CONSTRAINT "feedback_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "tickets" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "feedback_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE ); -- CreateTable diff --git a/db/sqlite/schema.prisma b/db/sqlite/schema.prisma index ebad7ce..7c080b0 100644 --- a/db/sqlite/schema.prisma +++ b/db/sqlite/schema.prisma @@ -102,9 +102,9 @@ model Feedback { guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade) guildId String rating Int - ticket Ticket @relation(fields: [ticketId], references: [id]) + ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade) ticketId String @id - user User? @relation(fields: [userId], references: [id]) + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) userId String? @@map("feedback") diff --git a/package.json b/package.json index aeb15dd..bc61256 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.5", + "version": "4.0.0-beta.6", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", @@ -49,7 +49,7 @@ "@fastify/http-proxy": "^8.4.0", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", - "@prisma/client": "^4.10.1", + "@prisma/client": "^4.11.0", "boxen": "^7.0.2", "cryptr": "^6.1.0", "discord.js": "^14.7.1", @@ -66,7 +66,7 @@ "node-dir": "^0.1.17", "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", - "prisma": "^4.10.1", + "prisma": "^4.11.0", "semver": "^7.3.8", "terminal-link": "^2.1.1", "yaml": "^1.10.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb6f6bc..2847364 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ specifiers: '@fastify/http-proxy': ^8.4.0 '@fastify/jwt': ^5.0.1 '@fastify/oauth2': ^5.1.0 - '@prisma/client': ^4.10.1 + '@prisma/client': ^4.11.0 all-contributors-cli: ^6.24.0 boxen: ^7.0.2 bufferutil: ^4.0.7 @@ -38,7 +38,7 @@ specifiers: node-emoji: ^1.11.0 nodemon: ^2.0.20 object-diffy: ^1.0.4 - prisma: ^4.10.1 + prisma: ^4.11.0 semver: ^7.3.8 terminal-link: ^2.1.1 utf-8-validate: ^5.0.10 @@ -55,7 +55,7 @@ dependencies: '@fastify/http-proxy': 8.4.0_3cxu5zja4e2r5wmvge7mdcljwq '@fastify/jwt': 5.0.1 '@fastify/oauth2': 5.1.0 - '@prisma/client': 4.10.1_prisma@4.10.1 + '@prisma/client': 4.11.0_prisma@4.11.0 boxen: 7.0.2 cryptr: 6.1.0 discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq @@ -72,7 +72,7 @@ dependencies: node-dir: 0.1.17 node-emoji: 1.11.0 object-diffy: 1.0.4 - prisma: 4.10.1 + prisma: 4.11.0 semver: 7.3.8 terminal-link: 2.1.1 yaml: 1.10.2 @@ -600,8 +600,8 @@ packages: fastq: 1.15.0 dev: true - /@prisma/client/4.10.1_prisma@4.10.1: - resolution: {integrity: sha512-VonXLJZybdt8e5XZH5vnIGCRNnIh6OMX1FS3H/yzMGLT3STj5TJ/OkMcednrvELgk8PK89Vo3aSh51MWNO0axA==} + /@prisma/client/4.11.0_prisma@4.11.0: + resolution: {integrity: sha512-0INHYkQIqgAjrt7NzhYpeDQi8x3Nvylc2uDngKyFDDj1tTRQ4uV1HnVmd1sQEraeVAN63SOK0dgCKQHlvjL0KA==} engines: {node: '>=14.17'} requiresBuild: true peerDependencies: @@ -610,16 +610,16 @@ packages: prisma: optional: true dependencies: - '@prisma/engines-version': 4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19 - prisma: 4.10.1 + '@prisma/engines-version': 4.11.0-57.8fde8fef4033376662cad983758335009d522acb + prisma: 4.11.0 dev: false - /@prisma/engines-version/4.10.1-2.aead147aa326ccb985dcfed5b065b4fdabd44b19: - resolution: {integrity: sha512-tsjTho7laDhf9EJ9EnDxAPEf7yrigSMDhniXeU4YoWc7azHAs4GPxRi2P9LTFonmHkJLMOLjR77J1oIP8Ife1w==} + /@prisma/engines-version/4.11.0-57.8fde8fef4033376662cad983758335009d522acb: + resolution: {integrity: sha512-3Vd8Qq06d5xD8Ch5WauWcUUrsVPdMC6Ge8ILji8RFfyhUpqon6qSyGM0apvr1O8n8qH8cKkEFqRPsYjuz5r83g==} dev: false - /@prisma/engines/4.10.1: - resolution: {integrity: sha512-B3tcTxjx196nuAu1GOTKO9cGPUgTFHYRdkPkTS4m5ptb2cejyBlH9X7GOfSt3xlI7p4zAJDshJP4JJivCg9ouA==} + /@prisma/engines/4.11.0: + resolution: {integrity: sha512-0AEBi2HXGV02cf6ASsBPhfsVIbVSDC9nbQed4iiY5eHttW9ZtMxHThuKZE1pnESbr8HRdgmFSa/Kn4OSNYuibg==} requiresBuild: true dev: false @@ -3240,13 +3240,13 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prisma/4.10.1: - resolution: {integrity: sha512-0jDxgg+DruB1kHVNlcspXQB9au62IFfVg9drkhzXudszHNUAQn0lVuu+T8np0uC2z1nKD5S3qPeCyR8u5YFLnA==} + /prisma/4.11.0: + resolution: {integrity: sha512-4zZmBXssPUEiX+GeL0MUq/Yyie4ltiKmGu7jCJFnYMamNrrulTBc+D+QwAQSJ01tyzeGHlD13kOnqPwRipnlNw==} engines: {node: '>=14.17'} hasBin: true requiresBuild: true dependencies: - '@prisma/engines': 4.10.1 + '@prisma/engines': 4.11.0 dev: false /process-nextick-args/2.0.1: From 25d7cdaee8c1a3dda4d5ef532fcd76723152faaf Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 9 Mar 2023 18:48:52 +0000 Subject: [PATCH 343/409] fix: catch errors --- src/menus/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menus/create.js b/src/menus/create.js index 47943f4..ea102be 100644 --- a/src/menus/create.js +++ b/src/menus/create.js @@ -14,7 +14,7 @@ module.exports = class CreateMenu extends Menu { * @param {import("discord.js").SelectMenuInteraction} interaction */ async run(id, interaction) { - if (!interaction.message.flags.has(MessageFlags.Ephemeral)) interaction.message.edit({ components: interaction.message.components }); // reset the select menu (minor client-side UI issue) + if (!interaction.message.flags.has(MessageFlags.Ephemeral)) interaction.message.edit({ components: interaction.message.components }).catch(() => { }); // reset the select menu (to fix a UI issue) await this.client.tickets.create({ ...id, categoryId: interaction.values[0], From ea9d3e4e3380ca8bffa1f07ac2b79851af35a23b Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Mar 2023 00:24:33 +0000 Subject: [PATCH 344/409] feat(api): show guilds that the bot isn't in --- package.json | 10 +-- pnpm-lock.yaml | 112 ++++++++++++++------------- src/http.js | 2 +- src/routes/api/admin/guilds/index.js | 23 +++--- src/routes/auth/callback.js | 7 +- src/routes/auth/logout.js | 2 +- 6 files changed, 82 insertions(+), 74 deletions(-) diff --git a/package.json b/package.json index bc61256..74eeaff 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "node": ">=18" }, "dependencies": { - "@discord-tickets/settings": "^1.4.2", + "@discord-tickets/settings": "^1.5.0", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", @@ -51,11 +51,11 @@ "@fastify/oauth2": "^5.1.0", "@prisma/client": "^4.11.0", "boxen": "^7.0.2", - "cryptr": "^6.1.0", + "cryptr": "^6.2.0", "discord.js": "^14.7.1", "dotenv": "^16.0.3", "express": "^4.18.2", - "fastify": "^4.13.0", + "fastify": "^4.14.1", "figlet": "^1.5.2", "fs-extra": "^10.1.0", "keyv": "^4.5.2", @@ -76,11 +76,11 @@ "@commitlint/config-conventional": "^17.4.4", "all-contributors-cli": "^6.24.0", "conventional-changelog-cli": "^2.2.2", - "eslint": "^8.34.0", + "eslint": "^8.35.0", "eslint-plugin-unused-imports": "^2.0.0", "husky": "^8.0.3", "lint-staged": "^13.1.2", - "nodemon": "^2.0.20" + "nodemon": "^2.0.21" }, "optionalDependencies": { "bufferutil": "^4.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2847364..bc5f224 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,7 +3,7 @@ lockfileVersion: 5.4 specifiers: '@commitlint/cli': ^17.4.4 '@commitlint/config-conventional': ^17.4.4 - '@discord-tickets/settings': ^1.4.2 + '@discord-tickets/settings': ^1.5.0 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -17,14 +17,14 @@ specifiers: boxen: ^7.0.2 bufferutil: ^4.0.7 conventional-changelog-cli: ^2.2.2 - cryptr: ^6.1.0 + cryptr: ^6.2.0 discord.js: ^14.7.1 dotenv: ^16.0.3 erlpack: github:discord/erlpack - eslint: ^8.34.0 + eslint: ^8.35.0 eslint-plugin-unused-imports: ^2.0.0 express: ^4.18.2 - fastify: ^4.13.0 + fastify: ^4.14.1 figlet: ^1.5.2 fs-extra: ^10.1.0 husky: ^8.0.3 @@ -36,7 +36,7 @@ specifiers: mustache: ^4.2.0 node-dir: ^0.1.17 node-emoji: ^1.11.0 - nodemon: ^2.0.20 + nodemon: ^2.0.21 object-diffy: ^1.0.4 prisma: ^4.11.0 semver: ^7.3.8 @@ -46,7 +46,7 @@ specifiers: zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 1.4.2_svelte@3.55.1 + '@discord-tickets/settings': 1.5.0_svelte@3.55.1 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -57,11 +57,11 @@ dependencies: '@fastify/oauth2': 5.1.0 '@prisma/client': 4.11.0_prisma@4.11.0 boxen: 7.0.2 - cryptr: 6.1.0 + cryptr: 6.2.0 discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq dotenv: 16.0.3 express: 4.18.2 - fastify: 4.13.0 + fastify: 4.14.1 figlet: 1.5.2 fs-extra: 10.1.0 keyv: 4.5.2 @@ -88,11 +88,11 @@ devDependencies: '@commitlint/config-conventional': 17.4.4 all-contributors-cli: 6.24.0 conventional-changelog-cli: 2.2.2 - eslint: 8.34.0 - eslint-plugin-unused-imports: 2.0.0_eslint@8.34.0 + eslint: 8.35.0 + eslint-plugin-unused-imports: 2.0.0_eslint@8.35.0 husky: 8.0.3 lint-staged: 13.1.2 - nodemon: 2.0.20 + nodemon: 2.0.21 packages: @@ -210,15 +210,15 @@ packages: '@commitlint/execute-rule': 17.4.0 '@commitlint/resolve-extends': 17.4.4 '@commitlint/types': 17.4.4 - '@types/node': 18.14.1 + '@types/node': 18.15.0 chalk: 4.1.2 - cosmiconfig: 8.0.0 - cosmiconfig-typescript-loader: 4.3.0_s4dpre5ezutgdzsn47klmddvia + cosmiconfig: 8.1.0 + cosmiconfig-typescript-loader: 4.3.0_lmcvhluzh3a7wwwkanu7ndhele lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1_uayvamxqnl5yeiojjysxwopmsy + ts-node: 10.9.1_lwgqdwokjtwlohdqtbb6s252kq typescript: 4.9.5 transitivePeerDependencies: - '@swc/core' @@ -299,8 +299,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@discord-tickets/settings/1.4.2_svelte@3.55.1: - resolution: {integrity: sha512-C8/aS9XTDUcnaX8wbFFUxH32M9SELcZ/BbsARTnXwFS3fGFs04mMI7L05Br0VHMYbKGmIykBMNZAGGco6Ka48A==} + /@discord-tickets/settings/1.5.0_svelte@3.55.1: + resolution: {integrity: sha512-zsP0FjbrHgs6lpXYRAuuPemqWog385rC27v6Rh0zZd8S60yH8ZQ5u/E8ZIR6Cx8c9OJwStzJ6uEZCfTy69ayGw==} dependencies: '@fortawesome/fontawesome-free': 6.3.0 '@skyra/discord-components-core': 3.6.0 @@ -373,8 +373,8 @@ packages: resolution: {integrity: sha512-nMQdHrGgpw+vNL5DbivELW2K3KAUGaMvTjjmFsEPf8mUW8+LAgRjvfFn2gkJq1mnlD6HoqUaHqEL4YpWr2T5MA==} dev: false - /@eslint/eslintrc/1.4.1: - resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} + /@eslint/eslintrc/2.0.0: + resolution: {integrity: sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 @@ -390,6 +390,11 @@ packages: - supports-color dev: true + /@eslint/js/8.35.0: + resolution: {integrity: sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + /@fastify/ajv-compiler/3.5.0: resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==} dependencies: @@ -401,7 +406,7 @@ packages: /@fastify/cookie/6.0.0: resolution: {integrity: sha512-Luy3Po3dOJmqAuPCiPcWsX0tV5+C3AOnULSdlsGjNGOvyE7jqzysp8kT9ICfsUvove+TeUMgTWl1y9XS3ZPPMg==} dependencies: - cookie-signature: 1.2.0 + cookie-signature: 1.2.1 fastify-plugin: 3.0.1 dev: false @@ -680,8 +685,8 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - /@types/node/18.14.1: - resolution: {integrity: sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ==} + /@types/node/18.15.0: + resolution: {integrity: sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==} /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -690,7 +695,7 @@ packages: /@types/ws/8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: - '@types/node': 18.14.1 + '@types/node': 18.15.0 dev: false /JSONStream/1.3.5: @@ -1366,8 +1371,8 @@ packages: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} dev: false - /cookie-signature/1.2.0: - resolution: {integrity: sha512-R0BOPfLGTitaKhgKROKZQN6iyq2iDQcH1DOF8nJoaWapguX5bC2w+Q/I9NmmM5lfcvEarnLZr+cCvmEYYSXvYA==} + /cookie-signature/1.2.1: + resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==} engines: {node: '>=6.6.0'} dev: false @@ -1380,7 +1385,7 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /cosmiconfig-typescript-loader/4.3.0_s4dpre5ezutgdzsn47klmddvia: + /cosmiconfig-typescript-loader/4.3.0_lmcvhluzh3a7wwwkanu7ndhele: resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -1389,14 +1394,14 @@ packages: ts-node: '>=10' typescript: '>=3' dependencies: - '@types/node': 18.14.1 - cosmiconfig: 8.0.0 - ts-node: 10.9.1_uayvamxqnl5yeiojjysxwopmsy + '@types/node': 18.15.0 + cosmiconfig: 8.1.0 + ts-node: 10.9.1_lwgqdwokjtwlohdqtbb6s252kq typescript: 4.9.5 dev: true - /cosmiconfig/8.0.0: - resolution: {integrity: sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==} + /cosmiconfig/8.1.0: + resolution: {integrity: sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg==} engines: {node: '>=14'} dependencies: import-fresh: 3.3.0 @@ -1418,8 +1423,8 @@ packages: which: 2.0.2 dev: true - /cryptr/6.1.0: - resolution: {integrity: sha512-vJBrDcKAZ/OpMW4EptbGTD2EfNEywh6QtsPDX5kd2u1MNBmTLGC3IlOoRh8Ov7dmZhFfRjUJ07u4eCoJ8+oM9A==} + /cryptr/6.2.0: + resolution: {integrity: sha512-jYi8SxvOFebTT7EYOABiPpHKY6lwWaP9IVcvT/aIVJUVoFdzTgi0ySPCL78q1ig8w2kwfXFCZACXoCXaye57aw==} dev: false /dargs/7.0.0: @@ -1617,7 +1622,7 @@ packages: engines: {node: '>=10'} dev: true - /eslint-plugin-unused-imports/2.0.0_eslint@8.34.0: + /eslint-plugin-unused-imports/2.0.0_eslint@8.35.0: resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1627,7 +1632,7 @@ packages: '@typescript-eslint/eslint-plugin': optional: true dependencies: - eslint: 8.34.0 + eslint: 8.35.0 eslint-rule-composer: 0.3.0 dev: true @@ -1644,13 +1649,13 @@ packages: estraverse: 5.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.34.0: + /eslint-utils/3.0.0_eslint@8.35.0: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' dependencies: - eslint: 8.34.0 + eslint: 8.35.0 eslint-visitor-keys: 2.1.0 dev: true @@ -1664,12 +1669,13 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.34.0: - resolution: {integrity: sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==} + /eslint/8.35.0: + resolution: {integrity: sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint/eslintrc': 1.4.1 + '@eslint/eslintrc': 2.0.0 + '@eslint/js': 8.35.0 '@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -1680,10 +1686,10 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.34.0 + eslint-utils: 3.0.0_eslint@8.35.0 eslint-visitor-keys: 3.3.0 espree: 9.4.1 - esquery: 1.4.2 + esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 @@ -1721,8 +1727,8 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /esquery/1.4.2: - resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==} + /esquery/1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 @@ -1907,8 +1913,8 @@ packages: resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==} dev: false - /fastify/4.13.0: - resolution: {integrity: sha512-p9ibdFWH3pZ7KPgmfHPKGUy2W4EWU2TEpwlcu58w4CwGyU3ARFfh2kwq6zpZ5W2ZGVbufi4tZbqHIHAlX/9Z/A==} + /fastify/4.14.1: + resolution: {integrity: sha512-yjrDeXe77j9gRlSV2UJry8mcFWbD0NQ5JYjnPi4tkFjHZVaG3/BD5wxOmRzGnHPC0YvaBJ0XWrIfFPl2IHRa1w==} dependencies: '@fastify/ajv-compiler': 3.5.0 '@fastify/error': 3.2.0 @@ -2902,8 +2908,8 @@ packages: hasBin: true dev: false - /nodemon/2.0.20: - resolution: {integrity: sha512-Km2mWHKKY5GzRg6i1j5OxOHQtuvVsgskLfigG25yTtbyfRGn/GNvIbRyOf1PSCKJ2aT/58TiuUsuOU5UToVViw==} + /nodemon/2.0.21: + resolution: {integrity: sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A==} engines: {node: '>=8.10.0'} hasBin: true requiresBuild: true @@ -3699,8 +3705,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /spdx-correct/3.1.1: - resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + /spdx-correct/3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: spdx-expression-parse: 3.0.1 spdx-license-ids: 3.0.12 @@ -3981,7 +3987,7 @@ packages: resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==} dev: false - /ts-node/10.9.1_uayvamxqnl5yeiojjysxwopmsy: + /ts-node/10.9.1_lwgqdwokjtwlohdqtbb6s252kq: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -4000,7 +4006,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 18.14.1 + '@types/node': 18.15.0 acorn: 8.8.2 acorn-walk: 8.2.0 arg: 4.1.3 @@ -4131,7 +4137,7 @@ packages: /validate-npm-package-license/3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: - spdx-correct: 3.1.1 + spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 dev: true diff --git a/src/http.js b/src/http.js index ad68d06..fb73b15 100644 --- a/src/http.js +++ b/src/http.js @@ -27,7 +27,7 @@ module.exports = async client => { }, }, name: 'discord', - scope: ['identify'], + scope: ['applications.commands.permissions.update', 'guilds', 'identify'], startRedirectPath: '/auth/login', }); diff --git a/src/routes/api/admin/guilds/index.js b/src/routes/api/admin/guilds/index.js index add69fc..1ad7a57 100644 --- a/src/routes/api/admin/guilds/index.js +++ b/src/routes/api/admin/guilds/index.js @@ -3,18 +3,17 @@ const { PermissionsBitField } = require('discord.js'); module.exports.get = fastify => ({ handler: async (req, res) => { const { client } = res.context.config; - const guilds = client.guilds.cache - .filter(async guild => { - const member = await guild.members.fetch(req.user.payload.id); - if (!member) return false; - return member.permissions.has(PermissionsBitField.Flags.ManageGuild); - }) - .map(guild => ({ - id: guild.id, - logo: guild.iconURL(), - name: guild.name, - })); - res.send(guilds); + const guilds = await (await fetch('https://discordapp.com/api/users/@me/guilds', { headers: { 'Authorization': `Bearer ${req.user.payload.access_token}` } })).json(); + res.send( + guilds + .filter(guild => guild.owner || new PermissionsBitField(guild.permissions.toString()).has(PermissionsBitField.Flags.ManageGuild)) + .map(guild => ({ + added: client.guilds.cache.has(guild.id), + id: guild.id, + logo: `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp`, + name: guild.name, + })), + ); }, onRequest: [fastify.authenticate], }); \ No newline at end of file diff --git a/src/routes/auth/callback.js b/src/routes/auth/callback.js index 447773b..c319b37 100644 --- a/src/routes/auth/callback.js +++ b/src/routes/auth/callback.js @@ -1,16 +1,19 @@ const { domain } = require('../../lib/http'); module.exports.get = () => ({ - handler: async function (req, res) { // must NOT use arrow function syntax + handler: async function (req, res) { // MUST NOT use arrow function syntax const { - access_token, expires_in, + access_token, + expires_in, } = await this.discord.getAccessTokenFromAuthorizationCodeFlow(req); const user = await (await fetch('https://discordapp.com/api/users/@me', { headers: { 'Authorization': `Bearer ${access_token}` } })).json(); const payload = { + access_token, avatar: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.webp`, discriminator: user.discriminator, expiresAt: Date.now() + (expires_in * 1000), id: user.id, + locale: user.locale, username: user.username, }; diff --git a/src/routes/auth/logout.js b/src/routes/auth/logout.js index 9a0a6c9..4e5910c 100644 --- a/src/routes/auth/logout.js +++ b/src/routes/auth/logout.js @@ -1,5 +1,5 @@ module.exports.get = () => ({ - handler: async function (req, res) { // must NOT use arrow function syntax + handler: async function (req, res) { res .clearCookie('token', '/') .send('Logged out.'); From b8c2a7cc1331f9707f75fe67167549fe928fe21b Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Mar 2023 00:25:07 +0000 Subject: [PATCH 345/409] fix(i18n): broken translations --- src/i18n/cs.yml | 2 +- src/i18n/de.yml | 28 ++++++++++++++-------------- src/i18n/en-GB.yml | 4 ++-- src/i18n/hu.yml | 2 +- src/i18n/it.yml | 2 +- src/stdin/commands.js | 7 ++++++- 6 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/i18n/cs.yml b/src/i18n/cs.yml index 4e3dd15..9eaa289 100644 --- a/src/i18n/cs.yml +++ b/src/i18n/cs.yml @@ -95,7 +95,7 @@ commands: title: ❓Jste si jist? description: "Chystáte se uzavřít **{count}** ticketů, které jsou neaktivní\ \ déle než `{time}`:\n{tickets}\n" - name: uzavřít silou + name: uzavřít-silou description: Násilně uzavřít tiketu no_tickets: title: ❌Žádné tikety diff --git a/src/i18n/de.yml b/src/i18n/de.yml index adcbe91..709f893 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -50,10 +50,10 @@ commands: description: Nur Teammitglieder können Benutzer zu Tickets anderer hinzufügen. options: member: - name: Mitglied + name: mitglied description: Das Mitglied, das dem Ticket hinzugefügt werden soll ticket: - name: Ticket + name: ticket description: Das Ticket, zu dem das Mitglied hinzugefügt werden soll success: description: '{member} wurde zu {ticket} hinzugefügt.' @@ -78,14 +78,14 @@ commands: force-close: options: reason: - name: Grund + name: grund description: Der Grund für das Schließen des/der Tickets category: - name: Kategorie + name: kategorie description: Schließe alle Tickets einer Kategorie (kann mit `time` benutzt werden) time: - name: Zeit + name: zeit description: Alle Tickets schließen, die für die angegebene Zeit inaktiv waren (mit `Kategorie` verwendbar) ticket: @@ -110,7 +110,7 @@ commands: description: '`{input}` ist kein gültiges Zeitformat.' options: reason: - name: Grund + name: grund description: Der Grund für das Schließen des Tickets description: Beantragen, dass ein Ticket geschlossen wird name: schließen @@ -121,13 +121,13 @@ commands: options: category: description: Die Kategorie in welche das Ticket verschoben werden soll - name: Kategorie + name: kategorie new: name: neu description: Neues Ticket erstellen options: references: - name: Verweise + name: verweise description: Die Nummer eines zugehörigen Tickets claim: description: Ticket übernehmen @@ -136,7 +136,7 @@ commands: options: priority: description: Die Priorität des Tickets - name: Priorität + name: priorität choices: HIGH: 🔴 Hoch LOW: 🟢 Niedrig @@ -153,7 +153,7 @@ commands: options: member: description: Das Mitglied, dessen Tickets aufgelistet werden sollen - name: Mitglied + name: mitglied name: tickets response: title: @@ -176,10 +176,10 @@ commands: title: ❌ Fehler options: member: - name: Mitglied + name: mitglied description: Das Mitglied, das aus dem Ticket entfernt werden soll ticket: - name: Ticket + name: ticket description: Das Ticket, aus dem das Mitglied entfernt werden soll success: title: ✅ Entfernt @@ -203,7 +203,7 @@ commands: transferred_from: 📨 {user} hat dieses Ticket von {from} auf {to} übertragen. options: member: - name: Mitglied + name: mitglied description: Das Mitglied, an das die Eigentümerschaft übertragen werden soll transferred: 📨 {user} hat dieses Ticket an {to} übertragen. @@ -213,7 +213,7 @@ commands: for: name: für tag: - name: Tag + name: tag description: Der Name des zu verwendenden Tags name: tag description: Verwende einen Tag diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 22322be..72e59b8 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -29,7 +29,7 @@ buttons: commands: message: create: - name: Create a ticket from message + name: Create ticket from message pin: name: Pin message not_pinnable: @@ -227,7 +227,7 @@ commands: transferred_from: 📨 {user} has transferred this ticket from {from} to {to}. user: create: - name: Create a ticket for user + name: Create ticket for user dm: closed: archived: Use the `/transcript` command in **{guild}** to view the archived messages. diff --git a/src/i18n/hu.yml b/src/i18n/hu.yml index d7de3c4..49e6b84 100644 --- a/src/i18n/hu.yml +++ b/src/i18n/hu.yml @@ -262,7 +262,7 @@ commands: name: hibajegy user: create: - name: Hibajegy létrehozása a felhasználó számára + name: Create ticket for user dm: closed: title: A hibajegyed bezárásra került diff --git a/src/i18n/it.yml b/src/i18n/it.yml index 89e7d25..bdf7076 100644 --- a/src/i18n/it.yml +++ b/src/i18n/it.yml @@ -43,7 +43,7 @@ commands: slash: add: description: Aggiungi un utente ad un ticket - name: Aggiungi + name: aggiungi not_staff: description: Solo i membri dello staff possono aggiungere utenti ai ticket altrui. diff --git a/src/stdin/commands.js b/src/stdin/commands.js index 7edb3fa..c408ffb 100644 --- a/src/stdin/commands.js +++ b/src/stdin/commands.js @@ -1,4 +1,5 @@ const { StdinCommand } = require('@eartharoid/dbf'); +const { inspect } = require('util'); module.exports = class Commands extends StdinCommand { constructor(client, options) { @@ -13,7 +14,11 @@ module.exports = class Commands extends StdinCommand { case 'publish': { this.client.commands.publish() .then(commands => this.client.log.success('Published %d commands', commands?.size)) - .catch(this.client.log.error); + .catch(error => { + this.client.log.warn('Failed to publish commands'); + this.client.log.error(error); + this.client.log.error(inspect(error.rawError?.errors, { depth: Infinity })); + }); break; } default: { From b14f057dd080bd8ef87d04f100b6e36ca2246ad5 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Mar 2023 23:28:46 +0000 Subject: [PATCH 346/409] feat: update command permissions (closes #392) --- .../[guild]/categories/[category]/index.js | 33 +++++++++++++++++ .../admin/guilds/[guild]/categories/index.js | 36 ++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js index 2aeae9b..9900ea1 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js @@ -1,6 +1,7 @@ const { logAdminEvent } = require('../../../../../../../lib/logging'); const { updateStaffRoles } = require('../../../../../../../lib/users'); const { randomUUID } = require('crypto'); +const { ApplicationCommandPermissionType } = require('discord.js'); module.exports.delete = fastify => ({ handler: async (req, res) => { @@ -71,6 +72,7 @@ module.exports.patch = fastify => ({ const client = res.context.config.client; const guildId = req.params.guild; const categoryId = Number(req.params.category); + /** @type {import('discord.js').Guild} */ const guild = client.guilds.cache.get(req.params.guild); const data = req.body; @@ -144,6 +146,37 @@ module.exports.patch = fastify => ({ await client.tickets.getCategory(categoryId, true); await updateStaffRoles(guild); + if (req.user.payload.accessToken && JSON.stringify(category.staffRoles) !== JSON.stringify(original.staffRoles)) { + Promise.all([ + 'Create ticket for user', + 'claim', + 'force-close', + 'move', + 'priority', + 'release', + ].map(name => + client.application.commands.permissions.set({ + command: client.application.commands.cache.find(cmd => cmd.name === name), + guild, + permissions: [ + { + id: guild.id, // @everyone + permission: false, + type: ApplicationCommandPermissionType.Role, + }, + ...category.staffRoles.map(id => ({ + id, + permission: true, + type: ApplicationCommandPermissionType.Role, + })), + ], + token: req.user.payload.accessToken, + }), + )) + .then(() => client.log.success('Updated application command permissions in "%s"', guild.name)) + .catch(error => client.log.error(error)); + } + logAdminEvent(client, { action: 'update', diff: { diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index 318914f..08f896a 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -1,7 +1,10 @@ const { logAdminEvent } = require('../../../../../../lib/logging'); const { updateStaffRoles } = require('../../../../../../lib/users'); const emoji = require('node-emoji'); -const { ChannelType: { GuildCategory } } = require('discord.js'); +const { + ApplicationCommandPermissionType, + ChannelType: { GuildCategory }, +} = require('discord.js'); const ms = require('ms'); module.exports.get = fastify => ({ @@ -98,6 +101,37 @@ module.exports.post = fastify => ({ await client.tickets.getCategory(category.id, true); await updateStaffRoles(guild); + if (req.user.payload.accessToken) { + Promise.all([ + 'Create ticket for user', + 'claim', + 'force-close', + 'move', + 'priority', + 'release', + ].map(name => + client.application.commands.permissions.set({ + command: client.application.commands.cache.find(cmd => cmd.name === name), + guild, + permissions: [ + { + id: guild.id, // @everyone + permission: false, + type: ApplicationCommandPermissionType.Role, + }, + ...category.staffRoles.map(id => ({ + id, + permission: true, + type: ApplicationCommandPermissionType.Role, + })), + ], + token: req.user.payload.accessToken, + }), + )) + .then(() => client.log.success('Updated application command permissions in "%s"', guild.name)) + .catch(error => client.log.error(error)); + } + logAdminEvent(client, { action: 'create', guildId: guild.id, From daadb5fe85d32cd86f46fd915073ba75138c9401 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Mar 2023 23:37:40 +0000 Subject: [PATCH 347/409] fix: check roles for staff-only commands --- src/commands/slash/move.js | 18 +++++++++++++++++- src/commands/slash/priority.js | 15 +++++++++++++++ src/i18n/en-GB.yml | 11 ++++++++++- src/lib/tickets/manager.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/commands/slash/move.js b/src/commands/slash/move.js index 4083ff6..25c582c 100644 --- a/src/commands/slash/move.js +++ b/src/commands/slash/move.js @@ -1,6 +1,7 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); const ExtendedEmbedBuilder = require('../../lib/embed'); +const { isStaff } = require('../../lib/users'); module.exports = class MoveSlashCommand extends SlashCommand { constructor(client, options) { @@ -62,10 +63,25 @@ module.exports = class MoveSlashCommand extends SlashCommand { }); } + const getMessage = client.i18n.getLocale(ticket.guild.locale); + + if (!(await isStaff(interaction.guild, interaction.user.id))) { // if user is not staff + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setTitle(getMessage('commands.slash.move.not_staff.title')) + .setDescription(getMessage('commands.slash.move.not_staff.description')), + ], + }); + } + const creator = await interaction.guild.members.fetch(ticket.createdById); const newCategory = await client.prisma.category.findUnique({ where: { id: interaction.options.getInteger('category', true) } }); const discordCategory = await interaction.guild.channels.fetch(newCategory.discordCategory); - const getMessage = client.i18n.getLocale(ticket.guild.locale); if (discordCategory.children.cache.size === 50) { return await interaction.editReply({ diff --git a/src/commands/slash/priority.js b/src/commands/slash/priority.js index b46c0c8..2a44394 100644 --- a/src/commands/slash/priority.js +++ b/src/commands/slash/priority.js @@ -2,6 +2,7 @@ const { SlashCommand } = require('@eartharoid/dbf'); const { ApplicationCommandOptionType } = require('discord.js'); const ExtendedEmbedBuilder = require('../../lib/embed'); const { logTicketEvent } = require('../../lib/logging'); +const { isStaff } = require('../../lib/users'); module.exports = class PrioritySlashCommand extends SlashCommand { constructor(client, options) { @@ -86,6 +87,20 @@ module.exports = class PrioritySlashCommand extends SlashCommand { }); } + if (!(await isStaff(interaction.guild, interaction.user.id))) { // if user is not staff + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setTitle(getMessage('commands.slash.move.not_staff.title')) + .setDescription(getMessage('commands.slash.move.not_staff.description')), + ], + }); + } + const priority = interaction.options.getString('priority', true); let name = interaction.channel.name; if (ticket.priority) name = name.replace(this.getEmoji(ticket.priority), this.getEmoji(priority)); diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 72e59b8..26e09d8 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -64,6 +64,9 @@ commands: claim: description: Claim a ticket name: claim + not_staff: + description: Only staff members can claim tickets. + title: ❌ Error close: description: Request a ticket to be closed invalid_time: @@ -126,6 +129,9 @@ commands: description: Move a ticket to another category moved: 🗃️ {by} has moved this ticket from **{from}** to **{to}**. name: move + not_staff: + description: Only staff members can move tickets. + title: ❌ Error options: category: description: The category to move the ticket to @@ -140,6 +146,9 @@ commands: priority: description: Set the priority of a ticket name: priority + not_staff: + description: Only staff members can change the priority of tickets. + title: ❌ Error options: priority: choices: @@ -158,7 +167,7 @@ commands: description: Remove a member from a ticket name: remove not_staff: - description: Only staff members can removed members from others' tickets. + description: Only staff members can remove members from others' tickets. title: ❌ Error options: member: diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 4ab35d2..f744d54 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -698,6 +698,20 @@ module.exports = class TicketManager { }); const getMessage = this.client.i18n.getLocale(ticket.guild.locale); + if (!(await isStaff(interaction.guild, interaction.user.id))) { // if user is not staff + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setTitle(getMessage('commands.slash.claim.not_staff.title')) + .setDescription(getMessage('commands.slash.claim.not_staff.description')), + ], + }); + } + await Promise.all([ interaction.channel.permissionOverwrites.edit(interaction.user, { 'ViewChannel': true }, `Ticket claimed by ${interaction.user.tag}`), ...ticket.category.staffRoles.map(role => interaction.channel.permissionOverwrites.edit(role, { 'ViewChannel': false }, `Ticket claimed by ${interaction.user.tag}`)), @@ -784,6 +798,20 @@ module.exports = class TicketManager { }); const getMessage = this.client.i18n.getLocale(ticket.guild.locale); + if (!(await isStaff(interaction.guild, interaction.user.id))) { // if user is not staff + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: ticket.guild.footer, + }) + .setColor(ticket.guild.errorColour) + .setTitle(getMessage('commands.slash.claim.not_staff.title')) + .setDescription(getMessage('commands.slash.claim.not_staff.description')), + ], + }); + } + await Promise.all([ interaction.channel.permissionOverwrites.delete(interaction.user, `Ticket released by ${interaction.user.tag}`), ...ticket.category.staffRoles.map(role => interaction.channel.permissionOverwrites.edit(role, { 'ViewChannel': true }, `Ticket released by ${interaction.user.tag}`)), From f55ee02ce51e84f45d6100194665a360a3038349 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Mar 2023 23:42:39 +0000 Subject: [PATCH 348/409] fix: revoke token on logout --- src/routes/auth/logout.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/routes/auth/logout.js b/src/routes/auth/logout.js index 4e5910c..82677f6 100644 --- a/src/routes/auth/logout.js +++ b/src/routes/auth/logout.js @@ -1,7 +1,13 @@ -module.exports.get = () => ({ +module.exports.get = fastify => ({ handler: async function (req, res) { + await fetch('https://discord.com/api/oauth2/token/revoke', { + body: new URLSearchParams({ token: req.user.payload.accessToken }).toString(), + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + method: 'POST', + }); res .clearCookie('token', '/') - .send('Logged out.'); + .send('The token has been revoked.'); }, + onRequest: [fastify.authenticate], }); \ No newline at end of file From aeb4450a5693743d791261804289b6060c067eff Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Mar 2023 23:46:24 +0000 Subject: [PATCH 349/409] feat: oauth2 callback redirect (closes #333) --- src/http.js | 15 ++++++++++++++- src/routes/auth/callback.js | 19 +++++++++---------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/http.js b/src/http.js index fb73b15..95ad08f 100644 --- a/src/http.js +++ b/src/http.js @@ -1,6 +1,6 @@ const fastify = require('fastify')({ trustProxy: process.env.HTTP_TRUST_PROXY === 'true' }); const oauth = require('@fastify/oauth2'); -const { domain } = require('./lib/http'); +const { randomBytes } = require('crypto'); const { short } = require('leeks.js'); const { join } = require('path'); const { files } = require('node-dir'); @@ -17,8 +17,16 @@ module.exports = async client => { }); // oauth2 plugin + fastify.states = new Map(); fastify.register(oauth, { callbackUri: `${process.env.HTTP_EXTERNAL}/auth/callback`, + checkStateFunction: (state, callback) => { + if (fastify.states.has(state)) { + callback(); + return; + } + callback(new Error('Invalid state')); + }, credentials: { auth: oauth.DISCORD_CONFIGURATION, client: { @@ -26,6 +34,11 @@ module.exports = async client => { secret: process.env.DISCORD_SECRET, }, }, + generateStateFunction: req => { + const state = randomBytes(12).toString('hex'); + fastify.states.set(state, req.query.r); + return state; + }, name: 'discord', scope: ['applications.commands.permissions.update', 'guilds', 'identify'], startRedirectPath: '/auth/login', diff --git a/src/routes/auth/callback.js b/src/routes/auth/callback.js index c319b37..2d04e9e 100644 --- a/src/routes/auth/callback.js +++ b/src/routes/auth/callback.js @@ -3,15 +3,15 @@ const { domain } = require('../../lib/http'); module.exports.get = () => ({ handler: async function (req, res) { // MUST NOT use arrow function syntax const { - access_token, - expires_in, + access_token: accessToken, + expires_in: expiresIn, } = await this.discord.getAccessTokenFromAuthorizationCodeFlow(req); - const user = await (await fetch('https://discordapp.com/api/users/@me', { headers: { 'Authorization': `Bearer ${access_token}` } })).json(); + const user = await (await fetch('https://discordapp.com/api/users/@me', { headers: { 'Authorization': `Bearer ${accessToken}` } })).json(); const payload = { - access_token, + accessToken, avatar: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.webp`, discriminator: user.discriminator, - expiresAt: Date.now() + (expires_in * 1000), + expiresAt: Date.now() + (expiresIn * 1000), id: user.id, locale: user.locale, username: user.username, @@ -20,15 +20,14 @@ module.exports.get = () => ({ const token = this.jwt.sign({ payload }); res .setCookie('token', token, { - domain: domain, + domain, httpOnly: true, - maxAge: 604800, // seconds, not milliseconds + maxAge: expiresIn, path: '/', sameSite: true, secure: false, }) - // .redirect('/settings') - .type('text/html') - .send('/settings'); // temp fix: redirecting causes weird discord<->callback loop, probably caching? + .redirect(this.states.get(req.query.state) || '/'); + this.states.delete(req.query.state); }, }); \ No newline at end of file From 26329453edded63b92690a5c10e573bf241559e6 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Mar 2023 23:46:56 +0000 Subject: [PATCH 350/409] fix: cache commands at startup --- src/commands/slash/help.js | 2 +- src/listeners/client/ready.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commands/slash/help.js b/src/commands/slash/help.js index cd41fbb..2d5f13a 100644 --- a/src/commands/slash/help.js +++ b/src/commands/slash/help.js @@ -27,7 +27,7 @@ module.exports = class ClaimSlashCommand extends SlashCommand { const staff = await isStaff(interaction.guild, interaction.member.id); const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); const getMessage = client.i18n.getLocale(settings.locale); - const commands = (await client.application.commands.fetch()) + const commands = client.application.commands.cache .filter(c => c.type === 1) .map(c => `> : ${c.description}`) .join('\n'); diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index e892210..9e78265 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -34,6 +34,9 @@ module.exports = class extends Listener { .catch(client.log.error); } + // commands are not cached automatically + await client.application.commands.fetch(); + // presence/activity let next = 0; const setPresence = async () => { From cd71843bb0b9abdac20f1450939b4a7ed8915b66 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Mar 2023 23:47:12 +0000 Subject: [PATCH 351/409] feat: public bot warnings --- src/listeners/client/ready.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 9e78265..81b48c2 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -34,6 +34,12 @@ module.exports = class extends Listener { .catch(client.log.error); } + if (process.env.PUBLIC_BOT === 'true' && !client.application.botPublic) { + client.log.warn('The `PUBLIC_BOT` environment variable is set to `true`, but the bot is not public.'); + } else if (process.env.PUBLIC_BOT === 'false' && client.application.botPublic) { + client.log.warn('Your bot is public, but public features are disabled. Set the `PUBLIC_BOT` environment variable to `true`, or make your bot private.'); + } + // commands are not cached automatically await client.application.commands.fetch(); From 96b5c927432347a589c4bd59235a4ac42ab5f036 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 10 Mar 2023 23:47:43 +0000 Subject: [PATCH 352/409] feat: settings v2 --- package.json | 11 +- pnpm-lock.yaml | 530 +++++---------------------- scripts/preinstall.js | 6 +- src/env.js | 6 - src/http.js | 49 +-- src/routes/api/admin/guilds/index.js | 2 +- src/routes/index.js | 7 - 7 files changed, 106 insertions(+), 505 deletions(-) delete mode 100644 src/routes/index.js diff --git a/package.json b/package.json index 74eeaff..2aa597c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.6", + "version": "4.0.0-beta.7", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", @@ -40,13 +40,11 @@ "node": ">=18" }, "dependencies": { - "@discord-tickets/settings": "^1.5.0", + "@discord-tickets/settings": "^2.0.0", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", "@fastify/cookie": "^6.0.0", - "@fastify/cors": "^8.2.0", - "@fastify/http-proxy": "^8.4.0", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", "@prisma/client": "^4.11.0", @@ -54,7 +52,6 @@ "cryptr": "^6.2.0", "discord.js": "^14.7.1", "dotenv": "^16.0.3", - "express": "^4.18.2", "fastify": "^4.14.1", "figlet": "^1.5.2", "fs-extra": "^10.1.0", @@ -76,10 +73,10 @@ "@commitlint/config-conventional": "^17.4.4", "all-contributors-cli": "^6.24.0", "conventional-changelog-cli": "^2.2.2", - "eslint": "^8.35.0", + "eslint": "^8.36.0", "eslint-plugin-unused-imports": "^2.0.0", "husky": "^8.0.3", - "lint-staged": "^13.1.2", + "lint-staged": "^13.2.0", "nodemon": "^2.0.21" }, "optionalDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc5f224..a5d1c6a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,13 +3,11 @@ lockfileVersion: 5.4 specifiers: '@commitlint/cli': ^17.4.4 '@commitlint/config-conventional': ^17.4.4 - '@discord-tickets/settings': ^1.5.0 + '@discord-tickets/settings': ^2.0.0 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 '@fastify/cookie': ^6.0.0 - '@fastify/cors': ^8.2.0 - '@fastify/http-proxy': ^8.4.0 '@fastify/jwt': ^5.0.1 '@fastify/oauth2': ^5.1.0 '@prisma/client': ^4.11.0 @@ -21,9 +19,8 @@ specifiers: discord.js: ^14.7.1 dotenv: ^16.0.3 erlpack: github:discord/erlpack - eslint: ^8.35.0 + eslint: ^8.36.0 eslint-plugin-unused-imports: ^2.0.0 - express: ^4.18.2 fastify: ^4.14.1 figlet: ^1.5.2 fs-extra: ^10.1.0 @@ -31,7 +28,7 @@ specifiers: keyv: ^4.5.2 leeks.js: ^0.3.0 leekslazylogger: ^5.0.1 - lint-staged: ^13.1.2 + lint-staged: ^13.2.0 ms: ^2.1.3 mustache: ^4.2.0 node-dir: ^0.1.17 @@ -46,13 +43,11 @@ specifiers: zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 1.5.0_svelte@3.55.1 + '@discord-tickets/settings': 2.0.0_svelte@3.56.0 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 '@fastify/cookie': 6.0.0 - '@fastify/cors': 8.2.0 - '@fastify/http-proxy': 8.4.0_3cxu5zja4e2r5wmvge7mdcljwq '@fastify/jwt': 5.0.1 '@fastify/oauth2': 5.1.0 '@prisma/client': 4.11.0_prisma@4.11.0 @@ -60,7 +55,6 @@ dependencies: cryptr: 6.2.0 discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq dotenv: 16.0.3 - express: 4.18.2 fastify: 4.14.1 figlet: 1.5.2 fs-extra: 10.1.0 @@ -88,10 +82,10 @@ devDependencies: '@commitlint/config-conventional': 17.4.4 all-contributors-cli: 6.24.0 conventional-changelog-cli: 2.2.2 - eslint: 8.35.0 - eslint-plugin-unused-imports: 2.0.0_eslint@8.35.0 + eslint: 8.36.0 + eslint-plugin-unused-imports: 2.0.0_eslint@8.36.0 husky: 8.0.3 - lint-staged: 13.1.2 + lint-staged: 13.2.0 nodemon: 2.0.21 packages: @@ -299,8 +293,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@discord-tickets/settings/1.5.0_svelte@3.55.1: - resolution: {integrity: sha512-zsP0FjbrHgs6lpXYRAuuPemqWog385rC27v6Rh0zZd8S60yH8ZQ5u/E8ZIR6Cx8c9OJwStzJ6uEZCfTy69ayGw==} + /@discord-tickets/settings/2.0.0_svelte@3.56.0: + resolution: {integrity: sha512-oVG8qfX21cmufi/jyJV4LrbzsZK86KUmFfgb5W39LCD/+zIg1+l8XrOfkONjwlbFUKbjtV0CV2xgx9Ns+0Wg2w==} dependencies: '@fortawesome/fontawesome-free': 6.3.0 '@skyra/discord-components-core': 3.6.0 @@ -310,7 +304,8 @@ packages: ms: 2.1.3 postcss: 8.4.21 sortablejs: 1.15.0 - svelte-modals: 1.2.1_svelte@3.55.1 + svelte-modals: 1.2.1_svelte@3.56.0 + svelte-toasts: 1.1.2 transitivePeerDependencies: - svelte dev: false @@ -373,13 +368,28 @@ packages: resolution: {integrity: sha512-nMQdHrGgpw+vNL5DbivELW2K3KAUGaMvTjjmFsEPf8mUW8+LAgRjvfFn2gkJq1mnlD6HoqUaHqEL4YpWr2T5MA==} dev: false - /@eslint/eslintrc/2.0.0: - resolution: {integrity: sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==} + /@eslint-community/eslint-utils/4.2.0_eslint@8.36.0: + resolution: {integrity: sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.36.0 + eslint-visitor-keys: 3.3.0 + dev: true + + /@eslint-community/regexpp/4.4.0: + resolution: {integrity: sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc/2.0.1: + resolution: {integrity: sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 9.4.1 + espree: 9.5.0 globals: 13.20.0 ignore: 5.2.4 import-fresh: 3.3.0 @@ -390,8 +400,8 @@ packages: - supports-color dev: true - /@eslint/js/8.35.0: - resolution: {integrity: sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==} + /@eslint/js/8.36.0: + resolution: {integrity: sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -410,13 +420,6 @@ packages: fastify-plugin: 3.0.1 dev: false - /@fastify/cors/8.2.0: - resolution: {integrity: sha512-qDgwpmg6C4D0D3nh8MTMuRXWyEwPnDZDBODaJv90FP2o9ukbahJByW4FtrM5Bpod5KbTf1oIExBmpItbUTQmHg==} - dependencies: - fastify-plugin: 4.5.0 - mnemonist: 0.39.5 - dev: false - /@fastify/deepmerge/1.3.0: resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} dev: false @@ -431,16 +434,6 @@ packages: fast-json-stringify: 5.6.2 dev: false - /@fastify/http-proxy/8.4.0_3cxu5zja4e2r5wmvge7mdcljwq: - resolution: {integrity: sha512-H8nwsmawFtKKRE6uhh1BtF1gQi/l147SmLsDGxB0HdYTHzjXz6uSQO3lEVmY7unKMzbArRjdoJQkEGpScszdSw==} - dependencies: - '@fastify/reply-from': 8.4.3 - ws: 8.12.1_3cxu5zja4e2r5wmvge7mdcljwq - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - /@fastify/jwt/5.0.1: resolution: {integrity: sha512-BF2JrhSRcMRwHim2pbFEgWLJE8DdRh0NaGeAgU+ZFICj2AHxGvrHSOaYRaXNUQixgmGjVaTrbRFl3bSBc6Pj+g==} engines: {node: '>=10'} @@ -461,18 +454,6 @@ packages: - supports-color dev: false - /@fastify/reply-from/8.4.3: - resolution: {integrity: sha512-8QDytjgFfxEbPGBpNj3nj0/QZt+SEyqAPeJ/2/zxxufWdQJ8fz77pEoTO7v/f2rHOJ0rr2ail3Tf6zFIs+78dg==} - dependencies: - '@fastify/error': 3.2.0 - end-of-stream: 1.4.4 - fast-querystring: 1.1.1 - fastify-plugin: 4.5.0 - pump: 3.0.0 - tiny-lru: 10.0.1 - undici: 5.20.0 - dev: false - /@fortawesome/fontawesome-free/6.3.0: resolution: {integrity: sha512-qVtd5i1Cc7cdrqnTWqTObKQHjPWAiRwjUPaXObaeNPcy7+WKxJumGBx66rfSFgK6LNpIasVKkEgW8oyf0tmPLA==} engines: {node: '>=6'} @@ -721,14 +702,6 @@ packages: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} dev: false - /accepts/1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - dev: false - /acorn-jsx/5.3.2_acorn@8.8.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -865,10 +838,6 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true - /array-flatten/1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - dev: false - /array-ify/1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} dev: true @@ -934,26 +903,6 @@ packages: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} dev: false - /body-parser/1.20.1: - resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.11.0 - raw-body: 2.5.1 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - dev: false - /boxen/7.0.2: resolution: {integrity: sha512-1Z4UJabXUP1/R9rLpoU3O2lEMnG3pPLAs/ZD2lF3t2q7qD5lM8rqbtnvtvm4N0wEyNlE+9yZVTVAGmd1V5jabg==} engines: {node: '>=14.16'} @@ -1003,18 +952,6 @@ packages: streamsearch: 1.1.0 dev: false - /bytes/3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - dev: false - - /call-bind/1.0.2: - resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} - dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.2.0 - dev: false - /callsites/3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1059,7 +996,6 @@ packages: /chalk/5.2.0: resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: false /chardet/0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -1173,9 +1109,9 @@ packages: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} dev: true - /commander/9.5.0: - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} - engines: {node: ^12.20.0 || >=14} + /commander/10.0.0: + resolution: {integrity: sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==} + engines: {node: '>=14'} dev: true /compare-func/2.0.0: @@ -1188,18 +1124,6 @@ packages: /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - /content-disposition/0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - dependencies: - safe-buffer: 5.2.1 - dev: false - - /content-type/1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - dev: false - /conventional-changelog-angular/5.0.13: resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} engines: {node: '>=10'} @@ -1367,10 +1291,6 @@ packages: through2: 4.0.2 dev: true - /cookie-signature/1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - dev: false - /cookie-signature/1.2.1: resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==} engines: {node: '>=6.6.0'} @@ -1441,17 +1361,6 @@ packages: resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} dev: true - /debug/2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.0.0 - dev: false - /debug/3.2.7_supports-color@5.5.0: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -1497,11 +1406,6 @@ packages: engines: {node: '>= 0.8'} dev: false - /destroy/1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - dev: false - /didyoumean/1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true @@ -1530,7 +1434,7 @@ packages: lodash.snakecase: 4.1.1 tslib: 2.5.0 undici: 5.20.0 - ws: 8.12.1_3cxu5zja4e2r5wmvge7mdcljwq + ws: 8.13.0_3cxu5zja4e2r5wmvge7mdcljwq transitivePeerDependencies: - bufferutil - utf-8-validate @@ -1564,10 +1468,6 @@ packages: safe-buffer: 5.2.1 dev: false - /ee-first/1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - dev: false - /emoji-name-map/1.2.9: resolution: {integrity: sha512-MSM8y6koSqh/2uEMI2VoKA+Ac0qL5RkgFGP/pzL6n5FOrOJ7FOZFxgs7+uNpqA+AT+WmdbMPXkd3HnFXXdz4AA==} dependencies: @@ -1586,17 +1486,6 @@ packages: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} dev: false - /encodeurl/1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - dev: false - - /end-of-stream/1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - dependencies: - once: 1.4.0 - dev: false - /error-ex/1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -1608,10 +1497,6 @@ packages: engines: {node: '>=6'} dev: true - /escape-html/1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - dev: false - /escape-string-regexp/1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -1622,7 +1507,7 @@ packages: engines: {node: '>=10'} dev: true - /eslint-plugin-unused-imports/2.0.0_eslint@8.35.0: + /eslint-plugin-unused-imports/2.0.0_eslint@8.36.0: resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1632,7 +1517,7 @@ packages: '@typescript-eslint/eslint-plugin': optional: true dependencies: - eslint: 8.35.0 + eslint: 8.36.0 eslint-rule-composer: 0.3.0 dev: true @@ -1649,33 +1534,20 @@ packages: estraverse: 5.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.35.0: - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} - peerDependencies: - eslint: '>=5' - dependencies: - eslint: 8.35.0 - eslint-visitor-keys: 2.1.0 - dev: true - - /eslint-visitor-keys/2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - dev: true - /eslint-visitor-keys/3.3.0: resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.35.0: - resolution: {integrity: sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==} + /eslint/8.36.0: + resolution: {integrity: sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint/eslintrc': 2.0.0 - '@eslint/js': 8.35.0 + '@eslint-community/eslint-utils': 4.2.0_eslint@8.36.0 + '@eslint-community/regexpp': 4.4.0 + '@eslint/eslintrc': 2.0.1 + '@eslint/js': 8.36.0 '@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -1686,9 +1558,8 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.35.0 eslint-visitor-keys: 3.3.0 - espree: 9.4.1 + espree: 9.5.0 esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -1710,7 +1581,6 @@ packages: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.1 - regexpp: 3.2.0 strip-ansi: 6.0.1 strip-json-comments: 3.1.1 text-table: 0.2.0 @@ -1718,8 +1588,8 @@ packages: - supports-color dev: true - /espree/9.4.1: - resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} + /espree/9.5.0: + resolution: {integrity: sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.8.2 @@ -1751,11 +1621,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /etag/1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - dev: false - /event-target-shim/5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -1781,13 +1646,13 @@ packages: strip-final-newline: 2.0.0 dev: true - /execa/6.1.0: - resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /execa/7.0.0: + resolution: {integrity: sha512-tQbH0pH/8LHTnwTrsKWideqi6rFB/QNUawEwrn+WHyz7PX1Tuz2u7wfTvbaNBdP5JD5LVWxNo8/A8CHNZ3bV6g==} + engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} dependencies: cross-spawn: 7.0.3 get-stream: 6.0.1 - human-signals: 3.0.1 + human-signals: 4.3.0 is-stream: 3.0.0 merge-stream: 2.0.0 npm-run-path: 5.1.0 @@ -1796,45 +1661,6 @@ packages: strip-final-newline: 3.0.0 dev: true - /express/4.18.2: - resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} - engines: {node: '>= 0.10.0'} - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.1 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.5.0 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.2.0 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.1 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.7 - proxy-addr: 2.0.7 - qs: 6.11.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - dev: false - /external-editor/3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -1922,7 +1748,7 @@ packages: abstract-logging: 2.0.1 avvio: 8.2.1 fast-content-type-parse: 1.0.0 - find-my-way: 7.5.0 + find-my-way: 7.6.0 light-my-request: 5.9.1 pino: 8.11.0 process-warning: 2.1.0 @@ -1994,23 +1820,8 @@ packages: to-regex-range: 5.0.1 dev: true - /finalhandler/1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} - engines: {node: '>= 0.8'} - dependencies: - debug: 2.6.9 - encodeurl: 1.0.2 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - dev: false - - /find-my-way/7.5.0: - resolution: {integrity: sha512-3ehydSBhGcS0TtMA/BYEyMAKi9Sv0MqF8aqiMO5oGBXyCcSlyEJyfGWsbNxAx7BekTNWUwD1ttLJLURni2vmJg==} + /find-my-way/7.6.0: + resolution: {integrity: sha512-H7berWdHJ+5CNVr4ilLWPai4ml7Y2qAsxjw3pfeBxPigZmaDTzF0wjJLj90xRCmGcWYcyt050yN+34OZDJm1eQ==} engines: {node: '>=14'} dependencies: fast-deep-equal: 3.1.3 @@ -2058,11 +1869,6 @@ packages: engines: {node: '>= 0.6'} dev: false - /fresh/0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - dev: false - /fs-extra/10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -2095,20 +1901,13 @@ packages: /function-bind/1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true /get-caller-file/2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} dev: true - /get-intrinsic/1.2.0: - resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} - dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-symbols: 1.0.3 - dev: false - /get-pkg-repo/4.2.1: resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} engines: {node: '>=6.9.0'} @@ -2233,16 +2032,12 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - /has-symbols/1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - dev: false - /has/1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 + dev: true /hex-to-rgba/2.0.1: resolution: {integrity: sha512-5XqPJBpsEUMsseJUi2w2Hl7cHFFi3+OO10M2pzAvKB1zL6fc+koGMhmBqoDOCB4GemiRM/zvDMRIhVw6EkB8dQ==} @@ -2275,9 +2070,9 @@ packages: engines: {node: '>=10.17.0'} dev: true - /human-signals/3.0.1: - resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} - engines: {node: '>=12.20.0'} + /human-signals/4.3.0: + resolution: {integrity: sha512-zyzVyMjpGBX2+6cDVZeFPCdtOtdsxOeseRhB9tkQ6xXmGUNrcnBzdEKPy3VPNYz+4gy1oukVOXcrJCunSyc6QQ==} + engines: {node: '>=14.18.0'} dev: true /husky/8.0.3: @@ -2291,6 +2086,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 + dev: true /ieee754/1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -2555,8 +2351,8 @@ packages: set-cookie-parser: 2.5.1 dev: false - /lilconfig/2.0.6: - resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} + /lilconfig/2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} dev: true @@ -2564,17 +2360,17 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - /lint-staged/13.1.2: - resolution: {integrity: sha512-K9b4FPbWkpnupvK3WXZLbgu9pchUJ6N7TtVZjbaPsoizkqFUDkUReUL25xdrCljJs7uLUF3tZ7nVPeo/6lp+6w==} + /lint-staged/13.2.0: + resolution: {integrity: sha512-GbyK5iWinax5Dfw5obm2g2ccUiZXNGtAS4mCbJ0Lv4rq6iEtfBSjOYdcbOtAIFtM114t0vdpViDDetjVTSd8Vw==} engines: {node: ^14.13.1 || >=16.0.0} hasBin: true dependencies: + chalk: 5.2.0 cli-truncate: 3.1.0 - colorette: 2.0.19 - commander: 9.5.0 + commander: 10.0.0 debug: 4.3.4 - execa: 6.1.0 - lilconfig: 2.0.6 + execa: 7.0.0 + lilconfig: 2.1.0 listr2: 5.0.7 micromatch: 4.0.5 normalize-path: 3.0.0 @@ -2726,11 +2522,6 @@ packages: hasBin: true dev: false - /media-typer/0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - dev: false - /meow/8.1.2: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} engines: {node: '>=10'} @@ -2748,19 +2539,10 @@ packages: yargs-parser: 20.2.9 dev: true - /merge-descriptors/1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} - dev: false - /merge-stream/2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true - /methods/1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - dev: false - /micromatch/4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -2769,24 +2551,6 @@ packages: picomatch: 2.3.1 dev: true - /mime-db/1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - dev: false - - /mime-types/2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 - dev: false - - /mime/1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - dev: false - /mimic-fn/2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2835,10 +2599,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /ms/2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - dev: false - /ms/2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -2869,11 +2629,6 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true - /negotiator/0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - dev: false - /neo-async/2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true @@ -2977,6 +2732,7 @@ packages: /object-inspect/1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: true /obliterator/2.0.4: resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} @@ -2986,17 +2742,11 @@ packages: resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} dev: false - /on-finished/2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - dependencies: - ee-first: 1.1.1 - dev: false - /once/1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 + dev: true /onetime/5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} @@ -3113,11 +2863,6 @@ packages: lines-and-columns: 1.2.4 dev: true - /parseurl/1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - dev: false - /path-exists/3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -3147,10 +2892,6 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true - /path-to-regexp/0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} - dev: false - /path-type/3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} @@ -3280,13 +3021,6 @@ packages: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} dev: true - /pump/3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - dev: false - /punycode/2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} @@ -3296,13 +3030,6 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} dev: true - /qs/6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} - engines: {node: '>=0.6'} - dependencies: - side-channel: 1.0.4 - dev: false - /queue-microtask/1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true @@ -3316,21 +3043,6 @@ packages: engines: {node: '>=8'} dev: true - /range-parser/1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - dev: false - - /raw-body/2.5.1: - resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} - engines: {node: '>= 0.8'} - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - dev: false - /read-pkg-up/3.0.0: resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} engines: {node: '>=4'} @@ -3379,8 +3091,8 @@ packages: util-deprecate: 1.0.2 dev: true - /readable-stream/3.6.1: - resolution: {integrity: sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==} + /readable-stream/3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} dependencies: inherits: 2.0.4 @@ -3401,7 +3113,7 @@ packages: resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} engines: {node: '>=8'} dependencies: - readable-stream: 3.6.1 + readable-stream: 3.6.2 dev: false /readdirp/3.6.0: @@ -3428,11 +3140,6 @@ packages: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} dev: true - /regexpp/3.2.0: - resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} - engines: {node: '>=8'} - dev: true - /require-directory/2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -3570,39 +3277,6 @@ packages: dependencies: lru-cache: 6.0.0 - /send/0.18.0: - resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} - engines: {node: '>= 0.8.0'} - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - dev: false - - /serve-static/1.15.0: - resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} - engines: {node: '>= 0.8.0'} - dependencies: - encodeurl: 1.0.2 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.18.0 - transitivePeerDependencies: - - supports-color - dev: false - /set-blocking/2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true @@ -3627,14 +3301,6 @@ packages: engines: {node: '>=8'} dev: true - /side-channel/1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.0 - object-inspect: 1.12.3 - dev: false - /signal-exit/3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true @@ -3709,7 +3375,7 @@ packages: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.12 + spdx-license-ids: 3.0.13 dev: true /spdx-exceptions/2.3.0: @@ -3720,11 +3386,11 @@ packages: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} dependencies: spdx-exceptions: 2.3.0 - spdx-license-ids: 3.0.12 + spdx-license-ids: 3.0.13 dev: true - /spdx-license-ids/3.0.12: - resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + /spdx-license-ids/3.0.13: + resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==} dev: true /split/1.0.1: @@ -3736,7 +3402,7 @@ packages: /split2/3.2.2: resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} dependencies: - readable-stream: 3.6.1 + readable-stream: 3.6.2 dev: true /split2/4.1.0: @@ -3869,16 +3535,20 @@ packages: engines: {node: '>= 0.4'} dev: true - /svelte-modals/1.2.1_svelte@3.55.1: + /svelte-modals/1.2.1_svelte@3.56.0: resolution: {integrity: sha512-7MEKUx5wb5YppkXWFGeRlYM5FMGEnpix39u9Y6GtCNTMXRDZ7DB2Z50IYLMRTMW5lOsCdtJgFbB0E3iZMKmsAA==} peerDependencies: svelte: ^3.0.0 dependencies: - svelte: 3.55.1 + svelte: 3.56.0 dev: false - /svelte/3.55.1: - resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==} + /svelte-toasts/1.1.2: + resolution: {integrity: sha512-m+yL4eEKXyJoyjTYaH1j1GFwF0Pi8YDqnVfwWPDmwi4712iZesv+TNCmToSNlav3R5Vkmc8ZBRkT8DOcu3sywQ==} + dev: false + + /svelte/3.56.0: + resolution: {integrity: sha512-LvXiJbjdvJKwB/0CQyYpDX0q+hFqCyWmybzC2G6eK1tJJA/RSRCytTfNmjHv+RHlLuA70vWG7nXp6gbeErYvRA==} engines: {node: '>= 8'} dev: false @@ -3932,7 +3602,7 @@ packages: /through2/4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} dependencies: - readable-stream: 3.6.1 + readable-stream: 3.6.2 dev: true /tiny-lru/10.0.1: @@ -4061,14 +3731,6 @@ packages: engines: {node: '>=12.20'} dev: false - /type-is/1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - dev: false - /typescript/4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} @@ -4098,11 +3760,6 @@ packages: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} - /unpipe/1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - dev: false - /uri-js/4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -4119,11 +3776,6 @@ packages: /util-deprecate/1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - /utils-merge/1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - dev: false - /uuid/3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. @@ -4141,11 +3793,6 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /vary/1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - dev: false - /webidl-conversions/3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: true @@ -4214,9 +3861,10 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true - /ws/8.12.1_3cxu5zja4e2r5wmvge7mdcljwq: - resolution: {integrity: sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==} + /ws/8.13.0_3cxu5zja4e2r5wmvge7mdcljwq: + resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 diff --git a/scripts/preinstall.js b/scripts/preinstall.js index 97931c4..c30c1f4 100644 --- a/scripts/preinstall.js +++ b/scripts/preinstall.js @@ -18,16 +18,14 @@ const env = { DISCORD_SECRET: '', DISCORD_TOKEN: '', ENCRYPTION_KEY: randomBytes(24).toString('hex'), - HTTP_EXTERNAL: 'http://127.0.0.1:8080', + HTTP_EXTERNAL: 'http://127.0.0.1:8169', HTTP_HOST: '0.0.0.0', - HTTP_PORT: 8080, + HTTP_PORT: 8169, HTTP_TRUST_PROXY: false, NODE_ENV: 'production', // not bot-specific OVERRIDE_ARCHIVE: '', PUBLIC_BOT: false, PUBLISH_COMMANDS: false, - SETTINGS_HOST: '127.0.0.1', - SETTINGS_PORT: 8169, SUPER: '319467558166069248', }; diff --git a/src/env.js b/src/env.js index 787e975..80d642e 100644 --- a/src/env.js +++ b/src/env.js @@ -36,12 +36,6 @@ const env = { OVERRIDE_ARCHIVE: () => true, // optional PUBLIC_BOT: () => true, // optional PUBLISH_COMMANDS: () => true, // optional - SETTINGS_HOST: v => - (!!v && !v.startsWith('http')) || - new Error('is required and must be an address, not a URL'), - SETTINGS_PORT: v => - !!v || - new Error('is required'), SUPER: () => true, // optional }; diff --git a/src/http.js b/src/http.js index 95ad08f..f89a3a9 100644 --- a/src/http.js +++ b/src/http.js @@ -6,16 +6,8 @@ const { join } = require('path'); const { files } = require('node-dir'); const { PermissionsBitField } = require('discord.js'); -process.env.PUBLIC_HOST = process.env.HTTP_EXTERNAL; // the SvelteKit app expects `PUBLIC_HOST` module.exports = async client => { - // cors plugin - fastify.register(require('@fastify/cors'), { - credentials: true, - methods: ['DELETE', 'GET', 'PATCH', 'PUT', 'POST'], - origin: true, - }); - // oauth2 plugin fastify.states = new Map(); fastify.register(oauth, { @@ -56,20 +48,6 @@ module.exports = async client => { secret: process.env.ENCRYPTION_KEY, }); - // proxy `/settings` to express - fastify.register(require('@fastify/http-proxy'), { - http2: false, - prefix: '/settings', - replyOptions: { - rewriteRequestHeaders: (req, headers) => ({ - ...headers, - 'host': domain, - }), - }, - rewritePrefix: '/settings', - upstream: `http://${process.env.SETTINGS_HOST}:${process.env.SETTINGS_PORT}`, - }); - // auth fastify.decorate('authenticate', async (req, res) => { try { @@ -176,24 +154,10 @@ module.exports = async client => { })); // register route }); - // express server for settings - const express = require('express')(); const { handler } = await import('@discord-tickets/settings/build/handler.js'); - process.on('sveltekit:error', ({ - error, - errorId, - }) => { - client.log.error.http(`Express ${errorId} ${error}`); - }); - express.set('trust proxy', true); - express.use((req, res, next) => { - next(); - client.log.verbose.http(short(`Express ${req.ip} ${req.method} ${req.route?.path ?? req.path}`)); - }); - express.use(handler); // let SvelteKit handle everything - express.listen(process.env.SETTINGS_PORT, process.env.SETTINGS_HOST, () => { // start the express server - client.log.verbose.http(`Express listening on port ${process.env.SETTINGS_PORT}`); - }); + + // https://stackoverflow.com/questions/72317071/how-to-set-up-fastify-correctly-so-that-sveltekit-works-fine + fastify.all('/*', {}, (req, res) => handler(req.raw, res.raw, () => { })); // start the fastify server fastify.listen({ @@ -203,4 +167,11 @@ module.exports = async client => { if (err) client.log.error.http(err); else client.log.success.http(`Listening at ${addr}`); }); + + process.on('sveltekit:error', ({ + error, + errorId, + }) => { + client.log.error.http(`SvelteKit ${errorId} ${error}`); + }); }; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/index.js b/src/routes/api/admin/guilds/index.js index 1ad7a57..0b05178 100644 --- a/src/routes/api/admin/guilds/index.js +++ b/src/routes/api/admin/guilds/index.js @@ -3,7 +3,7 @@ const { PermissionsBitField } = require('discord.js'); module.exports.get = fastify => ({ handler: async (req, res) => { const { client } = res.context.config; - const guilds = await (await fetch('https://discordapp.com/api/users/@me/guilds', { headers: { 'Authorization': `Bearer ${req.user.payload.access_token}` } })).json(); + const guilds = await (await fetch('https://discordapp.com/api/users/@me/guilds', { headers: { 'Authorization': `Bearer ${req.user.payload.accessToken}` } })).json(); res.send( guilds .filter(guild => guild.owner || new PermissionsBitField(guild.permissions.toString()).has(PermissionsBitField.Flags.ManageGuild)) diff --git a/src/routes/index.js b/src/routes/index.js deleted file mode 100644 index 203bc5b..0000000 --- a/src/routes/index.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports.get = () => ({ - handler: (req, res) => { - const { client } = res.context.config; - return `Hello, I am ${client.user.username}!`; - // res.redirect(process.env.SETTINGS_EXTERNAL); - }, -}); \ No newline at end of file From be7f43174662a33d3ff2c3ae3cf1ed032062f56f Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 11 Mar 2023 00:11:54 +0000 Subject: [PATCH 353/409] fix: settings app needed `ORIGIN` environment variable --- src/http.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/http.js b/src/http.js index f89a3a9..2ffea43 100644 --- a/src/http.js +++ b/src/http.js @@ -6,6 +6,7 @@ const { join } = require('path'); const { files } = require('node-dir'); const { PermissionsBitField } = require('discord.js'); +process.env.ORIGIN = process.env.HTTP_EXTERNAL; module.exports = async client => { // oauth2 plugin From 788f0fee05052bd8205b22d8ba2fc651b1effbf4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 11 Mar 2023 00:14:24 +0000 Subject: [PATCH 354/409] fix: http log spam --- src/http.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http.js b/src/http.js index 2ffea43..f582175 100644 --- a/src/http.js +++ b/src/http.js @@ -124,7 +124,7 @@ module.exports = async client => { : responseTime >= 5 ? '&e' : '&a') + responseTime + 'ms'; - const level = req.routerPath.startsWith('/settings') ? 'verbose' : 'info'; + const level = req.routerPath === '/*' ? 'verbose' : 'info'; client.log[level].http(short(`${req.id} ${req.ip} ${req.method} ${req.routerPath ?? '*'} &m-+>&r ${status}&b in ${responseTime}`)); if (!req.routerPath) client.log.verbose.http(`${req.id} ${req.method} ${req.url}`); done(); From d23830d9a3737da2df9cf307e6353415fbc201f5 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 11 Mar 2023 15:02:49 +0000 Subject: [PATCH 355/409] chore: add `.editorconfig` --- .editorconfig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..25b67fc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = tab + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[*.md] +indent_style = space +indent_size = 4 \ No newline at end of file From 4e21382e1ecee04cf36e355e57a1cc51405889cf Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 11 Mar 2023 15:03:11 +0000 Subject: [PATCH 356/409] fix(docker): update docker files --- Dockerfile | 7 +++---- docker-compose.yml | 8 +++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 562705e..7cd2482 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,15 +10,14 @@ COPY package.json pnpm-lock.yaml ./ COPY --link scripts scripts # install dependencies, CI=true to skip pre/postinstall scripts RUN CI=true pnpm install --prod --frozen-lockfile +RUN chmod +x /usr/bot/scripts/start.sh COPY --link . . FROM node:18-alpine AS runner ENV NODE_ENV=production \ HTTP_HOST=0.0.0.0 \ - HTTP_PORT=80 \ - SETTINGS_HOST=127.0.0.1 \ - SETTINGS_PORT=8169 + HTTP_PORT=80 WORKDIR /usr/bot COPY --from=builder /build ./ EXPOSE ${HTTP_PORT} -ENTRYPOINT [ "/usr/bot/scripts/start.sh" ] \ No newline at end of file +ENTRYPOINT [ "/usr/bot/scripts/start.sh" ] diff --git a/docker-compose.yml b/docker-compose.yml index dbb27fb..3d4c2d4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,7 +24,7 @@ services: networks: - discord-tickets ports: - - 8080:8080 + - 8169:8169 volumes: - tickets-bot:/usr/bot/user tty: true @@ -37,15 +37,13 @@ services: DISCORD_TOKEN: # required ENCRYPTION_KEY: # required DB_PROVIDER: mysql - HTTP_EXTERNAL: http://127.0.0.1:8080 # change this to your server's external IP (or domain) + HTTP_EXTERNAL: http://127.0.0.1:8169 # change this to your server's external IP (or domain) HTTP_HOST: 0.0.0.0 - HTTP_PORT: 8080 + HTTP_PORT: 8169 HTTP_TRUST_PROXY: false # set to true if you're using a reverse proxy OVERRIDE_ARCHIVE: null PUBLIC_BOT: false PUBLISH_COMMANDS: false - SETTINGS_PORT: 8169 - SETTINGS_HOST: 127.0.0.1 SUPER: 319467558166069248 # optionally add `,youruseridhere` networks: From bbfb3b2ca7a62f275dee4be93d5961a3e8a7299e Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 11 Mar 2023 18:45:06 +0000 Subject: [PATCH 357/409] chore: update editorconfig --- .editorconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 25b67fc..a8a125a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,8 @@ root = true [*] end_of_line = lf insert_final_newline = true -indent_style = tab +trim_trailing_whitespace = true +charset = utf-8 [*.{yml,yaml}] indent_style = space @@ -11,4 +12,4 @@ indent_size = 2 [*.md] indent_style = space -indent_size = 4 \ No newline at end of file +indent_size = 4 From 969e433154a25aa74d974a5724c95a6c0f546e19 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 11 Mar 2023 18:45:20 +0000 Subject: [PATCH 358/409] feat: add Caddyfile --- Caddyfile | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Caddyfile diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..6320210 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,3 @@ +tickets.example.com { + reverse_proxy 127.0.0.1:8169 +} From f23b7522e8777d7903ff1b87ba03362405c11f4c Mon Sep 17 00:00:00 2001 From: Ludovic CHEVALIER Date: Sun, 12 Mar 2023 01:56:39 +0100 Subject: [PATCH 359/409] feat(i18n): update French translations [skip ci] Currently translated at 100.0% (252 of 252 strings) Co-authored-by: Ludovic CHEVALIER Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/fr/ Translation: Discord Tickets/Bot --- src/i18n/fr.yml | 378 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 377 insertions(+), 1 deletion(-) diff --git a/src/i18n/fr.yml b/src/i18n/fr.yml index 28a5f12..6ea58d3 100644 --- a/src/i18n/fr.yml +++ b/src/i18n/fr.yml @@ -1,34 +1,410 @@ buttons: accept_close_request: text: Accepter + emoji: ✅ cancel: emoji: ✖️ text: Annuler claim: emoji: 🙌 + text: S'approprier close: text: Fermer + emoji: ✖️ reject_close_request: emoji: ✖️ text: Rejeter + confirm_open: + emoji: ✅ + text: Créer un ticket + create: + text: Créer un ticket + emoji: 🎫 + edit: + emoji: ✏️ + text: Modifier + unclaim: + emoji: ♻️ + text: Relâcher commands: message: pin: name: Épingler le message not_pinnable: title: ❌ Erreur + description: "Ce message ne peut être épinglé.\nVeuillez demander à un administrateur\ + \ de vérifier les permissions du bot\n" pinned: title: ✅ Message épinglé + description: Les message à été épinglé. + not_ticket: + title: ❌ Ceci n'est pas un salon de ticket + description: Vous pouvez seulement épingler des messages dans les tickets. + create: + name: Transformer ce message en ticket slash: add: not_staff: title: ❌ Erreur + description: Seul les membres de l'équipe peuvent ajouter des membres aux + tickets des autres. success: title: ✅ Ajouté + description: '{member} à été ajouté au ticket {ticket}.' + added: '{added} à été ajouté par {by}.' + description: Ajouter un membre à un ticket + name: ajouter + options: + member: + description: Le membre à ajouter au ticket + name: membre + ticket: + description: Le ticket auquel ajouter le membre + name: ticket close: invalid_time: title: ❌ Invalide + description: "`{input}` n'est pas un format de date valide." + options: + reason: + description: La raison de la clôture du ticket + name: raison + description: Demander la fermeture d'un ticket + name: fermer force-close: options: ticket: - name: billet + name: ticket + description: le ticket à clôturer + category: + description: Fermer tout les tickets dans la catégorie spécifiée (peut être + utilisée avec `time`) + name: catégorie + time: + description: Clôturer tout les tickets inactifs depuis le temps spécifié + (peut être utilisé avec `category`) + name: temps + reason: + name: raison + description: La raison de la fermeture du/des ticket(s) + description: Forcer la clôture d'un ticket + name: fermeture-forcée + no_tickets: + description: Il n'y as pas de tickets ouverts inactifs depuis plus de `{time}`. + title: ❌ Aucun ticket + not_staff: + description: Seuls les membres de l'équipe peuvent forcer la fermeture de + tickets. + title: ❌ Erreur + confirm_multiple: + description: "Vous êtes sur le point de clôturer **{count}** tickets inactifs\ + \ depuis plus de `{time}` :\n{tickets}\n" + title: ❓Êtes vous sûr ? + claim: + description: S'approprier un ticket + name: S'approprier + not_staff: + description: Seul les membres de l'équipe peuvent s'approprier un ticket. + title: ❌ Erreur + help: + name: aide + response: + links: + commands: Liste complète des commandes + docs: Documentation + support: Support + links: Liens utiles + feedback: Retour + commands: Commandes + settings: Paramètres du bot + description: '**Utilise {command} pour créer un ticket et obtenir un support.**' + title: Aide + description: Afficher le menu d'aide + move: + description: Déplacer un ticket dans une autre catégorie + options: + category: + name: catégorie + description: La catégorie dans laquelle déplacer le ticket + moved: 🗃️ {by} as déplacé ce ticket de **{from}** vers **{to}**. + name: déplacer + not_staff: + description: Seuls les membres de l'équipe peuvent déplacer des tickets. + title: ❌ Erreur + new: + name: nouveau + description: Créer un nouveau ticket + options: + references: + description: Le numéro d'un ticket associé + name: Références + priority: + name: priorité + options: + priority: + choices: + MEDIUM: 🟠 Moyenne + HIGH: 🔴 Haute + LOW: 🟢 Basse + description: La priorité du ticket + name: priorité + description: Définir la priorité d'un ticket + success: + title: ✅ Priorité définie + description: La priorité du ticket à été définie sur `{priority}`. + not_staff: + title: ❌ Erreur + description: Seuls les membres de l'équipe peuvent changer la priorité d'un + ticket. + release: + name: relâcher + description: Relâcher (ne plus s'approprier) un ticket + remove: + description: Retirer un membre d'un ticket + name: retirer + success: + title: ✅ Retiré + description: '{member} as été retiré de {ticket}.' + options: + member: + description: Le membre à retirer du ticket + name: membre + ticket: + description: Le ticket dont il faut retirer le membre + name: ticket + removed: ⬅️ {removed} as été retiré par {by}. + not_staff: + description: Seuls les membres de l'équipe peuvent retirer des membres des + tickets des autres. + title: ❌ Erreur + tag: + name: tag + description: Utiliser un tag + options: + for: + description: L'utilisateur à qui attribuer le tag + name: pour + tag: + description: Le nom à utiliser pour le tag + name: tag + tickets: + options: + member: + name: membre + description: Le membre dont il faut lister les tickets + description: Lister votre tickets ou ceux de quelqu'un d'autre + not_staff: + description: Seuls les membres de l'équipe peuvent voir les tickets des autres + membres. + title: ❌ Erreur + response: + fields: + closed: + none: + other: "{user} n'as ouvert aucun ticket." + own: "Vous n'avez ouvert aucun ticket\nUtilisez {new} pour ouvrir un\ + \ ticket.\n" + name: Tickets clôturés + open: + name: Tickets ouverts + description: Utilisez {transcript} pour télécharger la transcription du ticket. + title: + other: Tickets de {displayName} + own: Vos tickets + name: tickets + transcript: + name: transcription + description: Obtenir la transcription d'un ticket + options: + ticket: + name: ticket + description: Le nombre de ticket dont vous souhaitez obtenir la transcription + transfer: + description: Transférer la propriété d'un ticket à un autre membre + name: transférer + transferred_from: '{user} as transféré ce ticket de {from} à {to}.' + options: + member: + description: Le membre à qui transférer la propriété + name: membre + transferred: '{user} as transféré ce ticket à {to}.' + topic: + name: sujet + description: Changer le sujet d'un ticket + user: + create: + name: Créer un ticket pour un membre +misc: + update: + description: "> [Voir`{version}` sur GitHub]({github})\n> [Notice de changement]({changelog})\n\ + > [Guide de mise à jour]({guide})\n" + title: Une mise à jour est disponible + category_full: + description: "Cette catégorie as atteint sa capacité maximum.\nVeuillez réessayer\ + \ ultérieurement.\n" + title: ❌ Catégorie pleine + expires_in: Expire dans {time} + not_ticket: + title: ❌ Ce salon n'est pas un ticket + description: Vous ne pouvez utiliser cette commande que dans un ticket. + ratelimited: + description: Réessayez dans quelques secondes. + title: 🐢 Veuillez ralentir + blocked: + description: Vous n'êtes pas autorisé à créer un ticket. + title: ❌ Bloqué + cooldown: + title: ❌ Veuillez patienter + description: Veuillez patienter {time} avant de créer un autre ticket dans cette + catégorie. + error: + fields: + code: Code d'erreur + identifier: Identifiant + description: "Désolé, une erreur inattendue s'est produite.\nVeuillez transmettre\ + \ cette information à un administrateur.\n" + title: ⚠️ Quelque chose s'est mal passé + missing_roles: + description: Vous n'avez pas les rôles requis pour créer un ticket dans cette + catégorie. + title: ❌ Rôles manquants + no_categories: + description: Aucune catégorie de ticket n'est configurée. + title: ❌ Il n'existe pas de catégorie de ticket + expired: + description: Vous n'avez pas répondu à temps. Veuillez réessayer. + title: ⏰ Expiré + invalid_ticket: + description: Veuillez spécifier un ticket valide. + title: ❌ Ticket invalide + member_limit: + title: + - ❌ Vous avez déjà un ticket + - Vous avez déjà %d tickets ouverts + description: + - Veuillez utiliser votre ticket ouvert ou le clôturer avant d'en créer un nouveau. + - "Veuillez clôturer votre ticket avant d'en créer un autre.\nUtilisez `/tickets`\ + \ afin de voir vos tickets existants.\n" + unknown_category: + description: Veuillez tenter une autre catégorie. + title: ❌ Cette catégorie de ticket n'existe pas +modals: + feedback: + rating: + label: Notation + placeholder: 1-5 + title: Comment avons-nous fait ? + comment: + label: Commentaire + placeholder: Avez vous un retour supplémentaire ? + topic: + label: Sujet + placeholder: Quelle est la raison de ce ticket ? +ticket: + answers: + no_value: '*Pas de réponse*' + claimed: 🙌 {user} s'est approprié ce ticket. + close: + closed: + description: Ce salon sera supprimé dans quelques secondes… + title: ✅ Ticket clôturé + staff_request: + title: ❓ Ce ticket peut il être clôturé ? + description: "{requestedBy} souhaite clôturer ce ticket.\nCliquez sur \"Accepter\"\ + \ pour le fermer, or \"Refuser\" si vous avez toujours besoin d'aide.\n" + archived: "\nLes messages de ce salon seront archivés pour une référence future.\n" + forbidden: + title: ❌ Erreur + description: Vous n'avez pas la permission de clôturer ce ticket. + rejected: ✋ {user} as rejeté une demande de clôture de ce ticket. + user_request: + title: ❓ {requestedBy} souhaite clôturer le ticket + wait_for_staff: ✋ Veuillez attendre que l'équipe ferme ce ticket. + wait_for_user: ✋ Veuillez attendre une réponse de l'utilisateur. + released: ♻️ {user} as relâché ce ticket. + references_message: + title: ℹ️ Référence + description: Fait référence à [a message]({url}) envoyé {timestamp} par {author}. + references_ticket: + title: ℹ️ Référence + fields: + number: Nombre + topic: Sujet + date: Crée à + description: 'Ce ticket est lié à un ticket précédent :' + opening_message: + content: "{staff}\n{creator} as ouvert un nouveau ticket\n" + fields: + topic: Sujet + edited: + title: ✅ Ticket mis à jour + description: Vos modifications ont été enregistrées. + created: + description: 'Votre ticket as été crée : {channel}.' + title: ✅ Ticket crée + feedback: Merci pour votre retour. +dm: + closed: + title: Votre ticket as été clôturé + fields: + created: Crée à + closed: + name: Clôturé à + value: '{timestamp} (après {duration})' + closed_by: Clôturé par + feedback: Vos retours + ticket: Ticket + topic: Sujet + reason: Raison de la clôture + response: Temps de réponse + archived: Utilisez la commande `/transcript` sur **{guild}** pour voir les messages + archivés. + confirm_open: + title: Souhaitez vous ouvrir un ticket avec le sujet suivant ? +log: + admin: + changes: Modifications + description: + joined: '{user} {verb} {targetType}' + target: + tag: un tag + question: une question + settings: les paramètres + panel: un panneau + category: une catégorie + title: + target: + tag: Tag + category: Catégorie + settings: Paramètres + panel: Panneau + question: Question + joined: '{targetType} {verb}' + verb: + update: mis à jour + delete: supprimé + create: crée + ticket: + description: '{user} {verb} un ticket' + verb: + unclaim: Relâché + update: mis à jour + close: Fermé + create: Crée + claim: Approprié + added: Membres ajoutés + removed: Membres supprimés + title: Ticket {verb} + ticket: Ticket + message: + title: Message {verb} + description: '{user} {verb} un message' + verb: + update: mis à jour + delete: supprimé + message: Message +menus: + category: + placeholder: Sélectionnez une catégorie de ticket + guild: + placeholder: Sélectionnez un serveur From 642060cfb69aa0b9e1d545bc3b3290f2cbb5065a Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 12 Mar 2023 13:51:16 +0000 Subject: [PATCH 360/409] fix: make script executable --- scripts/start.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/start.sh diff --git a/scripts/start.sh b/scripts/start.sh old mode 100644 new mode 100755 From d09598dd3f52d6290f84ea1cf6ded9ca105035c3 Mon Sep 17 00:00:00 2001 From: Uzurka <101745008+Uzurka@users.noreply.github.com> Date: Sun, 12 Mar 2023 14:53:35 +0100 Subject: [PATCH 361/409] fix: Dockerfile (#394) * Update Dockerfile * Fix chmod command Fix the path so it can stay in the `builder` step, so it doesn't add a layer to the image. --------- Co-authored-by: Isaac --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7cd2482..ae5d9a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,9 +8,9 @@ RUN apk add --no-cache make gcc g++ python3 RUN npm install -g pnpm COPY package.json pnpm-lock.yaml ./ COPY --link scripts scripts +RUN chmod +x ./scripts/start.sh # install dependencies, CI=true to skip pre/postinstall scripts RUN CI=true pnpm install --prod --frozen-lockfile -RUN chmod +x /usr/bot/scripts/start.sh COPY --link . . FROM node:18-alpine AS runner From 6773d9ddbe6cc333686136c949d390162382dec7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 12 Mar 2023 22:21:21 +0000 Subject: [PATCH 362/409] feat: add API service keys --- package.json | 6 +- pnpm-lock.yaml | 69 +++++++++++-------- src/env.js | 3 +- src/http.js | 22 +++--- .../[guild]/categories/[category]/index.js | 10 +-- .../[category]/questions/[question].js | 4 +- .../admin/guilds/[guild]/categories/index.js | 10 +-- src/routes/api/admin/guilds/[guild]/panels.js | 4 +- .../api/admin/guilds/[guild]/settings.js | 6 +- .../api/admin/guilds/[guild]/tags/[tag].js | 6 +- .../api/admin/guilds/[guild]/tags/index.js | 4 +- src/routes/api/admin/guilds/index.js | 4 +- src/routes/api/users/{@me.js => @me/index.js} | 4 +- src/routes/api/users/@me/key.js | 19 +++++ src/routes/auth/callback.js | 10 ++- src/routes/auth/logout.js | 4 +- 16 files changed, 105 insertions(+), 80 deletions(-) rename src/routes/api/users/{@me.js => @me/index.js} (65%) create mode 100644 src/routes/api/users/@me/key.js diff --git a/package.json b/package.json index 2aa597c..4d16c5e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.7", + "version": "4.0.0-beta.8", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", @@ -40,7 +40,7 @@ "node": ">=18" }, "dependencies": { - "@discord-tickets/settings": "^2.0.0", + "@discord-tickets/settings": "^2.1.0", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", @@ -50,7 +50,7 @@ "@prisma/client": "^4.11.0", "boxen": "^7.0.2", "cryptr": "^6.2.0", - "discord.js": "^14.7.1", + "discord.js": "^14.8.0", "dotenv": "^16.0.3", "fastify": "^4.14.1", "figlet": "^1.5.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5d1c6a..d022fc8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,7 +3,7 @@ lockfileVersion: 5.4 specifiers: '@commitlint/cli': ^17.4.4 '@commitlint/config-conventional': ^17.4.4 - '@discord-tickets/settings': ^2.0.0 + '@discord-tickets/settings': ^2.1.0 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -16,7 +16,7 @@ specifiers: bufferutil: ^4.0.7 conventional-changelog-cli: ^2.2.2 cryptr: ^6.2.0 - discord.js: ^14.7.1 + discord.js: ^14.8.0 dotenv: ^16.0.3 erlpack: github:discord/erlpack eslint: ^8.36.0 @@ -43,7 +43,7 @@ specifiers: zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 2.0.0_svelte@3.56.0 + '@discord-tickets/settings': 2.1.0_svelte@3.56.0 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -53,7 +53,7 @@ dependencies: '@prisma/client': 4.11.0_prisma@4.11.0 boxen: 7.0.2 cryptr: 6.2.0 - discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq + discord.js: 14.8.0_3cxu5zja4e2r5wmvge7mdcljwq dotenv: 16.0.3 fastify: 4.14.1 figlet: 1.5.2 @@ -293,8 +293,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@discord-tickets/settings/2.0.0_svelte@3.56.0: - resolution: {integrity: sha512-oVG8qfX21cmufi/jyJV4LrbzsZK86KUmFfgb5W39LCD/+zIg1+l8XrOfkONjwlbFUKbjtV0CV2xgx9Ns+0Wg2w==} + /@discord-tickets/settings/2.1.0_svelte@3.56.0: + resolution: {integrity: sha512-eWs4hQ/5VtbMsPcs/jOHwrPBOB5XBKuGYY0xILUqYFINa5oJsHIzHEjHjEIDcxW2xUJ3g92soBe5GVM3PjkfMA==} dependencies: '@fortawesome/fontawesome-free': 6.3.0 '@skyra/discord-components-core': 3.6.0 @@ -310,11 +310,12 @@ packages: - svelte dev: false - /@discordjs/builders/1.4.0: - resolution: {integrity: sha512-nEeTCheTTDw5kO93faM1j8ZJPonAX86qpq/QVoznnSa8WWcCgJpjlu6GylfINTDW6o7zZY0my2SYdxx2mfNwGA==} + /@discordjs/builders/1.5.0: + resolution: {integrity: sha512-7XxT78mnNBPigHn2y6KAXkicxIBFtZREGWaRZ249EC1l6gBUEP8IyVY5JTciIjJArxkF+tg675aZvsTNTKBpmA==} engines: {node: '>=16.9.0'} dependencies: - '@discordjs/util': 0.1.0 + '@discordjs/formatters': 0.2.0 + '@discordjs/util': 0.2.0 '@sapphire/shapeshift': 3.8.1 discord-api-types: 0.37.35 fast-deep-equal: 3.1.3 @@ -322,17 +323,24 @@ packages: tslib: 2.5.0 dev: false - /@discordjs/collection/1.3.0: - resolution: {integrity: sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg==} + /@discordjs/collection/1.4.0: + resolution: {integrity: sha512-hiOJyk2CPFf1+FL3a4VKCuu1f448LlROVuu8nLz1+jCOAPokUcdFAV+l4pd3B3h6uJlJQSASoZzrdyNdjdtfzQ==} engines: {node: '>=16.9.0'} dev: false - /@discordjs/rest/1.5.0: - resolution: {integrity: sha512-lXgNFqHnbmzp5u81W0+frdXN6Etf4EUi8FAPcWpSykKd8hmlWh1xy6BmE0bsJypU1pxohaA8lQCgp70NUI3uzA==} + /@discordjs/formatters/0.2.0: + resolution: {integrity: sha512-vn4oMSXuMZUm8ITqVOtvE7/fMMISj4cI5oLsR09PEQXHKeKDAMLltG/DWeeIs7Idfy6V8Fk3rn1e69h7NfzuNA==} engines: {node: '>=16.9.0'} dependencies: - '@discordjs/collection': 1.3.0 - '@discordjs/util': 0.1.0 + discord-api-types: 0.37.35 + dev: false + + /@discordjs/rest/1.6.0: + resolution: {integrity: sha512-HGvqNCZ5Z5j0tQHjmT1lFvE5ETO4hvomJ1r0cbnpC1zM23XhCpZ9wgTCiEmaxKz05cyf2CI9p39+9LL+6Yz1bA==} + engines: {node: '>=16.9.0'} + dependencies: + '@discordjs/collection': 1.4.0 + '@discordjs/util': 0.2.0 '@sapphire/async-queue': 1.5.0 '@sapphire/snowflake': 3.4.0 discord-api-types: 0.37.35 @@ -341,15 +349,15 @@ packages: undici: 5.20.0 dev: false - /@discordjs/util/0.1.0: - resolution: {integrity: sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ==} + /@discordjs/util/0.2.0: + resolution: {integrity: sha512-/8qNbebFzLWKOOg+UV+RB8itp4SmU5jw0tBUD3ifElW6rYNOj1Ku5JaSW7lLl/WgjjxF01l/1uQPCzkwr110vg==} engines: {node: '>=16.9.0'} dev: false /@eartharoid/dbf/0.3.3_3cxu5zja4e2r5wmvge7mdcljwq: resolution: {integrity: sha512-eVDdpFlDV5CAvqoV5g1iAvoYhPjnvcyJ0Nnepc1YihlE1KIYGhVIK/2RaDsltzxRuiweO3Y7dvDj/cUpJnnFPA==} dependencies: - discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq + discord.js: 14.8.0_3cxu5zja4e2r5wmvge7mdcljwq node-dir: 0.1.17 transitivePeerDependencies: - bufferutil @@ -1419,14 +1427,15 @@ packages: resolution: {integrity: sha512-iyKZ/82k7FX3lcmHiAvvWu5TmyfVo78RtghBV/YsehK6CID83k5SI03DKKopBcln+TiEIYw5MGgq7SJXSpNzMg==} dev: false - /discord.js/14.7.1_3cxu5zja4e2r5wmvge7mdcljwq: - resolution: {integrity: sha512-1FECvqJJjjeYcjSm0IGMnPxLqja/pmG1B0W2l3lUY2Gi4KXiyTeQmU1IxWcbXHn2k+ytP587mMWqva2IA87EbA==} + /discord.js/14.8.0_3cxu5zja4e2r5wmvge7mdcljwq: + resolution: {integrity: sha512-UOxYtc/YnV7jAJ2gISluJyYeBw4e+j8gWn+IoqG8unaHAVuvZ13DdYN0M1f9fbUgUvSarV798inIrYFtDNDjwQ==} engines: {node: '>=16.9.0'} dependencies: - '@discordjs/builders': 1.4.0 - '@discordjs/collection': 1.3.0 - '@discordjs/rest': 1.5.0 - '@discordjs/util': 0.1.0 + '@discordjs/builders': 1.5.0 + '@discordjs/collection': 1.4.0 + '@discordjs/formatters': 0.2.0 + '@discordjs/rest': 1.6.0 + '@discordjs/util': 0.2.0 '@sapphire/snowflake': 3.4.0 '@types/ws': 8.5.4 discord-api-types: 0.37.35 @@ -1646,8 +1655,8 @@ packages: strip-final-newline: 2.0.0 dev: true - /execa/7.0.0: - resolution: {integrity: sha512-tQbH0pH/8LHTnwTrsKWideqi6rFB/QNUawEwrn+WHyz7PX1Tuz2u7wfTvbaNBdP5JD5LVWxNo8/A8CHNZ3bV6g==} + /execa/7.1.0: + resolution: {integrity: sha512-T6nIJO3LHxUZ6ahVRaxXz9WLEruXLqdcluA+UuTptXmLM7nDAn9lx9IfkxPyzEL21583qSt4RmL44pO71EHaJQ==} engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} dependencies: cross-spawn: 7.0.3 @@ -2369,9 +2378,9 @@ packages: cli-truncate: 3.1.0 commander: 10.0.0 debug: 4.3.4 - execa: 7.0.0 + execa: 7.1.0 lilconfig: 2.1.0 - listr2: 5.0.7 + listr2: 5.0.8 micromatch: 4.0.5 normalize-path: 3.0.0 object-inspect: 1.12.3 @@ -2383,8 +2392,8 @@ packages: - supports-color dev: true - /listr2/5.0.7: - resolution: {integrity: sha512-MD+qXHPmtivrHIDRwPYdfNkrzqDiuaKU/rfBcec3WMyMF3xylQj3jMq344OtvQxz7zaCFViRAeqlr2AFhPvXHw==} + /listr2/5.0.8: + resolution: {integrity: sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==} engines: {node: ^14.13.1 || >=16.0.0} peerDependencies: enquirer: '>= 2.3.0 < 3' diff --git a/src/env.js b/src/env.js index 80d642e..f872f25 100644 --- a/src/env.js +++ b/src/env.js @@ -33,6 +33,7 @@ const env = { !!v || new Error('is required'), HTTP_TRUST_PROXY: () => true, // optional + INVALIDATE_TOKENS: () => true, // optional OVERRIDE_ARCHIVE: () => true, // optional PUBLIC_BOT: () => true, // optional PUBLISH_COMMANDS: () => true, // optional @@ -53,4 +54,4 @@ const load = options => { module.exports = { env, load, -}; \ No newline at end of file +}; diff --git a/src/http.js b/src/http.js index f582175..25a2681 100644 --- a/src/http.js +++ b/src/http.js @@ -53,22 +53,20 @@ module.exports = async client => { fastify.decorate('authenticate', async (req, res) => { try { const data = await req.jwtVerify(); - if (data.payload.expiresAt < Date.now()) { - return res.code(401).send({ - error: 'Unauthorised', - message: 'You are not authenticated.', - statusCode: 401, - - }); - } - } catch (err) { - res.send(err); + if (data.expiresAt < Date.now()) throw 'expired'; + if (data.createdAt < new Date(process.env.INVALIDATE_TOKENS).getTime()) throw 'expired'; + } catch (error) { + return res.code(401).send({ + error: 'Unauthorised', + message: error === 'expired' ? 'Your token has expired; please re-authenticate.' : 'You are not authenticated.', + statusCode: 401, + }); } }); fastify.decorate('isAdmin', async (req, res) => { try { - const userId = req.user.payload.id; + const userId = req.user.id; const guildId = req.params.guild; const guild = client.guilds.cache.get(guildId); if (!guild) { @@ -175,4 +173,4 @@ module.exports = async client => { }) => { client.log.error.http(`SvelteKit ${errorId} ${error}`); }); -}; \ No newline at end of file +}; diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js index 9900ea1..413cfad 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category]/index.js @@ -23,7 +23,7 @@ module.exports.delete = fastify => ({ name: category.name, type: 'category', }, - userId: req.user.payload.id, + userId: req.user.id, }); return category; @@ -146,7 +146,7 @@ module.exports.patch = fastify => ({ await client.tickets.getCategory(categoryId, true); await updateStaffRoles(guild); - if (req.user.payload.accessToken && JSON.stringify(category.staffRoles) !== JSON.stringify(original.staffRoles)) { + if (req.user.accessToken && JSON.stringify(category.staffRoles) !== JSON.stringify(original.staffRoles)) { Promise.all([ 'Create ticket for user', 'claim', @@ -170,7 +170,7 @@ module.exports.patch = fastify => ({ type: ApplicationCommandPermissionType.Role, })), ], - token: req.user.payload.accessToken, + token: req.user.accessToken, }), )) .then(() => client.log.success('Updated application command permissions in "%s"', guild.name)) @@ -189,10 +189,10 @@ module.exports.patch = fastify => ({ name: category.name, type: 'category', }, - userId: req.user.payload.id, + userId: req.user.id, }); return category; }, onRequest: [fastify.authenticate, fastify.isAdmin], -}); \ No newline at end of file +}); diff --git a/src/routes/api/admin/guilds/[guild]/categories/[category]/questions/[question].js b/src/routes/api/admin/guilds/[guild]/categories/[category]/questions/[question].js index 213e102..160c20b 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/[category]/questions/[question].js +++ b/src/routes/api/admin/guilds/[guild]/categories/[category]/questions/[question].js @@ -20,10 +20,10 @@ module.exports.delete = fastify => ({ name: question.label, type: 'question', }, - userId: req.user.payload.id, + userId: req.user.id, }); return question; }, onRequest: [fastify.authenticate, fastify.isAdmin], -}); \ No newline at end of file +}); diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index 08f896a..a171454 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -54,7 +54,7 @@ module.exports.post = fastify => ({ /** @type {import('client')} */ const client = res.context.config.client; - const user = await client.users.fetch(req.user.payload.id); + const user = await client.users.fetch(req.user.id); const guild = client.guilds.cache.get(req.params.guild); const data = req.body; const allow = ['ViewChannel', 'ReadMessageHistory', 'SendMessages', 'EmbedLinks', 'AttachFiles']; @@ -101,7 +101,7 @@ module.exports.post = fastify => ({ await client.tickets.getCategory(category.id, true); await updateStaffRoles(guild); - if (req.user.payload.accessToken) { + if (req.user.accessToken) { Promise.all([ 'Create ticket for user', 'claim', @@ -125,7 +125,7 @@ module.exports.post = fastify => ({ type: ApplicationCommandPermissionType.Role, })), ], - token: req.user.payload.accessToken, + token: req.user.accessToken, }), )) .then(() => client.log.success('Updated application command permissions in "%s"', guild.name)) @@ -140,10 +140,10 @@ module.exports.post = fastify => ({ name: category.name, type: 'category', }, - userId: req.user.payload.id, + userId: req.user.id, }); return category; }, onRequest: [fastify.authenticate, fastify.isAdmin], -}); \ No newline at end of file +}); diff --git a/src/routes/api/admin/guilds/[guild]/panels.js b/src/routes/api/admin/guilds/[guild]/panels.js index 7181b1b..50991c4 100644 --- a/src/routes/api/admin/guilds/[guild]/panels.js +++ b/src/routes/api/admin/guilds/[guild]/panels.js @@ -134,10 +134,10 @@ module.exports.post = fastify => ({ id: channel.toString(), type: 'panel', }, - userId: req.user.payload.id, + userId: req.user.id, }); return true; }, onRequest: [fastify.authenticate, fastify.isAdmin], -}); \ No newline at end of file +}); diff --git a/src/routes/api/admin/guilds/[guild]/settings.js b/src/routes/api/admin/guilds/[guild]/settings.js index 0c6e929..09350f5 100644 --- a/src/routes/api/admin/guilds/[guild]/settings.js +++ b/src/routes/api/admin/guilds/[guild]/settings.js @@ -16,7 +16,7 @@ module.exports.delete = fastify => ({ name: client.guilds.cache.get(id), type: 'settings', }, - userId: req.user.payload.id, + userId: req.user.id, }); return settings; }, @@ -69,9 +69,9 @@ module.exports.patch = fastify => ({ name: client.guilds.cache.get(id).name, type: 'settings', }, - userId: req.user.payload.id, + userId: req.user.id, }); return settings; }, onRequest: [fastify.authenticate, fastify.isAdmin], -}); \ No newline at end of file +}); diff --git a/src/routes/api/admin/guilds/[guild]/tags/[tag].js b/src/routes/api/admin/guilds/[guild]/tags/[tag].js index 0f06192..13fb25d 100644 --- a/src/routes/api/admin/guilds/[guild]/tags/[tag].js +++ b/src/routes/api/admin/guilds/[guild]/tags/[tag].js @@ -30,7 +30,7 @@ module.exports.delete = fastify => ({ name: tag.name, type: 'tag', }, - userId: req.user.payload.id, + userId: req.user.id, }); return tag; @@ -97,10 +97,10 @@ module.exports.patch = fastify => ({ name: tag.name, type: 'tag', }, - userId: req.user.payload.id, + userId: req.user.id, }); return tag; }, onRequest: [fastify.authenticate, fastify.isAdmin], -}); \ No newline at end of file +}); diff --git a/src/routes/api/admin/guilds/[guild]/tags/index.js b/src/routes/api/admin/guilds/[guild]/tags/index.js index 35af225..46a84e8 100644 --- a/src/routes/api/admin/guilds/[guild]/tags/index.js +++ b/src/routes/api/admin/guilds/[guild]/tags/index.js @@ -56,10 +56,10 @@ module.exports.post = fastify => ({ name: tag.name, type: 'tag', }, - userId: req.user.payload.id, + userId: req.user.id, }); return tag; }, onRequest: [fastify.authenticate, fastify.isAdmin], -}); \ No newline at end of file +}); diff --git a/src/routes/api/admin/guilds/index.js b/src/routes/api/admin/guilds/index.js index 0b05178..9b6dc06 100644 --- a/src/routes/api/admin/guilds/index.js +++ b/src/routes/api/admin/guilds/index.js @@ -3,7 +3,7 @@ const { PermissionsBitField } = require('discord.js'); module.exports.get = fastify => ({ handler: async (req, res) => { const { client } = res.context.config; - const guilds = await (await fetch('https://discordapp.com/api/users/@me/guilds', { headers: { 'Authorization': `Bearer ${req.user.payload.accessToken}` } })).json(); + const guilds = await (await fetch('https://discordapp.com/api/users/@me/guilds', { headers: { 'Authorization': `Bearer ${req.user.accessToken}` } })).json(); res.send( guilds .filter(guild => guild.owner || new PermissionsBitField(guild.permissions.toString()).has(PermissionsBitField.Flags.ManageGuild)) @@ -16,4 +16,4 @@ module.exports.get = fastify => ({ ); }, onRequest: [fastify.authenticate], -}); \ No newline at end of file +}); diff --git a/src/routes/api/users/@me.js b/src/routes/api/users/@me/index.js similarity index 65% rename from src/routes/api/users/@me.js rename to src/routes/api/users/@me/index.js index f8fcf24..8608fc1 100644 --- a/src/routes/api/users/@me.js +++ b/src/routes/api/users/@me/index.js @@ -1,4 +1,4 @@ module.exports.get = fastify => ({ - handler: req => req.user.payload, + handler: req => req.user, onRequest: [fastify.authenticate], -}); \ No newline at end of file +}); diff --git a/src/routes/api/users/@me/key.js b/src/routes/api/users/@me/key.js new file mode 100644 index 0000000..7d8b202 --- /dev/null +++ b/src/routes/api/users/@me/key.js @@ -0,0 +1,19 @@ +module.exports.get = fastify => ({ + handler: async function (req, res) { // MUST NOT use arrow function syntax + if (process.env.PUBLIC_BOT === 'true') { + return res.code(400).send({ + error: 'Bad Request', + message: 'API keys are not available on public bots.', + statusCode: 400, + }); + } else { + return { + token: this.jwt.sign({ + createdAt: Date.now(), + id: req.user.id, + }), + }; + } + }, + onRequest: [fastify.authenticate], +}); diff --git a/src/routes/auth/callback.js b/src/routes/auth/callback.js index 2d04e9e..2860783 100644 --- a/src/routes/auth/callback.js +++ b/src/routes/auth/callback.js @@ -7,17 +7,15 @@ module.exports.get = () => ({ expires_in: expiresIn, } = await this.discord.getAccessTokenFromAuthorizationCodeFlow(req); const user = await (await fetch('https://discordapp.com/api/users/@me', { headers: { 'Authorization': `Bearer ${accessToken}` } })).json(); - const payload = { + const token = this.jwt.sign({ accessToken, - avatar: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.webp`, + avatar: user.avatar, discriminator: user.discriminator, expiresAt: Date.now() + (expiresIn * 1000), id: user.id, locale: user.locale, username: user.username, - - }; - const token = this.jwt.sign({ payload }); + }); res .setCookie('token', token, { domain, @@ -30,4 +28,4 @@ module.exports.get = () => ({ .redirect(this.states.get(req.query.state) || '/'); this.states.delete(req.query.state); }, -}); \ No newline at end of file +}); diff --git a/src/routes/auth/logout.js b/src/routes/auth/logout.js index 82677f6..22fc77c 100644 --- a/src/routes/auth/logout.js +++ b/src/routes/auth/logout.js @@ -1,7 +1,7 @@ module.exports.get = fastify => ({ handler: async function (req, res) { await fetch('https://discord.com/api/oauth2/token/revoke', { - body: new URLSearchParams({ token: req.user.payload.accessToken }).toString(), + body: new URLSearchParams({ token: req.user.accessToken }).toString(), headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, method: 'POST', }); @@ -10,4 +10,4 @@ module.exports.get = fastify => ({ .send('The token has been revoked.'); }, onRequest: [fastify.authenticate], -}); \ No newline at end of file +}); From 757f77fb1d7b6e6285e8e527b8f114de20f12b58 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 13 Mar 2023 17:04:42 +0000 Subject: [PATCH 363/409] fix: infinite redirect when logging in by setting `Same-Site=Lax`: https://bugs.chromium.org/p/chromium/issues/detail?id=696204#c41 --- src/routes/auth/callback.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/routes/auth/callback.js b/src/routes/auth/callback.js index 2860783..12c0622 100644 --- a/src/routes/auth/callback.js +++ b/src/routes/auth/callback.js @@ -6,6 +6,8 @@ module.exports.get = () => ({ access_token: accessToken, expires_in: expiresIn, } = await this.discord.getAccessTokenFromAuthorizationCodeFlow(req); + const redirect = this.states.get(req.query.state) || '/'; + this.states.delete(req.query.state); const user = await (await fetch('https://discordapp.com/api/users/@me', { headers: { 'Authorization': `Bearer ${accessToken}` } })).json(); const token = this.jwt.sign({ accessToken, @@ -16,16 +18,14 @@ module.exports.get = () => ({ locale: user.locale, username: user.username, }); - res - .setCookie('token', token, { - domain, - httpOnly: true, - maxAge: expiresIn, - path: '/', - sameSite: true, - secure: false, - }) - .redirect(this.states.get(req.query.state) || '/'); - this.states.delete(req.query.state); + res.setCookie('token', token, { + domain, + httpOnly: true, + maxAge: expiresIn, + path: '/', + sameSite: 'Lax', + secure: false, + }); + return res.redirect(303, redirect); }, }); From c7a1b05049cdc64d6c1006daa2b750c12e3e67ea Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 13 Mar 2023 17:09:51 +0000 Subject: [PATCH 364/409] chore: update dependencies --- package.json | 2 +- pnpm-lock.yaml | 46 +++++++++++++++++++++++----------------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 4d16c5e..1dba940 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "node": ">=18" }, "dependencies": { - "@discord-tickets/settings": "^2.1.0", + "@discord-tickets/settings": "^2.1.1", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d022fc8..3378b7b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,7 +3,7 @@ lockfileVersion: 5.4 specifiers: '@commitlint/cli': ^17.4.4 '@commitlint/config-conventional': ^17.4.4 - '@discord-tickets/settings': ^2.1.0 + '@discord-tickets/settings': ^2.1.1 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -43,7 +43,7 @@ specifiers: zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 2.1.0_svelte@3.56.0 + '@discord-tickets/settings': 2.1.1_svelte@3.56.0 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -204,15 +204,15 @@ packages: '@commitlint/execute-rule': 17.4.0 '@commitlint/resolve-extends': 17.4.4 '@commitlint/types': 17.4.4 - '@types/node': 18.15.0 + '@types/node': 18.15.1 chalk: 4.1.2 cosmiconfig: 8.1.0 - cosmiconfig-typescript-loader: 4.3.0_lmcvhluzh3a7wwwkanu7ndhele + cosmiconfig-typescript-loader: 4.3.0_jad34rn52rvsukepwt6d7357fa lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1_lwgqdwokjtwlohdqtbb6s252kq + ts-node: 10.9.1_r2vohjtqb453xa4ljp4dw3sqb4 typescript: 4.9.5 transitivePeerDependencies: - '@swc/core' @@ -293,8 +293,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@discord-tickets/settings/2.1.0_svelte@3.56.0: - resolution: {integrity: sha512-eWs4hQ/5VtbMsPcs/jOHwrPBOB5XBKuGYY0xILUqYFINa5oJsHIzHEjHjEIDcxW2xUJ3g92soBe5GVM3PjkfMA==} + /@discord-tickets/settings/2.1.1_svelte@3.56.0: + resolution: {integrity: sha512-fqxK0SRyFPBddtccu6iFiJJvfToOQvkjazlYVguekEiYdmMdT61vi83r1SXYx3Bs0qKyXMUnmWGOA/+4jIOMGA==} dependencies: '@fortawesome/fontawesome-free': 6.3.0 '@skyra/discord-components-core': 3.6.0 @@ -346,7 +346,7 @@ packages: discord-api-types: 0.37.35 file-type: 18.2.1 tslib: 2.5.0 - undici: 5.20.0 + undici: 5.21.0 dev: false /@discordjs/util/0.2.0: @@ -674,8 +674,8 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - /@types/node/18.15.0: - resolution: {integrity: sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==} + /@types/node/18.15.1: + resolution: {integrity: sha512-U2TWca8AeHSmbpi314QBESRk7oPjSZjDsR+c+H4ECC1l+kFgpZf8Ydhv3SJpPy51VyZHHqxlb6mTTqYNNRVAIw==} /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -684,7 +684,7 @@ packages: /@types/ws/8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: - '@types/node': 18.15.0 + '@types/node': 18.15.1 dev: false /JSONStream/1.3.5: @@ -1313,7 +1313,7 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /cosmiconfig-typescript-loader/4.3.0_lmcvhluzh3a7wwwkanu7ndhele: + /cosmiconfig-typescript-loader/4.3.0_jad34rn52rvsukepwt6d7357fa: resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -1322,9 +1322,9 @@ packages: ts-node: '>=10' typescript: '>=3' dependencies: - '@types/node': 18.15.0 + '@types/node': 18.15.1 cosmiconfig: 8.1.0 - ts-node: 10.9.1_lwgqdwokjtwlohdqtbb6s252kq + ts-node: 10.9.1_r2vohjtqb453xa4ljp4dw3sqb4 typescript: 4.9.5 dev: true @@ -1442,7 +1442,7 @@ packages: fast-deep-equal: 3.1.3 lodash.snakecase: 4.1.1 tslib: 2.5.0 - undici: 5.20.0 + undici: 5.21.0 ws: 8.13.0_3cxu5zja4e2r5wmvge7mdcljwq transitivePeerDependencies: - bufferutil @@ -1765,7 +1765,7 @@ packages: rfdc: 1.3.0 secure-json-parse: 2.7.0 semver: 7.3.8 - tiny-lru: 10.0.1 + tiny-lru: 10.1.0 transitivePeerDependencies: - supports-color dev: false @@ -3614,9 +3614,9 @@ packages: readable-stream: 3.6.2 dev: true - /tiny-lru/10.0.1: - resolution: {integrity: sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==} - engines: {node: '>=6'} + /tiny-lru/10.1.0: + resolution: {integrity: sha512-8nOOqPPLrvE4gcBQZcvPZbJANNIwY0Mwu1h3kklq7mraCGG/JDNcu/QBZt4HQArGClrGxmQ3V/wgHy9xmK38gQ==} + engines: {node: '>=12'} dev: false /tmp/0.0.33: @@ -3666,7 +3666,7 @@ packages: resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==} dev: false - /ts-node/10.9.1_lwgqdwokjtwlohdqtbb6s252kq: + /ts-node/10.9.1_r2vohjtqb453xa4ljp4dw3sqb4: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -3685,7 +3685,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 18.15.0 + '@types/node': 18.15.1 acorn: 8.8.2 acorn-walk: 8.2.0 arg: 4.1.3 @@ -3758,8 +3758,8 @@ packages: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true - /undici/5.20.0: - resolution: {integrity: sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==} + /undici/5.21.0: + resolution: {integrity: sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==} engines: {node: '>=12.18'} dependencies: busboy: 1.6.0 From 8b692fa5e218d5bb7fb2a67e70d7aceb63326870 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 17 Mar 2023 21:42:47 +0000 Subject: [PATCH 365/409] fix: update categories cache when guild settings are changed --- src/routes/api/admin/guilds/[guild]/settings.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/routes/api/admin/guilds/[guild]/settings.js b/src/routes/api/admin/guilds/[guild]/settings.js index 09350f5..2c14ad9 100644 --- a/src/routes/api/admin/guilds/[guild]/settings.js +++ b/src/routes/api/admin/guilds/[guild]/settings.js @@ -54,9 +54,16 @@ module.exports.patch = fastify => ({ const original = await client.prisma.guild.findUnique({ where: { id } }); const settings = await client.prisma.guild.update({ data: data, + include: { categories: { select: { id: true } } }, where: { id }, }); + // Update cached categories, which include guild settings + for (const { id } of settings.categories) await client.tickets.getCategory(id, true); + + // don't log the categories + delete settings.categories; + logAdminEvent(client, { action: 'update', diff: { From faf6edc463044b37522003f97451dfcea41b4f76 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 17 Mar 2023 21:45:53 +0000 Subject: [PATCH 366/409] feat: working hours (#304) --- package.json | 5 +- pnpm-lock.yaml | 102 ++++++++++++++++++++----------------- src/i18n/en-GB.yml | 11 ++++ src/lib/tickets/manager.js | 49 ++++++++++++++++-- 4 files changed, 113 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 1dba940..27d4392 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.8", + "version": "4.0.0-beta.9", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", @@ -40,7 +40,7 @@ "node": ">=18" }, "dependencies": { - "@discord-tickets/settings": "^2.1.1", + "@discord-tickets/settings": "^2.1.3", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", @@ -65,6 +65,7 @@ "object-diffy": "^1.0.4", "prisma": "^4.11.0", "semver": "^7.3.8", + "spacetime": "^7.4.1", "terminal-link": "^2.1.1", "yaml": "^1.10.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3378b7b..4a54282 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,7 +3,7 @@ lockfileVersion: 5.4 specifiers: '@commitlint/cli': ^17.4.4 '@commitlint/config-conventional': ^17.4.4 - '@discord-tickets/settings': ^2.1.1 + '@discord-tickets/settings': ^2.1.3 '@eartharoid/dbf': ^0.3.3 '@eartharoid/dtf': ^2.0.1 '@eartharoid/i18n': ^1.2.1 @@ -37,13 +37,14 @@ specifiers: object-diffy: ^1.0.4 prisma: ^4.11.0 semver: ^7.3.8 + spacetime: ^7.4.1 terminal-link: ^2.1.1 utf-8-validate: ^5.0.10 yaml: ^1.10.2 zlib-sync: ^0.1.8 dependencies: - '@discord-tickets/settings': 2.1.1_svelte@3.56.0 + '@discord-tickets/settings': 2.1.3_svelte@3.57.0 '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq '@eartharoid/dtf': 2.0.1 '@eartharoid/i18n': 1.2.1 @@ -68,6 +69,7 @@ dependencies: object-diffy: 1.0.4 prisma: 4.11.0 semver: 7.3.8 + spacetime: 7.4.1 terminal-link: 2.1.1 yaml: 1.10.2 @@ -204,15 +206,15 @@ packages: '@commitlint/execute-rule': 17.4.0 '@commitlint/resolve-extends': 17.4.4 '@commitlint/types': 17.4.4 - '@types/node': 18.15.1 + '@types/node': 18.15.3 chalk: 4.1.2 - cosmiconfig: 8.1.0 - cosmiconfig-typescript-loader: 4.3.0_jad34rn52rvsukepwt6d7357fa + cosmiconfig: 8.1.2 + cosmiconfig-typescript-loader: 4.3.0_kpxsywhbgzx5buqgdnmsfobuwi lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1_r2vohjtqb453xa4ljp4dw3sqb4 + ts-node: 10.9.1_cbfmry4sbbh4vatmdrsmatfg5a typescript: 4.9.5 transitivePeerDependencies: - '@swc/core' @@ -293,8 +295,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@discord-tickets/settings/2.1.1_svelte@3.56.0: - resolution: {integrity: sha512-fqxK0SRyFPBddtccu6iFiJJvfToOQvkjazlYVguekEiYdmMdT61vi83r1SXYx3Bs0qKyXMUnmWGOA/+4jIOMGA==} + /@discord-tickets/settings/2.1.3_svelte@3.57.0: + resolution: {integrity: sha512-rKwt87oXBydudfnKkSmgbnNtCFbkYM0ZBuZLqSfl+H8N1kT+bylGoyXfAF2OLoEc5vK/EyBCEP9trZMO60774g==} dependencies: '@fortawesome/fontawesome-free': 6.3.0 '@skyra/discord-components-core': 3.6.0 @@ -304,7 +306,7 @@ packages: ms: 2.1.3 postcss: 8.4.21 sortablejs: 1.15.0 - svelte-modals: 1.2.1_svelte@3.56.0 + svelte-modals: 1.2.1_svelte@3.57.0 svelte-toasts: 1.1.2 transitivePeerDependencies: - svelte @@ -317,7 +319,7 @@ packages: '@discordjs/formatters': 0.2.0 '@discordjs/util': 0.2.0 '@sapphire/shapeshift': 3.8.1 - discord-api-types: 0.37.35 + discord-api-types: 0.37.36 fast-deep-equal: 3.1.3 ts-mixer: 6.0.3 tslib: 2.5.0 @@ -332,7 +334,7 @@ packages: resolution: {integrity: sha512-vn4oMSXuMZUm8ITqVOtvE7/fMMISj4cI5oLsR09PEQXHKeKDAMLltG/DWeeIs7Idfy6V8Fk3rn1e69h7NfzuNA==} engines: {node: '>=16.9.0'} dependencies: - discord-api-types: 0.37.35 + discord-api-types: 0.37.36 dev: false /@discordjs/rest/1.6.0: @@ -343,7 +345,7 @@ packages: '@discordjs/util': 0.2.0 '@sapphire/async-queue': 1.5.0 '@sapphire/snowflake': 3.4.0 - discord-api-types: 0.37.35 + discord-api-types: 0.37.36 file-type: 18.2.1 tslib: 2.5.0 undici: 5.21.0 @@ -376,8 +378,8 @@ packages: resolution: {integrity: sha512-nMQdHrGgpw+vNL5DbivELW2K3KAUGaMvTjjmFsEPf8mUW8+LAgRjvfFn2gkJq1mnlD6HoqUaHqEL4YpWr2T5MA==} dev: false - /@eslint-community/eslint-utils/4.2.0_eslint@8.36.0: - resolution: {integrity: sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==} + /@eslint-community/eslint-utils/4.3.0_eslint@8.36.0: + resolution: {integrity: sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -639,13 +641,13 @@ packages: resolution: {integrity: sha512-13aVNoleHyAMqd4lcfcfjEduV4QavlHn9P2TUlTUPQC8m8cd8n8wSUTDUxKhQbYpzJ0hn7AHnlmLixfiqQT4FQ==} engines: {node: '>=v14.0.0'} dependencies: - '@stencil/core': 2.22.2 + '@stencil/core': 2.22.3 clsx: 1.2.1 hex-to-rgba: 2.0.1 dev: false - /@stencil/core/2.22.2: - resolution: {integrity: sha512-r+vbxsGNcBaV1VDOYW25lv4QfXTlNoIb5GpUX7rZ+cr59yqYCZC5tlV+IzX6YgHKW62ulCc9M3RYtTfHtNbNNw==} + /@stencil/core/2.22.3: + resolution: {integrity: sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==} engines: {node: '>=12.10.0', npm: '>=6.0.0'} hasBin: true dev: false @@ -674,8 +676,8 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - /@types/node/18.15.1: - resolution: {integrity: sha512-U2TWca8AeHSmbpi314QBESRk7oPjSZjDsR+c+H4ECC1l+kFgpZf8Ydhv3SJpPy51VyZHHqxlb6mTTqYNNRVAIw==} + /@types/node/18.15.3: + resolution: {integrity: sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==} /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -684,7 +686,7 @@ packages: /@types/ws/8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: - '@types/node': 18.15.1 + '@types/node': 18.15.3 dev: false /JSONStream/1.3.5: @@ -1313,7 +1315,7 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /cosmiconfig-typescript-loader/4.3.0_jad34rn52rvsukepwt6d7357fa: + /cosmiconfig-typescript-loader/4.3.0_kpxsywhbgzx5buqgdnmsfobuwi: resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -1322,14 +1324,14 @@ packages: ts-node: '>=10' typescript: '>=3' dependencies: - '@types/node': 18.15.1 - cosmiconfig: 8.1.0 - ts-node: 10.9.1_r2vohjtqb453xa4ljp4dw3sqb4 + '@types/node': 18.15.3 + cosmiconfig: 8.1.2 + ts-node: 10.9.1_cbfmry4sbbh4vatmdrsmatfg5a typescript: 4.9.5 dev: true - /cosmiconfig/8.1.0: - resolution: {integrity: sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg==} + /cosmiconfig/8.1.2: + resolution: {integrity: sha512-rmpUFKMZiawLfug8sP4NbpBSOpWftZB6UACOLEiNbnRAYM1TzgQuTWlMYFRuPgmoTCkcOxSMwQJQpJmiXv/eHw==} engines: {node: '>=14'} dependencies: import-fresh: 3.3.0 @@ -1423,8 +1425,8 @@ packages: engines: {node: '>=0.3.1'} dev: true - /discord-api-types/0.37.35: - resolution: {integrity: sha512-iyKZ/82k7FX3lcmHiAvvWu5TmyfVo78RtghBV/YsehK6CID83k5SI03DKKopBcln+TiEIYw5MGgq7SJXSpNzMg==} + /discord-api-types/0.37.36: + resolution: {integrity: sha512-Nlxmp10UpVr/utgZ9uODQvG2Or+5w7LFrvFMswyeKC9l/+UaqGT6H0OVgEFhu9GEO4U6K7NNO5W8Carv7irnCA==} dev: false /discord.js/14.8.0_3cxu5zja4e2r5wmvge7mdcljwq: @@ -1438,7 +1440,7 @@ packages: '@discordjs/util': 0.2.0 '@sapphire/snowflake': 3.4.0 '@types/ws': 8.5.4 - discord-api-types: 0.37.35 + discord-api-types: 0.37.36 fast-deep-equal: 3.1.3 lodash.snakecase: 4.1.1 tslib: 2.5.0 @@ -1553,7 +1555,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.2.0_eslint@8.36.0 + '@eslint-community/eslint-utils': 4.3.0_eslint@8.36.0 '@eslint-community/regexpp': 4.4.0 '@eslint/eslintrc': 2.0.1 '@eslint/js': 8.36.0 @@ -1655,8 +1657,8 @@ packages: strip-final-newline: 2.0.0 dev: true - /execa/7.1.0: - resolution: {integrity: sha512-T6nIJO3LHxUZ6ahVRaxXz9WLEruXLqdcluA+UuTptXmLM7nDAn9lx9IfkxPyzEL21583qSt4RmL44pO71EHaJQ==} + /execa/7.1.1: + resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==} engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} dependencies: cross-spawn: 7.0.3 @@ -1765,7 +1767,7 @@ packages: rfdc: 1.3.0 secure-json-parse: 2.7.0 semver: 7.3.8 - tiny-lru: 10.1.0 + tiny-lru: 10.2.1 transitivePeerDependencies: - supports-color dev: false @@ -1882,7 +1884,7 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} dependencies: - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.0 dev: false @@ -1891,7 +1893,7 @@ packages: resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} engines: {node: '>=14.14'} dependencies: - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.0 dev: true @@ -2007,8 +2009,8 @@ packages: type-fest: 0.20.2 dev: true - /graceful-fs/4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + /graceful-fs/4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} /grapheme-splitter/1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} @@ -2309,7 +2311,7 @@ packages: dependencies: universalify: 2.0.0 optionalDependencies: - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 /jsonparse/1.3.1: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} @@ -2378,7 +2380,7 @@ packages: cli-truncate: 3.1.0 commander: 10.0.0 debug: 4.3.4 - execa: 7.1.0 + execa: 7.1.1 lilconfig: 2.1.0 listr2: 5.0.8 micromatch: 4.0.5 @@ -2415,7 +2417,7 @@ packages: resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} engines: {node: '>=4'} dependencies: - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 parse-json: 4.0.0 pify: 3.0.0 strip-bom: 3.0.0 @@ -3380,6 +3382,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /spacetime/7.4.1: + resolution: {integrity: sha512-khQpvLNMhHFzfFJslMfunqqsjOxdmoDDYX5Wh4qYb8N6f8vBPI6HvbT7Wb2wcMa+oP1Xh1HpEcwfPFrj8UfvHQ==} + dev: false + /spdx-correct/3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: @@ -3544,20 +3550,20 @@ packages: engines: {node: '>= 0.4'} dev: true - /svelte-modals/1.2.1_svelte@3.56.0: + /svelte-modals/1.2.1_svelte@3.57.0: resolution: {integrity: sha512-7MEKUx5wb5YppkXWFGeRlYM5FMGEnpix39u9Y6GtCNTMXRDZ7DB2Z50IYLMRTMW5lOsCdtJgFbB0E3iZMKmsAA==} peerDependencies: svelte: ^3.0.0 dependencies: - svelte: 3.56.0 + svelte: 3.57.0 dev: false /svelte-toasts/1.1.2: resolution: {integrity: sha512-m+yL4eEKXyJoyjTYaH1j1GFwF0Pi8YDqnVfwWPDmwi4712iZesv+TNCmToSNlav3R5Vkmc8ZBRkT8DOcu3sywQ==} dev: false - /svelte/3.56.0: - resolution: {integrity: sha512-LvXiJbjdvJKwB/0CQyYpDX0q+hFqCyWmybzC2G6eK1tJJA/RSRCytTfNmjHv+RHlLuA70vWG7nXp6gbeErYvRA==} + /svelte/3.57.0: + resolution: {integrity: sha512-WMXEvF+RtAaclw0t3bPDTUe19pplMlfyKDsixbHQYgCWi9+O9VN0kXU1OppzrB9gPAvz4NALuoca2LfW2bOjTQ==} engines: {node: '>= 8'} dev: false @@ -3614,8 +3620,8 @@ packages: readable-stream: 3.6.2 dev: true - /tiny-lru/10.1.0: - resolution: {integrity: sha512-8nOOqPPLrvE4gcBQZcvPZbJANNIwY0Mwu1h3kklq7mraCGG/JDNcu/QBZt4HQArGClrGxmQ3V/wgHy9xmK38gQ==} + /tiny-lru/10.2.1: + resolution: {integrity: sha512-cxcNyX4O50FDnB5x9jdEJupYC+9PPutt6wT16TaOtXeHfV9t41aFqg0VniB9YK5KmBeqmZaYriNMajm/5TtA7Q==} engines: {node: '>=12'} dev: false @@ -3666,7 +3672,7 @@ packages: resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==} dev: false - /ts-node/10.9.1_r2vohjtqb453xa4ljp4dw3sqb4: + /ts-node/10.9.1_cbfmry4sbbh4vatmdrsmatfg5a: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -3685,7 +3691,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 18.15.1 + '@types/node': 18.15.3 acorn: 8.8.2 acorn-walk: 8.2.0 arg: 4.1.3 diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 26e09d8..b2c267e 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -420,3 +420,14 @@ ticket: topic: Topic title: ℹ️ Reference released: ♻️ {user} has released this ticket. + working_hours: + next: + description: + We'll be back at (), although + you may receive a response before then. + title: 🕗 We're not working at the moment + today: + description: + You may receive a response before, but we don't start working until + today (). + title: 🕗 We're not working at the moment diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index f744d54..a6d9d6a 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -18,6 +18,7 @@ const ExtendedEmbedBuilder = require('../embed'); const { logTicketEvent } = require('../logging'); const { isStaff } = require('../users'); const { Collection } = require('discord.js'); +const spacetime = require('spacetime'); const Cryptr = require('cryptr'); const { decrypt, @@ -440,8 +441,6 @@ module.exports = class TicketManager { ), ]; - // TODO: !staff || workingHours - if (answers) { embeds.push( new ExtendedEmbedBuilder() @@ -618,6 +617,7 @@ module.exports = class TicketManager { }; if (referencesTicketId) data.referencesTicket = { connect: { id: referencesTicketId } }; if (answers) data.questionAnswers = { createMany: { data: answers } }; + await interaction.editReply({ components: [], embeds: [ @@ -645,7 +645,7 @@ module.exports = class TicketManager { if (category.guild.archive && message) { if ( - await this.client.prisma.archivedMessage.findUnique({ where: { id: message.id } })|| + await this.client.prisma.archivedMessage.findUnique({ where: { id: message.id } }) || await this.archiver.saveMessage(ticket.id, message, true) ) { await this.client.prisma.ticket.update({ @@ -682,6 +682,47 @@ module.exports = class TicketManager { ], }); } + + const workingHours = category.guild.workingHours; + const timezone = workingHours[0]; + workingHours.shift(); // remove timezone + const now = spacetime.now(timezone); + const currentHours = workingHours[now.day()]; + const start = now.time(currentHours[0]); + const end = now.time(currentHours[1]); + + if (currentHours[0] === currentHours[1] || now.isAfter(end)) { // staff have the day off or have finished for the day + // first look for the next working day *this* week (after today) + let nextIndex = workingHours.findIndex((hours, i) => i > now.day() && hours[0] !== hours[1]); + // if there isn't one, look for the next working day *next* week (before and including today's weekday) + if (!nextIndex) nextIndex = workingHours.findIndex((hours, i) => i <= now.day() && hours[0] !== hours[1]); + if (nextIndex) { + const next = workingHours[nextIndex]; + let then = now.add(nextIndex - now.day(), 'day'); + if (nextIndex <= now.day()) then = then.add(1, 'week'); + const timestamp = Math.ceil(then.time(next[0]).d.getTime() / 1000); // in seconds + await channel.send({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(category.guild.primaryColour) + .setTitle(getMessage('ticket.working_hours.next.title')) + .setDescription(getMessage('ticket.working_hours.next.description', { timestamp })), + ], + }); + } + } else if (now.isBefore(start)) { // staff haven't started working yet + const timestamp = Math.ceil(start.d.getTime() / 1000); // in seconds + await channel.send({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(category.guild.primaryColour) + .setTitle(getMessage('ticket.working_hours.today.title')) + .setDescription(getMessage('ticket.working_hours.today.description', { timestamp })), + ], + }); + } + + // TODO: !staff } /** @@ -1226,4 +1267,4 @@ module.exports = class TicketManager { } } -}; \ No newline at end of file +}; From ee90fed50a3097d044299d5aa4bcaa2ac914468d Mon Sep 17 00:00:00 2001 From: Noel Horvath Date: Thu, 23 Mar 2023 14:41:49 +0100 Subject: [PATCH 367/409] feat(i18n): update Hungarian translations [skip ci] Currently translated at 99.6% (255 of 256 strings) Co-authored-by: Noel Horvath Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/hu/ Translation: Discord Tickets/Bot --- src/i18n/hu.yml | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/i18n/hu.yml b/src/i18n/hu.yml index 49e6b84..f644ca5 100644 --- a/src/i18n/hu.yml +++ b/src/i18n/hu.yml @@ -133,7 +133,7 @@ commands: docs: Dokumentáció feedback: Visszajelzés links: Hasznos linkek - settings: Bot beállítások + settings: Bot konfiguráció description: '**Használd a(z) {command} parancsot a hibajegy létrehozásához..**' description: Segítség menü megjelenítése title: Segítség @@ -181,14 +181,21 @@ commands: title: ✅ Prioritás beállítva description: 'A hibajegy prioritása beállításra került: `{priority}`.' description: Hibajegy prioritásának beállítása + not_staff: + description: Csak a személyzet tagjai tudják megváltoztatni a hibajegy prioritását. + title: ❌ Hiba move: description: Hibajegy áthelyezése másik kategóriába - moved: '🗃️ {by} áthelyezte a hibajegyet innen: **{from}** ide: **{to}**.' + moved: '🗃️ {by} áthelyezte ezt a jegyet a következőről: **{from}** a következőre: + **{to}**.' options: category: description: Kategória, amibe át szeretnéd helyezni a hibajegyet name: kategória - name: áthelyezés + name: mozgat + not_staff: + title: ❌ Hiba + description: Csak a személyzet tagjai mozgathatják a jegyeket. remove: options: ticket: @@ -199,8 +206,8 @@ commands: description: A felhasználó, akit el szeretnél távolítani a hibajegyből name: eltávolítás not_staff: - description: Csak a személyzeti tagok tudnak eltávolítani felhasználókat a - felhasználók hibajegyéből. + description: Csak személyzeti tagok tudnak eltávolítani felhasználókat a felhasználók + hibajegyéből. title: ❌ Hiba removed: ⬅️ {removed} eltávolításra került {by} által. success: @@ -250,6 +257,9 @@ commands: claim: description: Hibajegy begyűjtése name: begyűjtés + not_staff: + description: Csak a személyzet tagjai tudják begyűjteni a hibajegyet. + title: ❌ Hiba topic: description: Hibajegy témájának megváltoztatása name: téma @@ -329,6 +339,10 @@ misc: unknown_category: description: Kérlek próbálj egy másik kategóriát. title: ❌ Nem létezik ilyen hibajegy kategória + update: + description: "> [`{version}` megtekintése a GitHubon]({github})\n> [Változásnapló]({changelog})\n\ + > [Frissítési útmutató]({guide})\n" + title: Frissítés elérhető menus: category: placeholder: Kategória választása @@ -377,6 +391,15 @@ ticket: references_message: title: ℹ️ Referencia description: Referencia [üzenet]({url}) elküldve {timestamp} {author} által. + working_hours: + next: + description: Visszatérünk a () időpontra, + bár előfordulhat, hogy ez előtt választ kap. + title: 🕗 Jelenleg nem dolgozunk + today: + description: 'Előfordulhat, hogy korábban is kap választ, azonban nem dolgozunk + -ig (ma: )' + title: 🕗 Jelenleg nem dolgozunk modals: feedback: rating: From 59dec2880414bc2e3c6712433717c5e56184e788 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 23 Mar 2023 21:48:46 +0000 Subject: [PATCH 368/409] feat: notify when staff are offline (closes #304) --- package.json | 2 +- src/client.js | 5 +++-- src/i18n/en-GB.yml | 5 +++++ src/lib/tickets/manager.js | 23 ++++++++++++++++++++++- src/listeners/client/messageCreate.js | 21 ++++++++++++++++++++- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 27d4392..b51509e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.9", + "version": "4.0.0-beta.10", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", diff --git a/src/client.js b/src/client.js index 82122fb..0b2567d 100644 --- a/src/client.js +++ b/src/client.js @@ -23,10 +23,11 @@ module.exports = class Client extends FrameworkClient { GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, + GatewayIntentBits.GuildPresences, ], partials: [ - Partials.Message, Partials.Channel, + Partials.Message, Partials.Reaction, ], }); @@ -66,4 +67,4 @@ module.exports = class Client extends FrameworkClient { await this.prisma.$disconnect(); return super.destroy(); } -}; \ No newline at end of file +}; diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index b2c267e..042d884 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -403,6 +403,11 @@ ticket: description: Your changes have been saved. title: ✅ Ticket updated feedback: Thank you for your feedback. + offline: + description: + There aren't any staff members available at the moment, so it may + take longer than usual to get a response. + title: 😴 We're not online opening_message: content: | {staff} diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index a6d9d6a..cf9ffba 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -690,6 +690,7 @@ module.exports = class TicketManager { const currentHours = workingHours[now.day()]; const start = now.time(currentHours[0]); const end = now.time(currentHours[1]); + let working = true; if (currentHours[0] === currentHours[1] || now.isAfter(end)) { // staff have the day off or have finished for the day // first look for the next working day *this* week (after today) @@ -697,6 +698,7 @@ module.exports = class TicketManager { // if there isn't one, look for the next working day *next* week (before and including today's weekday) if (!nextIndex) nextIndex = workingHours.findIndex((hours, i) => i <= now.day() && hours[0] !== hours[1]); if (nextIndex) { + working = false; const next = workingHours[nextIndex]; let then = now.add(nextIndex - now.day(), 'day'); if (nextIndex <= now.day()) then = then.add(1, 'week'); @@ -711,6 +713,7 @@ module.exports = class TicketManager { }); } } else if (now.isBefore(start)) { // staff haven't started working yet + working = false; const timestamp = Math.ceil(start.d.getTime() / 1000); // in seconds await channel.send({ embeds: [ @@ -722,7 +725,25 @@ module.exports = class TicketManager { }); } - // TODO: !staff + + if (working) { + let online = 0; + for (const [, member] of message.channel.members) { + if (!await isStaff(message.channel.guild, member.id)) continue; + if (member.presence && member.presence !== 'offline') online++; + } + if (online === 0) { + await channel.send({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(category.guild.primaryColour) + .setTitle(getMessage('ticket.offline.title')) + .setDescription(getMessage('ticket.offline.description')), + ], + }); + this.client.keyv.set(`offline/${channel.id}`, Date.now(), ms('1h')); + } + } } /** diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js index 1cee0ea..96a31d0 100644 --- a/src/listeners/client/messageCreate.js +++ b/src/listeners/client/messageCreate.js @@ -182,6 +182,7 @@ module.exports = class extends Listener { } else { const settings = await client.prisma.guild.findUnique({ where: { id: message.guild.id } }); if (!settings) return; + const getMessage = client.i18n.getLocale(settings.locale); let ticket = await client.prisma.ticket.findUnique({ where: { id: message.channel.id } }); if (ticket) { @@ -230,7 +231,25 @@ module.exports = class extends Listener { } } - // TODO: if (!message.author.bot) staff status alert, working hours alerts + if (!message.author.bot) { + const key = `offline/${message.channel.id}`; + let online = 0; + for (const [, member] of message.channel.members) { + if (!await isStaff(message.channel.guild, member.id)) continue; + if (member.presence && member.presence !== 'offline') online++; + } + if (online === 0 && !client.keyv.has(key)) { + await message.channel.send({ + embeds: [ + new EmbedBuilder() + .setColor(settings.primaryColour) + .setTitle(getMessage('ticket.offline.title')) + .setDescription(getMessage('ticket.offline.description')), + ], + }); + client.keyv.set(key, Date.now(), ms('1h')); + } + } } // auto-tag From b6d0c0e1df8c21dea1d9326c23e4b476392ed9bf Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 24 Mar 2023 19:11:57 +0000 Subject: [PATCH 369/409] fix(i18n): lowercase command name (fr) --- src/i18n/fr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/fr.yml b/src/i18n/fr.yml index 6ea58d3..dde7026 100644 --- a/src/i18n/fr.yml +++ b/src/i18n/fr.yml @@ -137,7 +137,7 @@ commands: options: references: description: Le numéro d'un ticket associé - name: Références + name: références priority: name: priorité options: From 68e3ba69a9f20d9a1ba70bfd204afebb3ac58481 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 24 Mar 2023 19:14:39 +0000 Subject: [PATCH 370/409] fix(i18n): remove disallowed `/claim` command name --- src/i18n/fr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/fr.yml b/src/i18n/fr.yml index dde7026..1d0fc1d 100644 --- a/src/i18n/fr.yml +++ b/src/i18n/fr.yml @@ -102,7 +102,7 @@ commands: title: ❓Êtes vous sûr ? claim: description: S'approprier un ticket - name: S'approprier + name: claim not_staff: description: Seul les membres de l'équipe peuvent s'approprier un ticket. title: ❌ Erreur From 36b9fd3502537f5be0a5b35f5619d38cbe0daedc Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 28 Mar 2023 23:08:58 +0100 Subject: [PATCH 371/409] fix: use `channel` not `message.channel` --- src/lib/tickets/manager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index cf9ffba..2035027 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -728,8 +728,8 @@ module.exports = class TicketManager { if (working) { let online = 0; - for (const [, member] of message.channel.members) { - if (!await isStaff(message.channel.guild, member.id)) continue; + for (const [, member] of channel.members) { + if (!await isStaff(channel.guild, member.id)) continue; if (member.presence && member.presence !== 'offline') online++; } if (online === 0) { From 4010b9735cfacc21ea311255ec6bf1cad513cb77 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 31 Mar 2023 15:08:57 +0200 Subject: [PATCH 372/409] feat(i18n): add Greek translations [skip ci] Co-authored-by: Isaac --- src/i18n/el.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/el.yml diff --git a/src/i18n/el.yml b/src/i18n/el.yml new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/i18n/el.yml @@ -0,0 +1 @@ +{} From 6b70e315b589e224ef783a56562065a8426c93b7 Mon Sep 17 00:00:00 2001 From: Shinaii Date: Mon, 17 Apr 2023 01:51:36 +0200 Subject: [PATCH 373/409] feat(i18n): update German translations [skip ci] Currently translated at 93.4% (241 of 258 strings) Co-authored-by: Shinaii Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 709f893..a005574 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -212,6 +212,7 @@ commands: options: for: name: für + description: Der Benutzer, an den das Tag gerichtet werden soll tag: name: tag description: Der Name des zu verwendenden Tags From b2a8b039df06c4496f0d3ac4bad15e2657952fe4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 28 Apr 2023 16:49:27 +0100 Subject: [PATCH 374/409] chore: update lockfile --- pnpm-lock.yaml | 1471 ++++++++++++++++++++++++------------------------ 1 file changed, 730 insertions(+), 741 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a54282..c19d5cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,110 +1,149 @@ -lockfileVersion: 5.4 - -specifiers: - '@commitlint/cli': ^17.4.4 - '@commitlint/config-conventional': ^17.4.4 - '@discord-tickets/settings': ^2.1.3 - '@eartharoid/dbf': ^0.3.3 - '@eartharoid/dtf': ^2.0.1 - '@eartharoid/i18n': ^1.2.1 - '@fastify/cookie': ^6.0.0 - '@fastify/jwt': ^5.0.1 - '@fastify/oauth2': ^5.1.0 - '@prisma/client': ^4.11.0 - all-contributors-cli: ^6.24.0 - boxen: ^7.0.2 - bufferutil: ^4.0.7 - conventional-changelog-cli: ^2.2.2 - cryptr: ^6.2.0 - discord.js: ^14.8.0 - dotenv: ^16.0.3 - erlpack: github:discord/erlpack - eslint: ^8.36.0 - eslint-plugin-unused-imports: ^2.0.0 - fastify: ^4.14.1 - figlet: ^1.5.2 - fs-extra: ^10.1.0 - husky: ^8.0.3 - keyv: ^4.5.2 - leeks.js: ^0.3.0 - leekslazylogger: ^5.0.1 - lint-staged: ^13.2.0 - ms: ^2.1.3 - mustache: ^4.2.0 - node-dir: ^0.1.17 - node-emoji: ^1.11.0 - nodemon: ^2.0.21 - object-diffy: ^1.0.4 - prisma: ^4.11.0 - semver: ^7.3.8 - spacetime: ^7.4.1 - terminal-link: ^2.1.1 - utf-8-validate: ^5.0.10 - yaml: ^1.10.2 - zlib-sync: ^0.1.8 +lockfileVersion: '6.0' dependencies: - '@discord-tickets/settings': 2.1.3_svelte@3.57.0 - '@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq - '@eartharoid/dtf': 2.0.1 - '@eartharoid/i18n': 1.2.1 - '@fastify/cookie': 6.0.0 - '@fastify/jwt': 5.0.1 - '@fastify/oauth2': 5.1.0 - '@prisma/client': 4.11.0_prisma@4.11.0 - boxen: 7.0.2 - cryptr: 6.2.0 - discord.js: 14.8.0_3cxu5zja4e2r5wmvge7mdcljwq - dotenv: 16.0.3 - fastify: 4.14.1 - figlet: 1.5.2 - fs-extra: 10.1.0 - keyv: 4.5.2 - leeks.js: 0.3.0 - leekslazylogger: 5.0.1 - ms: 2.1.3 - mustache: 4.2.0 - node-dir: 0.1.17 - node-emoji: 1.11.0 - object-diffy: 1.0.4 - prisma: 4.11.0 - semver: 7.3.8 - spacetime: 7.4.1 - terminal-link: 2.1.1 - yaml: 1.10.2 + '@discord-tickets/settings': + specifier: ^2.1.3 + version: 2.1.3(svelte@3.57.0) + '@eartharoid/dbf': + specifier: ^0.3.3 + version: 0.3.3(bufferutil@4.0.7)(utf-8-validate@5.0.10) + '@eartharoid/dtf': + specifier: ^2.0.1 + version: 2.0.1 + '@eartharoid/i18n': + specifier: ^1.2.1 + version: 1.2.1 + '@fastify/cookie': + specifier: ^6.0.0 + version: 6.0.0 + '@fastify/jwt': + specifier: ^5.0.1 + version: 5.0.1 + '@fastify/oauth2': + specifier: ^5.1.0 + version: 5.1.0 + '@prisma/client': + specifier: ^4.11.0 + version: 4.11.0(prisma@4.11.0) + boxen: + specifier: ^7.0.2 + version: 7.0.2 + cryptr: + specifier: ^6.2.0 + version: 6.2.0 + discord.js: + specifier: ^14.8.0 + version: 14.8.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) + dotenv: + specifier: ^16.0.3 + version: 16.0.3 + fastify: + specifier: ^4.14.1 + version: 4.14.1 + figlet: + specifier: ^1.5.2 + version: 1.5.2 + fs-extra: + specifier: ^10.1.0 + version: 10.1.0 + keyv: + specifier: ^4.5.2 + version: 4.5.2 + leeks.js: + specifier: ^0.3.0 + version: 0.3.0 + leekslazylogger: + specifier: ^5.0.1 + version: 5.0.1 + ms: + specifier: ^2.1.3 + version: 2.1.3 + mustache: + specifier: ^4.2.0 + version: 4.2.0 + node-dir: + specifier: ^0.1.17 + version: 0.1.17 + node-emoji: + specifier: ^1.11.0 + version: 1.11.0 + object-diffy: + specifier: ^1.0.4 + version: 1.0.4 + prisma: + specifier: ^4.11.0 + version: 4.11.0 + semver: + specifier: ^7.3.8 + version: 7.3.8 + spacetime: + specifier: ^7.4.1 + version: 7.4.1 + terminal-link: + specifier: ^2.1.1 + version: 2.1.1 + yaml: + specifier: ^1.10.2 + version: 1.10.2 optionalDependencies: - bufferutil: 4.0.7 - erlpack: github.com/discord/erlpack/cbe76be04c2210fc9cb6ff95910f0937c1011d04 - utf-8-validate: 5.0.10 - zlib-sync: 0.1.8 + bufferutil: + specifier: ^4.0.7 + version: 4.0.7 + erlpack: + specifier: github:discord/erlpack + version: github.com/discord/erlpack/cbe76be04c2210fc9cb6ff95910f0937c1011d04 + utf-8-validate: + specifier: ^5.0.10 + version: 5.0.10 + zlib-sync: + specifier: ^0.1.8 + version: 0.1.8 devDependencies: - '@commitlint/cli': 17.4.4 - '@commitlint/config-conventional': 17.4.4 - all-contributors-cli: 6.24.0 - conventional-changelog-cli: 2.2.2 - eslint: 8.36.0 - eslint-plugin-unused-imports: 2.0.0_eslint@8.36.0 - husky: 8.0.3 - lint-staged: 13.2.0 - nodemon: 2.0.21 + '@commitlint/cli': + specifier: ^17.4.4 + version: 17.4.4 + '@commitlint/config-conventional': + specifier: ^17.4.4 + version: 17.4.4 + all-contributors-cli: + specifier: ^6.24.0 + version: 6.24.0 + conventional-changelog-cli: + specifier: ^2.2.2 + version: 2.2.2 + eslint: + specifier: ^8.36.0 + version: 8.36.0 + eslint-plugin-unused-imports: + specifier: ^2.0.0 + version: 2.0.0(eslint@8.36.0) + husky: + specifier: ^8.0.3 + version: 8.0.3 + lint-staged: + specifier: ^13.2.0 + version: 13.2.0 + nodemon: + specifier: ^2.0.21 + version: 2.0.21 packages: - /@babel/code-frame/7.18.6: + /@babel/code-frame@7.18.6: resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.18.6 dev: true - /@babel/helper-validator-identifier/7.19.1: + /@babel/helper-validator-identifier@7.19.1: resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} engines: {node: '>=6.9.0'} dev: true - /@babel/highlight/7.18.6: + /@babel/highlight@7.18.6: resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} engines: {node: '>=6.9.0'} dependencies: @@ -113,17 +152,16 @@ packages: js-tokens: 4.0.0 dev: true - /@babel/runtime/7.21.0: + /@babel/runtime@7.21.0: resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 dev: true - /@commitlint/cli/17.4.4: + /@commitlint/cli@17.4.4: resolution: {integrity: sha512-HwKlD7CPVMVGTAeFZylVNy14Vm5POVY0WxPkZr7EXLC/os0LH/obs6z4HRvJtH/nHCMYBvUBQhGwnufKfTjd5g==} engines: {node: '>=v14'} - hasBin: true dependencies: '@commitlint/format': 17.4.4 '@commitlint/lint': 17.4.4 @@ -140,14 +178,14 @@ packages: - '@swc/wasm' dev: true - /@commitlint/config-conventional/17.4.4: + /@commitlint/config-conventional@17.4.4: resolution: {integrity: sha512-u6ztvxqzi6NuhrcEDR7a+z0yrh11elY66nRrQIpqsqW6sZmpxYkDLtpRH8jRML+mmxYQ8s4qqF06Q/IQx5aJeQ==} engines: {node: '>=v14'} dependencies: conventional-changelog-conventionalcommits: 5.0.0 dev: true - /@commitlint/config-validator/17.4.4: + /@commitlint/config-validator@17.4.4: resolution: {integrity: sha512-bi0+TstqMiqoBAQDvdEP4AFh0GaKyLFlPPEObgI29utoKEYoPQTvF0EYqIwYYLEoJYhj5GfMIhPHJkTJhagfeg==} engines: {node: '>=v14'} dependencies: @@ -155,7 +193,7 @@ packages: ajv: 8.12.0 dev: true - /@commitlint/ensure/17.4.4: + /@commitlint/ensure@17.4.4: resolution: {integrity: sha512-AHsFCNh8hbhJiuZ2qHv/m59W/GRE9UeOXbkOqxYMNNg9pJ7qELnFcwj5oYpa6vzTSHtPGKf3C2yUFNy1GGHq6g==} engines: {node: '>=v14'} dependencies: @@ -167,12 +205,12 @@ packages: lodash.upperfirst: 4.3.1 dev: true - /@commitlint/execute-rule/17.4.0: + /@commitlint/execute-rule@17.4.0: resolution: {integrity: sha512-LIgYXuCSO5Gvtc0t9bebAMSwd68ewzmqLypqI2Kke1rqOqqDbMpYcYfoPfFlv9eyLIh4jocHWwCK5FS7z9icUA==} engines: {node: '>=v14'} dev: true - /@commitlint/format/17.4.4: + /@commitlint/format@17.4.4: resolution: {integrity: sha512-+IS7vpC4Gd/x+uyQPTAt3hXs5NxnkqAZ3aqrHd5Bx/R9skyCAWusNlNbw3InDbAK6j166D9asQM8fnmYIa+CXQ==} engines: {node: '>=v14'} dependencies: @@ -180,7 +218,7 @@ packages: chalk: 4.1.2 dev: true - /@commitlint/is-ignored/17.4.4: + /@commitlint/is-ignored@17.4.4: resolution: {integrity: sha512-Y3eo1SFJ2JQDik4rWkBC4tlRIxlXEFrRWxcyrzb1PUT2k3kZ/XGNuCDfk/u0bU2/yS0tOA/mTjFsV+C4qyACHw==} engines: {node: '>=v14'} dependencies: @@ -188,7 +226,7 @@ packages: semver: 7.3.8 dev: true - /@commitlint/lint/17.4.4: + /@commitlint/lint@17.4.4: resolution: {integrity: sha512-qgkCRRFjyhbMDWsti/5jRYVJkgYZj4r+ZmweZObnbYqPUl5UKLWMf9a/ZZisOI4JfiPmRktYRZ2JmqlSvg+ccw==} engines: {node: '>=v14'} dependencies: @@ -198,7 +236,7 @@ packages: '@commitlint/types': 17.4.4 dev: true - /@commitlint/load/17.4.4: + /@commitlint/load@17.4.4: resolution: {integrity: sha512-z6uFIQ7wfKX5FGBe1AkOF4l/ShOQsaa1ml/nLMkbW7R/xF8galGS7Zh0yHvzVp/srtfS0brC+0bUfQfmpMPFVQ==} engines: {node: '>=v14'} dependencies: @@ -209,24 +247,24 @@ packages: '@types/node': 18.15.3 chalk: 4.1.2 cosmiconfig: 8.1.2 - cosmiconfig-typescript-loader: 4.3.0_kpxsywhbgzx5buqgdnmsfobuwi + cosmiconfig-typescript-loader: 4.3.0(@types/node@18.15.3)(cosmiconfig@8.1.2)(ts-node@10.9.1)(typescript@4.9.5) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1_cbfmry4sbbh4vatmdrsmatfg5a + ts-node: 10.9.1(@types/node@18.15.3)(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' dev: true - /@commitlint/message/17.4.2: + /@commitlint/message@17.4.2: resolution: {integrity: sha512-3XMNbzB+3bhKA1hSAWPCQA3lNxR4zaeQAQcHj0Hx5sVdO6ryXtgUBGGv+1ZCLMgAPRixuc6en+iNAzZ4NzAa8Q==} engines: {node: '>=v14'} dev: true - /@commitlint/parse/17.4.4: + /@commitlint/parse@17.4.4: resolution: {integrity: sha512-EKzz4f49d3/OU0Fplog7nwz/lAfXMaDxtriidyGF9PtR+SRbgv4FhsfF310tKxs6EPj8Y+aWWuX3beN5s+yqGg==} engines: {node: '>=v14'} dependencies: @@ -235,7 +273,7 @@ packages: conventional-commits-parser: 3.2.4 dev: true - /@commitlint/read/17.4.4: + /@commitlint/read@17.4.4: resolution: {integrity: sha512-B2TvUMJKK+Svzs6eji23WXsRJ8PAD+orI44lVuVNsm5zmI7O8RSGJMvdEZEikiA4Vohfb+HevaPoWZ7PiFZ3zA==} engines: {node: '>=v14'} dependencies: @@ -246,7 +284,7 @@ packages: minimist: 1.2.8 dev: true - /@commitlint/resolve-extends/17.4.4: + /@commitlint/resolve-extends@17.4.4: resolution: {integrity: sha512-znXr1S0Rr8adInptHw0JeLgumS11lWbk5xAWFVno+HUFVN45875kUtqjrI6AppmD3JI+4s0uZlqqlkepjJd99A==} engines: {node: '>=v14'} dependencies: @@ -258,7 +296,7 @@ packages: resolve-global: 1.0.0 dev: true - /@commitlint/rules/17.4.4: + /@commitlint/rules@17.4.4: resolution: {integrity: sha512-0tgvXnHi/mVcyR8Y8mjTFZIa/FEQXA4uEutXS/imH2v1UNkYDSEMsK/68wiXRpfW1euSgEdwRkvE1z23+yhNrQ==} engines: {node: '>=v14'} dependencies: @@ -269,33 +307,33 @@ packages: execa: 5.1.1 dev: true - /@commitlint/to-lines/17.4.0: + /@commitlint/to-lines@17.4.0: resolution: {integrity: sha512-LcIy/6ZZolsfwDUWfN1mJ+co09soSuNASfKEU5sCmgFCvX5iHwRYLiIuoqXzOVDYOy7E7IcHilr/KS0e5T+0Hg==} engines: {node: '>=v14'} dev: true - /@commitlint/top-level/17.4.0: + /@commitlint/top-level@17.4.0: resolution: {integrity: sha512-/1loE/g+dTTQgHnjoCy0AexKAEFyHsR2zRB4NWrZ6lZSMIxAhBJnmCqwao7b4H8888PsfoTBCLBYIw8vGnej8g==} engines: {node: '>=v14'} dependencies: find-up: 5.0.0 dev: true - /@commitlint/types/17.4.4: + /@commitlint/types@17.4.4: resolution: {integrity: sha512-amRN8tRLYOsxRr6mTnGGGvB5EmW/4DDjLMgiwK3CCVEmN6Sr/6xePGEpWaspKkckILuUORCwe6VfDBw6uj4axQ==} engines: {node: '>=v14'} dependencies: chalk: 4.1.2 dev: true - /@cspotcode/source-map-support/0.8.1: + /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} dependencies: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@discord-tickets/settings/2.1.3_svelte@3.57.0: + /@discord-tickets/settings@2.1.3(svelte@3.57.0): resolution: {integrity: sha512-rKwt87oXBydudfnKkSmgbnNtCFbkYM0ZBuZLqSfl+H8N1kT+bylGoyXfAF2OLoEc5vK/EyBCEP9trZMO60774g==} dependencies: '@fortawesome/fontawesome-free': 6.3.0 @@ -306,13 +344,13 @@ packages: ms: 2.1.3 postcss: 8.4.21 sortablejs: 1.15.0 - svelte-modals: 1.2.1_svelte@3.57.0 + svelte-modals: 1.2.1(svelte@3.57.0) svelte-toasts: 1.1.2 transitivePeerDependencies: - svelte dev: false - /@discordjs/builders/1.5.0: + /@discordjs/builders@1.5.0: resolution: {integrity: sha512-7XxT78mnNBPigHn2y6KAXkicxIBFtZREGWaRZ249EC1l6gBUEP8IyVY5JTciIjJArxkF+tg675aZvsTNTKBpmA==} engines: {node: '>=16.9.0'} dependencies: @@ -325,19 +363,19 @@ packages: tslib: 2.5.0 dev: false - /@discordjs/collection/1.4.0: + /@discordjs/collection@1.4.0: resolution: {integrity: sha512-hiOJyk2CPFf1+FL3a4VKCuu1f448LlROVuu8nLz1+jCOAPokUcdFAV+l4pd3B3h6uJlJQSASoZzrdyNdjdtfzQ==} engines: {node: '>=16.9.0'} dev: false - /@discordjs/formatters/0.2.0: + /@discordjs/formatters@0.2.0: resolution: {integrity: sha512-vn4oMSXuMZUm8ITqVOtvE7/fMMISj4cI5oLsR09PEQXHKeKDAMLltG/DWeeIs7Idfy6V8Fk3rn1e69h7NfzuNA==} engines: {node: '>=16.9.0'} dependencies: discord-api-types: 0.37.36 dev: false - /@discordjs/rest/1.6.0: + /@discordjs/rest@1.6.0: resolution: {integrity: sha512-HGvqNCZ5Z5j0tQHjmT1lFvE5ETO4hvomJ1r0cbnpC1zM23XhCpZ9wgTCiEmaxKz05cyf2CI9p39+9LL+6Yz1bA==} engines: {node: '>=16.9.0'} dependencies: @@ -351,34 +389,34 @@ packages: undici: 5.21.0 dev: false - /@discordjs/util/0.2.0: + /@discordjs/util@0.2.0: resolution: {integrity: sha512-/8qNbebFzLWKOOg+UV+RB8itp4SmU5jw0tBUD3ifElW6rYNOj1Ku5JaSW7lLl/WgjjxF01l/1uQPCzkwr110vg==} engines: {node: '>=16.9.0'} dev: false - /@eartharoid/dbf/0.3.3_3cxu5zja4e2r5wmvge7mdcljwq: + /@eartharoid/dbf@0.3.3(bufferutil@4.0.7)(utf-8-validate@5.0.10): resolution: {integrity: sha512-eVDdpFlDV5CAvqoV5g1iAvoYhPjnvcyJ0Nnepc1YihlE1KIYGhVIK/2RaDsltzxRuiweO3Y7dvDj/cUpJnnFPA==} dependencies: - discord.js: 14.8.0_3cxu5zja4e2r5wmvge7mdcljwq + discord.js: 14.8.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) node-dir: 0.1.17 transitivePeerDependencies: - bufferutil - utf-8-validate dev: false - /@eartharoid/deep-merge/0.0.2: + /@eartharoid/deep-merge@0.0.2: resolution: {integrity: sha512-t7kmNd6m7BOGxf25tE1YBhPZbgMEsXZT0tQyVV/Mlo+rcPEmiEEc6HV1DBnYm63MvMpgTk4o6yBkeAJYCMzvNg==} dev: false - /@eartharoid/dtf/2.0.1: + /@eartharoid/dtf@2.0.1: resolution: {integrity: sha512-H0oLRShkVqTskVRA8KMMIHjtwAu6Yc6GkxQm9cLPWMA7V0ZOxD5rg2PaZuQfDbt6UAk2sp7qb+k4ih5EHc/Jgg==} dev: false - /@eartharoid/i18n/1.2.1: + /@eartharoid/i18n@1.2.1: resolution: {integrity: sha512-nMQdHrGgpw+vNL5DbivELW2K3KAUGaMvTjjmFsEPf8mUW8+LAgRjvfFn2gkJq1mnlD6HoqUaHqEL4YpWr2T5MA==} dev: false - /@eslint-community/eslint-utils/4.3.0_eslint@8.36.0: + /@eslint-community/eslint-utils@4.3.0(eslint@8.36.0): resolution: {integrity: sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -388,12 +426,12 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /@eslint-community/regexpp/4.4.0: + /@eslint-community/regexpp@4.4.0: resolution: {integrity: sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc/2.0.1: + /@eslint/eslintrc@2.0.1: resolution: {integrity: sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: @@ -410,41 +448,41 @@ packages: - supports-color dev: true - /@eslint/js/8.36.0: + /@eslint/js@8.36.0: resolution: {integrity: sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@fastify/ajv-compiler/3.5.0: + /@fastify/ajv-compiler@3.5.0: resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==} dependencies: ajv: 8.12.0 - ajv-formats: 2.1.1_ajv@8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) fast-uri: 2.2.0 dev: false - /@fastify/cookie/6.0.0: + /@fastify/cookie@6.0.0: resolution: {integrity: sha512-Luy3Po3dOJmqAuPCiPcWsX0tV5+C3AOnULSdlsGjNGOvyE7jqzysp8kT9ICfsUvove+TeUMgTWl1y9XS3ZPPMg==} dependencies: cookie-signature: 1.2.1 fastify-plugin: 3.0.1 dev: false - /@fastify/deepmerge/1.3.0: + /@fastify/deepmerge@1.3.0: resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} dev: false - /@fastify/error/3.2.0: + /@fastify/error@3.2.0: resolution: {integrity: sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==} dev: false - /@fastify/fast-json-stringify-compiler/4.2.0: + /@fastify/fast-json-stringify-compiler@4.2.0: resolution: {integrity: sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg==} dependencies: fast-json-stringify: 5.6.2 dev: false - /@fastify/jwt/5.0.1: + /@fastify/jwt@5.0.1: resolution: {integrity: sha512-BF2JrhSRcMRwHim2pbFEgWLJE8DdRh0NaGeAgU+ZFICj2AHxGvrHSOaYRaXNUQixgmGjVaTrbRFl3bSBc6Pj+g==} engines: {node: '>=10'} dependencies: @@ -455,7 +493,7 @@ packages: steed: 1.1.3 dev: false - /@fastify/oauth2/5.1.0: + /@fastify/oauth2@5.1.0: resolution: {integrity: sha512-Z/2ZR8v2BB+vwFm1t0DnavxAKymCGvJiGD/lTtNV/dTPHTHRQ1X68hFs/Nl2Rd0Gv9AlSlrpYsX9hfoNjTSiYQ==} dependencies: fastify-plugin: 4.5.0 @@ -464,42 +502,36 @@ packages: - supports-color dev: false - /@fortawesome/fontawesome-free/6.3.0: + /@fortawesome/fontawesome-free@6.3.0: resolution: {integrity: sha512-qVtd5i1Cc7cdrqnTWqTObKQHjPWAiRwjUPaXObaeNPcy7+WKxJumGBx66rfSFgK6LNpIasVKkEgW8oyf0tmPLA==} engines: {node: '>=6'} requiresBuild: true dev: false - /@hapi/address/2.1.4: + /@hapi/address@2.1.4: resolution: {integrity: sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==} - deprecated: Moved to 'npm install @sideway/address' dev: false - /@hapi/boom/7.4.11: + /@hapi/boom@7.4.11: resolution: {integrity: sha512-VSU/Cnj1DXouukYxxkes4nNJonCnlogHvIff1v1RVoN4xzkKhMXX+GRmb3NyH1iar10I9WFPDv2JPwfH3GaV0A==} - deprecated: This version has been deprecated and is no longer supported or maintained dependencies: '@hapi/hoek': 8.5.1 dev: false - /@hapi/bourne/1.3.2: + /@hapi/bourne@1.3.2: resolution: {integrity: sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==} - deprecated: This version has been deprecated and is no longer supported or maintained dev: false - /@hapi/formula/1.2.0: + /@hapi/formula@1.2.0: resolution: {integrity: sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==} - deprecated: Moved to 'npm install @sideway/formula' dev: false - /@hapi/hoek/8.5.1: + /@hapi/hoek@8.5.1: resolution: {integrity: sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==} - deprecated: This version has been deprecated and is no longer supported or maintained dev: false - /@hapi/joi/16.1.8: + /@hapi/joi@16.1.8: resolution: {integrity: sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg==} - deprecated: Switch to 'npm install joi' dependencies: '@hapi/address': 2.1.4 '@hapi/formula': 1.2.0 @@ -508,28 +540,25 @@ packages: '@hapi/topo': 3.1.6 dev: false - /@hapi/pinpoint/1.0.2: + /@hapi/pinpoint@1.0.2: resolution: {integrity: sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==} - deprecated: Moved to 'npm install @sideway/pinpoint' dev: false - /@hapi/topo/3.1.6: + /@hapi/topo@3.1.6: resolution: {integrity: sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==} - deprecated: This version has been deprecated and is no longer supported or maintained dependencies: '@hapi/hoek': 8.5.1 dev: false - /@hapi/wreck/15.1.0: + /@hapi/wreck@15.1.0: resolution: {integrity: sha512-tQczYRTTeYBmvhsek/D49En/5khcShaBEmzrAaDjMrFXKJRuF8xA8+tlq1ETLBFwGd6Do6g2OC74rt11kzawzg==} - deprecated: This version has been deprecated and is no longer supported or maintained dependencies: '@hapi/boom': 7.4.11 '@hapi/bourne': 1.3.2 '@hapi/hoek': 8.5.1 dev: false - /@humanwhocodes/config-array/0.11.8: + /@humanwhocodes/config-array@0.11.8: resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} dependencies: @@ -540,42 +569,42 @@ packages: - supports-color dev: true - /@humanwhocodes/module-importer/1.0.1: + /@humanwhocodes/module-importer@1.0.1: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} dev: true - /@humanwhocodes/object-schema/1.2.1: + /@humanwhocodes/object-schema@1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true - /@hutson/parse-repository-url/3.0.2: + /@hutson/parse-repository-url@3.0.2: resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} engines: {node: '>=6.9.0'} dev: true - /@jridgewell/resolve-uri/3.1.0: + /@jridgewell/resolve-uri@3.1.0: resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} dev: true - /@jridgewell/sourcemap-codec/1.4.14: + /@jridgewell/sourcemap-codec@1.4.14: resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} dev: true - /@jridgewell/trace-mapping/0.3.9: + /@jridgewell/trace-mapping@0.3.9: resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} dependencies: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 dev: true - /@lukeed/ms/2.0.1: + /@lukeed/ms@2.0.1: resolution: {integrity: sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==} engines: {node: '>=8'} dev: false - /@nodelib/fs.scandir/2.1.5: + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} dependencies: @@ -583,12 +612,12 @@ packages: run-parallel: 1.2.0 dev: true - /@nodelib/fs.stat/2.0.5: + /@nodelib/fs.stat@2.0.5: resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} dev: true - /@nodelib/fs.walk/1.2.8: + /@nodelib/fs.walk@1.2.8: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} dependencies: @@ -596,7 +625,7 @@ packages: fastq: 1.15.0 dev: true - /@prisma/client/4.11.0_prisma@4.11.0: + /@prisma/client@4.11.0(prisma@4.11.0): resolution: {integrity: sha512-0INHYkQIqgAjrt7NzhYpeDQi8x3Nvylc2uDngKyFDDj1tTRQ4uV1HnVmd1sQEraeVAN63SOK0dgCKQHlvjL0KA==} engines: {node: '>=14.17'} requiresBuild: true @@ -610,21 +639,21 @@ packages: prisma: 4.11.0 dev: false - /@prisma/engines-version/4.11.0-57.8fde8fef4033376662cad983758335009d522acb: + /@prisma/engines-version@4.11.0-57.8fde8fef4033376662cad983758335009d522acb: resolution: {integrity: sha512-3Vd8Qq06d5xD8Ch5WauWcUUrsVPdMC6Ge8ILji8RFfyhUpqon6qSyGM0apvr1O8n8qH8cKkEFqRPsYjuz5r83g==} dev: false - /@prisma/engines/4.11.0: + /@prisma/engines@4.11.0: resolution: {integrity: sha512-0AEBi2HXGV02cf6ASsBPhfsVIbVSDC9nbQed4iiY5eHttW9ZtMxHThuKZE1pnESbr8HRdgmFSa/Kn4OSNYuibg==} requiresBuild: true dev: false - /@sapphire/async-queue/1.5.0: + /@sapphire/async-queue@1.5.0: resolution: {integrity: sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dev: false - /@sapphire/shapeshift/3.8.1: + /@sapphire/shapeshift@3.8.1: resolution: {integrity: sha512-xG1oXXBhCjPKbxrRTlox9ddaZTvVpOhYLmKmApD/vIWOV1xEYXnpoFs68zHIZBGbqztq6FrUPNPerIrO1Hqeaw==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dependencies: @@ -632,12 +661,12 @@ packages: lodash: 4.17.21 dev: false - /@sapphire/snowflake/3.4.0: + /@sapphire/snowflake@3.4.0: resolution: {integrity: sha512-zZxymtVO6zeXVMPds+6d7gv/OfnCc25M1Z+7ZLB0oPmeMTPeRWVPQSS16oDJy5ZsyCOLj7M6mbZml5gWXcVRNw==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dev: false - /@skyra/discord-components-core/3.6.0: + /@skyra/discord-components-core@3.6.0: resolution: {integrity: sha512-13aVNoleHyAMqd4lcfcfjEduV4QavlHn9P2TUlTUPQC8m8cd8n8wSUTDUxKhQbYpzJ0hn7AHnlmLixfiqQT4FQ==} engines: {node: '>=v14.0.0'} dependencies: @@ -646,73 +675,71 @@ packages: hex-to-rgba: 2.0.1 dev: false - /@stencil/core/2.22.3: + /@stencil/core@2.22.3: resolution: {integrity: sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==} engines: {node: '>=12.10.0', npm: '>=6.0.0'} - hasBin: true dev: false - /@tokenizer/token/0.3.0: + /@tokenizer/token@0.3.0: resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} dev: false - /@tsconfig/node10/1.0.9: + /@tsconfig/node10@1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} dev: true - /@tsconfig/node12/1.0.11: + /@tsconfig/node12@1.0.11: resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} dev: true - /@tsconfig/node14/1.0.3: + /@tsconfig/node14@1.0.3: resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} dev: true - /@tsconfig/node16/1.0.3: + /@tsconfig/node16@1.0.3: resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} dev: true - /@types/minimist/1.2.2: + /@types/minimist@1.2.2: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - /@types/node/18.15.3: + /@types/node@18.15.3: resolution: {integrity: sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==} - /@types/normalize-package-data/2.4.1: + /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true - /@types/ws/8.5.4: + /@types/ws@8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: '@types/node': 18.15.3 dev: false - /JSONStream/1.3.5: + /JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true dependencies: jsonparse: 1.3.1 through: 2.3.8 dev: true - /abbrev/1.1.1: + /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} dev: true - /abort-controller/3.0.0: + /abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} dependencies: event-target-shim: 5.0.1 dev: false - /abstract-logging/2.0.1: + /abstract-logging@2.0.1: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} dev: false - /acorn-jsx/5.3.2_acorn@8.8.2: + /acorn-jsx@5.3.2(acorn@8.8.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -720,22 +747,21 @@ packages: acorn: 8.8.2 dev: true - /acorn-walk/8.2.0: + /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} dev: true - /acorn/8.8.2: + /acorn@8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} - hasBin: true dev: true - /add-stream/1.0.0: + /add-stream@1.0.0: resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} dev: true - /aggregate-error/3.1.0: + /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} dependencies: @@ -743,7 +769,7 @@ packages: indent-string: 4.0.0 dev: true - /ajv-formats/2.1.1_ajv@8.12.0: + /ajv-formats@2.1.1(ajv@8.12.0): resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: ajv: ^8.0.0 @@ -754,7 +780,7 @@ packages: ajv: 8.12.0 dev: false - /ajv/6.12.6: + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: fast-deep-equal: 3.1.3 @@ -763,7 +789,7 @@ packages: uri-js: 4.4.1 dev: true - /ajv/8.12.0: + /ajv@8.12.0: resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} dependencies: fast-deep-equal: 3.1.3 @@ -771,10 +797,9 @@ packages: require-from-string: 2.0.2 uri-js: 4.4.1 - /all-contributors-cli/6.24.0: + /all-contributors-cli@6.24.0: resolution: {integrity: sha512-7oSKr2PnqxsOotuSwciltcFTS1eVRdjR0cn99hbElfff7gRQBShVhsf/XBprY41sLcgqTk0l0MKgKv6QNgZdMg==} engines: {node: '>=4'} - hasBin: true dependencies: '@babel/runtime': 7.21.0 async: 3.2.4 @@ -790,45 +815,45 @@ packages: - encoding dev: true - /ansi-align/3.0.1: + /ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} dependencies: string-width: 4.2.3 dev: false - /ansi-escapes/4.3.2: + /ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} dependencies: type-fest: 0.21.3 - /ansi-regex/5.0.1: + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - /ansi-regex/6.0.1: + /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - /ansi-styles/3.2.1: + /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} dependencies: color-convert: 1.9.3 dev: true - /ansi-styles/4.3.0: + /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} dependencies: color-convert: 2.0.1 dev: true - /ansi-styles/6.2.1: + /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - /anymatch/3.1.3: + /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} dependencies: @@ -836,28 +861,28 @@ packages: picomatch: 2.3.1 dev: true - /archy/1.0.0: + /archy@1.0.0: resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} dev: false - /arg/4.1.3: + /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} dev: true - /argparse/2.0.1: + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true - /array-ify/1.0.0: + /array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} dev: true - /arrify/1.0.1: + /arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} dev: true - /asn1.js/5.4.1: + /asn1.js@5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} dependencies: bn.js: 4.12.0 @@ -866,21 +891,21 @@ packages: safer-buffer: 2.1.2 dev: false - /astral-regex/2.0.0: + /astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} dev: true - /async/3.2.4: + /async@3.2.4: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: true - /atomic-sleep/1.0.0: + /atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} dev: false - /avvio/8.2.1: + /avvio@8.2.1: resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==} dependencies: archy: 1.0.0 @@ -890,30 +915,30 @@ packages: - supports-color dev: false - /balanced-match/1.0.2: + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - /base64-js/1.5.1: + /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false - /binary-extensions/2.2.0: + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} dev: true - /bindings/1.5.0: + /bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} dependencies: file-uri-to-path: 1.0.0 dev: false optional: true - /bn.js/4.12.0: + /bn.js@4.12.0: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} dev: false - /boxen/7.0.2: + /boxen@7.0.2: resolution: {integrity: sha512-1Z4UJabXUP1/R9rLpoU3O2lEMnG3pPLAs/ZD2lF3t2q7qD5lM8rqbtnvtvm4N0wEyNlE+9yZVTVAGmd1V5jabg==} engines: {node: '>=14.16'} dependencies: @@ -927,27 +952,27 @@ packages: wrap-ansi: 8.1.0 dev: false - /brace-expansion/1.1.11: + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - /braces/3.0.2: + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} dependencies: fill-range: 7.0.1 dev: true - /buffer/6.0.3: + /buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 dev: false - /bufferutil/4.0.7: + /bufferutil@4.0.7: resolution: {integrity: sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==} engines: {node: '>=6.14.2'} requiresBuild: true @@ -955,19 +980,19 @@ packages: node-gyp-build: 4.6.0 dev: false - /busboy/1.6.0: + /busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} dependencies: streamsearch: 1.1.0 dev: false - /callsites/3.1.0: + /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} dev: true - /camelcase-keys/6.2.2: + /camelcase-keys@6.2.2: resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} engines: {node: '>=8'} dependencies: @@ -976,17 +1001,17 @@ packages: quick-lru: 4.0.1 dev: true - /camelcase/5.3.1: + /camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} dev: true - /camelcase/7.0.1: + /camelcase@7.0.1: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} dev: false - /chalk/2.4.2: + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} dependencies: @@ -995,7 +1020,7 @@ packages: supports-color: 5.5.0 dev: true - /chalk/4.1.2: + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} dependencies: @@ -1003,15 +1028,15 @@ packages: supports-color: 7.2.0 dev: true - /chalk/5.2.0: + /chalk@5.2.0: resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - /chardet/0.7.0: + /chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true - /chokidar/3.5.3: + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} dependencies: @@ -1026,24 +1051,24 @@ packages: fsevents: 2.3.2 dev: true - /clean-stack/2.2.0: + /clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} dev: true - /cli-boxes/3.0.0: + /cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} dev: false - /cli-cursor/3.1.0: + /cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} dependencies: restore-cursor: 3.1.0 dev: true - /cli-truncate/2.1.0: + /cli-truncate@2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} dependencies: @@ -1051,7 +1076,7 @@ packages: string-width: 4.2.3 dev: true - /cli-truncate/3.1.0: + /cli-truncate@3.1.0: resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: @@ -1059,12 +1084,12 @@ packages: string-width: 5.1.2 dev: true - /cli-width/3.0.0: + /cli-width@3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} dev: true - /cliui/6.0.0: + /cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} dependencies: string-width: 4.2.3 @@ -1072,7 +1097,7 @@ packages: wrap-ansi: 6.2.0 dev: true - /cliui/7.0.4: + /cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: string-width: 4.2.3 @@ -1080,7 +1105,7 @@ packages: wrap-ansi: 7.0.0 dev: true - /cliui/8.0.1: + /cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} dependencies: @@ -1089,52 +1114,52 @@ packages: wrap-ansi: 7.0.0 dev: true - /clsx/1.2.1: + /clsx@1.2.1: resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} engines: {node: '>=6'} dev: false - /color-convert/1.9.3: + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 dev: true - /color-convert/2.0.1: + /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 dev: true - /color-name/1.1.3: + /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} dev: true - /color-name/1.1.4: + /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true - /colorette/2.0.19: + /colorette@2.0.19: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} dev: true - /commander/10.0.0: + /commander@10.0.0: resolution: {integrity: sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==} engines: {node: '>=14'} dev: true - /compare-func/2.0.0: + /compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} dependencies: array-ify: 1.0.0 dot-prop: 5.3.0 dev: true - /concat-map/0.0.1: + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - /conventional-changelog-angular/5.0.13: + /conventional-changelog-angular@5.0.13: resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} engines: {node: '>=10'} dependencies: @@ -1142,17 +1167,16 @@ packages: q: 1.5.1 dev: true - /conventional-changelog-atom/2.0.8: + /conventional-changelog-atom@2.0.8: resolution: {integrity: sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-cli/2.2.2: + /conventional-changelog-cli@2.2.2: resolution: {integrity: sha512-8grMV5Jo8S0kP3yoMeJxV2P5R6VJOqK72IiSV9t/4H5r/HiRqEBQ83bYGuz4Yzfdj4bjaAEhZN/FFbsFXr5bOA==} engines: {node: '>=10'} - hasBin: true dependencies: add-stream: 1.0.0 conventional-changelog: 3.1.25 @@ -1161,14 +1185,14 @@ packages: tempfile: 3.0.0 dev: true - /conventional-changelog-codemirror/2.0.8: + /conventional-changelog-codemirror@2.0.8: resolution: {integrity: sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-conventionalcommits/4.6.3: + /conventional-changelog-conventionalcommits@4.6.3: resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==} engines: {node: '>=10'} dependencies: @@ -1177,7 +1201,7 @@ packages: q: 1.5.1 dev: true - /conventional-changelog-conventionalcommits/5.0.0: + /conventional-changelog-conventionalcommits@5.0.0: resolution: {integrity: sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw==} engines: {node: '>=10'} dependencies: @@ -1186,7 +1210,7 @@ packages: q: 1.5.1 dev: true - /conventional-changelog-core/4.2.4: + /conventional-changelog-core@4.2.4: resolution: {integrity: sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==} engines: {node: '>=10'} dependencies: @@ -1206,35 +1230,35 @@ packages: through2: 4.0.2 dev: true - /conventional-changelog-ember/2.0.9: + /conventional-changelog-ember@2.0.9: resolution: {integrity: sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-eslint/3.0.9: + /conventional-changelog-eslint@3.0.9: resolution: {integrity: sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-express/2.0.6: + /conventional-changelog-express@2.0.6: resolution: {integrity: sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-jquery/3.0.11: + /conventional-changelog-jquery@3.0.11: resolution: {integrity: sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==} engines: {node: '>=10'} dependencies: q: 1.5.1 dev: true - /conventional-changelog-jshint/2.0.9: + /conventional-changelog-jshint@2.0.9: resolution: {integrity: sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==} engines: {node: '>=10'} dependencies: @@ -1242,15 +1266,14 @@ packages: q: 1.5.1 dev: true - /conventional-changelog-preset-loader/2.3.4: + /conventional-changelog-preset-loader@2.3.4: resolution: {integrity: sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==} engines: {node: '>=10'} dev: true - /conventional-changelog-writer/5.0.1: + /conventional-changelog-writer@5.0.1: resolution: {integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==} engines: {node: '>=10'} - hasBin: true dependencies: conventional-commits-filter: 2.0.7 dateformat: 3.0.3 @@ -1263,7 +1286,7 @@ packages: through2: 4.0.2 dev: true - /conventional-changelog/3.1.25: + /conventional-changelog@3.1.25: resolution: {integrity: sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==} engines: {node: '>=10'} dependencies: @@ -1280,7 +1303,7 @@ packages: conventional-changelog-preset-loader: 2.3.4 dev: true - /conventional-commits-filter/2.0.7: + /conventional-commits-filter@2.0.7: resolution: {integrity: sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==} engines: {node: '>=10'} dependencies: @@ -1288,10 +1311,9 @@ packages: modify-values: 1.0.1 dev: true - /conventional-commits-parser/3.2.4: + /conventional-commits-parser@3.2.4: resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} engines: {node: '>=10'} - hasBin: true dependencies: JSONStream: 1.3.5 is-text-path: 1.0.1 @@ -1301,21 +1323,21 @@ packages: through2: 4.0.2 dev: true - /cookie-signature/1.2.1: + /cookie-signature@1.2.1: resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==} engines: {node: '>=6.6.0'} dev: false - /cookie/0.5.0: + /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} dev: false - /core-util-is/1.0.3: + /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /cosmiconfig-typescript-loader/4.3.0_kpxsywhbgzx5buqgdnmsfobuwi: + /cosmiconfig-typescript-loader@4.3.0(@types/node@18.15.3)(cosmiconfig@8.1.2)(ts-node@10.9.1)(typescript@4.9.5): resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -1326,11 +1348,11 @@ packages: dependencies: '@types/node': 18.15.3 cosmiconfig: 8.1.2 - ts-node: 10.9.1_cbfmry4sbbh4vatmdrsmatfg5a + ts-node: 10.9.1(@types/node@18.15.3)(typescript@4.9.5) typescript: 4.9.5 dev: true - /cosmiconfig/8.1.2: + /cosmiconfig@8.1.2: resolution: {integrity: sha512-rmpUFKMZiawLfug8sP4NbpBSOpWftZB6UACOLEiNbnRAYM1TzgQuTWlMYFRuPgmoTCkcOxSMwQJQpJmiXv/eHw==} engines: {node: '>=14'} dependencies: @@ -1340,11 +1362,11 @@ packages: path-type: 4.0.0 dev: true - /create-require/1.1.1: + /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true - /cross-spawn/7.0.3: + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} dependencies: @@ -1353,25 +1375,25 @@ packages: which: 2.0.2 dev: true - /cryptr/6.2.0: + /cryptr@6.2.0: resolution: {integrity: sha512-jYi8SxvOFebTT7EYOABiPpHKY6lwWaP9IVcvT/aIVJUVoFdzTgi0ySPCL78q1ig8w2kwfXFCZACXoCXaye57aw==} dev: false - /dargs/7.0.0: + /dargs@7.0.0: resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} engines: {node: '>=8'} dev: true - /date-fns/2.29.3: + /date-fns@2.29.3: resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} engines: {node: '>=0.11'} dev: false - /dateformat/3.0.3: + /dateformat@3.0.3: resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} dev: true - /debug/3.2.7_supports-color@5.5.0: + /debug@3.2.7(supports-color@5.5.0): resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: supports-color: '*' @@ -1383,7 +1405,7 @@ packages: supports-color: 5.5.0 dev: true - /debug/4.3.4: + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} peerDependencies: @@ -1394,7 +1416,7 @@ packages: dependencies: ms: 2.1.2 - /decamelize-keys/1.1.1: + /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} engines: {node: '>=0.10.0'} dependencies: @@ -1402,34 +1424,34 @@ packages: map-obj: 1.0.1 dev: true - /decamelize/1.2.0: + /decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} dev: true - /deep-is/0.1.4: + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /depd/2.0.0: + /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} dev: false - /didyoumean/1.2.2: + /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true - /diff/4.0.2: + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} dev: true - /discord-api-types/0.37.36: + /discord-api-types@0.37.36: resolution: {integrity: sha512-Nlxmp10UpVr/utgZ9uODQvG2Or+5w7LFrvFMswyeKC9l/+UaqGT6H0OVgEFhu9GEO4U6K7NNO5W8Carv7irnCA==} dev: false - /discord.js/14.8.0_3cxu5zja4e2r5wmvge7mdcljwq: + /discord.js@14.8.0(bufferutil@4.0.7)(utf-8-validate@5.0.10): resolution: {integrity: sha512-UOxYtc/YnV7jAJ2gISluJyYeBw4e+j8gWn+IoqG8unaHAVuvZ13DdYN0M1f9fbUgUvSarV798inIrYFtDNDjwQ==} engines: {node: '>=16.9.0'} dependencies: @@ -1445,41 +1467,41 @@ packages: lodash.snakecase: 4.1.1 tslib: 2.5.0 undici: 5.21.0 - ws: 8.13.0_3cxu5zja4e2r5wmvge7mdcljwq + ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate dev: false - /doctrine/3.0.0: + /doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} dependencies: esutils: 2.0.3 dev: true - /dot-prop/5.3.0: + /dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} dependencies: is-obj: 2.0.0 dev: true - /dotenv/16.0.3: + /dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} dev: false - /eastasianwidth/0.2.0: + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - /ecdsa-sig-formatter/1.0.11: + /ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} dependencies: safe-buffer: 5.2.1 dev: false - /emoji-name-map/1.2.9: + /emoji-name-map@1.2.9: resolution: {integrity: sha512-MSM8y6koSqh/2uEMI2VoKA+Ac0qL5RkgFGP/pzL6n5FOrOJ7FOZFxgs7+uNpqA+AT+WmdbMPXkd3HnFXXdz4AA==} dependencies: emojilib: 2.4.0 @@ -1487,38 +1509,38 @@ packages: map-o: 2.0.10 dev: false - /emoji-regex/8.0.0: + /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - /emoji-regex/9.2.2: + /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - /emojilib/2.4.0: + /emojilib@2.4.0: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} dev: false - /error-ex/1.3.2: + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 dev: true - /escalade/3.1.1: + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} dev: true - /escape-string-regexp/1.0.5: + /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} dev: true - /escape-string-regexp/4.0.0: + /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} dev: true - /eslint-plugin-unused-imports/2.0.0_eslint@8.36.0: + /eslint-plugin-unused-imports@2.0.0(eslint@8.36.0): resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1532,12 +1554,12 @@ packages: eslint-rule-composer: 0.3.0 dev: true - /eslint-rule-composer/0.3.0: + /eslint-rule-composer@0.3.0: resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} engines: {node: '>=4.0.0'} dev: true - /eslint-scope/7.1.1: + /eslint-scope@7.1.1: resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: @@ -1545,17 +1567,16 @@ packages: estraverse: 5.3.0 dev: true - /eslint-visitor-keys/3.3.0: + /eslint-visitor-keys@3.3.0: resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.36.0: + /eslint@8.36.0: resolution: {integrity: sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.3.0_eslint@8.36.0 + '@eslint-community/eslint-utils': 4.3.0(eslint@8.36.0) '@eslint-community/regexpp': 4.4.0 '@eslint/eslintrc': 2.0.1 '@eslint/js': 8.36.0 @@ -1599,50 +1620,50 @@ packages: - supports-color dev: true - /espree/9.5.0: + /espree@9.5.0: resolution: {integrity: sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.8.2 - acorn-jsx: 5.3.2_acorn@8.8.2 + acorn-jsx: 5.3.2(acorn@8.8.2) eslint-visitor-keys: 3.3.0 dev: true - /esquery/1.5.0: + /esquery@1.5.0: resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 dev: true - /esrecurse/4.3.0: + /esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} dependencies: estraverse: 5.3.0 dev: true - /estraverse/5.3.0: + /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} dev: true - /esutils/2.0.3: + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} dev: true - /event-target-shim/5.0.1: + /event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} dev: false - /events/3.3.0: + /events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} dev: false - /execa/5.1.1: + /execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} dependencies: @@ -1657,7 +1678,7 @@ packages: strip-final-newline: 2.0.0 dev: true - /execa/7.1.1: + /execa@7.1.1: resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==} engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} dependencies: @@ -1672,7 +1693,7 @@ packages: strip-final-newline: 3.0.0 dev: true - /external-editor/3.1.0: + /external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} dependencies: @@ -1681,33 +1702,33 @@ packages: tmp: 0.0.33 dev: true - /fast-content-type-parse/1.0.0: + /fast-content-type-parse@1.0.0: resolution: {integrity: sha512-Xbc4XcysUXcsP5aHUU7Nq3OwvHq97C+WnbkeIefpeYLX+ryzFJlU6OStFJhs6Ol0LkUGpcK+wL0JwfM+FCU5IA==} dev: false - /fast-decode-uri-component/1.0.1: + /fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} dev: false - /fast-deep-equal/3.1.3: + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - /fast-json-stable-stringify/2.1.0: + /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true - /fast-json-stringify/5.6.2: + /fast-json-stringify@5.6.2: resolution: {integrity: sha512-F6xkRrXvtGbAiDSEI5Rk7qk2P63Y9kc8bO6Dnsd3Rt6sBNr2QxNFWs0JbKftgiyOfGxnJaRoHe4SizCTqeAyrA==} dependencies: '@fastify/deepmerge': 1.3.0 ajv: 8.12.0 - ajv-formats: 2.1.1_ajv@8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) fast-deep-equal: 3.1.3 fast-uri: 2.2.0 rfdc: 1.3.0 dev: false - /fast-jwt/1.7.2: + /fast-jwt@1.7.2: resolution: {integrity: sha512-OEInypGXJhtURzq9GbFM5KaALUu9+4IV3kJEbWPuqOBN5JBe7A51Tx0CaQYHGC9GNfZnr5npA0lCIMaWiZmz/A==} engines: {node: '>=14 <20'} dependencies: @@ -1716,41 +1737,41 @@ packages: mnemonist: 0.39.5 dev: false - /fast-levenshtein/2.0.6: + /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /fast-querystring/1.1.1: + /fast-querystring@1.1.1: resolution: {integrity: sha512-qR2r+e3HvhEFmpdHMv//U8FnFlnYjaC6QKDuaXALDkw2kvHO8WDjxH+f/rHGR4Me4pnk8p9JAkRNTjYHAKRn2Q==} dependencies: fast-decode-uri-component: 1.0.1 dev: false - /fast-redact/3.1.2: + /fast-redact@3.1.2: resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} engines: {node: '>=6'} dev: false - /fast-uri/2.2.0: + /fast-uri@2.2.0: resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} dev: false - /fastfall/1.5.1: + /fastfall@1.5.1: resolution: {integrity: sha512-KH6p+Z8AKPXnmA7+Iz2Lh8ARCMr+8WNPVludm1LGkZoD2MjY6LVnRMtTKhkdzI+jr0RzQWXKzKyBJm1zoHEL4Q==} engines: {node: '>=0.10.0'} dependencies: reusify: 1.0.4 dev: false - /fastify-plugin/3.0.1: + /fastify-plugin@3.0.1: resolution: {integrity: sha512-qKcDXmuZadJqdTm6vlCqioEbyewF60b/0LOFCcYN1B6BIZGlYJumWWOYs70SFYLDAH4YqdE1cxH/RKMG7rFxgA==} dev: false - /fastify-plugin/4.5.0: + /fastify-plugin@4.5.0: resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==} dev: false - /fastify/4.14.1: + /fastify@4.14.1: resolution: {integrity: sha512-yjrDeXe77j9gRlSV2UJry8mcFWbD0NQ5JYjnPi4tkFjHZVaG3/BD5wxOmRzGnHPC0YvaBJ0XWrIfFPl2IHRa1w==} dependencies: '@fastify/ajv-compiler': 3.5.0 @@ -1772,45 +1793,45 @@ packages: - supports-color dev: false - /fastparallel/2.4.1: + /fastparallel@2.4.1: resolution: {integrity: sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==} dependencies: reusify: 1.0.4 xtend: 4.0.2 dev: false - /fastq/1.15.0: + /fastq@1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: reusify: 1.0.4 - /fastseries/1.7.2: + /fastseries@1.7.2: resolution: {integrity: sha512-dTPFrPGS8SNSzAt7u/CbMKCJ3s01N04s4JFbORHcmyvVfVKmbhMD1VtRbh5enGHxkaQDqWyLefiKOGGmohGDDQ==} dependencies: reusify: 1.0.4 xtend: 4.0.2 dev: false - /figlet/1.5.2: + /figlet@1.5.2: resolution: {integrity: sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==} engines: {node: '>= 0.4.0'} dev: false - /figures/3.2.0: + /figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} dependencies: escape-string-regexp: 1.0.5 dev: true - /file-entry-cache/6.0.1: + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: flat-cache: 3.0.4 dev: true - /file-type/18.2.1: + /file-type@18.2.1: resolution: {integrity: sha512-Yw5MtnMv7vgD2/6Bjmmuegc8bQEVA9GmAyaR18bMYWKqsWDG9wgYZ1j4I6gNMF5Y5JBDcUcjRQqNQx7Y8uotcg==} engines: {node: '>=14.16'} dependencies: @@ -1819,19 +1840,19 @@ packages: token-types: 5.0.1 dev: false - /file-uri-to-path/1.0.0: + /file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} dev: false optional: true - /fill-range/7.0.1: + /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 dev: true - /find-my-way/7.6.0: + /find-my-way@7.6.0: resolution: {integrity: sha512-H7berWdHJ+5CNVr4ilLWPai4ml7Y2qAsxjw3pfeBxPigZmaDTzF0wjJLj90xRCmGcWYcyt050yN+34OZDJm1eQ==} engines: {node: '>=14'} dependencies: @@ -1840,14 +1861,14 @@ packages: safe-regex2: 2.0.0 dev: false - /find-up/2.1.0: + /find-up@2.1.0: resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} engines: {node: '>=4'} dependencies: locate-path: 2.0.0 dev: true - /find-up/4.1.0: + /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} dependencies: @@ -1855,7 +1876,7 @@ packages: path-exists: 4.0.0 dev: true - /find-up/5.0.0: + /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} dependencies: @@ -1863,7 +1884,7 @@ packages: path-exists: 4.0.0 dev: true - /flat-cache/3.0.4: + /flat-cache@3.0.4: resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: @@ -1871,16 +1892,16 @@ packages: rimraf: 3.0.2 dev: true - /flatted/3.2.7: + /flatted@3.2.7: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true - /forwarded/0.2.0: + /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} dev: false - /fs-extra/10.1.0: + /fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} dependencies: @@ -1889,7 +1910,7 @@ packages: universalify: 2.0.0 dev: false - /fs-extra/11.1.0: + /fs-extra@11.1.0: resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} engines: {node: '>=14.14'} dependencies: @@ -1898,11 +1919,11 @@ packages: universalify: 2.0.0 dev: true - /fs.realpath/1.0.0: + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true - /fsevents/2.3.2: + /fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -1910,19 +1931,18 @@ packages: dev: true optional: true - /function-bind/1.1.1: + /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true - /get-caller-file/2.0.5: + /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} dev: true - /get-pkg-repo/4.2.1: + /get-pkg-repo@4.2.1: resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} engines: {node: '>=6.9.0'} - hasBin: true dependencies: '@hutson/parse-repository-url': 3.0.2 hosted-git-info: 4.1.0 @@ -1930,15 +1950,14 @@ packages: yargs: 16.2.0 dev: true - /get-stream/6.0.1: + /get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} dev: true - /git-raw-commits/2.0.11: + /git-raw-commits@2.0.11: resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} engines: {node: '>=10'} - hasBin: true dependencies: dargs: 7.0.0 lodash: 4.17.21 @@ -1947,7 +1966,7 @@ packages: through2: 4.0.2 dev: true - /git-remote-origin-url/2.0.0: + /git-remote-origin-url@2.0.0: resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} engines: {node: '>=4'} dependencies: @@ -1955,36 +1974,35 @@ packages: pify: 2.3.0 dev: true - /git-semver-tags/4.1.1: + /git-semver-tags@4.1.1: resolution: {integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==} engines: {node: '>=10'} - hasBin: true dependencies: meow: 8.1.2 semver: 6.3.0 dev: true - /gitconfiglocal/1.0.0: + /gitconfiglocal@1.0.0: resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} dependencies: ini: 1.3.8 dev: true - /glob-parent/5.1.2: + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 dev: true - /glob-parent/6.0.2: + /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 dev: true - /glob/7.2.3: + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: fs.realpath: 1.0.0 @@ -1995,31 +2013,30 @@ packages: path-is-absolute: 1.0.1 dev: true - /global-dirs/0.1.1: + /global-dirs@0.1.1: resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} engines: {node: '>=4'} dependencies: ini: 1.3.8 dev: true - /globals/13.20.0: + /globals@13.20.0: resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} engines: {node: '>=8'} dependencies: type-fest: 0.20.2 dev: true - /graceful-fs/4.2.11: + /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - /grapheme-splitter/1.0.4: + /grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true - /handlebars/4.7.7: + /handlebars@4.7.7: resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} engines: {node: '>=0.4.7'} - hasBin: true dependencies: minimist: 1.2.8 neo-async: 2.6.2 @@ -2029,43 +2046,43 @@ packages: uglify-js: 3.17.4 dev: true - /hard-rejection/2.1.0: + /hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} dev: true - /has-flag/3.0.0: + /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} dev: true - /has-flag/4.0.0: + /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - /has/1.0.3: + /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 dev: true - /hex-to-rgba/2.0.1: + /hex-to-rgba@2.0.1: resolution: {integrity: sha512-5XqPJBpsEUMsseJUi2w2Hl7cHFFi3+OO10M2pzAvKB1zL6fc+koGMhmBqoDOCB4GemiRM/zvDMRIhVw6EkB8dQ==} dev: false - /hosted-git-info/2.8.9: + /hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true - /hosted-git-info/4.1.0: + /hosted-git-info@4.1.0: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} dependencies: lru-cache: 6.0.0 dev: true - /http-errors/2.0.0: + /http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} dependencies: @@ -2076,43 +2093,42 @@ packages: toidentifier: 1.0.1 dev: false - /human-signals/2.1.0: + /human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} dev: true - /human-signals/4.3.0: + /human-signals@4.3.0: resolution: {integrity: sha512-zyzVyMjpGBX2+6cDVZeFPCdtOtdsxOeseRhB9tkQ6xXmGUNrcnBzdEKPy3VPNYz+4gy1oukVOXcrJCunSyc6QQ==} engines: {node: '>=14.18.0'} dev: true - /husky/8.0.3: + /husky@8.0.3: resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} engines: {node: '>=14'} - hasBin: true dev: true - /iconv-lite/0.4.24: + /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 dev: true - /ieee754/1.2.1: + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false - /ignore-by-default/1.0.1: + /ignore-by-default@1.0.1: resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} dev: true - /ignore/5.2.4: + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} dev: true - /import-fresh/3.3.0: + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} dependencies: @@ -2120,31 +2136,31 @@ packages: resolve-from: 4.0.0 dev: true - /imurmurhash/0.1.4: + /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} dev: true - /indent-string/4.0.0: + /indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} dev: true - /inflight/1.0.6: + /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: once: 1.4.0 wrappy: 1.0.2 dev: true - /inherits/2.0.4: + /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - /ini/1.3.8: + /ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: true - /inquirer/7.3.3: + /inquirer@7.3.3: resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==} engines: {node: '>=8.0.0'} dependencies: @@ -2163,118 +2179,117 @@ packages: through: 2.3.8 dev: true - /ipaddr.js/1.9.1: + /ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} dev: false - /is-arrayish/0.2.1: + /is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true - /is-binary-path/2.1.0: + /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 dev: true - /is-core-module/2.11.0: + /is-core-module@2.11.0: resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} dependencies: has: 1.0.3 dev: true - /is-extglob/2.1.1: + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} dev: true - /is-fullwidth-code-point/3.0.0: + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - /is-fullwidth-code-point/4.0.0: + /is-fullwidth-code-point@4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} engines: {node: '>=12'} dev: true - /is-glob/4.0.3: + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 dev: true - /is-number/7.0.0: + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} dev: true - /is-obj/2.0.0: + /is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} dev: true - /is-path-inside/3.0.3: + /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} dev: true - /is-plain-obj/1.1.0: + /is-plain-obj@1.1.0: resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} engines: {node: '>=0.10.0'} dev: true - /is-stream/2.0.1: + /is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} dev: true - /is-stream/3.0.0: + /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true - /is-text-path/1.0.1: + /is-text-path@1.0.1: resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} engines: {node: '>=0.10.0'} dependencies: text-extensions: 1.9.0 dev: true - /isarray/1.0.0: + /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: true - /isexe/2.0.0: + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true - /iterate-object/1.3.4: + /iterate-object@1.3.4: resolution: {integrity: sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==} dev: false - /js-sdsl/4.3.0: + /js-sdsl@4.3.0: resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} dev: true - /js-tokens/4.0.0: + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true - /js-yaml/4.1.0: + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true dependencies: argparse: 2.0.1 dev: true - /json-buffer/3.0.1: + /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} dev: false - /json-fixer/1.6.15: + /json-fixer@1.6.15: resolution: {integrity: sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==} engines: {node: '>=10'} dependencies: @@ -2283,61 +2298,61 @@ packages: pegjs: 0.10.0 dev: true - /json-parse-better-errors/1.0.2: + /json-parse-better-errors@1.0.2: resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} dev: true - /json-parse-even-better-errors/2.3.1: + /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true - /json-schema-traverse/0.4.1: + /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true - /json-schema-traverse/1.0.0: + /json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - /json-stable-stringify-without-jsonify/1.0.1: + /json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true - /json-stringify-safe/5.0.1: + /json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} dev: true - /jsonfile/6.1.0: + /jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: universalify: 2.0.0 optionalDependencies: graceful-fs: 4.2.11 - /jsonparse/1.3.1: + /jsonparse@1.3.1: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} dev: true - /keyv/4.5.2: + /keyv@4.5.2: resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} dependencies: json-buffer: 3.0.1 dev: false - /kind-of/6.0.3: + /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} dev: true - /leeks.js/0.2.4: + /leeks.js@0.2.4: resolution: {integrity: sha512-yFR6BtcTA/5s2FJAVkPn2VEzqO76DqBIdkC+1vNyersz1Hw0DxhunRu7bI7/XKxwrNy8x0K4e1p4YQKIGRCBww==} dev: false - /leeks.js/0.3.0: + /leeks.js@0.3.0: resolution: {integrity: sha512-6viaW7OYf8ijUe3G+rQ6CvbCLkN32vmeaEnVg82M474MbEeB++FcA80Qya1lv3cCgwkuxnfzDgnmWxol1bfhfA==} dev: false - /leekslazylogger/5.0.1: + /leekslazylogger@5.0.1: resolution: {integrity: sha512-JPA1o6psxrovivF/X84VbBHeMfwnAqxHwAv4S7LlnSX5aSHikDL1P77jGy7fxW0NKj3RA4F7G8lM0Eb813wqvg==} engines: {node: '>=14'} dependencies: @@ -2346,7 +2361,7 @@ packages: leeks.js: 0.2.4 dev: false - /levn/0.4.1: + /levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} dependencies: @@ -2354,7 +2369,7 @@ packages: type-check: 0.4.0 dev: true - /light-my-request/5.9.1: + /light-my-request@5.9.1: resolution: {integrity: sha512-UT7pUk8jNCR1wR7w3iWfIjx32DiB2f3hFdQSOwy3/EPQ3n3VocyipUxcyRZR0ahoev+fky69uA+GejPa9KuHKg==} dependencies: cookie: 0.5.0 @@ -2362,19 +2377,18 @@ packages: set-cookie-parser: 2.5.1 dev: false - /lilconfig/2.1.0: + /lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} dev: true - /lines-and-columns/1.2.4: + /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - /lint-staged/13.2.0: + /lint-staged@13.2.0: resolution: {integrity: sha512-GbyK5iWinax5Dfw5obm2g2ccUiZXNGtAS4mCbJ0Lv4rq6iEtfBSjOYdcbOtAIFtM114t0vdpViDDetjVTSd8Vw==} engines: {node: ^14.13.1 || >=16.0.0} - hasBin: true dependencies: chalk: 5.2.0 cli-truncate: 3.1.0 @@ -2394,7 +2408,7 @@ packages: - supports-color dev: true - /listr2/5.0.8: + /listr2@5.0.8: resolution: {integrity: sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==} engines: {node: ^14.13.1 || >=16.0.0} peerDependencies: @@ -2413,7 +2427,7 @@ packages: wrap-ansi: 7.0.0 dev: true - /load-json-file/4.0.0: + /load-json-file@4.0.0: resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} engines: {node: '>=4'} dependencies: @@ -2423,7 +2437,7 @@ packages: strip-bom: 3.0.0 dev: true - /locate-path/2.0.0: + /locate-path@2.0.0: resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} engines: {node: '>=4'} dependencies: @@ -2431,67 +2445,67 @@ packages: path-exists: 3.0.0 dev: true - /locate-path/5.0.0: + /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} dependencies: p-locate: 4.1.0 dev: true - /locate-path/6.0.0: + /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} dependencies: p-locate: 5.0.0 dev: true - /lodash.camelcase/4.3.0: + /lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} dev: true - /lodash.isfunction/3.0.9: + /lodash.isfunction@3.0.9: resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} dev: true - /lodash.ismatch/4.4.0: + /lodash.ismatch@4.4.0: resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} dev: true - /lodash.isplainobject/4.0.6: + /lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} dev: true - /lodash.kebabcase/4.1.1: + /lodash.kebabcase@4.1.1: resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} dev: true - /lodash.merge/4.6.2: + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true - /lodash.mergewith/4.6.2: + /lodash.mergewith@4.6.2: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} dev: true - /lodash.snakecase/4.1.1: + /lodash.snakecase@4.1.1: resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} - /lodash.startcase/4.4.0: + /lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} dev: true - /lodash.uniq/4.5.0: + /lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} dev: true - /lodash.upperfirst/4.3.1: + /lodash.upperfirst@4.3.1: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} dev: true - /lodash/4.17.21: + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - /log-update/4.0.0: + /log-update@4.0.0: resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} engines: {node: '>=10'} dependencies: @@ -2501,39 +2515,38 @@ packages: wrap-ansi: 6.2.0 dev: true - /lru-cache/6.0.0: + /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} dependencies: yallist: 4.0.0 - /make-error/1.3.6: + /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true - /map-o/2.0.10: + /map-o@2.0.10: resolution: {integrity: sha512-BxazE81fVByHWasyXhqKeo2m7bFKYu+ZbEfiuexMOnklXW+tzDvnlTi/JaklEeuuwqcqJzPaf9q+TWptSGXeLg==} dependencies: iterate-object: 1.3.4 dev: false - /map-obj/1.0.1: + /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} dev: true - /map-obj/4.3.0: + /map-obj@4.3.0: resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} engines: {node: '>=8'} dev: true - /marked/4.2.12: + /marked@4.2.12: resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==} engines: {node: '>= 12'} - hasBin: true dev: false - /meow/8.1.2: + /meow@8.1.2: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} engines: {node: '>=10'} dependencies: @@ -2550,11 +2563,11 @@ packages: yargs-parser: 20.2.9 dev: true - /merge-stream/2.0.0: + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true - /micromatch/4.0.5: + /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} dependencies: @@ -2562,31 +2575,31 @@ packages: picomatch: 2.3.1 dev: true - /mimic-fn/2.1.0: + /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} dev: true - /mimic-fn/4.0.0: + /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} dev: true - /min-indent/1.0.1: + /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} dev: true - /minimalistic-assert/1.0.1: + /minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} dev: false - /minimatch/3.1.2: + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 - /minimist-options/4.1.0: + /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} dependencies: @@ -2595,69 +2608,67 @@ packages: kind-of: 6.0.3 dev: true - /minimist/1.2.8: + /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: true - /mnemonist/0.39.5: + /mnemonist@0.39.5: resolution: {integrity: sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==} dependencies: obliterator: 2.0.4 dev: false - /modify-values/1.0.1: + /modify-values@1.0.1: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} dev: true - /ms/2.1.2: + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - /ms/2.1.3: + /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /mustache/4.2.0: + /mustache@4.2.0: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} - hasBin: true dev: false - /mute-stream/0.0.8: + /mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} dev: true - /nan/2.17.0: + /nan@2.17.0: resolution: {integrity: sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==} dev: false optional: true - /nanoid/3.3.4: + /nanoid@3.3.4: resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true dev: false - /natural-compare/1.4.0: + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true - /neo-async/2.6.2: + /neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /node-dir/0.1.17: + /node-dir@0.1.17: resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} engines: {node: '>= 0.10.5'} dependencies: minimatch: 3.1.2 dev: false - /node-emoji/1.11.0: + /node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} dependencies: lodash: 4.17.21 dev: false - /node-fetch/2.6.9: + /node-fetch@2.6.9: resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==} engines: {node: 4.x || >=6.0.0} peerDependencies: @@ -2669,19 +2680,16 @@ packages: whatwg-url: 5.0.0 dev: true - /node-gyp-build/4.6.0: + /node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} - hasBin: true dev: false - /nodemon/2.0.21: + /nodemon@2.0.21: resolution: {integrity: sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A==} engines: {node: '>=8.10.0'} - hasBin: true - requiresBuild: true dependencies: chokidar: 3.5.3 - debug: 3.2.7_supports-color@5.5.0 + debug: 3.2.7(supports-color@5.5.0) ignore-by-default: 1.0.1 minimatch: 3.1.2 pstree.remy: 1.1.8 @@ -2692,14 +2700,13 @@ packages: undefsafe: 2.0.5 dev: true - /nopt/1.0.10: + /nopt@1.0.10: resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} - hasBin: true dependencies: abbrev: 1.1.1 dev: true - /normalize-package-data/2.5.0: + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 @@ -2708,7 +2715,7 @@ packages: validate-npm-package-license: 3.0.4 dev: true - /normalize-package-data/3.0.3: + /normalize-package-data@3.0.3: resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} engines: {node: '>=10'} dependencies: @@ -2718,62 +2725,62 @@ packages: validate-npm-package-license: 3.0.4 dev: true - /normalize-path/3.0.0: + /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} dev: true - /npm-run-path/4.0.1: + /npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} dependencies: path-key: 3.1.1 dev: true - /npm-run-path/5.1.0: + /npm-run-path@5.1.0: resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: path-key: 4.0.0 dev: true - /object-diffy/1.0.4: + /object-diffy@1.0.4: resolution: {integrity: sha512-zsgUWZhu9YtP7kp+PvCzUhYtOurBa7qIS2XUJFyVooq+I/ZlwFe0aHp1pyek/dpqd+EEYxM46j8czpW54JM2EA==} dev: false - /object-inspect/1.12.3: + /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} dev: true - /obliterator/2.0.4: + /obliterator@2.0.4: resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} dev: false - /on-exit-leak-free/2.1.0: + /on-exit-leak-free@2.1.0: resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} dev: false - /once/1.4.0: + /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 dev: true - /onetime/5.1.2: + /onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 dev: true - /onetime/6.0.0: + /onetime@6.0.0: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} dependencies: mimic-fn: 4.0.0 dev: true - /optionator/0.9.1: + /optionator@0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} dependencies: @@ -2785,78 +2792,78 @@ packages: word-wrap: 1.2.3 dev: true - /os-tmpdir/1.0.2: + /os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} dev: true - /p-limit/1.3.0: + /p-limit@1.3.0: resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} engines: {node: '>=4'} dependencies: p-try: 1.0.0 dev: true - /p-limit/2.3.0: + /p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} dependencies: p-try: 2.2.0 dev: true - /p-limit/3.1.0: + /p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 dev: true - /p-locate/2.0.0: + /p-locate@2.0.0: resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} engines: {node: '>=4'} dependencies: p-limit: 1.3.0 dev: true - /p-locate/4.1.0: + /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} dependencies: p-limit: 2.3.0 dev: true - /p-locate/5.0.0: + /p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} dependencies: p-limit: 3.1.0 dev: true - /p-map/4.0.0: + /p-map@4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} dependencies: aggregate-error: 3.1.0 dev: true - /p-try/1.0.0: + /p-try@1.0.0: resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} engines: {node: '>=4'} dev: true - /p-try/2.2.0: + /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} dev: true - /parent-module/1.0.1: + /parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} dependencies: callsites: 3.1.0 dev: true - /parse-json/4.0.0: + /parse-json@4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} dependencies: @@ -2864,7 +2871,7 @@ packages: json-parse-better-errors: 1.0.2 dev: true - /parse-json/5.2.0: + /parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: @@ -2874,102 +2881,99 @@ packages: lines-and-columns: 1.2.4 dev: true - /path-exists/3.0.0: + /path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} dev: true - /path-exists/4.0.0: + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} dev: true - /path-is-absolute/1.0.1: + /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} dev: true - /path-key/3.1.1: + /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} dev: true - /path-key/4.0.0: + /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} dev: true - /path-parse/1.0.7: + /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true - /path-type/3.0.0: + /path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} dependencies: pify: 3.0.0 dev: true - /path-type/4.0.0: + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} dev: true - /peek-readable/5.0.0: + /peek-readable@5.0.0: resolution: {integrity: sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==} engines: {node: '>=14.16'} dev: false - /pegjs/0.10.0: + /pegjs@0.10.0: resolution: {integrity: sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==} engines: {node: '>=0.10'} - hasBin: true dev: true - /picocolors/1.0.0: + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: false - /picomatch/2.3.1: + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} dev: true - /pidtree/0.6.0: + /pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} - hasBin: true dev: true - /pify/2.3.0: + /pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} dev: true - /pify/3.0.0: + /pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} dev: true - /pify/5.0.0: + /pify@5.0.0: resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} engines: {node: '>=10'} dev: true - /pino-abstract-transport/1.0.0: + /pino-abstract-transport@1.0.0: resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==} dependencies: readable-stream: 4.3.0 split2: 4.1.0 dev: false - /pino-std-serializers/6.1.0: + /pino-std-serializers@6.1.0: resolution: {integrity: sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==} dev: false - /pino/8.11.0: + /pino@8.11.0: resolution: {integrity: sha512-Z2eKSvlrl2rH8p5eveNUnTdd4AjJk8tAsLkHYZQKGHP4WTh2Gi1cOSOs3eWPqaj+niS3gj4UkoreoaWgF3ZWYg==} - hasBin: true dependencies: atomic-sleep: 1.0.0 fast-redact: 3.1.2 @@ -2984,7 +2988,7 @@ packages: thread-stream: 2.3.0 dev: false - /postcss/8.4.21: + /postcss@8.4.21: resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} engines: {node: ^10 || ^12 || >=14} dependencies: @@ -2993,34 +2997,33 @@ packages: source-map-js: 1.0.2 dev: false - /prelude-ls/1.2.1: + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} dev: true - /prisma/4.11.0: + /prisma@4.11.0: resolution: {integrity: sha512-4zZmBXssPUEiX+GeL0MUq/Yyie4ltiKmGu7jCJFnYMamNrrulTBc+D+QwAQSJ01tyzeGHlD13kOnqPwRipnlNw==} engines: {node: '>=14.17'} - hasBin: true requiresBuild: true dependencies: '@prisma/engines': 4.11.0 dev: false - /process-nextick-args/2.0.1: + /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true - /process-warning/2.1.0: + /process-warning@2.1.0: resolution: {integrity: sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg==} dev: false - /process/0.11.10: + /process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} dev: false - /proxy-addr/2.0.7: + /proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} dependencies: @@ -3028,33 +3031,33 @@ packages: ipaddr.js: 1.9.1 dev: false - /pstree.remy/1.1.8: + /pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} dev: true - /punycode/2.3.0: + /punycode@2.3.0: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} - /q/1.5.1: + /q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} dev: true - /queue-microtask/1.2.3: + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true - /quick-format-unescaped/4.0.4: + /quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} dev: false - /quick-lru/4.0.1: + /quick-lru@4.0.1: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} dev: true - /read-pkg-up/3.0.0: + /read-pkg-up@3.0.0: resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} engines: {node: '>=4'} dependencies: @@ -3062,7 +3065,7 @@ packages: read-pkg: 3.0.0 dev: true - /read-pkg-up/7.0.1: + /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} dependencies: @@ -3071,7 +3074,7 @@ packages: type-fest: 0.8.1 dev: true - /read-pkg/3.0.0: + /read-pkg@3.0.0: resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} engines: {node: '>=4'} dependencies: @@ -3080,7 +3083,7 @@ packages: path-type: 3.0.0 dev: true - /read-pkg/5.2.0: + /read-pkg@5.2.0: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} dependencies: @@ -3090,7 +3093,7 @@ packages: type-fest: 0.6.0 dev: true - /readable-stream/2.3.8: + /readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: core-util-is: 1.0.3 @@ -3102,7 +3105,7 @@ packages: util-deprecate: 1.0.2 dev: true - /readable-stream/3.6.2: + /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} dependencies: @@ -3110,7 +3113,7 @@ packages: string_decoder: 1.3.0 util-deprecate: 1.0.2 - /readable-stream/4.3.0: + /readable-stream@4.3.0: resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: @@ -3120,26 +3123,26 @@ packages: process: 0.11.10 dev: false - /readable-web-to-node-stream/3.0.2: + /readable-web-to-node-stream@3.0.2: resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} engines: {node: '>=8'} dependencies: readable-stream: 3.6.2 dev: false - /readdirp/3.6.0: + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 dev: true - /real-require/0.2.0: + /real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} dev: false - /redent/3.0.0: + /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} dependencies: @@ -3147,50 +3150,49 @@ packages: strip-indent: 3.0.0 dev: true - /regenerator-runtime/0.13.11: + /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} dev: true - /require-directory/2.1.1: + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} dev: true - /require-from-string/2.0.2: + /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - /require-main-filename/2.0.0: + /require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: true - /resolve-from/4.0.0: + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} dev: true - /resolve-from/5.0.0: + /resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} dev: true - /resolve-global/1.0.0: + /resolve-global@1.0.0: resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} engines: {node: '>=8'} dependencies: global-dirs: 0.1.1 dev: true - /resolve/1.22.1: + /resolve@1.22.1: resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} - hasBin: true dependencies: is-core-module: 2.11.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true - /restore-cursor/3.1.0: + /restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} dependencies: @@ -3198,127 +3200,121 @@ packages: signal-exit: 3.0.7 dev: true - /ret/0.2.2: + /ret@0.2.2: resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==} engines: {node: '>=4'} dev: false - /reusify/1.0.4: + /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - /rfdc/1.3.0: + /rfdc@1.3.0: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} - /rimraf/3.0.2: + /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true dependencies: glob: 7.2.3 dev: true - /run-async/2.4.1: + /run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} dev: true - /run-parallel/1.2.0: + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 dev: true - /rxjs/6.6.7: + /rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} dependencies: tslib: 1.14.1 dev: true - /rxjs/7.8.0: + /rxjs@7.8.0: resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} dependencies: tslib: 2.5.0 dev: true - /safe-buffer/5.1.2: + /safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} dev: true - /safe-buffer/5.2.1: + /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - /safe-regex2/2.0.0: + /safe-regex2@2.0.0: resolution: {integrity: sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==} dependencies: ret: 0.2.2 dev: false - /safe-stable-stringify/2.4.2: + /safe-stable-stringify@2.4.2: resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==} engines: {node: '>=10'} dev: false - /safer-buffer/2.1.2: + /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - /secure-json-parse/2.7.0: + /secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} dev: false - /semver/5.7.1: + /semver@5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} - hasBin: true dev: true - /semver/6.3.0: + /semver@6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true dev: true - /semver/7.0.0: + /semver@7.0.0: resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} - hasBin: true dev: true - /semver/7.3.8: + /semver@7.3.8: resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} engines: {node: '>=10'} - hasBin: true dependencies: lru-cache: 6.0.0 - /set-blocking/2.0.0: + /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true - /set-cookie-parser/2.5.1: + /set-cookie-parser@2.5.1: resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==} dev: false - /setprototypeof/1.2.0: + /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: false - /shebang-command/2.0.0: + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 dev: true - /shebang-regex/3.0.0: + /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} dev: true - /signal-exit/3.0.7: + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true - /simple-oauth2/3.4.0: + /simple-oauth2@3.4.0: resolution: {integrity: sha512-gDPCC/xjq82FJnzF7+XGUUMWWfHeibuGsp3OYOV7yHwIibxHkq4+WSxywY63/3BF9j8SfIDygGqBrPLynx/iuQ==} - deprecated: simple-oauth2 v3 is no longer supported. Use simple-oauth2 v4 or higher for continued support dependencies: '@hapi/hoek': 8.5.1 '@hapi/joi': 16.1.8 @@ -3329,14 +3325,14 @@ packages: - supports-color dev: false - /simple-update-notifier/1.1.0: + /simple-update-notifier@1.1.0: resolution: {integrity: sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==} engines: {node: '>=8.10.0'} dependencies: semver: 7.0.0 dev: true - /slice-ansi/3.0.0: + /slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} dependencies: @@ -3345,7 +3341,7 @@ packages: is-fullwidth-code-point: 3.0.0 dev: true - /slice-ansi/4.0.0: + /slice-ansi@4.0.0: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} dependencies: @@ -3354,7 +3350,7 @@ packages: is-fullwidth-code-point: 3.0.0 dev: true - /slice-ansi/5.0.0: + /slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} dependencies: @@ -3362,75 +3358,75 @@ packages: is-fullwidth-code-point: 4.0.0 dev: true - /sonic-boom/3.2.1: + /sonic-boom@3.2.1: resolution: {integrity: sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==} dependencies: atomic-sleep: 1.0.0 dev: false - /sortablejs/1.15.0: + /sortablejs@1.15.0: resolution: {integrity: sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==} dev: false - /source-map-js/1.0.2: + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} dev: false - /source-map/0.6.1: + /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} dev: true - /spacetime/7.4.1: + /spacetime@7.4.1: resolution: {integrity: sha512-khQpvLNMhHFzfFJslMfunqqsjOxdmoDDYX5Wh4qYb8N6f8vBPI6HvbT7Wb2wcMa+oP1Xh1HpEcwfPFrj8UfvHQ==} dev: false - /spdx-correct/3.2.0: + /spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: spdx-expression-parse: 3.0.1 spdx-license-ids: 3.0.13 dev: true - /spdx-exceptions/2.3.0: + /spdx-exceptions@2.3.0: resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} dev: true - /spdx-expression-parse/3.0.1: + /spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} dependencies: spdx-exceptions: 2.3.0 spdx-license-ids: 3.0.13 dev: true - /spdx-license-ids/3.0.13: + /spdx-license-ids@3.0.13: resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==} dev: true - /split/1.0.1: - resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} - dependencies: - through: 2.3.8 - dev: true - - /split2/3.2.2: + /split2@3.2.2: resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} dependencies: readable-stream: 3.6.2 dev: true - /split2/4.1.0: + /split2@4.1.0: resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} engines: {node: '>= 10.x'} dev: false - /statuses/2.0.1: + /split@1.0.1: + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + dependencies: + through: 2.3.8 + dev: true + + /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} dev: false - /steed/1.1.3: + /steed@1.1.3: resolution: {integrity: sha512-EUkci0FAUiE4IvGTSKcDJIQ/eRUP2JJb56+fvZ4sdnguLTqIdKjSxUe138poW8mkvKWXW2sFPrgTsxqoISnmoA==} dependencies: fastfall: 1.5.1 @@ -3440,17 +3436,17 @@ packages: reusify: 1.0.4 dev: false - /streamsearch/1.1.0: + /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} dev: false - /string-argv/0.3.1: + /string-argv@0.3.1: resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} engines: {node: '>=0.6.19'} dev: true - /string-width/4.2.3: + /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} dependencies: @@ -3458,7 +3454,7 @@ packages: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - /string-width/5.1.2: + /string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} dependencies: @@ -3466,57 +3462,57 @@ packages: emoji-regex: 9.2.2 strip-ansi: 7.0.1 - /string_decoder/1.1.1: + /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: safe-buffer: 5.1.2 dev: true - /string_decoder/1.3.0: + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 - /strip-ansi/6.0.1: + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - /strip-ansi/7.0.1: + /strip-ansi@7.0.1: resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - /strip-bom/3.0.0: + /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} dev: true - /strip-final-newline/2.0.0: + /strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} dev: true - /strip-final-newline/3.0.0: + /strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} dev: true - /strip-indent/3.0.0: + /strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} dependencies: min-indent: 1.0.1 dev: true - /strip-json-comments/3.1.1: + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} dev: true - /strtok3/7.0.0: + /strtok3@7.0.0: resolution: {integrity: sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==} engines: {node: '>=14.16'} dependencies: @@ -3524,20 +3520,20 @@ packages: peek-readable: 5.0.0 dev: false - /supports-color/5.5.0: + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} dependencies: has-flag: 3.0.0 dev: true - /supports-color/7.2.0: + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - /supports-hyperlinks/2.3.0: + /supports-hyperlinks@2.3.0: resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} engines: {node: '>=8'} dependencies: @@ -3545,12 +3541,12 @@ packages: supports-color: 7.2.0 dev: false - /supports-preserve-symlinks-flag/1.0.0: + /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} dev: true - /svelte-modals/1.2.1_svelte@3.57.0: + /svelte-modals@1.2.1(svelte@3.57.0): resolution: {integrity: sha512-7MEKUx5wb5YppkXWFGeRlYM5FMGEnpix39u9Y6GtCNTMXRDZ7DB2Z50IYLMRTMW5lOsCdtJgFbB0E3iZMKmsAA==} peerDependencies: svelte: ^3.0.0 @@ -3558,21 +3554,21 @@ packages: svelte: 3.57.0 dev: false - /svelte-toasts/1.1.2: + /svelte-toasts@1.1.2: resolution: {integrity: sha512-m+yL4eEKXyJoyjTYaH1j1GFwF0Pi8YDqnVfwWPDmwi4712iZesv+TNCmToSNlav3R5Vkmc8ZBRkT8DOcu3sywQ==} dev: false - /svelte/3.57.0: + /svelte@3.57.0: resolution: {integrity: sha512-WMXEvF+RtAaclw0t3bPDTUe19pplMlfyKDsixbHQYgCWi9+O9VN0kXU1OppzrB9gPAvz4NALuoca2LfW2bOjTQ==} engines: {node: '>= 8'} dev: false - /temp-dir/2.0.0: + /temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} dev: true - /tempfile/3.0.0: + /tempfile@3.0.0: resolution: {integrity: sha512-uNFCg478XovRi85iD42egu+eSFUmmka750Jy7L5tfHI5hQKKtbPnxaSaXAbBqCDYrw3wx4tXjKwci4/QmsZJxw==} engines: {node: '>=8'} dependencies: @@ -3580,7 +3576,7 @@ packages: uuid: 3.4.0 dev: true - /terminal-link/2.1.1: + /terminal-link@2.1.1: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} dependencies: @@ -3588,63 +3584,63 @@ packages: supports-hyperlinks: 2.3.0 dev: false - /text-extensions/1.9.0: + /text-extensions@1.9.0: resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} engines: {node: '>=0.10'} dev: true - /text-table/0.2.0: + /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true - /thread-stream/2.3.0: + /thread-stream@2.3.0: resolution: {integrity: sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==} dependencies: real-require: 0.2.0 dev: false - /through/2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - dev: true - - /through2/2.0.5: + /through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} dependencies: readable-stream: 2.3.8 xtend: 4.0.2 dev: true - /through2/4.0.2: + /through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} dependencies: readable-stream: 3.6.2 dev: true - /tiny-lru/10.2.1: + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true + + /tiny-lru@10.2.1: resolution: {integrity: sha512-cxcNyX4O50FDnB5x9jdEJupYC+9PPutt6wT16TaOtXeHfV9t41aFqg0VniB9YK5KmBeqmZaYriNMajm/5TtA7Q==} engines: {node: '>=12'} dev: false - /tmp/0.0.33: + /tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} dependencies: os-tmpdir: 1.0.2 dev: true - /to-regex-range/5.0.1: + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 dev: true - /toidentifier/1.0.1: + /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} dev: false - /token-types/5.0.1: + /token-types@5.0.1: resolution: {integrity: sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==} engines: {node: '>=14.16'} dependencies: @@ -3652,29 +3648,27 @@ packages: ieee754: 1.2.1 dev: false - /touch/3.1.0: + /touch@3.1.0: resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} - hasBin: true dependencies: nopt: 1.0.10 dev: true - /tr46/0.0.3: + /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: true - /trim-newlines/3.0.1: + /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} dev: true - /ts-mixer/6.0.3: + /ts-mixer@6.0.3: resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==} dev: false - /ts-node/10.9.1_cbfmry4sbbh4vatmdrsmatfg5a: + /ts-node@10.9.1(@types/node@18.15.3)(typescript@4.9.5): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} - hasBin: true peerDependencies: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' @@ -3703,84 +3697,82 @@ packages: yn: 3.1.1 dev: true - /tslib/1.14.1: + /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - /tslib/2.5.0: + /tslib@2.5.0: resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} - /type-check/0.4.0: + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 dev: true - /type-fest/0.18.1: + /type-fest@0.18.1: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} dev: true - /type-fest/0.20.2: + /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} dev: true - /type-fest/0.21.3: + /type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - /type-fest/0.6.0: + /type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} dev: true - /type-fest/0.8.1: + /type-fest@0.8.1: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} dev: true - /type-fest/2.19.0: + /type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} dev: false - /typescript/4.9.5: + /typescript@4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} - hasBin: true dev: true - /uglify-js/3.17.4: + /uglify-js@3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} engines: {node: '>=0.8.0'} - hasBin: true requiresBuild: true dev: true optional: true - /undefsafe/2.0.5: + /undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true - /undici/5.21.0: + /undici@5.21.0: resolution: {integrity: sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==} engines: {node: '>=12.18'} dependencies: busboy: 1.6.0 dev: false - /universalify/2.0.0: + /universalify@2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} - /uri-js/4.4.1: + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.0 - /utf-8-validate/5.0.10: + /utf-8-validate@5.0.10: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} requiresBuild: true @@ -3788,66 +3780,63 @@ packages: node-gyp-build: 4.6.0 dev: false - /util-deprecate/1.0.2: + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - /uuid/3.4.0: + /uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. - hasBin: true dev: true - /v8-compile-cache-lib/3.0.1: + /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} dev: true - /validate-npm-package-license/3.0.4: + /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 dev: true - /webidl-conversions/3.0.1: + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: true - /whatwg-url/5.0.0: + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 dev: true - /which-module/2.0.0: + /which-module@2.0.0: resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} dev: true - /which/2.0.2: + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} - hasBin: true dependencies: isexe: 2.0.0 dev: true - /widest-line/4.0.1: + /widest-line@4.0.1: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} engines: {node: '>=12'} dependencies: string-width: 5.1.2 dev: false - /word-wrap/1.2.3: + /word-wrap@1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} dev: true - /wordwrap/1.0.0: + /wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} dev: true - /wrap-ansi/6.2.0: + /wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} dependencies: @@ -3856,7 +3845,7 @@ packages: strip-ansi: 6.0.1 dev: true - /wrap-ansi/7.0.0: + /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} dependencies: @@ -3865,7 +3854,7 @@ packages: strip-ansi: 6.0.1 dev: true - /wrap-ansi/8.1.0: + /wrap-ansi@8.1.0: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} dependencies: @@ -3874,11 +3863,11 @@ packages: strip-ansi: 7.0.1 dev: false - /wrappy/1.0.2: + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true - /ws/8.13.0_3cxu5zja4e2r5wmvge7mdcljwq: + /ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10): resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==} engines: {node: '>=10.0.0'} peerDependencies: @@ -3894,33 +3883,33 @@ packages: utf-8-validate: 5.0.10 dev: false - /xtend/4.0.2: + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - /y18n/4.0.3: + /y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} dev: true - /y18n/5.0.8: + /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} dev: true - /yallist/4.0.0: + /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - /yaml/1.10.2: + /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} dev: false - /yaml/2.2.1: + /yaml@2.2.1: resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} engines: {node: '>= 14'} dev: true - /yargs-parser/18.1.3: + /yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} dependencies: @@ -3928,17 +3917,17 @@ packages: decamelize: 1.2.0 dev: true - /yargs-parser/20.2.9: + /yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} dev: true - /yargs-parser/21.1.1: + /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} dev: true - /yargs/15.4.1: + /yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} dependencies: @@ -3955,7 +3944,7 @@ packages: yargs-parser: 18.1.3 dev: true - /yargs/16.2.0: + /yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} dependencies: @@ -3968,7 +3957,7 @@ packages: yargs-parser: 20.2.9 dev: true - /yargs/17.7.1: + /yargs@17.7.1: resolution: {integrity: sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==} engines: {node: '>=12'} dependencies: @@ -3981,17 +3970,17 @@ packages: yargs-parser: 21.1.1 dev: true - /yn/3.1.1: + /yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} dev: true - /yocto-queue/0.1.0: + /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true - /zlib-sync/0.1.8: + /zlib-sync@0.1.8: resolution: {integrity: sha512-Xbu4odT5SbLsa1HFz8X/FvMgUbJYWxJYKB2+bqxJ6UOIIPaVGrqHEB3vyXDltSA6tTqBhSGYLgiVpzPQHYi3lA==} requiresBuild: true dependencies: From d577b9d057927e1062b5b00d886351537c236385 Mon Sep 17 00:00:00 2001 From: Uzurka <101745008+Uzurka@users.noreply.github.com> Date: Fri, 28 Apr 2023 22:33:06 +0200 Subject: [PATCH 375/409] fix(api): logout (#415) * Fixed logout Fixed users can't logout * Update logout.js * Fix ESLint --- src/routes/auth/logout.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/routes/auth/logout.js b/src/routes/auth/logout.js index 22fc77c..3e8b164 100644 --- a/src/routes/auth/logout.js +++ b/src/routes/auth/logout.js @@ -1,13 +1,22 @@ +const { domain } = require('../../lib/http'); + module.exports.get = fastify => ({ handler: async function (req, res) { + const { accessToken } = req.user; + await fetch('https://discord.com/api/oauth2/token/revoke', { - body: new URLSearchParams({ token: req.user.accessToken }).toString(), + body: new URLSearchParams({ token: accessToken }).toString(), headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, method: 'POST', }); - res - .clearCookie('token', '/') - .send('The token has been revoked.'); + + res.clearCookie('token', { + domain, + httpOnly: true, + path: '/', + sameSite: 'Lax', + secure: false, + }).send('The token has been revoked.'); }, onRequest: [fastify.authenticate], }); From 1dd444926024a37515e62847704874e0ce1a1145 Mon Sep 17 00:00:00 2001 From: Kirill Plotnikov Date: Sat, 29 Apr 2023 22:51:55 +0200 Subject: [PATCH 376/409] feat(i18n): update Russian translations [skip ci] Currently translated at 7.3% (19 of 258 strings) Co-authored-by: Kirill Plotnikov Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/ru/ Translation: Discord Tickets/Bot --- src/i18n/ru.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/i18n/ru.yml b/src/i18n/ru.yml index a6c9fca..126ce7a 100644 --- a/src/i18n/ru.yml +++ b/src/i18n/ru.yml @@ -3,3 +3,27 @@ buttons: emoji: ✅ cancel: emoji: ✖️ + text: Отмена + confirm_open: + emoji: ✅ + text: Создать тикет + close: + emoji: ✖️ + claim: + emoji: 🙌 + create: + emoji: 🎫 + text: Создать тикет + edit: + emoji: ✏️ + text: Редактировать + reject_close_request: + emoji: ✖️ + unclaim: + emoji: ♻️ +commands: + message: + create: + name: Создать тикет из сообщения + pin: + name: Закрепить сообщение From ac58cdea975a2f6fda766db96042c501e21e3260 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Sat, 29 Apr 2023 22:51:55 +0200 Subject: [PATCH 377/409] feat(i18n): update Russian translations [skip ci] Currently translated at 7.3% (19 of 258 strings) Co-authored-by: Alexander Morozov Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/ru/ Translation: Discord Tickets/Bot --- src/i18n/ru.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/i18n/ru.yml b/src/i18n/ru.yml index 126ce7a..4bcb512 100644 --- a/src/i18n/ru.yml +++ b/src/i18n/ru.yml @@ -9,8 +9,10 @@ buttons: text: Создать тикет close: emoji: ✖️ + text: Закрыть claim: emoji: 🙌 + text: Взять create: emoji: 🎫 text: Создать тикет @@ -19,6 +21,7 @@ buttons: text: Редактировать reject_close_request: emoji: ✖️ + text: Отклонить unclaim: emoji: ♻️ commands: From d7e538ee3c0b19607c309675cb3a8d87370a382a Mon Sep 17 00:00:00 2001 From: i3sey Date: Sat, 29 Apr 2023 22:51:56 +0200 Subject: [PATCH 378/409] feat(i18n): update Russian translations [skip ci] Currently translated at 7.3% (19 of 258 strings) Co-authored-by: i3sey Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/ru/ Translation: Discord Tickets/Bot --- src/i18n/ru.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/ru.yml b/src/i18n/ru.yml index 4bcb512..7d850af 100644 --- a/src/i18n/ru.yml +++ b/src/i18n/ru.yml @@ -1,6 +1,7 @@ buttons: accept_close_request: emoji: ✅ + text: Принять cancel: emoji: ✖️ text: Отмена From cf93f085ac4309ba1da24341b13b4ddbada5b2a4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 6 May 2023 00:06:39 +0100 Subject: [PATCH 379/409] fix(stats): average response/resolution times --- src/lib/stats.js | 2 ++ src/lib/tickets/manager.js | 13 +++++++++---- src/listeners/client/ready.js | 8 ++++++-- .../api/admin/guilds/[guild]/categories/index.js | 8 ++++++-- src/routes/api/admin/guilds/[guild]/index.js | 10 +++++++--- src/routes/api/client.js | 9 ++++++--- 6 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 src/lib/stats.js diff --git a/src/lib/stats.js b/src/lib/stats.js new file mode 100644 index 0000000..3aa9e7c --- /dev/null +++ b/src/lib/stats.js @@ -0,0 +1,2 @@ +module.exports.getAvgResolutionTime = tickets => (tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1) / tickets.length; +module.exports.getAvgResponseTime = tickets => (tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1) / tickets.length; diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 2035027..480ce9b 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -20,6 +20,10 @@ const { isStaff } = require('../users'); const { Collection } = require('discord.js'); const spacetime = require('spacetime'); const Cryptr = require('cryptr'); +const { + getAvgResolutionTime, + getAvgResponseTime, +} = require('../stats'); const { decrypt, encrypt, @@ -404,10 +408,11 @@ module.exports = class TicketManager { if (category.image) await channel.send(category.image); + const needsStats = category.openingMessage.match(/{+\s?avgResponseTime\s?}+/i) || category.openingMessage.match(/{+\s?avgResolutionTime\s?}+/i); const statsCacheKey = `cache/category-stats/${categoryId}`; let stats = await this.client.keyv.get(statsCacheKey); - if (!stats) { - const tickets = await this.client.prisma.ticket.findMany({ + if (needsStats && !stats) { + const closedTickets = await this.client.prisma.ticket.findMany({ select: { closedAt: true, createdAt: true, @@ -420,8 +425,8 @@ module.exports = class TicketManager { }, }); stats = { - avgResolutionTime: ms(tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / tickets.length, { long: true }), - avgResponseTime: ms(tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / tickets.length, { long: true }), + avgResolutionTime: ms(getAvgResolutionTime(closedTickets), { long: true }), + avgResponseTime: ms(getAvgResponseTime(closedTickets), { long: true }), }; this.client.keyv.set(statsCacheKey, stats, ms('1h')); } diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 81b48c2..53d5402 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -1,5 +1,9 @@ const { Listener } = require('@eartharoid/dbf'); const crypto = require('crypto'); +const { + getAvgResolutionTime, + getAvgResponseTime, +} = require('../../lib/stats'); const ms = require('ms'); const { version } = require('../../../package.json'); const { msToMins } = require('../../lib/misc'); @@ -58,8 +62,8 @@ module.exports = class extends Listener { }); const closedTickets = tickets.filter(t => t.firstResponseAt && t.closedAt); cached = { - avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), - avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + avgResolutionTime: ms(getAvgResolutionTime(closedTickets)), + avgResponseTime: ms(getAvgResponseTime(closedTickets)), openTickets: tickets.length - closedTickets.length, totalTickets: tickets.length, }; diff --git a/src/routes/api/admin/guilds/[guild]/categories/index.js b/src/routes/api/admin/guilds/[guild]/categories/index.js index a171454..409baa2 100644 --- a/src/routes/api/admin/guilds/[guild]/categories/index.js +++ b/src/routes/api/admin/guilds/[guild]/categories/index.js @@ -6,6 +6,10 @@ const { ChannelType: { GuildCategory }, } = require('discord.js'); const ms = require('ms'); +const { + getAvgResolutionTime, + getAvgResponseTime, +} = require('../../../../../../lib/stats'); module.exports.get = fastify => ({ handler: async (req, res) => { @@ -36,8 +40,8 @@ module.exports.get = fastify => ({ c = { ...c, stats: { - avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), - avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + avgResolutionTime: ms(getAvgResolutionTime(closedTickets)), + avgResponseTime: ms(getAvgResponseTime(closedTickets)), }, }; delete c.tickets; diff --git a/src/routes/api/admin/guilds/[guild]/index.js b/src/routes/api/admin/guilds/[guild]/index.js index 38e9b62..bb787fd 100644 --- a/src/routes/api/admin/guilds/[guild]/index.js +++ b/src/routes/api/admin/guilds/[guild]/index.js @@ -1,4 +1,8 @@ /* eslint-disable no-underscore-dangle */ +const { + getAvgResolutionTime, + getAvgResponseTime, +} = require('../../../../../lib/stats'); const ms = require('ms'); module.exports.get = fastify => ({ @@ -36,8 +40,8 @@ module.exports.get = fastify => ({ logo: guild.iconURL(), name: guild.name, stats: { - avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), - avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + avgResolutionTime: ms(getAvgResolutionTime(closedTickets)), + avgResponseTime: ms(getAvgResponseTime(closedTickets)), categories: categories.map(c => ({ id: c.id, name: c.name, @@ -53,4 +57,4 @@ module.exports.get = fastify => ({ return cached; }, onRequest: [fastify.authenticate, fastify.isAdmin], -}); \ No newline at end of file +}); diff --git a/src/routes/api/client.js b/src/routes/api/client.js index 4c793fb..e18c1df 100644 --- a/src/routes/api/client.js +++ b/src/routes/api/client.js @@ -1,3 +1,6 @@ +const { + getAvgResolutionTime, getAvgResponseTime, +} = require('../../lib/stats'); const ms = require('ms'); module.exports.get = () => ({ @@ -25,8 +28,8 @@ module.exports.get = () => ({ stats: { activatedUsers: users.length, archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they can be deleted - avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), - avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), + avgResolutionTime: ms(getAvgResolutionTime(closedTickets)), + avgResponseTime: ms(getAvgResponseTime(closedTickets)), categories: await client.prisma.category.count(), guilds: client.guilds.cache.size, members: client.guilds.cache.reduce((t, g) => t + g.memberCount, 0), @@ -40,4 +43,4 @@ module.exports.get = () => ({ return cached; }, -}); \ No newline at end of file +}); From 6a25e3efb918e7601d5d60f330d6d2bac922719f Mon Sep 17 00:00:00 2001 From: Ettore Atalan Date: Sun, 7 May 2023 20:50:53 +0200 Subject: [PATCH 380/409] feat(i18n): update German translations [skip ci] Currently translated at 95.3% (246 of 258 strings) Co-authored-by: Ettore Atalan Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index a005574..19233f3 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -41,7 +41,7 @@ commands: description: Die Nachricht wurde angeheftet. title: ✅ Nachricht angeheftet create: - name: Erstelle Ticket aus Nachricht + name: Ticket aus Nachricht erstellen slash: add: added: ➡️ {added} wurde von {by} hinzugefügt. From f8450af34ef17d6bbd4f7356792d9e0b3664577a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Weigend?= Date: Sun, 7 May 2023 20:50:54 +0200 Subject: [PATCH 381/409] feat(i18n): update German translations [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 95.3% (246 of 258 strings) Co-authored-by: Jörn Weigend Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/de/ Translation: Discord Tickets/Bot --- src/i18n/de.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/i18n/de.yml b/src/i18n/de.yml index 19233f3..5831a22 100644 --- a/src/i18n/de.yml +++ b/src/i18n/de.yml @@ -122,6 +122,9 @@ commands: category: description: Die Kategorie in welche das Ticket verschoben werden soll name: kategorie + not_staff: + description: Nur Mitarbeiter können Tickets verschieben. + title: ❌ Fehler new: name: neu description: Neues Ticket erstellen @@ -132,6 +135,8 @@ commands: claim: description: Ticket übernehmen name: übernehmen + not_staff: + title: ❌ Fehler priority: options: priority: @@ -146,6 +151,8 @@ commands: description: Die Priorität dieses Tickets wurde auf `{priority}` gesetzt. name: priorität description: Legt die Priorität eines Tickets fest + not_staff: + title: ❌ Fehler tickets: not_staff: title: ❌ Fehler From 1992ff641cde3e4845540f93a68de45e9f180b3e Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 23 May 2023 12:29:03 +0100 Subject: [PATCH 382/409] feat: `version` stdin command (closes #402) --- src/stdin/version.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/stdin/version.js diff --git a/src/stdin/version.js b/src/stdin/version.js new file mode 100644 index 0000000..d857b72 --- /dev/null +++ b/src/stdin/version.js @@ -0,0 +1,17 @@ +const { StdinCommand } = require('@eartharoid/dbf'); +const { version } = require('../../package.json'); +const checkForUpdates = require('../lib/updates'); + +module.exports = class extends StdinCommand { + constructor(client, options) { + super(client, { + ...options, + id: 'version', + }); + } + + async run() { + this.client.log.info('Current version:', version); + checkForUpdates(this.client); + } +}; From a4696273ea062833b0cb4c1065febf10f0603352 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 23 May 2023 16:29:51 +0100 Subject: [PATCH 383/409] fix: ticket creation --- src/lib/tickets/manager.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 480ce9b..ee75512 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -408,7 +408,7 @@ module.exports = class TicketManager { if (category.image) await channel.send(category.image); - const needsStats = category.openingMessage.match(/{+\s?avgResponseTime\s?}+/i) || category.openingMessage.match(/{+\s?avgResolutionTime\s?}+/i); + const needsStats = /{+\s?(avgResponseTime|avgResolutionTime)\s?}+/i.test(category.openingMessage); const statsCacheKey = `cache/category-stats/${categoryId}`; let stats = await this.client.keyv.get(statsCacheKey); if (needsStats && !stats) { @@ -441,8 +441,8 @@ module.exports = class TicketManager { .setDescription( category.openingMessage .replace(/{+\s?(user)?name\s?}+/gi, creator.user.toString()) - .replace(/{+\s?avgResponseTime\s?}+/gi, stats.avgResponseTime) - .replace(/{+\s?avgResolutionTime\s?}+/gi, stats.avgResolutionTime), + .replace(/{+\s?avgResponseTime\s?}+/gi, stats?.avgResponseTime) + .replace(/{+\s?avgResolutionTime\s?}+/gi, stats?.avgResolutionTime), ), ]; From fa921fa5a6be16eaae0b7c9f11c9714d4ad3a51e Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 23 May 2023 16:31:53 +0100 Subject: [PATCH 384/409] fix: duplicated ticket numbers (fixes #418) Now there is a change that a number will be skipped (if there's an error), but that isn't a problem. --- src/lib/sync.js | 12 +++++++++++- src/lib/tickets/manager.js | 8 +++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/lib/sync.js b/src/lib/sync.js index 91f3d58..b0c3800 100644 --- a/src/lib/sync.js +++ b/src/lib/sync.js @@ -2,6 +2,16 @@ * @param {import("client")} client */ module.exports = async client => { + // load ticket numbers + const guilds = await client.prisma.guild.findMany({ select: { id: true } }); + for (const guild of guilds) { + const { _max: { number: max } } = await client.prisma.ticket.aggregate({ + _max: { number: true }, + where: { guildId: guild.id }, + }); + client.tickets.$numbers[guild.id] = max ?? 0; + } + // load total number of open tickets const categories = await client.prisma.category.findMany({ select: { @@ -60,4 +70,4 @@ module.exports = async client => { client.log.info(`Loaded ${cooldowns} active cooldowns`); client.log.info(`Closed ${deleted} deleted tickets`); return true; -}; \ No newline at end of file +}; diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index ee75512..9e0167e 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -48,6 +48,7 @@ module.exports = class TicketManager { this.client = client; this.archiver = new TicketArchiver(client); this.$count = { categories: {} }; + this.$numbers = {}; this.$stale = new Collection(); } @@ -134,6 +135,11 @@ module.exports = class TicketManager { return await this.client.keyv.get(cacheKey); } + getNextNumber(guildId) { + this.$numbers[guildId] += 1; + return this.$numbers[guildId]; + } + /** * @param {object} data * @param {string} data.categoryId @@ -373,7 +379,7 @@ module.exports = class TicketManager { const guild = this.client.guilds.cache.get(category.guild.id); const getMessage = this.client.i18n.getLocale(category.guild.locale); const creator = await guild.members.fetch(interaction.user.id); - const number = (await this.client.prisma.ticket.count({ where: { guildId: category.guild.id } })) + 1; + const number = this.getNextNumber(category.guild.id); const channelName = category.channelName .replace(/{+\s?(user)?name\s?}+/gi, creator.user.username) .replace(/{+\s?(nick|display)(name)?\s?}+/gi, creator.displayName) From 739efdcc3aa7f052d518fdb768fa4ea4c7b5921b Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 23 May 2023 16:51:19 +0100 Subject: [PATCH 385/409] fix: guild selector not filtering guilds (closes #408) --- src/lib/users.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib/users.js b/src/lib/users.js index ca196f8..c263662 100644 --- a/src/lib/users.js +++ b/src/lib/users.js @@ -2,14 +2,11 @@ const { PermissionsBitField } = require('discord.js'); /** * - * @param {import("client")} client + * @param {import("discord.js").Client} client * @param {string} userId * @returns {Promise} */ -module.exports.getCommonGuilds = async (client, userId) => await client.guilds.cache.filter(async guild => { - const member = await guild.members.fetch(userId); - return !!member; -}); +module.exports.getCommonGuilds = (client, userId) => client.guilds.cache.filter(guild => guild.members.cache.has(userId)); /** * @param {import("discord.js").Guild} guild @@ -48,4 +45,4 @@ module.exports.isStaff = async (guild, userId) => { if (guildMember.permissions.has(PermissionsBitField.Flags.ManageGuild)) return true; const staffRoles = await client.keyv.get(`cache/guild-staff:${guild.id}`) || await updateStaffRoles(guild); return staffRoles.some(r => guildMember.roles.cache.has(r)); -}; \ No newline at end of file +}; From 0a73633ea92f011cd0225e53212c6c096f727b70 Mon Sep 17 00:00:00 2001 From: Jiri Nakola Date: Wed, 24 May 2023 07:50:46 +0200 Subject: [PATCH 386/409] feat(i18n): update Finnish translations [skip ci] Currently translated at 32.5% (84 of 258 strings) Co-authored-by: Jiri Nakola Translate-URL: https://hosted.weblate.org/projects/discord-tickets/bot/fi/ Translation: Discord Tickets/Bot --- src/i18n/fi.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/fi.yml b/src/i18n/fi.yml index 2f118f9..47af9f7 100644 --- a/src/i18n/fi.yml +++ b/src/i18n/fi.yml @@ -9,6 +9,7 @@ commands: title: ❌ Virhe pinned: description: Viesti on kiinnitetty. + title: ✅ Kiinnitetty viesti create: name: Luo tiketti viestistä slash: From cdfdf7202660f7325e0b831a20f2abd7c43f0e26 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 24 May 2023 16:24:19 +0100 Subject: [PATCH 387/409] fix: HTTP log colours --- src/http.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/http.js b/src/http.js index 25a2681..2d1ff78 100644 --- a/src/http.js +++ b/src/http.js @@ -117,9 +117,9 @@ module.exports = async client => { ? '&2' : '&f') + res.statusCode; let responseTime = res.getResponseTime().toFixed(2); - responseTime = (responseTime >= 20 + responseTime = (responseTime >= 100 ? '&c' - : responseTime >= 5 + : responseTime >= 10 ? '&e' : '&a') + responseTime + 'ms'; const level = req.routerPath === '/*' ? 'verbose' : 'info'; From 22b15d7fd7e4e67cb599c370eb7b7bf14f3569c0 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 24 May 2023 20:50:10 +0100 Subject: [PATCH 388/409] refactor: ticket logs --- src/lib/logging.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/logging.js b/src/lib/logging.js index 61463d1..5b24f86 100644 --- a/src/lib/logging.js +++ b/src/lib/logging.js @@ -138,6 +138,8 @@ async function logTicketEvent(client, { const member = await guild.members.fetch(userId); client.log.info.tickets(`${member.user.tag} ${client.i18n.getMessage('en-GB', `log.ticket.verb.${action}`)} ticket ${target.id}`); if (!ticket.guild.logChannel) return; + const channel = client.channels.cache.get(ticket.guild.logChannel); + if (!channel) return; const colour = action === 'create' ? 'Aqua' : action === 'close' ? 'DarkAqua' : action === 'update' @@ -149,8 +151,6 @@ async function logTicketEvent(client, { user: `<@${member.user.id}>`, verb: getMessage(`log.ticket.verb.${action}`), }; - const channel = client.channels.cache.get(ticket.guild.logChannel); - if (!channel) return; const embeds = [ new EmbedBuilder() .setColor(colour) @@ -238,4 +238,4 @@ module.exports = { logAdminEvent, logMessageEvent, logTicketEvent, -}; \ No newline at end of file +}; From afa0123d228ac678d73d7d23a9dbe1c9d2d5a59d Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 24 May 2023 21:04:28 +0100 Subject: [PATCH 389/409] perf: select 10 rows in SQL rather than JS --- src/commands/slash/tickets.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/commands/slash/tickets.js b/src/commands/slash/tickets.js index 95a0d91..e39dd8c 100644 --- a/src/commands/slash/tickets.js +++ b/src/commands/slash/tickets.js @@ -73,6 +73,7 @@ module.exports = class TicketsSlashCommand extends SlashCommand { const closed = await client.prisma.ticket.findMany({ include: { category: true }, orderBy: { createdAt: 'desc' }, + take: 10, // max 10 rows where: { createdById: member.id, guildId: interaction.guild.id, @@ -102,7 +103,7 @@ module.exports = class TicketsSlashCommand extends SlashCommand { } else { fields.push({ name: getMessage('commands.slash.tickets.response.fields.closed.name'), - value: closed.slice(0, 10).map(ticket => { // max 10 rows + value: closed.map(ticket => { const topic = ticket.topic ? `- \`${decrypt(ticket.topic).replace(/\n/g, ' ').slice(0, 30)}\`` : ''; return `> ${ticket.category.name} #${ticket.number} ${topic}`; }).join('\n'), @@ -129,4 +130,4 @@ module.exports = class TicketsSlashCommand extends SlashCommand { return await interaction.editReply({ embeds: [embed] }); } -}; \ No newline at end of file +}; From a44539914eac9bb9196115d1361b6b85433daa22 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 24 May 2023 22:04:19 +0100 Subject: [PATCH 390/409] fix: allow staff to get transcripts of other members (closes #400) --- src/autocomplete/ticket.js | 7 +++++-- src/commands/slash/transcript.js | 7 ++++++- src/i18n/en-GB.yml | 5 ++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/autocomplete/ticket.js b/src/autocomplete/ticket.js index 767a6e8..c18eec6 100644 --- a/src/autocomplete/ticket.js +++ b/src/autocomplete/ticket.js @@ -5,6 +5,7 @@ const Cryptr = require('cryptr'); const { decrypt } = new Cryptr(process.env.ENCRYPTION_KEY); const Keyv = require('keyv'); const ms = require('ms'); +const { isStaff } = require('../lib/users'); module.exports = class TicketCompleter extends Autocompleter { constructor(client, options) { @@ -72,12 +73,14 @@ module.exports = class TicketCompleter extends Autocompleter { * @param {import("discord.js").AutocompleteInteraction} interaction */ async run(value, command, interaction) { + const otherMember = await isStaff(interaction.guild, interaction.user.id) && interaction.options.data[1]?.value; + const userId = otherMember || interaction.user.id; await interaction.respond( await this.getOptions(value, { guildId: interaction.guild.id, open: ['add', 'close', 'force-close', 'remove'].includes(command.name), // false for `new`, `transcript` etc - userId: interaction.user.id, + userId, }), ); } -}; \ No newline at end of file +}; diff --git a/src/commands/slash/transcript.js b/src/commands/slash/transcript.js index b889f4d..c9642a5 100644 --- a/src/commands/slash/transcript.js +++ b/src/commands/slash/transcript.js @@ -24,6 +24,11 @@ module.exports = class TranscriptSlashCommand extends SlashCommand { required: true, type: ApplicationCommandOptionType.String, }, + { + name: 'member', + required: false, + type: ApplicationCommandOptionType.User, + }, ].map(option => { option.descriptionLocalizations = client.i18n.getAllMessages(`commands.slash.${name}.options.${option.name}.description`); option.description = option.descriptionLocalizations['en-GB']; @@ -143,4 +148,4 @@ module.exports = class TranscriptSlashCommand extends SlashCommand { await interaction.editReply({ files: [attachment] }); // TODO: add portal link } -}; \ No newline at end of file +}; diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 042d884..1f8f977 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -222,8 +222,11 @@ commands: description: Get the transcript of a ticket name: transcript options: + member: + description: The member to search for tickets of + name: member ticket: - description: The number of the ticket to get the transcript of + description: The ticket to get the transcript of name: ticket transfer: description: Transfer ownership of a ticket to another member From d1267360c84dc40d39b5311900987ecaf4ec7148 Mon Sep 17 00:00:00 2001 From: Isaac Date: Wed, 24 May 2023 22:20:29 +0100 Subject: [PATCH 391/409] fix: closing ticket with missing creator (closes #401) --- src/lib/tickets/manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 9e0167e..7cec820 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1217,7 +1217,7 @@ module.exports = class TicketManager { } try { - const creator = await channel?.guild.members.fetch(ticket.createdById); + const creator = channel?.guild.members.cache.has(ticket.createdById); if (creator) { const embed = new ExtendedEmbedBuilder({ iconURL: channel.guild.iconURL(), From 5b4f69ec9eead5745e8e338e56d5f4b427c9c55d Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 25 May 2023 16:23:25 +0100 Subject: [PATCH 392/409] fix: `NaN` stats --- src/lib/stats.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/stats.js b/src/lib/stats.js index 3aa9e7c..19ef47e 100644 --- a/src/lib/stats.js +++ b/src/lib/stats.js @@ -1,2 +1,2 @@ -module.exports.getAvgResolutionTime = tickets => (tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1) / tickets.length; -module.exports.getAvgResponseTime = tickets => (tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1) / tickets.length; +module.exports.getAvgResolutionTime = tickets => (tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) || 1) / tickets.length; +module.exports.getAvgResponseTime = tickets => (tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) || 1) / tickets.length; From ea7e730aa41cfe05ee760048ff4b86b56a6e2e4d Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 25 May 2023 16:24:10 +0100 Subject: [PATCH 393/409] chore(version): `4.0.0-beta.11` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b51509e..cce5fdf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.10", + "version": "4.0.0-beta.11", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", From f07e1576429f5ebc2b432eae86cd70e22b1937a4 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 26 May 2023 15:55:38 +0100 Subject: [PATCH 394/409] fix: `Infinity` stats --- src/lib/stats.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/stats.js b/src/lib/stats.js index 19ef47e..3ced74a 100644 --- a/src/lib/stats.js +++ b/src/lib/stats.js @@ -1,2 +1,2 @@ -module.exports.getAvgResolutionTime = tickets => (tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) || 1) / tickets.length; -module.exports.getAvgResponseTime = tickets => (tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) || 1) / tickets.length; +module.exports.getAvgResolutionTime = tickets => (tickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) || 1) / Math.max(tickets.length, 1); +module.exports.getAvgResponseTime = tickets => (tickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) || 1) / Math.max(tickets.length, 1); From 921bdfa4476fabc16c3d9f8a9fd935afba566b25 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 26 May 2023 16:11:31 +0100 Subject: [PATCH 395/409] fix(working hours): invalid timestamps with timezones (closes #417) --- src/lib/tickets/manager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 7cec820..101f4a8 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -713,7 +713,7 @@ module.exports = class TicketManager { const next = workingHours[nextIndex]; let then = now.add(nextIndex - now.day(), 'day'); if (nextIndex <= now.day()) then = then.add(1, 'week'); - const timestamp = Math.ceil(then.time(next[0]).d.getTime() / 1000); // in seconds + const timestamp = Math.ceil(then.time(next[0]).goto('utc').d.getTime() / 1000); // in seconds await channel.send({ embeds: [ new ExtendedEmbedBuilder() @@ -725,7 +725,7 @@ module.exports = class TicketManager { } } else if (now.isBefore(start)) { // staff haven't started working yet working = false; - const timestamp = Math.ceil(start.d.getTime() / 1000); // in seconds + const timestamp = Math.ceil(start.goto('utc').d.getTime() / 1000); // in seconds await channel.send({ embeds: [ new ExtendedEmbedBuilder() From 621c49c4e65410bd47fb216291c5b8063eb65f06 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 26 May 2023 22:28:12 +0100 Subject: [PATCH 396/409] chore: update dependencies --- package.json | 30 +-- pnpm-lock.yaml | 688 +++++++++++++++++++++++++++---------------------- 2 files changed, 398 insertions(+), 320 deletions(-) diff --git a/package.json b/package.json index cce5fdf..eb60a29 100644 --- a/package.json +++ b/package.json @@ -40,20 +40,20 @@ "node": ">=18" }, "dependencies": { - "@discord-tickets/settings": "^2.1.3", + "@discord-tickets/settings": "^2.1.4", "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", "@fastify/cookie": "^6.0.0", "@fastify/jwt": "^5.0.1", "@fastify/oauth2": "^5.1.0", - "@prisma/client": "^4.11.0", - "boxen": "^7.0.2", + "@prisma/client": "^4.14.1", + "boxen": "^7.1.0", "cryptr": "^6.2.0", - "discord.js": "^14.8.0", + "discord.js": "^14.11.0", "dotenv": "^16.0.3", - "fastify": "^4.14.1", - "figlet": "^1.5.2", + "fastify": "^4.17.0", + "figlet": "^1.6.0", "fs-extra": "^10.1.0", "keyv": "^4.5.2", "leeks.js": "^0.3.0", @@ -63,22 +63,22 @@ "node-dir": "^0.1.17", "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", - "prisma": "^4.11.0", - "semver": "^7.3.8", - "spacetime": "^7.4.1", + "prisma": "^4.14.1", + "semver": "^7.5.1", + "spacetime": "^7.4.3", "terminal-link": "^2.1.1", "yaml": "^1.10.2" }, "devDependencies": { - "@commitlint/cli": "^17.4.4", - "@commitlint/config-conventional": "^17.4.4", - "all-contributors-cli": "^6.24.0", + "@commitlint/cli": "^17.6.3", + "@commitlint/config-conventional": "^17.6.3", + "all-contributors-cli": "^6.25.1", "conventional-changelog-cli": "^2.2.2", - "eslint": "^8.36.0", + "eslint": "^8.41.0", "eslint-plugin-unused-imports": "^2.0.0", "husky": "^8.0.3", - "lint-staged": "^13.2.0", - "nodemon": "^2.0.21" + "lint-staged": "^13.2.2", + "nodemon": "^2.0.22" }, "optionalDependencies": { "bufferutil": "^4.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c19d5cd..f5375d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,8 +2,8 @@ lockfileVersion: '6.0' dependencies: '@discord-tickets/settings': - specifier: ^2.1.3 - version: 2.1.3(svelte@3.57.0) + specifier: ^2.1.4 + version: 2.1.4(svelte@3.59.1) '@eartharoid/dbf': specifier: ^0.3.3 version: 0.3.3(bufferutil@4.0.7)(utf-8-validate@5.0.10) @@ -23,26 +23,26 @@ dependencies: specifier: ^5.1.0 version: 5.1.0 '@prisma/client': - specifier: ^4.11.0 - version: 4.11.0(prisma@4.11.0) + specifier: ^4.14.1 + version: 4.14.1(prisma@4.14.1) boxen: - specifier: ^7.0.2 - version: 7.0.2 + specifier: ^7.1.0 + version: 7.1.0 cryptr: specifier: ^6.2.0 version: 6.2.0 discord.js: - specifier: ^14.8.0 - version: 14.8.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) + specifier: ^14.11.0 + version: 14.11.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) dotenv: specifier: ^16.0.3 version: 16.0.3 fastify: - specifier: ^4.14.1 - version: 4.14.1 + specifier: ^4.17.0 + version: 4.17.0 figlet: - specifier: ^1.5.2 - version: 1.5.2 + specifier: ^1.6.0 + version: 1.6.0 fs-extra: specifier: ^10.1.0 version: 10.1.0 @@ -71,14 +71,14 @@ dependencies: specifier: ^1.0.4 version: 1.0.4 prisma: - specifier: ^4.11.0 - version: 4.11.0 + specifier: ^4.14.1 + version: 4.14.1 semver: - specifier: ^7.3.8 - version: 7.3.8 + specifier: ^7.5.1 + version: 7.5.1 spacetime: - specifier: ^7.4.1 - version: 7.4.1 + specifier: ^7.4.3 + version: 7.4.3 terminal-link: specifier: ^2.1.1 version: 2.1.1 @@ -102,37 +102,37 @@ optionalDependencies: devDependencies: '@commitlint/cli': - specifier: ^17.4.4 - version: 17.4.4 + specifier: ^17.6.3 + version: 17.6.3 '@commitlint/config-conventional': - specifier: ^17.4.4 - version: 17.4.4 + specifier: ^17.6.3 + version: 17.6.3 all-contributors-cli: - specifier: ^6.24.0 - version: 6.24.0 + specifier: ^6.25.1 + version: 6.25.1 conventional-changelog-cli: specifier: ^2.2.2 version: 2.2.2 eslint: - specifier: ^8.36.0 - version: 8.36.0 + specifier: ^8.41.0 + version: 8.41.0 eslint-plugin-unused-imports: specifier: ^2.0.0 - version: 2.0.0(eslint@8.36.0) + version: 2.0.0(eslint@8.41.0) husky: specifier: ^8.0.3 version: 8.0.3 lint-staged: - specifier: ^13.2.0 - version: 13.2.0 + specifier: ^13.2.2 + version: 13.2.2 nodemon: - specifier: ^2.0.21 - version: 2.0.21 + specifier: ^2.0.22 + version: 2.0.22 packages: - /@babel/code-frame@7.18.6: - resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + /@babel/code-frame@7.21.4: + resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} engines: {node: '>=6.9.0'} dependencies: '@babel/highlight': 7.18.6 @@ -152,34 +152,34 @@ packages: js-tokens: 4.0.0 dev: true - /@babel/runtime@7.21.0: - resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==} + /@babel/runtime@7.22.0: + resolution: {integrity: sha512-TT6NB0oszYQ4oxLNUdG+FNHIc3MohXVCKA2BeyQ4WeM2VCSC6wBZ6P0Yfkdzxv+87D8Xk0LJyHeCKlWMvpZt0g==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 - dev: true - /@commitlint/cli@17.4.4: - resolution: {integrity: sha512-HwKlD7CPVMVGTAeFZylVNy14Vm5POVY0WxPkZr7EXLC/os0LH/obs6z4HRvJtH/nHCMYBvUBQhGwnufKfTjd5g==} + /@commitlint/cli@17.6.3: + resolution: {integrity: sha512-ItSz2fd4F+CujgIbQOfNNerDF1eFlsBGEfp9QcCb1kxTYMuKTYZzA6Nu1YRRrIaaWwe2E7awUGpIMrPoZkOG3A==} engines: {node: '>=v14'} + hasBin: true dependencies: '@commitlint/format': 17.4.4 - '@commitlint/lint': 17.4.4 - '@commitlint/load': 17.4.4 - '@commitlint/read': 17.4.4 + '@commitlint/lint': 17.6.3 + '@commitlint/load': 17.5.0 + '@commitlint/read': 17.5.1 '@commitlint/types': 17.4.4 execa: 5.1.1 lodash.isfunction: 3.0.9 resolve-from: 5.0.0 resolve-global: 1.0.0 - yargs: 17.7.1 + yargs: 17.7.2 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' dev: true - /@commitlint/config-conventional@17.4.4: - resolution: {integrity: sha512-u6ztvxqzi6NuhrcEDR7a+z0yrh11elY66nRrQIpqsqW6sZmpxYkDLtpRH8jRML+mmxYQ8s4qqF06Q/IQx5aJeQ==} + /@commitlint/config-conventional@17.6.3: + resolution: {integrity: sha512-bLyHEjjRWqlLQWIgYFHmUPbEFMOOLXeF3QbUinDIJev/u9e769tkoTH9YPknEywiuIrAgZaVo+OfzAIsJP0fsw==} engines: {node: '>=v14'} dependencies: conventional-changelog-conventionalcommits: 5.0.0 @@ -218,42 +218,42 @@ packages: chalk: 4.1.2 dev: true - /@commitlint/is-ignored@17.4.4: - resolution: {integrity: sha512-Y3eo1SFJ2JQDik4rWkBC4tlRIxlXEFrRWxcyrzb1PUT2k3kZ/XGNuCDfk/u0bU2/yS0tOA/mTjFsV+C4qyACHw==} + /@commitlint/is-ignored@17.6.3: + resolution: {integrity: sha512-LQbNdnPbxrpbcrVKR5yf51SvquqktpyZJwqXx3lUMF6+nT9PHB8xn3wLy8pi2EQv5Zwba484JnUwDE1ygVYNQA==} engines: {node: '>=v14'} dependencies: '@commitlint/types': 17.4.4 - semver: 7.3.8 + semver: 7.5.0 dev: true - /@commitlint/lint@17.4.4: - resolution: {integrity: sha512-qgkCRRFjyhbMDWsti/5jRYVJkgYZj4r+ZmweZObnbYqPUl5UKLWMf9a/ZZisOI4JfiPmRktYRZ2JmqlSvg+ccw==} + /@commitlint/lint@17.6.3: + resolution: {integrity: sha512-fBlXwt6SHJFgm3Tz+luuo3DkydAx9HNC5y4eBqcKuDuMVqHd2ugMNr+bQtx6riv9mXFiPoKp7nE4Xn/ls3iVDA==} engines: {node: '>=v14'} dependencies: - '@commitlint/is-ignored': 17.4.4 + '@commitlint/is-ignored': 17.6.3 '@commitlint/parse': 17.4.4 - '@commitlint/rules': 17.4.4 + '@commitlint/rules': 17.6.1 '@commitlint/types': 17.4.4 dev: true - /@commitlint/load@17.4.4: - resolution: {integrity: sha512-z6uFIQ7wfKX5FGBe1AkOF4l/ShOQsaa1ml/nLMkbW7R/xF8galGS7Zh0yHvzVp/srtfS0brC+0bUfQfmpMPFVQ==} + /@commitlint/load@17.5.0: + resolution: {integrity: sha512-l+4W8Sx4CD5rYFsrhHH8HP01/8jEP7kKf33Xlx2Uk2out/UKoKPYMOIRcDH5ppT8UXLMV+x6Wm5osdRKKgaD1Q==} engines: {node: '>=v14'} dependencies: '@commitlint/config-validator': 17.4.4 '@commitlint/execute-rule': 17.4.0 '@commitlint/resolve-extends': 17.4.4 '@commitlint/types': 17.4.4 - '@types/node': 18.15.3 + '@types/node': 20.2.4 chalk: 4.1.2 - cosmiconfig: 8.1.2 - cosmiconfig-typescript-loader: 4.3.0(@types/node@18.15.3)(cosmiconfig@8.1.2)(ts-node@10.9.1)(typescript@4.9.5) + cosmiconfig: 8.1.3 + cosmiconfig-typescript-loader: 4.3.0(@types/node@20.2.4)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.0.4) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1(@types/node@18.15.3)(typescript@4.9.5) - typescript: 4.9.5 + ts-node: 10.9.1(@types/node@20.2.4)(typescript@5.0.4) + typescript: 5.0.4 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' @@ -273,13 +273,13 @@ packages: conventional-commits-parser: 3.2.4 dev: true - /@commitlint/read@17.4.4: - resolution: {integrity: sha512-B2TvUMJKK+Svzs6eji23WXsRJ8PAD+orI44lVuVNsm5zmI7O8RSGJMvdEZEikiA4Vohfb+HevaPoWZ7PiFZ3zA==} + /@commitlint/read@17.5.1: + resolution: {integrity: sha512-7IhfvEvB//p9aYW09YVclHbdf1u7g7QhxeYW9ZHSO8Huzp8Rz7m05aCO1mFG7G8M+7yfFnXB5xOmG18brqQIBg==} engines: {node: '>=v14'} dependencies: '@commitlint/top-level': 17.4.0 '@commitlint/types': 17.4.4 - fs-extra: 11.1.0 + fs-extra: 11.1.1 git-raw-commits: 2.0.11 minimist: 1.2.8 dev: true @@ -296,8 +296,8 @@ packages: resolve-global: 1.0.0 dev: true - /@commitlint/rules@17.4.4: - resolution: {integrity: sha512-0tgvXnHi/mVcyR8Y8mjTFZIa/FEQXA4uEutXS/imH2v1UNkYDSEMsK/68wiXRpfW1euSgEdwRkvE1z23+yhNrQ==} + /@commitlint/rules@17.6.1: + resolution: {integrity: sha512-lUdHw6lYQ1RywExXDdLOKxhpp6857/4c95Dc/1BikrHgdysVUXz26yV0vp1GL7Gv+avx9WqZWTIVB7pNouxlfw==} engines: {node: '>=v14'} dependencies: '@commitlint/ensure': 17.4.4 @@ -333,71 +333,89 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@discord-tickets/settings@2.1.3(svelte@3.57.0): - resolution: {integrity: sha512-rKwt87oXBydudfnKkSmgbnNtCFbkYM0ZBuZLqSfl+H8N1kT+bylGoyXfAF2OLoEc5vK/EyBCEP9trZMO60774g==} + /@discord-tickets/settings@2.1.4(svelte@3.59.1): + resolution: {integrity: sha512-eABoWIOPLh8gdCRN9QfzFl7Nl9l0rVWthOQyawcbrdrmsSt1rKveNZ1aAZftJ9z8JvJMIacDRQurxVbb18/Wjg==} dependencies: - '@fortawesome/fontawesome-free': 6.3.0 + '@fortawesome/fontawesome-free': 6.4.0 '@skyra/discord-components-core': 3.6.0 cookie: 0.5.0 emoji-name-map: 1.2.9 - marked: 4.2.12 + marked: 4.3.0 ms: 2.1.3 - postcss: 8.4.21 + postcss: 8.4.23 sortablejs: 1.15.0 - svelte-modals: 1.2.1(svelte@3.57.0) + svelte-modals: 1.2.1(svelte@3.59.1) svelte-toasts: 1.1.2 transitivePeerDependencies: - svelte dev: false - /@discordjs/builders@1.5.0: - resolution: {integrity: sha512-7XxT78mnNBPigHn2y6KAXkicxIBFtZREGWaRZ249EC1l6gBUEP8IyVY5JTciIjJArxkF+tg675aZvsTNTKBpmA==} + /@discordjs/builders@1.6.3: + resolution: {integrity: sha512-CTCh8NqED3iecTNuiz49mwSsrc2iQb4d0MjMdmS/8pb69Y4IlzJ/DIy/p5GFlgOrFbNO2WzMHkWKQSiJ3VNXaw==} engines: {node: '>=16.9.0'} dependencies: - '@discordjs/formatters': 0.2.0 - '@discordjs/util': 0.2.0 - '@sapphire/shapeshift': 3.8.1 - discord-api-types: 0.37.36 + '@discordjs/formatters': 0.3.1 + '@discordjs/util': 0.3.1 + '@sapphire/shapeshift': 3.9.0 + discord-api-types: 0.37.42 fast-deep-equal: 3.1.3 ts-mixer: 6.0.3 - tslib: 2.5.0 + tslib: 2.5.2 dev: false - /@discordjs/collection@1.4.0: - resolution: {integrity: sha512-hiOJyk2CPFf1+FL3a4VKCuu1f448LlROVuu8nLz1+jCOAPokUcdFAV+l4pd3B3h6uJlJQSASoZzrdyNdjdtfzQ==} + /@discordjs/collection@1.5.1: + resolution: {integrity: sha512-aWEc9DCf3TMDe9iaJoOnO2+JVAjeRNuRxPZQA6GVvBf+Z3gqUuWYBy2NWh4+5CLYq5uoc3MOvUQ5H5m8CJBqOA==} engines: {node: '>=16.9.0'} dev: false - /@discordjs/formatters@0.2.0: - resolution: {integrity: sha512-vn4oMSXuMZUm8ITqVOtvE7/fMMISj4cI5oLsR09PEQXHKeKDAMLltG/DWeeIs7Idfy6V8Fk3rn1e69h7NfzuNA==} + /@discordjs/formatters@0.3.1: + resolution: {integrity: sha512-M7X4IGiSeh4znwcRGcs+49B5tBkNDn4k5bmhxJDAUhRxRHTiFAOTVUNQ6yAKySu5jZTnCbSvTYHW3w0rAzV1MA==} engines: {node: '>=16.9.0'} dependencies: - discord-api-types: 0.37.36 + discord-api-types: 0.37.42 dev: false - /@discordjs/rest@1.6.0: - resolution: {integrity: sha512-HGvqNCZ5Z5j0tQHjmT1lFvE5ETO4hvomJ1r0cbnpC1zM23XhCpZ9wgTCiEmaxKz05cyf2CI9p39+9LL+6Yz1bA==} + /@discordjs/rest@1.7.1: + resolution: {integrity: sha512-Ofa9UqT0U45G/eX86cURQnX7gzOJLG2oC28VhIk/G6IliYgQF7jFByBJEykPSHE4MxPhqCleYvmsrtfKh1nYmQ==} engines: {node: '>=16.9.0'} dependencies: - '@discordjs/collection': 1.4.0 - '@discordjs/util': 0.2.0 + '@discordjs/collection': 1.5.1 + '@discordjs/util': 0.3.1 '@sapphire/async-queue': 1.5.0 - '@sapphire/snowflake': 3.4.0 - discord-api-types: 0.37.36 - file-type: 18.2.1 - tslib: 2.5.0 - undici: 5.21.0 + '@sapphire/snowflake': 3.5.1 + discord-api-types: 0.37.42 + file-type: 18.4.0 + tslib: 2.5.2 + undici: 5.22.1 dev: false - /@discordjs/util@0.2.0: - resolution: {integrity: sha512-/8qNbebFzLWKOOg+UV+RB8itp4SmU5jw0tBUD3ifElW6rYNOj1Ku5JaSW7lLl/WgjjxF01l/1uQPCzkwr110vg==} + /@discordjs/util@0.3.1: + resolution: {integrity: sha512-HxXKYKg7vohx2/OupUN/4Sd02Ev3PBJ5q0gtjdcvXb0ErCva8jNHWfe/v5sU3UKjIB/uxOhc+TDOnhqffj9pRA==} engines: {node: '>=16.9.0'} dev: false + /@discordjs/ws@0.8.3(bufferutil@4.0.7)(utf-8-validate@5.0.10): + resolution: {integrity: sha512-hcYtppanjHecbdNyCKQNH2I4RP9UrphDgmRgLYrATEQF1oo4sYSve7ZmGsBEXSzH72MO2tBPdWSThunbxUVk0g==} + engines: {node: '>=16.9.0'} + dependencies: + '@discordjs/collection': 1.5.1 + '@discordjs/rest': 1.7.1 + '@discordjs/util': 0.3.1 + '@sapphire/async-queue': 1.5.0 + '@types/ws': 8.5.4 + '@vladfrangu/async_event_emitter': 2.2.2 + discord-api-types: 0.37.42 + tslib: 2.5.2 + ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /@eartharoid/dbf@0.3.3(bufferutil@4.0.7)(utf-8-validate@5.0.10): resolution: {integrity: sha512-eVDdpFlDV5CAvqoV5g1iAvoYhPjnvcyJ0Nnepc1YihlE1KIYGhVIK/2RaDsltzxRuiweO3Y7dvDj/cUpJnnFPA==} dependencies: - discord.js: 14.8.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) + discord.js: 14.11.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) node-dir: 0.1.17 transitivePeerDependencies: - bufferutil @@ -416,28 +434,28 @@ packages: resolution: {integrity: sha512-nMQdHrGgpw+vNL5DbivELW2K3KAUGaMvTjjmFsEPf8mUW8+LAgRjvfFn2gkJq1mnlD6HoqUaHqEL4YpWr2T5MA==} dev: false - /@eslint-community/eslint-utils@4.3.0(eslint@8.36.0): - resolution: {integrity: sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==} + /@eslint-community/eslint-utils@4.4.0(eslint@8.41.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.36.0 - eslint-visitor-keys: 3.3.0 + eslint: 8.41.0 + eslint-visitor-keys: 3.4.1 dev: true - /@eslint-community/regexpp@4.4.0: - resolution: {integrity: sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==} + /@eslint-community/regexpp@4.5.1: + resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc@2.0.1: - resolution: {integrity: sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==} + /@eslint/eslintrc@2.0.3: + resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 9.5.0 + espree: 9.5.2 globals: 13.20.0 ignore: 5.2.4 import-fresh: 3.3.0 @@ -448,8 +466,8 @@ packages: - supports-color dev: true - /@eslint/js@8.36.0: - resolution: {integrity: sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==} + /@eslint/js@8.41.0: + resolution: {integrity: sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -476,10 +494,10 @@ packages: resolution: {integrity: sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==} dev: false - /@fastify/fast-json-stringify-compiler@4.2.0: - resolution: {integrity: sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg==} + /@fastify/fast-json-stringify-compiler@4.3.0: + resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==} dependencies: - fast-json-stringify: 5.6.2 + fast-json-stringify: 5.7.0 dev: false /@fastify/jwt@5.0.1: @@ -502,36 +520,42 @@ packages: - supports-color dev: false - /@fortawesome/fontawesome-free@6.3.0: - resolution: {integrity: sha512-qVtd5i1Cc7cdrqnTWqTObKQHjPWAiRwjUPaXObaeNPcy7+WKxJumGBx66rfSFgK6LNpIasVKkEgW8oyf0tmPLA==} + /@fortawesome/fontawesome-free@6.4.0: + resolution: {integrity: sha512-0NyytTlPJwB/BF5LtRV8rrABDbe3TdTXqNB3PdZ+UUUZAEIrdOJdmABqKjt4AXwIoJNaRVVZEXxpNrqvE1GAYQ==} engines: {node: '>=6'} requiresBuild: true dev: false /@hapi/address@2.1.4: resolution: {integrity: sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==} + deprecated: Moved to 'npm install @sideway/address' dev: false /@hapi/boom@7.4.11: resolution: {integrity: sha512-VSU/Cnj1DXouukYxxkes4nNJonCnlogHvIff1v1RVoN4xzkKhMXX+GRmb3NyH1iar10I9WFPDv2JPwfH3GaV0A==} + deprecated: This version has been deprecated and is no longer supported or maintained dependencies: '@hapi/hoek': 8.5.1 dev: false /@hapi/bourne@1.3.2: resolution: {integrity: sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==} + deprecated: This version has been deprecated and is no longer supported or maintained dev: false /@hapi/formula@1.2.0: resolution: {integrity: sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA==} + deprecated: Moved to 'npm install @sideway/formula' dev: false /@hapi/hoek@8.5.1: resolution: {integrity: sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==} + deprecated: This version has been deprecated and is no longer supported or maintained dev: false /@hapi/joi@16.1.8: resolution: {integrity: sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg==} + deprecated: Switch to 'npm install joi' dependencies: '@hapi/address': 2.1.4 '@hapi/formula': 1.2.0 @@ -542,16 +566,19 @@ packages: /@hapi/pinpoint@1.0.2: resolution: {integrity: sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ==} + deprecated: Moved to 'npm install @sideway/pinpoint' dev: false /@hapi/topo@3.1.6: resolution: {integrity: sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==} + deprecated: This version has been deprecated and is no longer supported or maintained dependencies: '@hapi/hoek': 8.5.1 dev: false /@hapi/wreck@15.1.0: resolution: {integrity: sha512-tQczYRTTeYBmvhsek/D49En/5khcShaBEmzrAaDjMrFXKJRuF8xA8+tlq1ETLBFwGd6Do6g2OC74rt11kzawzg==} + deprecated: This version has been deprecated and is no longer supported or maintained dependencies: '@hapi/boom': 7.4.11 '@hapi/bourne': 1.3.2 @@ -583,20 +610,20 @@ packages: engines: {node: '>=6.9.0'} dev: true - /@jridgewell/resolve-uri@3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} dev: true - /@jridgewell/sourcemap-codec@1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} dev: true /@jridgewell/trace-mapping@0.3.9: resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 dev: true /@lukeed/ms@2.0.1: @@ -625,8 +652,8 @@ packages: fastq: 1.15.0 dev: true - /@prisma/client@4.11.0(prisma@4.11.0): - resolution: {integrity: sha512-0INHYkQIqgAjrt7NzhYpeDQi8x3Nvylc2uDngKyFDDj1tTRQ4uV1HnVmd1sQEraeVAN63SOK0dgCKQHlvjL0KA==} + /@prisma/client@4.14.1(prisma@4.14.1): + resolution: {integrity: sha512-TZIswkeX1ccsHG/eN2kICzg/csXll0osK3EHu1QKd8VJ3XLcXozbNELKkCNfsCUvKJAwPdDtFCzF+O+raIVldw==} engines: {node: '>=14.17'} requiresBuild: true peerDependencies: @@ -635,16 +662,16 @@ packages: prisma: optional: true dependencies: - '@prisma/engines-version': 4.11.0-57.8fde8fef4033376662cad983758335009d522acb - prisma: 4.11.0 + '@prisma/engines-version': 4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c + prisma: 4.14.1 dev: false - /@prisma/engines-version@4.11.0-57.8fde8fef4033376662cad983758335009d522acb: - resolution: {integrity: sha512-3Vd8Qq06d5xD8Ch5WauWcUUrsVPdMC6Ge8ILji8RFfyhUpqon6qSyGM0apvr1O8n8qH8cKkEFqRPsYjuz5r83g==} + /@prisma/engines-version@4.14.0-67.d9a4c5988f480fa576d43970d5a23641aa77bc9c: + resolution: {integrity: sha512-3jum8/YSudeSN0zGW5qkpz+wAN2V/NYCQ+BPjvHYDfWatLWlQkqy99toX0GysDeaUoBIJg1vaz2yKqiA3CFcQw==} dev: false - /@prisma/engines@4.11.0: - resolution: {integrity: sha512-0AEBi2HXGV02cf6ASsBPhfsVIbVSDC9nbQed4iiY5eHttW9ZtMxHThuKZE1pnESbr8HRdgmFSa/Kn4OSNYuibg==} + /@prisma/engines@4.14.1: + resolution: {integrity: sha512-APqFddPVHYmWNKqc+5J5SqrLFfOghKOLZxobmguDUacxOwdEutLsbXPVhNnpFDmuQWQFbXmrTTPoRrrF6B1MWA==} requiresBuild: true dev: false @@ -653,16 +680,16 @@ packages: engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dev: false - /@sapphire/shapeshift@3.8.1: - resolution: {integrity: sha512-xG1oXXBhCjPKbxrRTlox9ddaZTvVpOhYLmKmApD/vIWOV1xEYXnpoFs68zHIZBGbqztq6FrUPNPerIrO1Hqeaw==} + /@sapphire/shapeshift@3.9.0: + resolution: {integrity: sha512-iJpHmjAdwX9aSL6MvFpVyo+tkokDtInmSjoJHbz/k4VJfnim3DjvG0hgGEKWtWZgCu45RaLgcoNgR1fCPdIz3w==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dependencies: fast-deep-equal: 3.1.3 lodash: 4.17.21 dev: false - /@sapphire/snowflake@3.4.0: - resolution: {integrity: sha512-zZxymtVO6zeXVMPds+6d7gv/OfnCc25M1Z+7ZLB0oPmeMTPeRWVPQSS16oDJy5ZsyCOLj7M6mbZml5gWXcVRNw==} + /@sapphire/snowflake@3.5.1: + resolution: {integrity: sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dev: false @@ -678,6 +705,7 @@ packages: /@stencil/core@2.22.3: resolution: {integrity: sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==} engines: {node: '>=12.10.0', npm: '>=6.0.0'} + hasBin: true dev: false /@tokenizer/token@0.3.0: @@ -696,16 +724,16 @@ packages: resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} dev: true - /@tsconfig/node16@1.0.3: - resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} dev: true /@types/minimist@1.2.2: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - /@types/node@18.15.3: - resolution: {integrity: sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==} + /@types/node@20.2.4: + resolution: {integrity: sha512-ni5f8Xlf4PwnT/Z3f0HURc3ZSw8UyrqMqmM3L5ysa7VjHu8c3FOmIo1nKCcLrV/OAmtf3N4kFna/aJqxsfEtnA==} /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -714,11 +742,17 @@ packages: /@types/ws@8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: - '@types/node': 18.15.3 + '@types/node': 20.2.4 + dev: false + + /@vladfrangu/async_event_emitter@2.2.2: + resolution: {integrity: sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} dev: false /JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} + hasBin: true dependencies: jsonparse: 1.3.1 through: 2.3.8 @@ -755,6 +789,7 @@ packages: /acorn@8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} + hasBin: true dev: true /add-stream@1.0.0: @@ -797,18 +832,19 @@ packages: require-from-string: 2.0.2 uri-js: 4.4.1 - /all-contributors-cli@6.24.0: - resolution: {integrity: sha512-7oSKr2PnqxsOotuSwciltcFTS1eVRdjR0cn99hbElfff7gRQBShVhsf/XBprY41sLcgqTk0l0MKgKv6QNgZdMg==} + /all-contributors-cli@6.25.1: + resolution: {integrity: sha512-Q9MfsO6FH09h71IG6OobfESA7lbooB3/bnO2wnuXbLPye1lFsYsa/jpNZP0YBx6zbhwXqqm6CXRZ9HO/tmD4NQ==} engines: {node: '>=4'} + hasBin: true dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.0 async: 3.2.4 chalk: 4.1.2 didyoumean: 1.2.2 inquirer: 7.3.3 json-fixer: 1.6.15 lodash: 4.17.21 - node-fetch: 2.6.9 + node-fetch: 2.6.11 pify: 5.0.0 yargs: 15.4.1 transitivePeerDependencies: @@ -938,8 +974,8 @@ packages: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} dev: false - /boxen@7.0.2: - resolution: {integrity: sha512-1Z4UJabXUP1/R9rLpoU3O2lEMnG3pPLAs/ZD2lF3t2q7qD5lM8rqbtnvtvm4N0wEyNlE+9yZVTVAGmd1V5jabg==} + /boxen@7.1.0: + resolution: {integrity: sha512-ScG8CDo8dj7McqCZ5hz4dIBp20xj4unQ2lXIDa7ff6RcZElCpuNzutdwzKVvRikfNjm7CFAlR3HJHcoHkDOExQ==} engines: {node: '>=14.16'} dependencies: ansi-align: 3.0.1 @@ -1140,12 +1176,12 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true - /colorette@2.0.19: - resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} + /colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} dev: true - /commander@10.0.0: - resolution: {integrity: sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==} + /commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} dev: true @@ -1177,6 +1213,7 @@ packages: /conventional-changelog-cli@2.2.2: resolution: {integrity: sha512-8grMV5Jo8S0kP3yoMeJxV2P5R6VJOqK72IiSV9t/4H5r/HiRqEBQ83bYGuz4Yzfdj4bjaAEhZN/FFbsFXr5bOA==} engines: {node: '>=10'} + hasBin: true dependencies: add-stream: 1.0.0 conventional-changelog: 3.1.25 @@ -1274,6 +1311,7 @@ packages: /conventional-changelog-writer@5.0.1: resolution: {integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==} engines: {node: '>=10'} + hasBin: true dependencies: conventional-commits-filter: 2.0.7 dateformat: 3.0.3 @@ -1314,6 +1352,7 @@ packages: /conventional-commits-parser@3.2.4: resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} engines: {node: '>=10'} + hasBin: true dependencies: JSONStream: 1.3.5 is-text-path: 1.0.1 @@ -1337,7 +1376,7 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /cosmiconfig-typescript-loader@4.3.0(@types/node@18.15.3)(cosmiconfig@8.1.2)(ts-node@10.9.1)(typescript@4.9.5): + /cosmiconfig-typescript-loader@4.3.0(@types/node@20.2.4)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.0.4): resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -1346,14 +1385,14 @@ packages: ts-node: '>=10' typescript: '>=3' dependencies: - '@types/node': 18.15.3 - cosmiconfig: 8.1.2 - ts-node: 10.9.1(@types/node@18.15.3)(typescript@4.9.5) - typescript: 4.9.5 + '@types/node': 20.2.4 + cosmiconfig: 8.1.3 + ts-node: 10.9.1(@types/node@20.2.4)(typescript@5.0.4) + typescript: 5.0.4 dev: true - /cosmiconfig@8.1.2: - resolution: {integrity: sha512-rmpUFKMZiawLfug8sP4NbpBSOpWftZB6UACOLEiNbnRAYM1TzgQuTWlMYFRuPgmoTCkcOxSMwQJQpJmiXv/eHw==} + /cosmiconfig@8.1.3: + resolution: {integrity: sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==} engines: {node: '>=14'} dependencies: import-fresh: 3.3.0 @@ -1384,9 +1423,11 @@ packages: engines: {node: '>=8'} dev: true - /date-fns@2.29.3: - resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.22.0 dev: false /dateformat@3.0.3: @@ -1447,26 +1488,27 @@ packages: engines: {node: '>=0.3.1'} dev: true - /discord-api-types@0.37.36: - resolution: {integrity: sha512-Nlxmp10UpVr/utgZ9uODQvG2Or+5w7LFrvFMswyeKC9l/+UaqGT6H0OVgEFhu9GEO4U6K7NNO5W8Carv7irnCA==} + /discord-api-types@0.37.42: + resolution: {integrity: sha512-1Huaj9cQ1W7/uryS8MZs/tZemnoKB94thM1cE40lep3rpU3q7WHqkdjN/veX0prTkYlPhcyLd/DeF/pBO8X8oQ==} dev: false - /discord.js@14.8.0(bufferutil@4.0.7)(utf-8-validate@5.0.10): - resolution: {integrity: sha512-UOxYtc/YnV7jAJ2gISluJyYeBw4e+j8gWn+IoqG8unaHAVuvZ13DdYN0M1f9fbUgUvSarV798inIrYFtDNDjwQ==} + /discord.js@14.11.0(bufferutil@4.0.7)(utf-8-validate@5.0.10): + resolution: {integrity: sha512-CkueWYFQ28U38YPR8HgsBR/QT35oPpMbEsTNM30Fs8loBIhnA4s70AwQEoy6JvLcpWWJO7GY0y2BUzZmuBMepQ==} engines: {node: '>=16.9.0'} dependencies: - '@discordjs/builders': 1.5.0 - '@discordjs/collection': 1.4.0 - '@discordjs/formatters': 0.2.0 - '@discordjs/rest': 1.6.0 - '@discordjs/util': 0.2.0 - '@sapphire/snowflake': 3.4.0 + '@discordjs/builders': 1.6.3 + '@discordjs/collection': 1.5.1 + '@discordjs/formatters': 0.3.1 + '@discordjs/rest': 1.7.1 + '@discordjs/util': 0.3.1 + '@discordjs/ws': 0.8.3(bufferutil@4.0.7)(utf-8-validate@5.0.10) + '@sapphire/snowflake': 3.5.1 '@types/ws': 8.5.4 - discord-api-types: 0.37.36 + discord-api-types: 0.37.42 fast-deep-equal: 3.1.3 lodash.snakecase: 4.1.1 - tslib: 2.5.0 - undici: 5.21.0 + tslib: 2.5.2 + undici: 5.22.1 ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil @@ -1540,7 +1582,7 @@ packages: engines: {node: '>=10'} dev: true - /eslint-plugin-unused-imports@2.0.0(eslint@8.36.0): + /eslint-plugin-unused-imports@2.0.0(eslint@8.41.0): resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1550,7 +1592,7 @@ packages: '@typescript-eslint/eslint-plugin': optional: true dependencies: - eslint: 8.36.0 + eslint: 8.41.0 eslint-rule-composer: 0.3.0 dev: true @@ -1559,27 +1601,28 @@ packages: engines: {node: '>=4.0.0'} dev: true - /eslint-scope@7.1.1: - resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + /eslint-scope@7.2.0: + resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 dev: true - /eslint-visitor-keys@3.3.0: - resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + /eslint-visitor-keys@3.4.1: + resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.36.0: - resolution: {integrity: sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==} + /eslint@8.41.0: + resolution: {integrity: sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.3.0(eslint@8.36.0) - '@eslint-community/regexpp': 4.4.0 - '@eslint/eslintrc': 2.0.1 - '@eslint/js': 8.36.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.41.0) + '@eslint-community/regexpp': 4.5.1 + '@eslint/eslintrc': 2.0.3 + '@eslint/js': 8.41.0 '@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -1589,9 +1632,9 @@ packages: debug: 4.3.4 doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.1.1 - eslint-visitor-keys: 3.3.0 - espree: 9.5.0 + eslint-scope: 7.2.0 + eslint-visitor-keys: 3.4.1 + espree: 9.5.2 esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -1599,13 +1642,12 @@ packages: find-up: 5.0.0 glob-parent: 6.0.2 globals: 13.20.0 - grapheme-splitter: 1.0.4 + graphemer: 1.4.0 ignore: 5.2.4 import-fresh: 3.3.0 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-sdsl: 4.3.0 js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 @@ -1620,13 +1662,13 @@ packages: - supports-color dev: true - /espree@9.5.0: - resolution: {integrity: sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==} + /espree@9.5.2: + resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.8.2 acorn-jsx: 5.3.2(acorn@8.8.2) - eslint-visitor-keys: 3.3.0 + eslint-visitor-keys: 3.4.1 dev: true /esquery@1.5.0: @@ -1684,7 +1726,7 @@ packages: dependencies: cross-spawn: 7.0.3 get-stream: 6.0.1 - human-signals: 4.3.0 + human-signals: 4.3.1 is-stream: 3.0.0 merge-stream: 2.0.0 npm-run-path: 5.1.0 @@ -1717,8 +1759,8 @@ packages: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true - /fast-json-stringify@5.6.2: - resolution: {integrity: sha512-F6xkRrXvtGbAiDSEI5Rk7qk2P63Y9kc8bO6Dnsd3Rt6sBNr2QxNFWs0JbKftgiyOfGxnJaRoHe4SizCTqeAyrA==} + /fast-json-stringify@5.7.0: + resolution: {integrity: sha512-sBVPTgnAZseLu1Qgj6lUbQ0HfjFhZWXAmpZ5AaSGkyLh5gAXBga/uPJjQPHpDFjC9adWIpdOcCLSDTgrZ7snoQ==} dependencies: '@fastify/deepmerge': 1.3.0 ajv: 8.12.0 @@ -1747,8 +1789,8 @@ packages: fast-decode-uri-component: 1.0.1 dev: false - /fast-redact@3.1.2: - resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} + /fast-redact@3.2.0: + resolution: {integrity: sha512-zaTadChr+NekyzallAMXATXLOR8MNx3zqpZ0MUF2aGf4EathnG0f32VLODNlY8IuGY3HoRO2L6/6fSzNsLaHIw==} engines: {node: '>=6'} dev: false @@ -1771,24 +1813,25 @@ packages: resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==} dev: false - /fastify@4.14.1: - resolution: {integrity: sha512-yjrDeXe77j9gRlSV2UJry8mcFWbD0NQ5JYjnPi4tkFjHZVaG3/BD5wxOmRzGnHPC0YvaBJ0XWrIfFPl2IHRa1w==} + /fastify@4.17.0: + resolution: {integrity: sha512-tzuY1tgWJo2Y6qEKwmLhFvACUmr68Io2pqP/sDKU71KRM6A6R3DrCDqLGqANbeLZcKUfdfY58ut35CGqemcTgg==} dependencies: '@fastify/ajv-compiler': 3.5.0 '@fastify/error': 3.2.0 - '@fastify/fast-json-stringify-compiler': 4.2.0 + '@fastify/fast-json-stringify-compiler': 4.3.0 abstract-logging: 2.0.1 avvio: 8.2.1 fast-content-type-parse: 1.0.0 - find-my-way: 7.6.0 + fast-json-stringify: 5.7.0 + find-my-way: 7.6.2 light-my-request: 5.9.1 - pino: 8.11.0 - process-warning: 2.1.0 + pino: 8.14.1 + process-warning: 2.2.0 proxy-addr: 2.0.7 rfdc: 1.3.0 secure-json-parse: 2.7.0 - semver: 7.3.8 - tiny-lru: 10.2.1 + semver: 7.5.1 + tiny-lru: 11.0.1 transitivePeerDependencies: - supports-color dev: false @@ -1812,9 +1855,10 @@ packages: xtend: 4.0.2 dev: false - /figlet@1.5.2: - resolution: {integrity: sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==} + /figlet@1.6.0: + resolution: {integrity: sha512-31EQGhCEITv6+hi2ORRPyn3bulaV9Fl4xOdR169cBzH/n1UqcxsiSB/noo6SJdD7Kfb1Ljit+IgR1USvF/XbdA==} engines: {node: '>= 0.4.0'} + hasBin: true dev: false /figures@3.2.0: @@ -1831,8 +1875,8 @@ packages: flat-cache: 3.0.4 dev: true - /file-type@18.2.1: - resolution: {integrity: sha512-Yw5MtnMv7vgD2/6Bjmmuegc8bQEVA9GmAyaR18bMYWKqsWDG9wgYZ1j4I6gNMF5Y5JBDcUcjRQqNQx7Y8uotcg==} + /file-type@18.4.0: + resolution: {integrity: sha512-o6MQrZKTAK6WpvmQk3jqTVUmqxYBxW5bloUfrdH1ZnRFDvvAPNr+l+rgOxM3nkqWT+3khaj3FRMDydWe0xhu+w==} engines: {node: '>=14.16'} dependencies: readable-web-to-node-stream: 3.0.2 @@ -1852,8 +1896,8 @@ packages: to-regex-range: 5.0.1 dev: true - /find-my-way@7.6.0: - resolution: {integrity: sha512-H7berWdHJ+5CNVr4ilLWPai4ml7Y2qAsxjw3pfeBxPigZmaDTzF0wjJLj90xRCmGcWYcyt050yN+34OZDJm1eQ==} + /find-my-way@7.6.2: + resolution: {integrity: sha512-0OjHn1b1nCX3eVbm9ByeEHiscPYiHLfhei1wOUU9qffQkk98wE0Lo8VrVYfSGMgnSnDh86DxedduAnBf4nwUEw==} engines: {node: '>=14'} dependencies: fast-deep-equal: 3.1.3 @@ -1910,8 +1954,8 @@ packages: universalify: 2.0.0 dev: false - /fs-extra@11.1.0: - resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} + /fs-extra@11.1.1: + resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} engines: {node: '>=14.14'} dependencies: graceful-fs: 4.2.11 @@ -1943,6 +1987,7 @@ packages: /get-pkg-repo@4.2.1: resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} engines: {node: '>=6.9.0'} + hasBin: true dependencies: '@hutson/parse-repository-url': 3.0.2 hosted-git-info: 4.1.0 @@ -1958,6 +2003,7 @@ packages: /git-raw-commits@2.0.11: resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} engines: {node: '>=10'} + hasBin: true dependencies: dargs: 7.0.0 lodash: 4.17.21 @@ -1977,6 +2023,7 @@ packages: /git-semver-tags@4.1.1: resolution: {integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==} engines: {node: '>=10'} + hasBin: true dependencies: meow: 8.1.2 semver: 6.3.0 @@ -2030,13 +2077,14 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - /grapheme-splitter@1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true /handlebars@4.7.7: resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} engines: {node: '>=0.4.7'} + hasBin: true dependencies: minimist: 1.2.8 neo-async: 2.6.2 @@ -2098,14 +2146,15 @@ packages: engines: {node: '>=10.17.0'} dev: true - /human-signals@4.3.0: - resolution: {integrity: sha512-zyzVyMjpGBX2+6cDVZeFPCdtOtdsxOeseRhB9tkQ6xXmGUNrcnBzdEKPy3VPNYz+4gy1oukVOXcrJCunSyc6QQ==} + /human-signals@4.3.1: + resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} engines: {node: '>=14.18.0'} dev: true /husky@8.0.3: resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} engines: {node: '>=14'} + hasBin: true dev: true /iconv-lite@0.4.24: @@ -2195,8 +2244,8 @@ packages: binary-extensions: 2.2.0 dev: true - /is-core-module@2.11.0: - resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + /is-core-module@2.12.1: + resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} dependencies: has: 1.0.3 dev: true @@ -2271,16 +2320,13 @@ packages: resolution: {integrity: sha512-4dG1D1x/7g8PwHS9aK6QV5V94+ZvyP4+d19qDv43EzImmrndysIl4prmJ1hWWIGCqrZHyaHBm6BSEWHOLnpoNw==} dev: false - /js-sdsl@4.3.0: - resolution: {integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==} - dev: true - /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true dependencies: argparse: 2.0.1 dev: true @@ -2293,7 +2339,7 @@ packages: resolution: {integrity: sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==} engines: {node: '>=10'} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.22.0 chalk: 4.1.2 pegjs: 0.10.0 dev: true @@ -2373,8 +2419,8 @@ packages: resolution: {integrity: sha512-UT7pUk8jNCR1wR7w3iWfIjx32DiB2f3hFdQSOwy3/EPQ3n3VocyipUxcyRZR0ahoev+fky69uA+GejPa9KuHKg==} dependencies: cookie: 0.5.0 - process-warning: 2.1.0 - set-cookie-parser: 2.5.1 + process-warning: 2.2.0 + set-cookie-parser: 2.6.0 dev: false /lilconfig@2.1.0: @@ -2386,13 +2432,14 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - /lint-staged@13.2.0: - resolution: {integrity: sha512-GbyK5iWinax5Dfw5obm2g2ccUiZXNGtAS4mCbJ0Lv4rq6iEtfBSjOYdcbOtAIFtM114t0vdpViDDetjVTSd8Vw==} + /lint-staged@13.2.2: + resolution: {integrity: sha512-71gSwXKy649VrSU09s10uAT0rWCcY3aewhMaHyl2N84oBk4Xs9HgxvUp3AYu+bNsK4NrOYYxvSgg7FyGJ+jGcA==} engines: {node: ^14.13.1 || >=16.0.0} + hasBin: true dependencies: chalk: 5.2.0 cli-truncate: 3.1.0 - commander: 10.0.0 + commander: 10.0.1 debug: 4.3.4 execa: 7.1.1 lilconfig: 2.1.0 @@ -2401,8 +2448,8 @@ packages: normalize-path: 3.0.0 object-inspect: 1.12.3 pidtree: 0.6.0 - string-argv: 0.3.1 - yaml: 2.2.1 + string-argv: 0.3.2 + yaml: 2.3.1 transitivePeerDependencies: - enquirer - supports-color @@ -2418,11 +2465,11 @@ packages: optional: true dependencies: cli-truncate: 2.1.0 - colorette: 2.0.19 + colorette: 2.0.20 log-update: 4.0.0 p-map: 4.0.0 rfdc: 1.3.0 - rxjs: 7.8.0 + rxjs: 7.8.1 through: 2.3.8 wrap-ansi: 7.0.0 dev: true @@ -2541,9 +2588,10 @@ packages: engines: {node: '>=8'} dev: true - /marked@4.2.12: - resolution: {integrity: sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==} + /marked@4.3.0: + resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} engines: {node: '>= 12'} + hasBin: true dev: false /meow@8.1.2: @@ -2631,6 +2679,7 @@ packages: /mustache@4.2.0: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true dev: false /mute-stream@0.0.8: @@ -2642,9 +2691,10 @@ packages: dev: false optional: true - /nanoid@3.3.4: - resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true dev: false /natural-compare@1.4.0: @@ -2668,8 +2718,8 @@ packages: lodash: 4.17.21 dev: false - /node-fetch@2.6.9: - resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==} + /node-fetch@2.6.11: + resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==} engines: {node: 4.x || >=6.0.0} peerDependencies: encoding: ^0.1.0 @@ -2682,11 +2732,13 @@ packages: /node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} + hasBin: true dev: false - /nodemon@2.0.21: - resolution: {integrity: sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A==} + /nodemon@2.0.22: + resolution: {integrity: sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==} engines: {node: '>=8.10.0'} + hasBin: true dependencies: chokidar: 3.5.3 debug: 3.2.7(supports-color@5.5.0) @@ -2702,6 +2754,7 @@ packages: /nopt@1.0.10: resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} + hasBin: true dependencies: abbrev: 1.1.1 dev: true @@ -2710,7 +2763,7 @@ packages: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.1 + resolve: 1.22.2 semver: 5.7.1 validate-npm-package-license: 3.0.4 dev: true @@ -2720,8 +2773,8 @@ packages: engines: {node: '>=10'} dependencies: hosted-git-info: 4.1.0 - is-core-module: 2.11.0 - semver: 7.3.8 + is-core-module: 2.12.1 + semver: 7.5.1 validate-npm-package-license: 3.0.4 dev: true @@ -2875,7 +2928,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.18.6 + '@babel/code-frame': 7.21.4 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -2930,6 +2983,7 @@ packages: /pegjs@0.10.0: resolution: {integrity: sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==} engines: {node: '>=0.10'} + hasBin: true dev: true /picocolors@1.0.0: @@ -2944,6 +2998,7 @@ packages: /pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} + hasBin: true dev: true /pify@2.3.0: @@ -2964,35 +3019,36 @@ packages: /pino-abstract-transport@1.0.0: resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==} dependencies: - readable-stream: 4.3.0 - split2: 4.1.0 + readable-stream: 4.4.0 + split2: 4.2.0 dev: false - /pino-std-serializers@6.1.0: - resolution: {integrity: sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==} + /pino-std-serializers@6.2.1: + resolution: {integrity: sha512-wHuWB+CvSVb2XqXM0W/WOYUkVSPbiJb9S5fNB7TBhd8s892Xq910bRxwHtC4l71hgztObTjXL6ZheZXFjhDrDQ==} dev: false - /pino@8.11.0: - resolution: {integrity: sha512-Z2eKSvlrl2rH8p5eveNUnTdd4AjJk8tAsLkHYZQKGHP4WTh2Gi1cOSOs3eWPqaj+niS3gj4UkoreoaWgF3ZWYg==} + /pino@8.14.1: + resolution: {integrity: sha512-8LYNv7BKWXSfS+k6oEc6occy5La+q2sPwU3q2ljTX5AZk7v+5kND2o5W794FyRaqha6DJajmkNRsWtPpFyMUdw==} + hasBin: true dependencies: atomic-sleep: 1.0.0 - fast-redact: 3.1.2 + fast-redact: 3.2.0 on-exit-leak-free: 2.1.0 pino-abstract-transport: 1.0.0 - pino-std-serializers: 6.1.0 - process-warning: 2.1.0 + pino-std-serializers: 6.2.1 + process-warning: 2.2.0 quick-format-unescaped: 4.0.4 real-require: 0.2.0 - safe-stable-stringify: 2.4.2 - sonic-boom: 3.2.1 + safe-stable-stringify: 2.4.3 + sonic-boom: 3.3.0 thread-stream: 2.3.0 dev: false - /postcss@8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + /postcss@8.4.23: + resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.3.4 + nanoid: 3.3.6 picocolors: 1.0.0 source-map-js: 1.0.2 dev: false @@ -3002,20 +3058,21 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prisma@4.11.0: - resolution: {integrity: sha512-4zZmBXssPUEiX+GeL0MUq/Yyie4ltiKmGu7jCJFnYMamNrrulTBc+D+QwAQSJ01tyzeGHlD13kOnqPwRipnlNw==} + /prisma@4.14.1: + resolution: {integrity: sha512-z6hxzTMYqT9SIKlzD08dhzsLUpxjFKKsLpp5/kBDnSqiOjtUyyl/dC5tzxLcOa3jkEHQ8+RpB/fE3w8bgNP51g==} engines: {node: '>=14.17'} + hasBin: true requiresBuild: true dependencies: - '@prisma/engines': 4.11.0 + '@prisma/engines': 4.14.1 dev: false /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true - /process-warning@2.1.0: - resolution: {integrity: sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg==} + /process-warning@2.2.0: + resolution: {integrity: sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==} dev: false /process@0.11.10: @@ -3113,8 +3170,8 @@ packages: string_decoder: 1.3.0 util-deprecate: 1.0.2 - /readable-stream@4.3.0: - resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==} + /readable-stream@4.4.0: + resolution: {integrity: sha512-kDMOq0qLtxV9f/SQv522h8cxZBqNZXuXNyjyezmfAAuribMyVXziljpQ/uQhfE1XLg2/TLTW2DsnoE4VAi/krg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: abort-controller: 3.0.0 @@ -3152,7 +3209,6 @@ packages: /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - dev: true /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} @@ -3184,10 +3240,11 @@ packages: global-dirs: 0.1.1 dev: true - /resolve@1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + /resolve@1.22.2: + resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} + hasBin: true dependencies: - is-core-module: 2.11.0 + is-core-module: 2.12.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true @@ -3214,6 +3271,7 @@ packages: /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true dependencies: glob: 7.2.3 dev: true @@ -3236,10 +3294,10 @@ packages: tslib: 1.14.1 dev: true - /rxjs@7.8.0: - resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: - tslib: 2.5.0 + tslib: 2.5.2 dev: true /safe-buffer@5.1.2: @@ -3255,8 +3313,8 @@ packages: ret: 0.2.2 dev: false - /safe-stable-stringify@2.4.2: - resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==} + /safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} engines: {node: '>=10'} dev: false @@ -3269,19 +3327,31 @@ packages: /semver@5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true dev: true /semver@6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true dev: true /semver@7.0.0: resolution: {integrity: sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==} + hasBin: true dev: true - /semver@7.3.8: - resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} + /semver@7.5.0: + resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==} engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + + /semver@7.5.1: + resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==} + engines: {node: '>=10'} + hasBin: true dependencies: lru-cache: 6.0.0 @@ -3289,8 +3359,8 @@ packages: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true - /set-cookie-parser@2.5.1: - resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==} + /set-cookie-parser@2.6.0: + resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} dev: false /setprototypeof@1.2.0: @@ -3315,11 +3385,12 @@ packages: /simple-oauth2@3.4.0: resolution: {integrity: sha512-gDPCC/xjq82FJnzF7+XGUUMWWfHeibuGsp3OYOV7yHwIibxHkq4+WSxywY63/3BF9j8SfIDygGqBrPLynx/iuQ==} + deprecated: simple-oauth2 v3 is no longer supported. Use simple-oauth2 v4 or higher for continued support dependencies: '@hapi/hoek': 8.5.1 '@hapi/joi': 16.1.8 '@hapi/wreck': 15.1.0 - date-fns: 2.29.3 + date-fns: 2.30.0 debug: 4.3.4 transitivePeerDependencies: - supports-color @@ -3358,8 +3429,8 @@ packages: is-fullwidth-code-point: 4.0.0 dev: true - /sonic-boom@3.2.1: - resolution: {integrity: sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==} + /sonic-boom@3.3.0: + resolution: {integrity: sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==} dependencies: atomic-sleep: 1.0.0 dev: false @@ -3378,8 +3449,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /spacetime@7.4.1: - resolution: {integrity: sha512-khQpvLNMhHFzfFJslMfunqqsjOxdmoDDYX5Wh4qYb8N6f8vBPI6HvbT7Wb2wcMa+oP1Xh1HpEcwfPFrj8UfvHQ==} + /spacetime@7.4.3: + resolution: {integrity: sha512-jKrPVk8TPaYhKSR6azA4gzO5LD2ZB29YxqQBl9zTVG0QLtfOJiliTRCNmJA9tm/dul5Aq7oyzzuWJ8VTYQkMkQ==} dev: false /spdx-correct@3.2.0: @@ -3410,8 +3481,8 @@ packages: readable-stream: 3.6.2 dev: true - /split2@4.1.0: - resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} dev: false @@ -3441,8 +3512,8 @@ packages: engines: {node: '>=10.0.0'} dev: false - /string-argv@0.3.1: - resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} + /string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} dev: true @@ -3546,20 +3617,20 @@ packages: engines: {node: '>= 0.4'} dev: true - /svelte-modals@1.2.1(svelte@3.57.0): + /svelte-modals@1.2.1(svelte@3.59.1): resolution: {integrity: sha512-7MEKUx5wb5YppkXWFGeRlYM5FMGEnpix39u9Y6GtCNTMXRDZ7DB2Z50IYLMRTMW5lOsCdtJgFbB0E3iZMKmsAA==} peerDependencies: svelte: ^3.0.0 dependencies: - svelte: 3.57.0 + svelte: 3.59.1 dev: false /svelte-toasts@1.1.2: resolution: {integrity: sha512-m+yL4eEKXyJoyjTYaH1j1GFwF0Pi8YDqnVfwWPDmwi4712iZesv+TNCmToSNlav3R5Vkmc8ZBRkT8DOcu3sywQ==} dev: false - /svelte@3.57.0: - resolution: {integrity: sha512-WMXEvF+RtAaclw0t3bPDTUe19pplMlfyKDsixbHQYgCWi9+O9VN0kXU1OppzrB9gPAvz4NALuoca2LfW2bOjTQ==} + /svelte@3.59.1: + resolution: {integrity: sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==} engines: {node: '>= 8'} dev: false @@ -3616,8 +3687,8 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true - /tiny-lru@10.2.1: - resolution: {integrity: sha512-cxcNyX4O50FDnB5x9jdEJupYC+9PPutt6wT16TaOtXeHfV9t41aFqg0VniB9YK5KmBeqmZaYriNMajm/5TtA7Q==} + /tiny-lru@11.0.1: + resolution: {integrity: sha512-iNgFugVuQgBKrqeO/mpiTTgmBsTP0WL6yeuLfLs/Ctf0pI/ixGqIRm8sDCwMcXGe9WWvt2sGXI5mNqZbValmJg==} engines: {node: '>=12'} dev: false @@ -3650,6 +3721,7 @@ packages: /touch@3.1.0: resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} + hasBin: true dependencies: nopt: 1.0.10 dev: true @@ -3667,8 +3739,9 @@ packages: resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==} dev: false - /ts-node@10.9.1(@types/node@18.15.3)(typescript@4.9.5): + /ts-node@10.9.1(@types/node@20.2.4)(typescript@5.0.4): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true peerDependencies: '@swc/core': '>=1.2.50' '@swc/wasm': '>=1.2.50' @@ -3684,15 +3757,15 @@ packages: '@tsconfig/node10': 1.0.9 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.3 - '@types/node': 18.15.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.2.4 acorn: 8.8.2 acorn-walk: 8.2.0 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 4.9.5 + typescript: 5.0.4 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 dev: true @@ -3701,8 +3774,8 @@ packages: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - /tslib@2.5.0: - resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} + /tslib@2.5.2: + resolution: {integrity: sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==} /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} @@ -3740,14 +3813,16 @@ packages: engines: {node: '>=12.20'} dev: false - /typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} + /typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} + engines: {node: '>=12.20'} + hasBin: true dev: true /uglify-js@3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} engines: {node: '>=0.8.0'} + hasBin: true requiresBuild: true dev: true optional: true @@ -3756,9 +3831,9 @@ packages: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true - /undici@5.21.0: - resolution: {integrity: sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==} - engines: {node: '>=12.18'} + /undici@5.22.1: + resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==} + engines: {node: '>=14.0'} dependencies: busboy: 1.6.0 dev: false @@ -3785,6 +3860,8 @@ packages: /uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true dev: true /v8-compile-cache-lib@3.0.1: @@ -3809,13 +3886,14 @@ packages: webidl-conversions: 3.0.1 dev: true - /which-module@2.0.0: - resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} + /which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} dev: true /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} + hasBin: true dependencies: isexe: 2.0.0 dev: true @@ -3904,8 +3982,8 @@ packages: engines: {node: '>= 6'} dev: false - /yaml@2.2.1: - resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} + /yaml@2.3.1: + resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} engines: {node: '>= 14'} dev: true @@ -3939,7 +4017,7 @@ packages: require-main-filename: 2.0.0 set-blocking: 2.0.0 string-width: 4.2.3 - which-module: 2.0.0 + which-module: 2.0.1 y18n: 4.0.3 yargs-parser: 18.1.3 dev: true @@ -3957,8 +4035,8 @@ packages: yargs-parser: 20.2.9 dev: true - /yargs@17.7.1: - resolution: {integrity: sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==} + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} dependencies: cliui: 8.0.1 From 3a47a7df3f5d5140066eef6a8efb69d20764c034 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 27 May 2023 01:56:29 +0100 Subject: [PATCH 397/409] feat: inactivity warnings and automatic closure (closes #299 and #305) --- src/i18n/en-GB.yml | 10 +++ src/listeners/client/ready.js | 116 ++++++++++++++++++++++++++++++---- 2 files changed, 112 insertions(+), 14 deletions(-) diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 1f8f977..281c174 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -399,6 +399,11 @@ ticket: title: ❓ {requestedBy} wants to close this ticket wait_for_staff: ✋ Please wait for staff to close this ticket. wait_for_user: ✋ Please wait for the user to respond. + closing_soon: + description: | + This ticket will be closed due to inactivity . + Send a message to cancel this automation. + title: ⌛ This ticket will be closed soon created: description: "Your ticket channel has been created: {channel}." title: ✅ Ticket created @@ -406,6 +411,11 @@ ticket: description: Your changes have been saved. title: ✅ Ticket updated feedback: Thank you for your feedback. + inactive: + description: | + There hasn't been any activity in this channel since . + Please continue the conversation or {close} the ticket. + title: ⏰ This ticket is inactive offline: description: There aren't any staff members available at the moment, so it may diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index 53d5402..ec729ef 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -9,6 +9,13 @@ const { version } = require('../../../package.json'); const { msToMins } = require('../../lib/misc'); const sync = require('../../lib/sync'); const checkForUpdates = require('../../lib/updates'); +const { isStaff } = require('../../lib/users'); +const { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, +} = require('discord.js'); +const ExtendedEmbedBuilder = require('../../lib/embed'); module.exports = class extends Listener { constructor(client, options) { @@ -137,20 +144,101 @@ module.exports = class extends Listener { setInterval(() => checkForUpdates(client), ms('1w')); } - setInterval(() => { - // TODO: check lastMessageAt and set stale - // this.$stale.set(ticket.id, { - // closeAt: ticket.guild.autoClose ? Date.now() + ticket.guild.autoClose : null, - // closedBy: null, // null if set as stale due to inactivity - // message: sent, - // messages: 0, - // reason: 'inactivity', - // staleSince: Date.now(), - // }); + // send inactivity warnings and close stale tickets + const staleInterval = ms('5m'); + setInterval(async () => { + // close stale tickets + for (const [ticketId, $] of client.tickets.$stale) { + const autoCloseAfter = $.closeAt - $.staleSince; + const halfway = $.closeAt - (autoCloseAfter / 2); + if (Date.now() >= halfway && Date.now() < halfway + staleInterval) { + const channel = client.channels.cache.get(ticketId); + if (!channel) continue; + const { guild } = await client.prisma.ticket.findUnique({ + select: { guild: true }, + where: { id: ticketId }, + }); + const getMessage = client.i18n.getLocale(guild.locale); + await channel.send({ + embeds: [ + new ExtendedEmbedBuilder() + .setColor(guild.primaryColour) + .setTitle(getMessage('ticket.closing_soon.title')) + .setDescription(getMessage('ticket.closing_soon.description', { timestamp: Math.floor($.closeAt / 1000) })), + ], + }); + } else if ($.closeAt < Date.now()) { + client.tickets.finallyClose(ticketId, $.reason); + } + } - // for (const [ticketId, $] of client.tickets.$stale) { - // // ⌛ - // } - }, ms('5m')); + const guilds = await client.prisma.guild.findMany({ + include: { + tickets: { + include: { category: true }, + where: { open: true }, + }, + }, + // where: { staleAfter: { not: null } }, + where: { staleAfter: { gte: staleInterval } }, + }); + + // set inactive tickets as stale + for (const guild of guilds) { + for (const ticket of guild.tickets) { + // if (ticket.lastMessageAt && ticket.lastMessageAt < Date.now() - guild.staleAfter) + if (ticket.lastMessageAt && Date.now() - ticket.lastMessageAt > guild.staleAfter) { + /** @type {import("discord.js").TextChannel} */ + const channel = client.channels.cache.get(ticket.id); + const messages = (await channel.messages.fetch({ limit: 5 })).filter(m => m.author.id !== client.user.id); + let ping = ''; + + if (messages.size > 0) { + const lastMessage = messages.first(); + const staff = await isStaff(channel.guild, lastMessage.author.id); + if (staff) ping = lastMessage.author.toString(); + else ping = ticket.category.pingRoles.map(r => `<@&${r}>`).join(' '); + } + + const getMessage = client.i18n.getLocale(guild.locale); + const closeComamnd = client.application.commands.cache.find(c => c.name === 'close'); + const sent = await channel.send({ + components: [ + new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ action: 'close' })) + .setStyle(ButtonStyle.Danger) + .setEmoji(getMessage('buttons.close.emoji')) + .setLabel(getMessage('buttons.close.text')), + ), + ], + content: ping, + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: channel.guild.iconURL(), + text: guild.footer, + }) + .setColor(guild.primaryColour) + .setTitle(getMessage('ticket.inactive.title')) + .setDescription(getMessage('ticket.inactive.description', { + close: ``, + timestamp: Math.floor(ticket.lastMessageAt.getTime() / 1000), + })), + ], + }); + + client.tickets.$stale.set(ticket.id, { + closeAt: guild.autoClose ? Date.now() + guild.autoClose : null, + closedBy: null, + message: sent, + messages: 0, + reason: 'inactivity', + staleSince: Date.now(), + }); + } + } + } + }, staleInterval); } }; From c3620309bd4907d35906f75c8ed294405be67fc5 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 27 May 2023 01:57:15 +0100 Subject: [PATCH 398/409] fix: remove footer from close request embed --- src/lib/tickets/manager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 101f4a8..9876858 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1072,10 +1072,10 @@ module.exports = class TicketManager { action: 'close', expect: staff ? 'user' : 'staff', }; - const embed = new ExtendedEmbedBuilder({ + const embed = new ExtendedEmbedBuilder(/* { iconURL: interaction.guild.iconURL(), text: ticket.guild.footer, - }) + } */) .setColor(ticket.guild.primaryColour) .setTitle(getMessage(`ticket.close.${staff ? 'staff' : 'user'}_request.title`, { requestedBy: interaction.member.displayName })); From f69bc9a185bd4b7a73ecb827a41dd3c704a485b8 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 27 May 2023 21:11:03 +0100 Subject: [PATCH 399/409] fix: inactivity warning spam --- src/lib/tickets/manager.js | 1 + src/listeners/client/ready.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 9876858..4eca9b0 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1159,6 +1159,7 @@ module.exports = class TicketManager { closedBy = null, reason = null, }) { + if (this.$stale.has(ticketId)) this.$stale.delete(ticketId); let ticket = await this.getTicket(ticketId); const getMessage = this.client.i18n.getLocale(ticket.guild.locale); this.$count.categories[ticket.categoryId].total -= 1; diff --git a/src/listeners/client/ready.js b/src/listeners/client/ready.js index ec729ef..eb2e343 100644 --- a/src/listeners/client/ready.js +++ b/src/listeners/client/ready.js @@ -186,7 +186,7 @@ module.exports = class extends Listener { // set inactive tickets as stale for (const guild of guilds) { for (const ticket of guild.tickets) { - // if (ticket.lastMessageAt && ticket.lastMessageAt < Date.now() - guild.staleAfter) + if (client.tickets.$stale.has(ticket.id)) continue; if (ticket.lastMessageAt && Date.now() - ticket.lastMessageAt > guild.staleAfter) { /** @type {import("discord.js").TextChannel} */ const channel = client.channels.cache.get(ticket.id); From 60582b029985fbef4dcdcd2599a44135d9761ce2 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 29 May 2023 16:50:13 +0100 Subject: [PATCH 400/409] chore: update dependencies --- package.json | 6 ++-- pnpm-lock.yaml | 94 ++++++++++++++++++++++++++++---------------------- 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index eb60a29..48e804b 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ }, "dependencies": { "@discord-tickets/settings": "^2.1.4", - "@eartharoid/dbf": "^0.3.3", + "@eartharoid/dbf": "^0.4.0", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.2.1", "@fastify/cookie": "^6.0.0", @@ -65,14 +65,14 @@ "object-diffy": "^1.0.4", "prisma": "^4.14.1", "semver": "^7.5.1", - "spacetime": "^7.4.3", + "spacetime": "^7.4.4", "terminal-link": "^2.1.1", "yaml": "^1.10.2" }, "devDependencies": { "@commitlint/cli": "^17.6.3", "@commitlint/config-conventional": "^17.6.3", - "all-contributors-cli": "^6.25.1", + "all-contributors-cli": "^6.26.0", "conventional-changelog-cli": "^2.2.2", "eslint": "^8.41.0", "eslint-plugin-unused-imports": "^2.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5375d7..22c5413 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,8 +5,8 @@ dependencies: specifier: ^2.1.4 version: 2.1.4(svelte@3.59.1) '@eartharoid/dbf': - specifier: ^0.3.3 - version: 0.3.3(bufferutil@4.0.7)(utf-8-validate@5.0.10) + specifier: ^0.4.0 + version: 0.4.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) '@eartharoid/dtf': specifier: ^2.0.1 version: 2.0.1 @@ -77,8 +77,8 @@ dependencies: specifier: ^7.5.1 version: 7.5.1 spacetime: - specifier: ^7.4.3 - version: 7.4.3 + specifier: ^7.4.4 + version: 7.4.4 terminal-link: specifier: ^2.1.1 version: 2.1.1 @@ -108,8 +108,8 @@ devDependencies: specifier: ^17.6.3 version: 17.6.3 all-contributors-cli: - specifier: ^6.25.1 - version: 6.25.1 + specifier: ^6.26.0 + version: 6.26.0 conventional-changelog-cli: specifier: ^2.2.2 version: 2.2.2 @@ -152,8 +152,8 @@ packages: js-tokens: 4.0.0 dev: true - /@babel/runtime@7.22.0: - resolution: {integrity: sha512-TT6NB0oszYQ4oxLNUdG+FNHIc3MohXVCKA2BeyQ4WeM2VCSC6wBZ6P0Yfkdzxv+87D8Xk0LJyHeCKlWMvpZt0g==} + /@babel/runtime@7.22.3: + resolution: {integrity: sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==} engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 @@ -244,15 +244,15 @@ packages: '@commitlint/execute-rule': 17.4.0 '@commitlint/resolve-extends': 17.4.4 '@commitlint/types': 17.4.4 - '@types/node': 20.2.4 + '@types/node': 20.2.5 chalk: 4.1.2 cosmiconfig: 8.1.3 - cosmiconfig-typescript-loader: 4.3.0(@types/node@20.2.4)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.0.4) + cosmiconfig-typescript-loader: 4.3.0(@types/node@20.2.5)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.0.4) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1(@types/node@20.2.4)(typescript@5.0.4) + ts-node: 10.9.1(@types/node@20.2.5)(typescript@5.0.4) typescript: 5.0.4 transitivePeerDependencies: - '@swc/core' @@ -342,7 +342,7 @@ packages: emoji-name-map: 1.2.9 marked: 4.3.0 ms: 2.1.3 - postcss: 8.4.23 + postcss: 8.4.24 sortablejs: 1.15.0 svelte-modals: 1.2.1(svelte@3.59.1) svelte-toasts: 1.1.2 @@ -357,7 +357,7 @@ packages: '@discordjs/formatters': 0.3.1 '@discordjs/util': 0.3.1 '@sapphire/shapeshift': 3.9.0 - discord-api-types: 0.37.42 + discord-api-types: 0.37.43 fast-deep-equal: 3.1.3 ts-mixer: 6.0.3 tslib: 2.5.2 @@ -372,7 +372,7 @@ packages: resolution: {integrity: sha512-M7X4IGiSeh4znwcRGcs+49B5tBkNDn4k5bmhxJDAUhRxRHTiFAOTVUNQ6yAKySu5jZTnCbSvTYHW3w0rAzV1MA==} engines: {node: '>=16.9.0'} dependencies: - discord-api-types: 0.37.42 + discord-api-types: 0.37.43 dev: false /@discordjs/rest@1.7.1: @@ -383,7 +383,7 @@ packages: '@discordjs/util': 0.3.1 '@sapphire/async-queue': 1.5.0 '@sapphire/snowflake': 3.5.1 - discord-api-types: 0.37.42 + discord-api-types: 0.37.43 file-type: 18.4.0 tslib: 2.5.2 undici: 5.22.1 @@ -404,7 +404,7 @@ packages: '@sapphire/async-queue': 1.5.0 '@types/ws': 8.5.4 '@vladfrangu/async_event_emitter': 2.2.2 - discord-api-types: 0.37.42 + discord-api-types: 0.37.43 tslib: 2.5.2 ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) transitivePeerDependencies: @@ -412,8 +412,8 @@ packages: - utf-8-validate dev: false - /@eartharoid/dbf@0.3.3(bufferutil@4.0.7)(utf-8-validate@5.0.10): - resolution: {integrity: sha512-eVDdpFlDV5CAvqoV5g1iAvoYhPjnvcyJ0Nnepc1YihlE1KIYGhVIK/2RaDsltzxRuiweO3Y7dvDj/cUpJnnFPA==} + /@eartharoid/dbf@0.4.0(bufferutil@4.0.7)(utf-8-validate@5.0.10): + resolution: {integrity: sha512-jKHLV/ZEdro8iVgmM38ZRCb2G7ZI3T1o5ibPjps/8LsiunMAI1OqdIYEdSswD0Amud4aStNkqVot63haxnG4vw==} dependencies: discord.js: 14.11.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) node-dir: 0.1.17 @@ -732,8 +732,8 @@ packages: resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} dev: true - /@types/node@20.2.4: - resolution: {integrity: sha512-ni5f8Xlf4PwnT/Z3f0HURc3ZSw8UyrqMqmM3L5ysa7VjHu8c3FOmIo1nKCcLrV/OAmtf3N4kFna/aJqxsfEtnA==} + /@types/node@20.2.5: + resolution: {integrity: sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==} /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -742,7 +742,7 @@ packages: /@types/ws@8.5.4: resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} dependencies: - '@types/node': 20.2.4 + '@types/node': 20.2.5 dev: false /@vladfrangu/async_event_emitter@2.2.2: @@ -832,12 +832,12 @@ packages: require-from-string: 2.0.2 uri-js: 4.4.1 - /all-contributors-cli@6.25.1: - resolution: {integrity: sha512-Q9MfsO6FH09h71IG6OobfESA7lbooB3/bnO2wnuXbLPye1lFsYsa/jpNZP0YBx6zbhwXqqm6CXRZ9HO/tmD4NQ==} + /all-contributors-cli@6.26.0: + resolution: {integrity: sha512-HOMfawD0XyNbOvLUn7rOAP5N9RLnbH+Y/9/IoxwPzCmy6srHSFyRMwbpD0H7Tw+1QzdJT8RH7bTe1IZkPhF+NQ==} engines: {node: '>=4'} hasBin: true dependencies: - '@babel/runtime': 7.22.0 + '@babel/runtime': 7.22.3 async: 3.2.4 chalk: 4.1.2 didyoumean: 1.2.2 @@ -847,6 +847,8 @@ packages: node-fetch: 2.6.11 pify: 5.0.0 yargs: 15.4.1 + optionalDependencies: + prettier: 2.8.8 transitivePeerDependencies: - encoding dev: true @@ -1376,7 +1378,7 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /cosmiconfig-typescript-loader@4.3.0(@types/node@20.2.4)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.0.4): + /cosmiconfig-typescript-loader@4.3.0(@types/node@20.2.5)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@5.0.4): resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -1385,9 +1387,9 @@ packages: ts-node: '>=10' typescript: '>=3' dependencies: - '@types/node': 20.2.4 + '@types/node': 20.2.5 cosmiconfig: 8.1.3 - ts-node: 10.9.1(@types/node@20.2.4)(typescript@5.0.4) + ts-node: 10.9.1(@types/node@20.2.5)(typescript@5.0.4) typescript: 5.0.4 dev: true @@ -1427,7 +1429,7 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} dependencies: - '@babel/runtime': 7.22.0 + '@babel/runtime': 7.22.3 dev: false /dateformat@3.0.3: @@ -1488,8 +1490,8 @@ packages: engines: {node: '>=0.3.1'} dev: true - /discord-api-types@0.37.42: - resolution: {integrity: sha512-1Huaj9cQ1W7/uryS8MZs/tZemnoKB94thM1cE40lep3rpU3q7WHqkdjN/veX0prTkYlPhcyLd/DeF/pBO8X8oQ==} + /discord-api-types@0.37.43: + resolution: {integrity: sha512-bBhDWU3TF9KADxR/mHp1K4Bvu/LRtFQdGyBjADu4e66F3ZnD4kp12W/SJCttIaCcMXzPV3sfty6eDGRNRph51Q==} dev: false /discord.js@14.11.0(bufferutil@4.0.7)(utf-8-validate@5.0.10): @@ -1504,7 +1506,7 @@ packages: '@discordjs/ws': 0.8.3(bufferutil@4.0.7)(utf-8-validate@5.0.10) '@sapphire/snowflake': 3.5.1 '@types/ws': 8.5.4 - discord-api-types: 0.37.42 + discord-api-types: 0.37.43 fast-deep-equal: 3.1.3 lodash.snakecase: 4.1.1 tslib: 2.5.2 @@ -2339,7 +2341,7 @@ packages: resolution: {integrity: sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==} engines: {node: '>=10'} dependencies: - '@babel/runtime': 7.22.0 + '@babel/runtime': 7.22.3 chalk: 4.1.2 pegjs: 0.10.0 dev: true @@ -3044,8 +3046,8 @@ packages: thread-stream: 2.3.0 dev: false - /postcss@8.4.23: - resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} + /postcss@8.4.24: + resolution: {integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.6 @@ -3058,6 +3060,14 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + requiresBuild: true + dev: true + optional: true + /prisma@4.14.1: resolution: {integrity: sha512-z6hxzTMYqT9SIKlzD08dhzsLUpxjFKKsLpp5/kBDnSqiOjtUyyl/dC5tzxLcOa3jkEHQ8+RpB/fE3w8bgNP51g==} engines: {node: '>=14.17'} @@ -3449,8 +3459,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /spacetime@7.4.3: - resolution: {integrity: sha512-jKrPVk8TPaYhKSR6azA4gzO5LD2ZB29YxqQBl9zTVG0QLtfOJiliTRCNmJA9tm/dul5Aq7oyzzuWJ8VTYQkMkQ==} + /spacetime@7.4.4: + resolution: {integrity: sha512-88oqexyzGTzMCOibaBTE+3HXmaT+mw3EZVMymNTn1Xkg/6bC+nA3ZvsfmFEwxzc0VfG9BYYaXevvLiDRP7RJ6A==} dev: false /spdx-correct@3.2.0: @@ -3531,7 +3541,7 @@ packages: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.0.1 + strip-ansi: 7.1.0 /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -3550,8 +3560,8 @@ packages: dependencies: ansi-regex: 5.0.1 - /strip-ansi@7.0.1: - resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 @@ -3739,7 +3749,7 @@ packages: resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==} dev: false - /ts-node@10.9.1(@types/node@20.2.4)(typescript@5.0.4): + /ts-node@10.9.1(@types/node@20.2.5)(typescript@5.0.4): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -3758,7 +3768,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.2.4 + '@types/node': 20.2.5 acorn: 8.8.2 acorn-walk: 8.2.0 arg: 4.1.3 @@ -3938,7 +3948,7 @@ packages: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 - strip-ansi: 7.0.1 + strip-ansi: 7.1.0 dev: false /wrappy@1.0.2: From 6f227f2f48c17d375f58f2c60ff20355e940918a Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 29 May 2023 18:09:04 +0100 Subject: [PATCH 401/409] style: change banner text --- src/lib/banner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/banner.js b/src/lib/banner.js index 2ade4cb..ae474ed 100644 --- a/src/lib/banner.js +++ b/src/lib/banner.js @@ -5,5 +5,5 @@ const link = require('terminal-link'); module.exports = version => colours.cyan(figlet.textSync('Discord', { font: 'Banner3' })) + colours.cyan('\n\n' + figlet.textSync('Tickets', { font: 'Banner3' })) + colours.cyanBright(`\n\n${link('Discord Tickets', 'https://discordtickets.app')} bot v${version} by eartharoid`) + - colours.cyanBright('\n' + link('Sponsor this project', 'https://discordtickets.app/sponsor')) + + colours.cyanBright('\nSponsor this project at https://discordtickets.app/sponsor') + '\n\n'; From 6f36ef9204879538d479d4225edee4ba38557da6 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 29 May 2023 18:41:59 +0100 Subject: [PATCH 402/409] fix: ticket close DM --- src/lib/tickets/manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js index 4eca9b0..ca2c900 100644 --- a/src/lib/tickets/manager.js +++ b/src/lib/tickets/manager.js @@ -1218,7 +1218,7 @@ module.exports = class TicketManager { } try { - const creator = channel?.guild.members.cache.has(ticket.createdById); + const creator = channel?.guild.members.cache.get(ticket.createdById); if (creator) { const embed = new ExtendedEmbedBuilder({ iconURL: channel.guild.iconURL(), From f1029b8320762e483e253f97ae00dce83d60d687 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 29 May 2023 18:47:30 +0100 Subject: [PATCH 403/409] fix: infinite feedback loop (closes #407) --- src/buttons/close.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buttons/close.js b/src/buttons/close.js index 292ae43..53ef089 100644 --- a/src/buttons/close.js +++ b/src/buttons/close.js @@ -22,7 +22,7 @@ module.exports = class CloseButton extends Button { // the close button on the opening message, the same as using /close await client.tickets.beforeRequestClose(interaction); } else { - const ticket = await client.tickets.getTicket(interaction.channel.id); + const ticket = await client.tickets.getTicket(interaction.channel.id, true); // true to override cache and load new feedback const getMessage = client.i18n.getLocale(ticket.guild.locale); const staff = await isStaff(interaction.guild, interaction.user.id); @@ -78,4 +78,4 @@ module.exports = class CloseButton extends Button { } } } -}; \ No newline at end of file +}; From 6b066c177aa6e3dd61793fcbfc2cae33be629bbd Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 29 May 2023 23:01:36 +0100 Subject: [PATCH 404/409] fix(logging): don't log useless `messageDelete` events --- src/listeners/client/messageDelete.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/listeners/client/messageDelete.js b/src/listeners/client/messageDelete.js index 68c7953..d6ba785 100644 --- a/src/listeners/client/messageDelete.js +++ b/src/listeners/client/messageDelete.js @@ -13,6 +13,9 @@ module.exports = class extends Listener { }); } + /** + * @param {import("discord.js").Message} message + */ async run(message) { /** @type {import("client")} */ const client = this.client; @@ -64,15 +67,17 @@ module.exports = class extends Listener { } } - await logMessageEvent(this.client, { - action: 'delete', - diff: { - original: { content }, - updated: { content: '' }, - }, - executor, - target: message, - ticket, - }); + if (message.author.id !== client.user.id && !message.flags.has('Ephemeral')) { + await logMessageEvent(this.client, { + action: 'delete', + diff: { + original: { content }, + updated: { content: '' }, + }, + executor, + target: message, + ticket, + }); + } } }; From 540ee547eaf52c79fc11b9f024ed330d437fac65 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 29 May 2023 23:15:50 +0100 Subject: [PATCH 405/409] feat: finish `/force-close` command (closes #311) --- src/autocomplete/category.js | 10 +- src/commands/slash/force-close.js | 149 ++++++++++++++++++++---------- src/i18n/en-GB.yml | 19 ++-- 3 files changed, 121 insertions(+), 57 deletions(-) diff --git a/src/autocomplete/category.js b/src/autocomplete/category.js index 2b78d6e..fbba71d 100644 --- a/src/autocomplete/category.js +++ b/src/autocomplete/category.js @@ -18,8 +18,12 @@ module.exports = class CategoryCompleter extends Autocompleter { const client = this.client; let categories = await client.prisma.category.findMany({ where: { guildId: interaction.guild.id } }); - const ticket = await client.prisma.ticket.findUnique({ where: { id: interaction.channel.id } }); - if (ticket) categories = categories.filter(category => ticket.categoryId !== category.id); + + if (command.name === 'move') { + const ticket = await client.prisma.ticket.findUnique({ where: { id: interaction.channel.id } }); + if (ticket) categories = categories.filter(category => ticket.categoryId !== category.id); + } + const options = value ? categories.filter(category => category.name.match(new RegExp(value, 'i'))) : categories; await interaction.respond( options @@ -30,4 +34,4 @@ module.exports = class CategoryCompleter extends Autocompleter { })), ); } -}; \ No newline at end of file +}; diff --git a/src/commands/slash/force-close.js b/src/commands/slash/force-close.js index 7fca14e..261a34c 100644 --- a/src/commands/slash/force-close.js +++ b/src/commands/slash/force-close.js @@ -59,7 +59,7 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { /** @type {import("client")} */ const client = this.client; - await interaction.deferReply(); + await interaction.deferReply({ ephemeral: true }); const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } }); const getMessage = client.i18n.getLocale(settings.locale); @@ -79,7 +79,46 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { }); } - if (interaction.options.getString('time', false)) { // if time option is passed + if (interaction.options.getString('ticket', false)) { // if ticket option is passed + ticket = await client.prisma.ticket.findUnique({ + include: { category: true }, + where: { id: interaction.options.getString('ticket') }, + }); + + if (!ticket) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.invalid_ticket.title')) + .setDescription(getMessage('misc.invalid_ticket.description')), + ], + }); + } + + await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.successColour) + .setTitle(getMessage('commands.slash.force-close.closed_one.title')) + .setDescription(getMessage('commands.slash.force-close.closed_one.description', { ticket: ticket.id })), + ], + }); + + setTimeout(async () => { + await client.tickets.finallyClose(ticket.id, { + closedBy: interaction.user.id, + reason: interaction.options.getString('reason', false), + }); + }, ms('3s')); + + } else if (interaction.options.getString('time', false)) { // if time option is passed const time = ms(interaction.options.getString('time', false)); if (!time) { @@ -96,9 +135,10 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { }); } - // TODO: category + const categoryId = interaction.options.getInteger('category', false); const tickets = await client.prisma.ticket.findMany({ where: { + categoryId: categoryId ?? undefined, // must be undefined not null lastMessageAt: { lte: new Date(Date.now() - time) }, open: true, }, @@ -118,7 +158,6 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { }); } - let confirmed = false; const collectorTime = ms('15s'); const confirmationM = await interaction.editReply({ components: [ @@ -157,58 +196,53 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { ], }); - confirmationM.awaitMessageComponent({ componentType: ComponentType.Button, - filter: i => { - i.deferUpdate(); - return i.user.id === interaction.user.id; - }, + filter: i => i.user.id === interaction.user.id, time: collectorTime, }) - .then(i => { + .then(async i => { if (JSON.parse(i.customId).id === 'close') { - confirmed = true; - // TODO: i.editReply + await i.reply({ + components: [], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.successColour) + .setTitle(getMessage('commands.slash.force-close.confirmed_multiple.title', tickets.length, tickets.length)) + .setDescription(getMessage('commands.slash.force-close.confirmed_multiple.description')), + ], + ephemeral: true, + }); + setTimeout(async () => { + for (const ticket of tickets) { + await client.tickets.finallyClose(ticket.id, { + closedBy: interaction.user.id, + reason: interaction.options.getString('reason', false), + }); + } + }, ms('3s')); } else { - // TODO: cancelled + await interaction.deleteReply(); } }) - .catch(() => interaction.editReply({ - components: [], - embeds: [ - new ExtendedEmbedBuilder({ - iconURL: interaction.guild.iconURL(), - text: settings.footer, - }) - .setColor(settings.errorColour) - .setTitle(getMessage('misc.expired.title')) - .setDescription(getMessage('misc.expired.description', { time: ms(time, { long: true }) })), - ], - })); - - if (!confirmed) return; - - // TODO: tickets: for each, close (check reason) - } else if (interaction.options.getString('ticket', false)) { // if ticket option is passed - ticket = await client.prisma.ticket.findUnique({ - include: { category: true }, - where: { id: interaction.options.getString('ticket', false) }, - }); - - if (!ticket) { - return await interaction.editReply({ - embeds: [ - new ExtendedEmbedBuilder({ - iconURL: interaction.guild.iconURL(), - text: settings.footer, - }) - .setColor(settings.errorColour) - .setTitle(getMessage('misc.invalid_ticket.title')) - .setDescription(getMessage('misc.invalid_ticket.description')), - ], + .catch(async error => { + client.log.error(error); + await interaction.reply({ + components: [], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.expired.title')) + .setDescription(getMessage('misc.expired.description', { time: ms(time, { long: true }) })), + ], + }); }); - } } else { ticket = await client.prisma.ticket.findUnique({ include: { category: true }, @@ -228,6 +262,25 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand { ], }); } + + await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.successColour) + .setTitle(getMessage('commands.slash.force-close.closed_one.title')) + .setDescription(getMessage('commands.slash.force-close.closed_one.description', { ticket: ticket.id })), + ], + }); + + setTimeout(async () => { + await client.tickets.finallyClose(ticket.id, { + closedBy: interaction.user.id, + reason: interaction.options.getString('reason', false), + }); + }, ms('3s')); } } -}; \ No newline at end of file +}; diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index 281c174..e16413e 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -3,7 +3,7 @@ buttons: emoji: ✅ text: Accept cancel: - emoji: ✖️ + emoji: ➖ text: Cancel claim: emoji: 🙌 @@ -78,6 +78,9 @@ commands: description: The reason for closing the ticket name: reason force-close: + closed_one: + description: The channel will be deleted in a few seconds. + title: ✅ Ticket closed confirm_multiple: description: > You are about to close **{count}** tickets that have been inactive @@ -85,6 +88,11 @@ commands: {tickets} title: ❓ Are you sure? + confirmed_multiple: + description: The channels will be deleted in a few seconds. + title: + - ✅ Closing %d ticket + - ✅ Closing %d tickets description: Forcibly close a ticket name: force-close no_tickets: @@ -97,8 +105,9 @@ commands: title: ❌ Error options: category: - description: >- - Close all tickets in the specified category (can be used with `time`) + description: + Close all tickets in the specified category (must be used with + `time`) name: category reason: description: The reason for closing the ticket(s) @@ -107,9 +116,7 @@ commands: description: The ticket to close name: ticket time: - description: >- - Close all tickets that have been inactive for the specified time - (can be used with `category`) + description: Close all tickets that have been inactive for the specified time name: time help: description: Show the help menu From 8f51ff885c4000bbcf38c4a09c1d7dfb46792d0b Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 30 May 2023 00:25:25 +0100 Subject: [PATCH 406/409] feat: finish user `create` command (closes #291) --- src/buttons/create.js | 3 +- src/commands/user/create.js | 158 ++++++++++++++++++++++++++++++++++-- src/i18n/en-GB.yml | 9 ++ 3 files changed, 163 insertions(+), 7 deletions(-) diff --git a/src/buttons/create.js b/src/buttons/create.js index 2e7d02c..b5f5cf1 100644 --- a/src/buttons/create.js +++ b/src/buttons/create.js @@ -13,10 +13,11 @@ module.exports = class CreateButton extends Button { * @param {import("discord.js").ButtonInteraction} interaction */ async run(id, interaction) { + if (id.targetUser && id.targetUser !== interaction.user.id) return; await this.client.tickets.create({ categoryId: id.target, interaction, topic: id.topic, }); } -}; \ No newline at end of file +}; diff --git a/src/commands/user/create.js b/src/commands/user/create.js index 4cf06a0..7176d40 100644 --- a/src/commands/user/create.js +++ b/src/commands/user/create.js @@ -1,4 +1,16 @@ const { UserCommand } = require('@eartharoid/dbf'); +const { isStaff } = require('../../lib/users'); +const ExtendedEmbedBuilder = require('../../lib/embed'); +const ms = require('ms'); +const { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + ComponentType, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, +} = require('discord.js'); +const emoji = require('node-emoji'); module.exports = class CreateUserCommand extends UserCommand { constructor(client, options) { @@ -13,10 +25,144 @@ module.exports = class CreateUserCommand extends UserCommand { }); } - async run(/* interaction */) { - // TODO: isStaff? - // TODO: user->create - // select category - // send button + /** + * @param {import("discord.js").UserContextMenuCommandInteraction} interaction + */ + async run(interaction) { + /** @type {import("client")} */ + const client = this.client; + + await interaction.deferReply({ ephemeral: true }); + + const settings = await client.prisma.guild.findUnique({ + include: { categories:true }, + where: { id: interaction.guild.id }, + }); + const getMessage = client.i18n.getLocale(settings.locale); + + if (!await isStaff(interaction.guild, interaction.user.id)) { + return await interaction.editReply({ + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('commands.user.create.not_staff.title')) + .setDescription(getMessage('commands.user.create.not_staff.description')), + ], + }); + } + + const prompt = async categoryId => { + interaction.followUp({ + components: [ + new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(JSON.stringify({ + action: 'create', + target: categoryId, + targetUser: interaction.targetId, + })) + .setStyle(ButtonStyle.Primary) + .setEmoji(getMessage('buttons.create.emoji')) // emoji.get('ticket') + .setLabel(getMessage('buttons.create.text')), + ), + ], + content: interaction.targetUser.toString(), + embeds: [ + new ExtendedEmbedBuilder() + .setColor(settings.primaryColour) + .setAuthor({ + iconURL: interaction.member.displayAvatarURL(), + name: interaction.member.displayName, + }) + .setTitle(getMessage('commands.user.create.prompt.title')) + .setDescription(getMessage('commands.user.create.prompt.description')), + ], + ephemeral: false, + }); + }; + + if (settings.categories.length === 0) { + interaction.reply({ + components: [], + embeds: [ + new ExtendedEmbedBuilder() + .setColor(settings.errorColour) + .setTitle(getMessage('misc.no_categories.title')) + .setDescription(getMessage('misc.no_categories.description')), + ], + ephemeral: true, + }); + } else if (settings.categories.length === 1) { + await prompt(settings.categories[0].id); + } else { + const collectorTime = ms('15s'); + const confirmationM = await interaction.editReply({ + components: [ + new ActionRowBuilder() + .setComponents( + new StringSelectMenuBuilder() + .setCustomId(JSON.stringify({ + action: 'promptCreate', + user: interaction.targetId, + })) + .setPlaceholder(getMessage('menus.category.placeholder')) + .setOptions( + settings.categories.map(category => + new StringSelectMenuOptionBuilder() + .setValue(String(category.id)) + .setLabel(category.name) + .setDescription(category.description) + .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), + ), + ), + ), + ], + }); + + confirmationM.awaitMessageComponent({ + componentType: ComponentType.StringSelect, + filter: i => i.user.id === interaction.user.id, + time: collectorTime, + }) + .then(async i => { + const category = settings.categories.find(c => c.id === Number(i.values[0])); + await i.update({ + components: [], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.successColour) + .setTitle(getMessage('commands.user.create.sent.title')) + .setDescription(getMessage('commands.user.create.sent.description', { + category: category.name, + user: interaction.targetUser.toString(), + })), + ], + ephemeral: true, + }); + await prompt(category.id); + }) + .catch(async error => { + client.log.error(error); + await interaction.reply({ + components: [], + embeds: [ + new ExtendedEmbedBuilder({ + iconURL: interaction.guild.iconURL(), + text: settings.footer, + }) + .setColor(settings.errorColour) + .setTitle(getMessage('misc.expired.title')) + .setDescription(getMessage('misc.expired.description')), + ], + }); + }); + } } -}; \ No newline at end of file +}; diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index e16413e..803f574 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -247,6 +247,15 @@ commands: user: create: name: Create ticket for user + not_staff: + description: Only staff members can open tickets for other members. + title: ❌ Error + prompt: + description: Click the button below to create a ticket. + title: Please create a ticket + sent: + description: "{user} has been invited to create a ticket in **{category}**." + title: ✅ Prompt sent dm: closed: archived: Use the `/transcript` command in **{guild}** to view the archived messages. From ca9c82f1cb5c3c72401e9b75fe1835380ddf52b7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 30 May 2023 00:29:02 +0100 Subject: [PATCH 407/409] chore(version): `4.0.0-beta.12` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48e804b..d4da2c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-tickets", - "version": "4.0.0-beta.11", + "version": "4.0.0-beta.12", "private": "true", "description": "An open-source Discord bot for ticket management", "main": "src/", From cc35f59a15ba51b83a368a3e453d19cd8b59e1a1 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 30 May 2023 00:29:22 +0100 Subject: [PATCH 408/409] chore(readme): clean up --- README.md | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 395352d..1cbdbab 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,3 @@ -> **Warning** -> -> This is the development branch, it may be unstable and may not work as expected. -> -> *dev notes:* -> - https://static.eartharoid.me/k/22/08/02185801.png - for user/create, slash/force-close, slash/claim, slash/release, and slash/move -> - menu question max length cannot be higher than question options -> - TODO: update notifications -> - TODO: user:create, force-close - -
![bots](https://img.shields.io/badge/dynamic/json?color=5865F2&label=bots&query=clients.total&url=https%3A%2F%2Fstats.discordtickets.app%2Fapi%2Fv3%2Fcurrent&logo=discord&logoColor=white&style=for-the-badge) @@ -103,7 +92,6 @@ There are 3 ways to get started with Discord Tickets: **[Read the documentation](https://discordtickets.app/getting-started/)** for more information. -> **Warning** > This button is here because it looks nice, but I will cry if you click it before reading the [documentation](https://discordtickets.app/getting-started/). :) [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/eB6TkX?referralCode=Z3aYd2) @@ -125,7 +113,6 @@ Please consider sponsoring the project if it adds value to your business/communi > **Note** > > Your logo will only appear here if you sponsor through GitHub Sponsors. -> > [Create an organisation](https://github.com/account/organizations/new?plan=free) if you want to use your business/community logo. ## 🎖️ Contributors @@ -155,7 +142,7 @@ or contribute in any other way, please read the [contributing guidelines](https:
Show graph - + [![Star History Chart](https://api.star-history.com/svg?repos=discord-tickets/bot&type=Date)](https://star-history.com/#discord-tickets/bot&Date)
@@ -166,4 +153,4 @@ Discord Tickets by eartharoid™️ is licensed under the [GPLv3 license](https: This is not an official Discord product. It is not affiliated with nor endorsed by Discord Inc. -© 2023 Isaac Saunders \ No newline at end of file +© 2023 Isaac Saunders From 810439e1e476ce65127921de34caaf858cd345b7 Mon Sep 17 00:00:00 2001 From: Isaac Date: Tue, 30 May 2023 00:55:17 +0100 Subject: [PATCH 409/409] fix: lockfile --- pnpm-lock.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3603eb..22c5413 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1514,7 +1514,6 @@ packages: ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - - encoding - utf-8-validate dev: false @@ -1980,6 +1979,7 @@ packages: /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} @@ -2115,6 +2115,7 @@ packages: engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 + dev: true /hex-to-rgba@2.0.1: resolution: {integrity: sha512-5XqPJBpsEUMsseJUi2w2Hl7cHFFi3+OO10M2pzAvKB1zL6fc+koGMhmBqoDOCB4GemiRM/zvDMRIhVw6EkB8dQ==} @@ -3010,6 +3011,7 @@ packages: /pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} + dev: true /pify@5.0.0: resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==}