From 0299978262794269443337c4f476f4199ce408bd Mon Sep 17 00:00:00 2001 From: Scott Bucher <47844014+scottbucher@users.noreply.github.com> Date: Sun, 11 Jul 2021 19:28:06 -0400 Subject: [PATCH] 3.0 Update (#144) * Updated commands to use the lang utils * forgot to save oops * Added database system for member anniversary roles * Added model for anniversary roles * Updated service to use correct model * Use correct config value * Added config values for member Anniversary roles * Updated procedure to take in a year * Updated parse utils to catch invalid input * Continued work on member anniversary role system * Add lang embed for user not giving a year * Add dummy mode to debug (#126) * Create "dev" and "info" commands (#125) * Create "dev" and "info" commands * Dev command shows stats, memory sizes per server * Show bot ID in dev command * Show bot ID in dev command * Sync lang functionality from template (#127) * Update lang functionality from template * Make prefix a REF * Field orders * Remove unused "limiter" package (#128) * Fix imports (#129) * Misc small updates from template (#130) * Rename "Read Messages" to "View Channel" * Array of error codes, shorten comments * Combine if statement conditions, add comment * Update template packages * Disable message cache completely * Cleanup launch.json * Update MessageUtils from template * Add log for when all shards have been spawned * Update discord links * Updated and added methods for new setup commands * Removed message setup * Updated setup commands * Updated required setup to use lang system * Updated logic for inline input in bday set * Exit loop as soon as we find a timezone * Updated trusted setup to use lang system * Updated anniversary setup to use lang system * Updated anniversary setup to use Lang system * Updated clear and add to work well wil user specific messages * Added values for anniversary utils * Added utils for the anniversary service * Started new celebration job * Updated methods for birthday utils to improve accuracy * Indexing * "READ_MESSAGE_HISTORY" needed with "ADD_REACTIONS" (#131) * Reactions require READ_MESSAGE_HISTORY * READ_MESSAGE_HISTORY needed to ADD_REACTIONS * Add docker, additional utils, template changes, misc (#132) * Update packages * Add docker * Add gitbook.yaml * Prettier ignore docs * Update tsconfig * Fix in order for build to succeed * Lint fixes * Require Node.js v15 or newer * Add some missing utils * Move isLeap() to TimeUtils * Rename to SqlUtils * Fix utils import * Rename methods * Use MathUtils to sum in shard methods * Add PartialUtils * Add @types/remove-markdown * Better message for startupInProcess * Log errors on reactions * Temp * Merge master into updates (#140) * Auto-format * Update to models and start of new models * Refractored bday utils to celebration utils * Installed packages * Added code to get data needed for the celebration job in bulk * Added method and model to convert the raw guild celebration data into a sorted model * Simplifiy class * Updated logic of isBirthday method * Added models for the planned services * Added methods to make filters cleaner * Finish sorting to prepare for message service * Removed unused Models * Leap year dates are celebrated on march 1st now * Refractored methods to calculate birthday times * Added method to find birthday roles to remove * Added calculations for role service * Remove uneeded variables and imports * Update method * Added method to get the members year * Added requireAllTrustedRoles to guildData * Updated inputs * Added implementation of the role service * Use the filtered array * Improve logic to prevent calculating things before we know we need them * Added utils to make message service cleaner * Implemented birthday messages with message service * Made trusted check generic and use correct number of equals * Added util to get the server age in years * Implemented rough code for anniversaries * Miscellaneous changes * Call the services at the end of the celebration job * Update message handler to split different member anniversary messages based on the anniversary year * Removed old birthday system and added new (with some bad code... oops) * Update config default for celebration job * Use correct config values * Update anniversary roles * Clarification on global birthdays * intintialize arrays * Improve validation * Generalize and rework message add command * Improve validation on celebration job * Updated message add command to work with lang system and fixed numerous bugs * Improved and simplified message list commands * Implemented user specific message improvements to list and fixed a bug with page jumping * Updated reaction handler for list emote movement * Removed separate command for user message list * Continue implementing lang system throughout the project * replace placeholder with user @ in user specific messages * Split up settings and implemented lang system * Implemented Lang system * Added methods for differentiating before types and extracting settings * Implemented lang system * Display user mention for user specific messages in confirmation messages * prevent out of bounds exception when checking for guild member * Use lang system to give multi-lingual support to types * Continued conversion to the lang system * Add advanced settings embed * Continued implementing the lang system * Allow user with the manage server permission flag to manage the bot * Change labels to be more specific * Updated subscription command to use lang system * Subscription events now use the lang system * Added more lang implementation, refractored some utils, and removed comma one lists of two users * Removed and replaced unneeded method * Various bug fixes * Remove and replace unneeded util and converted to lang system * Updated to lang system * Get month names from lang * Remove ununused variable * Use lang system for canceled action message * check birthday using lang * Add and update doc links * Get and value from lang file * Added vote command * Update help system and move it to the lang system * Converted stats command to lang system even though I said I wasn't going to * todo comments * Only use one rusted role if guild doesn't have premim * Added util method to get a list of valid trusted roles and fixed a bug where getting the mention setting didn't account for the message type * Add methods to check if all roles in a list can be given a method to resolve all member anniversary roles * implemented previously mentioned methods * Use new utils * Log for an invalid message type * Reworked test command * Added result messages for tests * Re-Implemented test command * A lot of auto formatting * Use correct name format in test commands * Added more permissions to default creation of channels * Fixed wrong placeholder for years and fix bug where user doesn't have a nickname for name format to use * Fixed various bugs with bday test * Formatting * replace prefix with reference * Replace bot name with reference * Convert to the lang system * Allow for translation of subcommand names and most options * get action type from lang file * Added update command (probably too large) * get from reference * Added anniversaries to next command * Heavily improved logic of calculating next member anniversary * Switch to logic which is consistant with the member anniversary calculations * Various bug fixes and improvments * Added command for listing member anniversaries * Added command to view member anniversaries * Added config option for the size of the member anniversary list * Various Bug Fixes and improvements * Implemented member anniversary list into paging system * Added member anniversary role claim command * Added sleep time after giving/taking role * Added permission checking at the sub command level * Added claim command * Added clearer documentation * Moved the test command to the lang system * Default large bot sharding to false * Ensure a role is found before accessing its guild id * Remove wrong arg count check * Update lang * Update to lang file * Prevent api calls where possible * Formatting and update names * Updated lang file * Fix name of values being passed to lang system * Formatting * Declare an empty array to prevent undefined errors * Fixed bugs with adding anniversary roles * Fixed various bugs with claiming anniversary roles * Update packages * Updated Logging * Fixed various bugs & Formatting * Fixed bug causing placeholders to no be replaced * Lint and format fixes (#142) * Lint fixes * Format fixes * Remove unused "limiter" package (#143) * Removed unneeded awaits from getting the mention string * Update packages * Updated packages * When listing two things include and * Simplified replacing placeholder and fixed some bugs * Improve replacing placeholders for lang options or user id when displaying * moment diff already returns a whole number * Use name format in bday next command * Added option to clear server's default timezone * Fixed bugs * Fix bug with applying timezone * Fix lang * Added anniversary help and misc * Fixed setup args * Fixed bug with moment timezone validation * Require server timezone for anniversary system * Grammar is hard * Fix bug with timezone check * Improve timezone validation * Updated lang system and simplified update command * Removed thumbnail to fit more text in horizontally * Added setting value of number of trusted roles * Possibly fix bug for celebrating a member's 0th anniversary * Add config value for delays * Added delays to send messages * Apply config delays to services * Apply delay to the mar claim command * Remove outdated scripts * Small changes to README * Update README.md * Update packages * Update footers to include icons and add authors * Fix issue with mentioning everyone * Simplified logic * Formatting * Updated bug where it was using the wrong channel and changed the member aninversary message to only send the mention setting once * Added support for getting all subscriptions * Fix references * All guilds are premium if payments are disabled * Added back missing lang options * Give icon placeholder value * Fixed doc link * Fixed lang reference * Clarify the deafult timezone prompt * Improved clarity when setting your birthday * Fix emote references * Improved logic for timezone setting * Im prove logic and lang when suggesting timezones * Fix logic in setting timezone.... again * oops i did it again! * Simply user prompts for setting a birthday * Updated logic for replying and sending messages * Expired prompts now use the reply feature and generic message * Split message util methods for delay * Try and combine member anniversary messages when possible to prevent sending mass amounts of messages * Fix logic for calculating member years * Updated logic for calculating messages * Updated documentation links * Update documentation links in README * Fixed returning the mention string * Fix logic for timezone setting * Include type in test * Fix logic for validating the mention string * Update anniversary role arguments * Bug fixes * Lang updates * Remove unneeded year check * Fix years in bday test * Fix lang reference * Remove deprecated useEmbed reference * Update packages Co-authored-by: Kevin Novak --- .dockerignore | 5 + .gitbook.yaml | 1 + .prettierignore | 1 + .vscode/launch.json | 39 +- .vscode/settings.json | 3 + Dockerfile | 19 + README.md | 127 +- config/config.example.json | 68 +- config/debug.example.json | 8 +- lang/lang.en-US.json | 2844 +++++++++++++++++ lang/logs.json | 9 +- ...ay Bot Cluster API.postman_collection.json | 39 +- package-lock.json | 1281 +++++--- package.json | 37 +- scripts/create-database.sql | 1060 ------ scripts/migrate-database-v1-to-v2.sql | 63 - src/bot.ts | 61 +- src/commands/blacklist-command.ts | 52 +- .../blacklist/blacklist-add-sub-command.ts | 53 +- .../blacklist/blacklist-clear-sub-command.ts | 59 +- .../blacklist/blacklist-list-sub-command.ts | 12 +- .../blacklist/blacklist-remove-sub-command.ts | 63 +- src/commands/clear-command.ts | 67 - src/commands/config-command.ts | 91 + ...config-birthday-master-role-sub-command.ts | 94 + .../config/config-channel-sub-command.ts | 143 + .../config/config-name-format-sub-command.ts | 46 + ...g-require-all-trusted-roles-sub-command.ts | 36 + .../config/config-role-sub-command.ts | 142 + .../config/config-timezone-sub-command.ts | 56 + ...config-trusted-prevents-msg-sub-command.ts | 38 + ...onfig-trusted-prevents-role-sub-command.ts | 38 + .../config/config-use-timezone-sub-command.ts | 32 + src/commands/config/index.ts | 9 + src/commands/create-command.ts | 154 - src/commands/dev-command.ts | 81 + src/commands/documentation-command.ts | 25 +- src/commands/donate-command.ts | 25 +- src/commands/faq-command.ts | 25 +- src/commands/help-command.ts | 178 +- src/commands/index.ts | 40 +- src/commands/info-command.ts | 26 + src/commands/invite-command.ts | 25 +- src/commands/list-command.ts | 132 +- src/commands/map-command.ts | 22 +- .../member-anniversary-role-command.ts | 90 + src/commands/memberAnniversaryRole/index.ts | 5 + ...member-anniversary-role-add-sub-command.ts | 117 + .../member-anniversary-role-claim-command.ts | 99 + ...mber-anniversary-role-clear-sub-command.ts | 91 + ...ember-anniversary-role-list-sub-command.ts | 54 + ...ber-anniversary-role-remove-sub-command.ts | 90 + src/commands/message-command.ts | 84 +- src/commands/message/index.ts | 5 +- .../message/message-add-sub-command.ts | 449 ++- .../message/message-clear-sub-command.ts | 107 +- .../message/message-color-sub-command.ts | 173 - .../message/message-embed-sub-command.ts | 42 - .../message/message-list-sub-command.ts | 93 +- .../message/message-mention-sub-command.ts | 105 + .../message/message-mention-sub-commands.ts | 86 - .../message/message-remove-sub-command.ts | 176 +- .../message/message-test-sub-command.ts | 168 +- .../message/message-time-sub-command.ts | 87 +- src/commands/next-command.ts | 192 +- src/commands/premium-command.ts | 92 + src/commands/premium-commands.ts | 98 - src/commands/purge-command.ts | 70 +- src/commands/set-attempts-command.ts | 82 +- src/commands/set-command.ts | 372 +-- src/commands/settings-command.ts | 265 +- src/commands/setup-command.ts | 88 +- src/commands/setup/index.ts | 2 +- src/commands/setup/setup-anniversary.ts | 337 ++ src/commands/setup/setup-message.ts | 249 -- src/commands/setup/setup-required.ts | 238 +- src/commands/setup/setup-trusted.ts | 276 +- src/commands/stats-command.ts | 37 +- src/commands/subscribe-command.ts | 92 +- src/commands/support-command.ts | 18 +- src/commands/test-command.ts | 567 +++- src/commands/trusted-command.ts | 98 - src/commands/trusted-role-command.ts | 64 + src/commands/trusted/index.ts | 4 + .../trusted/trusted-role-add-sub-command.ts | 101 + .../trusted/trusted-role-clear-sub-command.ts | 89 + .../trusted-role-list-sub-command.ts} | 32 +- .../trusted-role-remove-sub-command.ts | 90 + src/commands/update-command.ts | 267 +- src/commands/view-command.ts | 135 +- src/commands/vote-command.ts | 29 + src/events/guild-join-handler.ts | 28 +- src/events/guild-leave-handler.ts | 3 +- src/events/message-handler.ts | 145 +- src/events/reaction-add-handler.ts | 763 +++-- src/extensions/custom-client.ts | 116 +- src/jobs/celebration-job.ts | 250 ++ src/jobs/index.ts | 2 +- src/jobs/job.ts | 2 +- src/jobs/post-birthdays-job.ts | 168 - src/jobs/update-server-count-job.ts | 9 +- src/manager.ts | 11 +- src/models/database/blacklisted-models.ts | 1 + src/models/database/custom-messages-models.ts | 4 + .../database/guild-celebration-models.ts | 13 + src/models/database/guild-models.ts | 18 +- src/models/database/index.ts | 4 + .../member-anniversary-role-models.ts | 18 + .../database/raw-guild-celebration-models.ts | 15 + src/models/database/trusted-role-models.ts | 17 + src/models/enums/index.ts | 1 + src/models/enums/language.ts | 28 + src/models/index.ts | 7 + src/services/birthday-service.ts | 292 -- src/services/database/data-access.ts | 4 +- src/services/database/procedure.ts | 45 +- src/services/database/repos/blacklist-repo.ts | 8 +- src/services/database/repos/combined-repo.ts | 27 + .../database/repos/custom-message-repo.ts | 71 +- src/services/database/repos/guild-repo.ts | 134 +- src/services/database/repos/index.ts | 7 +- .../repos/member-anniversary-role-repo.ts | 55 + .../database/repos/trusted-role-repo.ts | 45 + src/services/database/repos/user-repo.ts | 24 +- src/services/index.ts | 4 +- src/services/job-service.ts | 6 +- src/services/lang.ts | 38 + src/services/message-service.ts | 509 +++ src/services/role-service.ts | 161 + src/services/subscription-service.ts | 13 + src/start.ts | 253 +- src/utils/action-utils.ts | 34 +- src/utils/anniversary-utils.ts | 65 + src/utils/array-utils.ts | 2 +- src/utils/bday-utils.ts | 112 - src/utils/celebration-utils.ts | 459 +++ src/utils/client-utils.ts | 69 + src/utils/color-utils.ts | 2 +- src/utils/format-utils.ts | 691 +++- src/utils/guild-utils.ts | 21 +- src/utils/index.ts | 20 +- src/utils/list-utils.ts | 118 +- src/utils/math-utils.ts | 11 +- src/utils/message-utils.ts | 129 +- src/utils/parse-utils.ts | 10 +- src/utils/partial-utils.ts | 45 + src/utils/permission-utils.ts | 53 +- src/utils/regex-utils.ts | 27 + src/utils/shard-utils.ts | 1 + src/utils/sql-utils.ts | 2 +- src/utils/string-utils.ts | 20 + src/utils/time-utils.ts | 10 +- 152 files changed, 12557 insertions(+), 6197 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitbook.yaml create mode 100644 Dockerfile create mode 100644 lang/lang.en-US.json delete mode 100644 scripts/create-database.sql delete mode 100644 scripts/migrate-database-v1-to-v2.sql delete mode 100644 src/commands/clear-command.ts create mode 100644 src/commands/config-command.ts create mode 100644 src/commands/config/config-birthday-master-role-sub-command.ts create mode 100644 src/commands/config/config-channel-sub-command.ts create mode 100644 src/commands/config/config-name-format-sub-command.ts create mode 100644 src/commands/config/config-require-all-trusted-roles-sub-command.ts create mode 100644 src/commands/config/config-role-sub-command.ts create mode 100644 src/commands/config/config-timezone-sub-command.ts create mode 100644 src/commands/config/config-trusted-prevents-msg-sub-command.ts create mode 100644 src/commands/config/config-trusted-prevents-role-sub-command.ts create mode 100644 src/commands/config/config-use-timezone-sub-command.ts create mode 100644 src/commands/config/index.ts delete mode 100644 src/commands/create-command.ts create mode 100644 src/commands/dev-command.ts create mode 100644 src/commands/info-command.ts create mode 100644 src/commands/member-anniversary-role-command.ts create mode 100644 src/commands/memberAnniversaryRole/index.ts create mode 100644 src/commands/memberAnniversaryRole/member-anniversary-role-add-sub-command.ts create mode 100644 src/commands/memberAnniversaryRole/member-anniversary-role-claim-command.ts create mode 100644 src/commands/memberAnniversaryRole/member-anniversary-role-clear-sub-command.ts create mode 100644 src/commands/memberAnniversaryRole/member-anniversary-role-list-sub-command.ts create mode 100644 src/commands/memberAnniversaryRole/member-anniversary-role-remove-sub-command.ts delete mode 100644 src/commands/message/message-color-sub-command.ts delete mode 100644 src/commands/message/message-embed-sub-command.ts create mode 100644 src/commands/message/message-mention-sub-command.ts delete mode 100644 src/commands/message/message-mention-sub-commands.ts create mode 100644 src/commands/premium-command.ts delete mode 100644 src/commands/premium-commands.ts create mode 100644 src/commands/setup/setup-anniversary.ts delete mode 100644 src/commands/setup/setup-message.ts delete mode 100644 src/commands/trusted-command.ts create mode 100644 src/commands/trusted-role-command.ts create mode 100644 src/commands/trusted/index.ts create mode 100644 src/commands/trusted/trusted-role-add-sub-command.ts create mode 100644 src/commands/trusted/trusted-role-clear-sub-command.ts rename src/commands/{message/message-user-list-sub-command.ts => trusted/trusted-role-list-sub-command.ts} (50%) create mode 100644 src/commands/trusted/trusted-role-remove-sub-command.ts create mode 100644 src/commands/vote-command.ts create mode 100644 src/jobs/celebration-job.ts delete mode 100644 src/jobs/post-birthdays-job.ts create mode 100644 src/models/database/guild-celebration-models.ts create mode 100644 src/models/database/member-anniversary-role-models.ts create mode 100644 src/models/database/raw-guild-celebration-models.ts create mode 100644 src/models/database/trusted-role-models.ts create mode 100644 src/models/enums/index.ts create mode 100644 src/models/enums/language.ts create mode 100644 src/models/index.ts delete mode 100644 src/services/birthday-service.ts create mode 100644 src/services/database/repos/combined-repo.ts create mode 100644 src/services/database/repos/member-anniversary-role-repo.ts create mode 100644 src/services/database/repos/trusted-role-repo.ts create mode 100644 src/services/lang.ts create mode 100644 src/services/message-service.ts create mode 100644 src/services/role-service.ts create mode 100644 src/utils/anniversary-utils.ts delete mode 100644 src/utils/bday-utils.ts create mode 100644 src/utils/celebration-utils.ts create mode 100644 src/utils/client-utils.ts create mode 100644 src/utils/partial-utils.ts create mode 100644 src/utils/regex-utils.ts create mode 100644 src/utils/string-utils.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..4c763dc5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +node_modules +npm-debug.log +dist +docs diff --git a/.gitbook.yaml b/.gitbook.yaml new file mode 100644 index 00000000..e454be0e --- /dev/null +++ b/.gitbook.yaml @@ -0,0 +1 @@ +root: ./docs/ diff --git a/.prettierignore b/.prettierignore index f06235c4..b87a2cb4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ node_modules dist +docs diff --git a/.vscode/launch.json b/.vscode/launch.json index 62cf987a..d14ccea4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,24 +2,49 @@ "version": "0.2.0", "configurations": [ { + "name": "Start Dev", "type": "node", "request": "launch", - "name": "Start", + "protocol": "inspector", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ts-node-dev", + "args": [ + "--watch", + "src/**/*.ts,config/**/*.json,lang/**/*.json", + "${workspaceFolder}/src/start.ts" + ], + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"], + "internalConsoleOptions": "openOnSessionStart", "skipFiles": ["/**"], - "program": "${workspaceFolder}\\dist\\start.js", + "restart": true + }, + { + "name": "Start", + "type": "node", + "request": "launch", + "protocol": "inspector", "preLaunchTask": "tsc", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "node", + "program": "${workspaceFolder}\\dist\\start.js", + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"], "internalConsoleOptions": "openOnSessionStart", - "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"] + "skipFiles": ["/**"], + "restart": true }, { + "name": "Start Shard", "type": "node", "request": "launch", - "name": "Start Shard", - "skipFiles": ["/**"], - "program": "${workspaceFolder}\\dist\\app.js", + "protocol": "inspector", "preLaunchTask": "tsc", + "cwd": "${workspaceFolder}", + "runtimeExecutable": "node", + "program": "${workspaceFolder}\\dist\\app.js", + "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"], + "skipFiles": ["/**"], "internalConsoleOptions": "openOnSessionStart", - "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"] + "restart": true } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 891596c1..68adaf8b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,11 +18,14 @@ "chrono", "customizability", "datas", + "deletable", "discordbotlist", "discordlabs", + "filesize", "isnt", "ondiscord", "parens", + "regexes", "repos", "respawn" ] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f21f9d3b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM node:15 + +# Create app directory +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install packages +RUN npm install + +# Copy the app code +COPY . . + +# Build the project +RUN npm run build + +# Run the application +CMD [ "node", "dist/app.js" ] diff --git a/README.md b/README.md index 3e6f676f..b6bff1d7 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,67 @@ [![Discord Bots](https://top.gg/api/widget/status/656621136808902656.svg?noavatar=true)](https://top.gg/bot/656621136808902656) [![License](https://img.shields.io/badge/license-No%20License-blue)](https://choosealicense.com/no-permission/) [![Stars](https://img.shields.io/github/stars/scottbucher/BirthdayBot.svg)](https://github.com/scottbucher/BirthdayBot/stargazers) -[![Discord Shield](https://discordapp.com/api/guilds/660711235766976553/widget.png?style=shield)](https://discordapp.com/invite/9gUQFtz) +[![Discord Shield](https://discord.com/api/guilds/660711235766976553/widget.png?style=shield)](https://discord.com/invite/9gUQFtz) [![Discord Bots](https://top.gg/api/widget/owner/656621136808902656.svg?noavatar=true)](https://top.gg/bot/656621136808902656) -**Discord Bot** - Celebrate birthdays with automatic birthday roles and announcements! Highly customizable and easy to use! Use `bday help` to get started! +## Important Note + +**For the most detailed and up-to-date information and guide please visit the Official Birthday Bot Documentation [here!](https://birthdaybot.scottbucher.dev)** + +**Discord Bot** - Celebrate birthdays and anniversaries (**NEW**) with configurable roles, messages and more! Highly customizable and easy to use! Use `bday help` to get started! ## [Click here to add Birthday Bot to your Discord server!](https://discord.com/api/oauth2/authorize?client_id=656621136808902656&permissions=268921936&scope=bot%20applications.commands) [Join The Support Server](https://discord.gg/9gUQFtz) | [Donate with Paypal!](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=PE97AGAPRX35Q¤cy_code=USD&source=url) +# Features: + +## Free Features + +- Each user can [set their birthday & time zone](https://birthdaybot.scottbucher.dev/setting-your-birthday) into the bot which stores that information into a database allowing the bot to know on what day and what time zone to celebrate the user's birthday +- Use the Anniversary System to celebrate the anniversary of when members first joined the server and when the server was created. + - Requires the [Default Timezone](https://birthdaybot.scottbucher.dev/faq-1/general#what-is-the-default-timezone-setting) Setting to be set + - Server owners have heavy control over how, when, and what Birthday Bot does in their Discord + - Customization of the [Celebration Messages](https://birthdaybot.scottbucher.dev/faq-1/custom-messages#what-are-the-different-types-of-custom-messages) (Message contents and design) + - There can be multiple custom messages, the bot will choose one random for each birthday/anniversary + - Customizable Birthday Role and/or Birthday/Anniversary Channels + - Custom Birthday/Anniversary Message Time + - Customizable [Trusted Role System](https://birthdaybot.scottbucher.dev/faq-1/birthday-system/trusted-system) (Birthday System Only) - Using this system server owners decide whose birthdays are celebrated in their Discord. Additionally, server owners can toggle whether the Trusted Role is only required to receive the Birthday Role or Birthday Message, or both. + - Ban specific users from having their birthday celebrated using the birthday blacklist +- All birthdays are stored Globally. This means if you have multiple Discord servers that you own or are in with Birthday Bot, your users will only have to input their birthday and time zone once. +- View upcoming birthdays/anniversaries. +- Clear your information from the database at any time. +- Near 24/7 up-time! + +## [Premium Features](https://birthdaybot.scottbucher.dev/premium-features): + +- Avoid having to vote to use some commands. +- Setup [Member Anniversary Roles](https://birthdaybot.scottbucher.dev/faq-1/member-anniversaries#what-are-anniversary-roles) to celebrate how long members have been in your discord + - For example, the **5-Year Veteran** role can be set to be given on a member's 5 Year anniversary in your discord. +- Setup Multiple [Trusted Roles](https://birthdaybot.scottbucher.dev/faq-1/birthday-system/trusted-system#do-i-need-to-set-up-the-trusted-role) + - Use the [**RequireAllTrustedRoles**](https://birthdaybot.scottbucher.dev/faq-1/birthday-system/trusted-system#what-is-the-require-all-trusted-role-setting) setting to decide if users need all trusted roles or just one to have their birthday celebrated. +- More control over the Custom Message(s). + - Up to **500** custom birthday messages (_vs_ **_3_** _for free_). + - Up to **500** custom member anniversary messages (_vs_ **_3_** _for free_). + - Up to **500** custom server anniversary messages (_vs_ **_1_** _for free_). + - Decide what [color each custom message](https://birthdaybot.scottbucher.dev/faq-1/custom-messages#what-is-a-message-embed-color) is (You can have a different color for each!) + - Decide which messages are [embedded](https://birthdaybot.scottbucher.dev/faq-1/custom-messages#what-is-a-message-embed-setting) (Color setting only applies to messages that are embedded) + - Set [user-specific custom messages](https://birthdaybot.scottbucher.dev/premium-features#user-specific-custom-messages) + - One custom message per user + - Unlimited user-specific messages per server +- Support Development! + - Since I started Birthday Bot I have maintained development and server cost completely out of my own expense, Birthday Bot Premium allows me to continue to maintain development as Birthday Bot continues to gain tens of thousands of servers each month. + - Subscriptions to Birthday Bot Premium go straight to server costs. + +### Support & Troubleshooting + +Birthday Bot is a part of the Arilyn Bot family and any support, questions or feedback are welcome in our support [Discord](https://discord.com/invite/9gUQFtz). Please visit our [FAQ](https://birthdaybot.scottbucher.dev/faq) and setup guide, as well as previous user questions before contacting staff, thanks! + **Example Birthday Announcement (Fully customizable):** ![Example Birthday Announcement](https://i.imgur.com/BZcEJ5j.png) -In your Discord server Birthday Bot will track your users' birthdays and using their time zone celebrate their birthday through its customizable birthday role and message. Even if you have a large discord, use Birthday Bots trusted-role system to only celebrate the users you want to avoid the spam of tracking everyone. +In your Discord server Birthday Bot will track your users' birthdays and using their time zone celebrate their birthday through its customizable birthday role and message. Even if you have a large discord, use Birthday Bots trusted-role system to only celebrate the users you want to avoid the spam of tracking everyone. Similar messages can also be setup to celebrate the anniversary of members joining your discord server and the anniversary of the server itself! ## Setting your birthday @@ -50,7 +97,7 @@ Finally, the confirmation menu will appear. Ensure this is the correct information, then confirm by clicking the checkmark. Note: Each user only has a limited amount of Birthday Sets. These multiple sets are made to account for incorrect information input, time zone changes, etc. -Learn more [here](https://birthdaybot.scottbucher.dev/faq#how-many-times-can-i-set-my-birthday). +Learn more [here](https://birthdaybot.scottbucher.dev/faq-1/user#how-many-times-can-i-set-my-birthday). ![End](https://i.imgur.com/2F8u3Cw.png) @@ -64,78 +111,6 @@ Simply click your location on the map and copy the name of the selected time zon ![Setting your time zone](https://i.imgur.com/ibPmjNs.png) -## Commands - -The following are commands for BirthdayBot. To run a command, prefix the command with `bday`, for example, `bday help` or `@BirthdayBot help`. - -### Utilities - -- `premium` - View information about birthday bot premium. -- `subscribe` - Subscribe to birthday bot premium. -- `set` - Set your birthday. -- `purge` - Remove your birthday data. -- `next` - View the next birthday(s) in the server. -- `view [name]` - View your birthday or a user's birthday. -- `invite` - Invite Birthday Bot to a server. -- `support` - Join the support server. -- `map` - View the timezone map. - -### Information - -- `help` - Help with Birthday Bot -- `help setup` - Help for server setup. -- `help setup message` - Help for the birthday message settings. -- `help setup trusted` - Help for the trusted system. -- `settings` - View server's settings. - -### Server Configuration (Admins only) - -#### Required Settings - -- `setup` - Interactive guide for server setup. -- `create ` - Create the default birthday role/channel. -- `update <#channel/@role>` - Update the birthday role/channel. -- `clear ` - Clear the birthday channel/role - -#### Birthday Message Settings - -- `setup message` - Interactive guide for message settings setup. -- `message list [page]` - List all custom birthday messages. -- `message add ` - Add a custom birthday message. -- `message remove ` - Remove a certain birthday message. -- `message clear` - Clear all custom birthday messages. -- `message time <0-23>` - Set the birthday message time. -- `message mention ` - Set the birthday message mention setting. -- `message embed ` - Should the birthday message be embedded. -- `message test [user count]` - Test a birthday message. - -#### Trusted System Settings - -- `setup trusted` - Interactive guide for trusted system settings setup. -- `create trustedRole` - Create the default trusted role. -- `updated trustedRole ` - Update the trusted role. -- `clear trustedRole` - Clear the trusted role. -- `trusted preventMsg ` - If trusted role is required for a birthday message. -- `trusted preventRole ` - If trusted role is required to get the birthday role. - -#### Permission Settings - -- `create birthdayMasterRole` - Create the default birthday master role. -- `updated birthdayMasterRole ` - Update the birthday master role. -- `clear birthdayMasterRole` - Clear the trusted role. -- `blacklist add ` - Add a user to the birthday blacklist. -- `blacklist remove ` - Remove a user from the birthday blacklist. -- `blacklist clear` - Clear the birthday blacklist. -- `blacklist list` - View the birthday blacklist list. - -### Premium Commands - -- `premium` - View information about birthday bot premium. -- `message add ` - Add a user specific birthday message. User as the placeholder. Full example usage: bday message add @Scott Happy Birthday ! -- `message remove ` - Remove a certain birthday message. -- `message remove ` - Remove a certain birthday message. -- `message color [color]` - Set the birthday message embed color. - ## Help For additional help join the support server [here](https://discord.gg/9gUQFtz). diff --git a/config/config.example.json b/config/config.example.json index c6975bd2..0ad34d96 100644 --- a/config/config.example.json +++ b/config/config.example.json @@ -11,12 +11,15 @@ "DIRECT_MESSAGES", "DIRECT_MESSAGE_REACTIONS" ], - "partials": ["MESSAGE", "REACTION"], + "partials": [ + "MESSAGE", + "REACTION" + ], "caches": { "messages": { "size": 0, "lifetime": 0, - "sweepInterval": 1 + "sweepInterval": 0 } } }, @@ -33,7 +36,10 @@ "secret": "00000000-0000-0000-0000-000000000000" }, "support": { - "owners": ["478288246858711040", "212772875793334272"], + "owners": [ + "478288246858711040", + "212772875793334272" + ], "server": "660711235766976553", "role": "660746189419053057" }, @@ -52,17 +58,27 @@ "schedule": "0 */5 * * * *", "log": false }, - "postBirthdays": { + "postCelebrationJob": { "schedule": "0 0 * * * *", "log": false, "interval": 0.6 } }, + "delays": { + "enabled": true, + "messages": 50, + "roles": 100 + }, "experience": { "promptExpireTime": 120, + "fastForwardAmount": "5", + "fastRewindAmount": "5", "birthdayListSize": "30", + "memberAnniversaryListSize": "30", "blacklistSize": "30", - "birthdayMessageListSize": "3" + "birthdayMessageListSize": "3", + "trustedRoleListSize": "10", + "memberAnniversaryRoleListSize": "10" }, "colors": { "default": "#4eefff", @@ -74,9 +90,11 @@ "emotes": { "confirm": "✅", "deny": "❌", + "fastReverse": "⏪", "nextPage": "➡️", - "previousPage": "⬅️", "jumpToPage": "↗️", + "previousPage": "⬅️", + "fastForward": "⏩", "create": "🔨", "select": "🖱", "birthday": "🎂", @@ -93,13 +111,41 @@ }, "custom": "✏" }, - "stopCommands": ["stop", "cancel", "exit", "close", "quit"], + "stopCommands": [ + "stop", + "cancel", + "exit", + "close", + "quit" + ], "validation": { "message": { "maxLength": 500, "maxCount": { - "free": 10, - "paid": 500 + "birthday": { + "free": 5, + "paid": 500 + }, + "memberAnniversary": { + "free": 5, + "paid": 500 + }, + "serverAnniversary": { + "free": 5, + "paid": 500 + } + } + }, + "trustedRoles": { + "maxCount": { + "free": 1, + "paid": 250 + } + }, + "memberAnniversaryRoles": { + "maxCount": { + "free": 0, + "paid": 250 } }, "regions": [ @@ -119,7 +165,7 @@ "spawnDelay": 5, "spawnTimeout": 300, "serversPerShard": 1000, - "largeBotSharding": true + "largeBotSharding": false }, "clustering": { "enabled": false, @@ -157,4 +203,4 @@ "token": "", "allowNewTransactions": true } -} +} \ No newline at end of file diff --git a/config/debug.example.json b/config/debug.example.json index 79138ae5..c7b9ea1f 100644 --- a/config/debug.example.json +++ b/config/debug.example.json @@ -1,10 +1,16 @@ { "alwaysSendBirthdayMessage": false, "alwaysGiveBirthdayRole": false, + "alwaysMemberAnniversary": false, + "alwaysServerAnniversary": false, "override": { "shardMode": { "enabled": false, "value": "process" } + }, + "dummyMode": { + "enabled": false, + "whitelist": ["212772875793334272", "478288246858711040"] } -} \ No newline at end of file +} diff --git a/lang/lang.en-US.json b/lang/lang.en-US.json new file mode 100644 index 00000000..fb2249a3 --- /dev/null +++ b/lang/lang.en-US.json @@ -0,0 +1,2844 @@ +{ + "defaultEmbed": { + "color": "#4eefff" + }, + "embeds": { + "serverPrompts": { + "inputChannel": { + "description": "Please mention a channel or input a channel's name." + }, + "inputRole": { + "description": "Please mention a role or input a role's name." + }, + "blacklistClearConfirmation": { + "description": "Are you sure you want to clear __**{{TOTAL}}**__ blacklisted user(s)?", + "footer": { + "text": "{{REF:footers.irreversible}}", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.warn}}" + }, + "trustedRoleClearConfirmation": { + "description": "Are you sure you want to clear __**{{TOTAL}}**__ trusted role(s)?", + "footer": { + "text": "{{REF:footers.irreversible}}", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.warn}}" + }, + "memberAnniversaryRoleClearConfirmation": { + "description": "Are you sure you want to clear __**{{TOTAL}}**__ member anniversary role(s)?", + "footer": { + "text": "{{REF:footers.irreversible}}", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.warn}}" + }, + "requiredSetupBirthdayChannel": { + "title": "Server Setup - Birthday Channel", + "description": [ + "For help, view the required setup guide [here]({{REF:docLinks.setupRequiredGuide}})!", + "", + "To begin you must set up the birthday channel [(?)]({{REF:docLinks.whatIsBirthdayChannel}})", + "", + "Please select an option" + ], + "fields": [ + { + "name": [ + "Create New Channel {{REF:emotes.create}}", + "Select Pre-Existing Channel {{REF:emotes.select}}", + "No Birthday Channel {{REF:emotes.deny}}" + ], + "value": "\u200b" + } + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "requiredSetupBirthdayRole": { + "title": "Server Setup - Birthday Role", + "description": [ + "Now, set up the birthday role [(?)]({{REF:docLinks.whatIsBirthdayRole}})", + "", + "Note: The Birthday Role is actively removed from those whose birthday it isn't.", + "", + "Please select an option" + ], + "fields": [ + { + "name": [ + "Create New Role {{REF:emotes.create}}", + "Select Pre-Existing Role {{REF:emotes.select}}", + "No Birthday Role {{REF:emotes.deny}}" + ], + "value": "\u200b" + } + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "trustedSetupPreventsMessage": { + "title": "Trusted Setup - Trusted Prevents Message", + "description": [ + "Should the trusted role prevent the birthday message? [(?)]({{REF:docLinks.trustedPreventsMessage)", + "", + "True: {{REF:emotes.confirm}}", + "False: {{REF:emotes.deny}}" + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "trustedSetupPreventsRole": { + "title": "Trusted Setup - Trusted Prevents Role", + "description": [ + "Should the trusted role prevent the birthday role? [(?)]({{REF:docLinks.trustedPreventsRole}})", + "", + "True: {{REF:emotes.confirm}}", + "False: {{REF:emotes.deny}}" + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "trustedSetupRequireAll": { + "title": "Trusted Setup - Require All Trusted Roles", + "description": [ + "With premium you can set multiple trusted roles. Because of this, you can choose if a user needs one or all of the trusted roles to have their birthday celebrated.", + "", + "Should birthdays require all trusted roles? [(?)]({{REF:docLinks.requireAllTrustedRoles}})", + "", + "True: {{REF:emotes.confirm}}", + "False: {{REF:emotes.deny}}" + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "anniversarySetupMemberChannel": { + "title": "Anniversary Setup - Member Anniversary Channel", + "description": [ + "For help, view the anniversary setup guide [here]({{REF:dockLinks.setupAnniversaryGuide}})!", + "", + "To begin you must set up the member anniversary channel [(?)]({{REF:dockLinks.whatIsMemberAnniversaryChannel}})", + "", + "Please select an option" + ], + "fields": [ + { + "name": [ + "Create New Channel {{REF:emotes.create}}", + "Select Pre-Existing Channel {{REF:emotes.select}}", + "No Birthday Channel {{REF:emotes.deny}}" + ], + "value": "\u200b" + } + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "anniversarySetupServerChannel": { + "title": "Anniversary Setup - Server Anniversary Channel", + "description": [ + "For help, view the anniversary setup guide [here]({{REF:dockLinks.setupAnniversaryGuide}})!", + "", + "To begin you must set up the server anniversary channel [(?)]({{REF:dockLinks.whatIsServerAnniversaryChannel}})", + "", + "Please select an option" + ], + "fields": [ + { + "name": [ + "Create New Channel {{REF:emotes.create}}", + "Select Pre-Existing Channel {{REF:emotes.select}}", + "No Birthday Channel {{REF:emotes.deny}}" + ], + "value": "\u200b" + } + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "customMessageColorSelection": { + "title": "Custom Message Color Selection", + "description": [ + "Please input the color you would like your message. [(?)]({{REF:docLinks.whatIsMessageEmbedColor}})", + "Both color names and hex values are accepted." + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "customMessageEmbedSelection": { + "title": "Custom Message - Embedded", + "description": [ + "Should this message be embedded? [(?)]({{REF:docLinks.whatIsAnEmbed}})", + "Hint: This message is embed! Non-embedded messages are just plain text.", + "", + "True {{REF:emotes.confirm}}", + "False {{REF:emotes.deny}}" + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "confirmClearMessages": { + "description": "Are you sure you want to clear __**{{MESSAGE_COUNT}}**__ {{DISPLAY_TYPE}}?", + "footer": { + "text": "This action is irreversible!", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.warn}}" + } + }, + "userPrompts": { + "birthdaySetupTimeZone": { + "author": { + "name": "{{TAG}}", + "icon": "{{AUTHOR_ICON}}", + "url": "{{REF:docLinks.setupBirthday}}" + }, + "title": "Time Zone Setup - {{TARGET}}", + "description": [ + "First, please type/paste your time zone below. [(?)]({{REF:docLinks.whyDoesItNeedMyTimezone}})", + "To find your time zone please use the [map time zone picker]({{REF:links.map}})!", + "", + "Simply click your location on the map and copy the name of the selected time zone.", + "", + "**Example Usage:** `America/New_York`", + "", + "**Important**: By submitting this information you agree it can be shown to anyone." + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "birthdaySetupBirthday": { + "author": { + "name": "{{TAG}}", + "icon": "{{AUTHOR_ICON}}", + "url": "{{REF:docLinks.setupBirthday}}" + }, + "title": "Birthday Setup - {{TARGET}}", + "description": [ + "Please type your birth month and day below. [(?)]({{REF:docLinks.whyDoesItNeedOnlyMonthAndDate}})", + "", + "**Example Usage:** `08/28` (MM/DD)", + "", + "**Important**: By submitting this information you agree it can be shown to anyone." + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "birthdayConfirmPurge": { + "title": "Clear User Data", + "description": [ + "This command will remove both your time zone and your birthday from the database. [(?)]({{REF:docLinks.whyDoesItNeedMyTimezone}})", + "", + "This will **NOT** reset your birthday attempts. (You have {{CHANGES_LEFT}} left) [(?)]({{REF:docLinks.whyDoesItNeedOnlyMonthAndDate}})", + "Birthdays are stored globally not by server! This will erase your birthday for all servers!", + "", + "{{APPEND}}" + ], + "fields": [ + { + "name": "Actions", + "value": ["{{REF:emotes.confirm}} Confirm", "{{REF:emotes.deny}} Cancel"] + } + ], + "footer": { + "text": "{{REF:footers.expire}}", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.warn}}", + "timestamp": true + }, + "defaultTimeZoneAvailable": { + "title": "Time Zone Setup - {{TARGET}}", + "description": [ + "This server has a default timezone of **{{SERVER_TIMEZONE}}**. Do you want to use the server's timezone?" + ], + "color": "{{REF:colors.success}}" + }, + "defaultTimeZoneAvailableOverride": { + "title": "Time Zone Setup - {{TARGET}}", + "description": [ + "This server has a default timezone of **{{SERVER_TIMEZONE}}**. Do you want to use **{{INPUTTED_TIMEZONE}}** instead?", + "", + "Clicking {{REF:emotes.confirm}} will set your timezone to **{{INPUTTED_TIMEZONE}}**", + "Clicking {{REF:emotes.deny}} will set your timezone **{{SERVER_TIMEZONE}}**" + ], + "color": "{{REF:colors.success}}" + }, + "inputPage": { + "description": "Please input the page you would like to jump to:" + }, + "confirmFirstBirthday": { + "description": "{{TARGET}}, please confirm this information is correct: **{{BIRTHDAY}}, {{TIMEZONE}}**", + "footer": { + "text": "You have {{CHANGES_LEFT}} attempts left. By clicking confirm you will use one of them.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.warn}}" + }, + "confirmFirstBirthdaySuggest": { + "description": "{{TARGET}}, please confirm this information is correct: **{{BIRTHDAY}}, {{TIMEZONE}}**", + "footer": { + "text": "{{TARGET_FOOTER}} has {{CHANGES_LEFT}} attempts left. By clicking confirm they will use one of them.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.warn}}" + }, + "confirmChangeBirthday": { + "description": "{{TARGET}}, your birthday is already set, please confirm this information is correct to change it: **{{BIRTHDAY}}, {{TIMEZONE}}**", + "footer": { + "text": "You have {{CHANGES_LEFT}} attempts left. By clicking confirm you will use one of them.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.warn}}" + }, + "confirmChangeBirthdaySuggest": { + "description": "{{TARGET}}, your birthday is already set, please confirm this information is correct to change it: **{{BIRTHDAY}}, {{TIMEZONE}}**", + "footer": { + "text": "{{TARGET_FOOTER}} has {{CHANGES_LEFT}} attempts left. By clicking confirm they will use one of them.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.warn}}" + } + }, + "results": { + "requiredSetup": { + "title": "Server Setup - Completed", + "description": [ + "You have successfully completed the required server setup!", + "", + "**Birthday Channel**: {{CHANNEL}}", + "**Birthday Role**: {{ROLE}}" + ], + "footer": { + "text": "All server commands unlocked!", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}", + "timestamp": true + }, + "birthdayTest": { + "title": "Birthday Event Test", + "description": [ + "Below are the checks to ensure your settings are correct for the birthday event." + ], + "footer": { + "text": "This is the info from your birthday event test.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}", + "timestamp": true + }, + "memberAnniversaryTest": { + "title": "Member Anniversary Event Test", + "description": [ + "Below are the checks to ensure your settings are correct for the member anniversary event." + ], + "footer": { + "text": "This is the info from your latest member anniversary event test.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}", + "timestamp": true + }, + "serverAnniversaryTest": { + "title": "Birthday Event Test", + "description": [ + "Below are the checks to ensure your settings are correct for the server anniversary event." + ], + "footer": { + "text": "This is the info from your latest server anniversary event test.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}", + "timestamp": true + }, + "trustedSetup": { + "title": "Trusted Setup - Completed", + "description": [ + "You have successfully completed the trusted server setup!", + "", + "**Trusted Prevents Role**: {{PREVENTS_ROLE}}", + "**Trusted Prevents Role**: {{PREVENTS_MESSAGE}}" + ], + "footer": { + "text": "Trusted Setup Complete!", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}", + "timestamp": true + }, + "trustedSetupPremium": { + "title": "Trusted Setup - Completed", + "description": [ + "You have successfully completed the trusted server setup!", + "", + "**Trusted Prevents Role**: {{PREVENTS_ROLE}}", + "**Trusted Prevents Role**: {{PREVENTS_MESSAGE}}", + "**Require All Trusted Roles**: {{REQUIRE_ALL_ROLES}}" + ], + "footer": { + "text": "Trusted Setup Complete!", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}", + "timestamp": true + }, + "anniversarySetup": { + "title": "Anniversary Setup - Completed", + "description": [ + "You have successfully completed the required server setup!", + "", + "**Member Anniversary Channel**: {{MEMBER_CHANNEL}}", + "**Server Anniversary Channel**: {{SERVER_CHANNEL}}" + ], + "footer": { + "text": "Anniversary Setup Complete!", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}", + "timestamp": true + }, + "setBirthday": { + "description": [ + "Successfully set your birthday to **{{BIRTHDAY}}, {{TIMEZONE}}**", + "", + "**Important Note:** Birthdays are stored globally. This means your birthday is now set for all servers with Birthday Bot!" + ], + "footer": { + "text": "You now have {{AMOUNT}} birthday attempts left.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}" + }, + "setAttempts": { + "description": "Successfully set {{USER}}'s birthday attempts tp {{AMOUNT}}!", + "color": "{{REF:colors.success}}" + }, + "purgeSuccessful": { + "description": "Successfully purged your data from the database.", + "color": "{{REF:colors.success}}" + }, + "nextBirthday": { + "description": "{{USERS}}'s birthday is next on **{{BIRTHDAY}}**!", + "color": "{{REF:colors.default}}" + }, + "nextMemberAnniversary": { + "description": "{{USERS}}'s member anniversary is next on **{{DATE}}**!", + "color": "{{REF:colors.default}}" + }, + "nextServerAnniversary": { + "description": "**{{SERVER}}**'s anniversary is on **{{DATE}}**. It will be turning **{{YEARS}}**!", + "color": "{{REF:colors.default}}" + }, + "memberAnniversaryRolesClaimed": { + "description": "Successfully claimed all member anniversary roles available to you/", + "footer": { + "text": "If some roles were not given Birthday Bot may not have permission to give them.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}" + }, + "removeMessage": { + "title": "Remove Custom Message", + "description": "{{MESSAGE}}", + "footer": { + "text": "{{REF:emotes.confirm}} Message Removed", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}" + }, + "promptExpired": { + "description": "This command has expired.", + "color": "{{REF:colors.error}}" + }, + "blacklistClearSuccess": { + "description": "Successfully cleared the birthday blacklist!", + "color": "{{REF:colors.success}}" + }, + "blacklistAddSuccess": { + "description": "Successfully removed {{TARGET}} from the birthday blacklist!", + "color": "{{REF:colors.success}}" + }, + "blacklistRemoveSuccess": { + "description": "Successfully added {{TARGET}} to the birthday blacklist!", + "color": "{{REF:colors.success}}" + }, + "masterRoleCreated": { + "description": "Successfully created the birthday master role {{ROLE}}!", + "color": "{{REF:colors.success}}" + }, + "masterRoleSet": { + "description": "Successfully set the birthday master role to {{ROLE}}!", + "color": "{{REF:colors.success}}" + }, + "masterRoleCleared": { + "description": "Successfully cleared the birthday master role!", + "color": "{{REF:colors.success}}" + }, + "birthdayRoleCreated": { + "description": "Successfully created the master role {{ROLE}}!", + "color": "{{REF:colors.success}}" + }, + "birthdayRoleSet": { + "description": "Successfully set the birthday role to {{ROLE}}!", + "color": "{{REF:colors.success}}" + }, + "customMessagesCleared": { + "description": "Successfully cleared all {{DISPLAY_TYPE}} from the database!", + "color": "{{REF:colors.success}}" + }, + "birthdayRoleCleared": { + "description": "Successfully cleared the birthday role!", + "color": "{{REF:colors.success}}" + }, + "birthdayChannelCreated": { + "description": "Successfully created the birthday channel {{CHANNEL}}!", + "color": "{{REF:colors.success}}" + }, + "birthdayChannelCleared": { + "description": "Successfully cleared the birthday channel!", + "color": "{{REF:colors.success}}" + }, + "birthdayChannelSet": { + "description": "Successfully set the birthday channel to {{CHANNEL}}", + "color": "{{REF:colors.success}}" + }, + "memberAnniversaryChannelCreated": { + "description": "Successfully created the member anniversary channel {{CHANNEL}}!", + "color": "{{REF:colors.success}}" + }, + "memberAnniversaryChannelCleared": { + "description": "Successfully cleared the member anniversary channel!", + "color": "{{REF:colors.success}}" + }, + "memberAnniversaryChannelSet": { + "description": "Successfully set the member anniversary channel to {{CHANNEL}}", + "color": "{{REF:colors.success}}" + }, + "serverAnniversaryChannelCreated": { + "description": "Successfully created the server anniversary channel {{CHANNEL}}!", + "color": "{{REF:colors.success}}" + }, + "serverAnniversaryChannelCleared": { + "description": "Successfully cleared the server anniversary channel!", + "color": "{{REF:colors.success}}" + }, + "serverAnniversaryChannelSet": { + "description": "Successfully set the server anniversary channel to {{CHANNEL}}", + "color": "{{REF:colors.success}}" + }, + "nameFormatSet": { + "description": [ + "Successfully updated your name format setting to `{{SETTING}}`!", + "Name will now appear in this format: **{{FORMAT}}**" + ], + "color": "{{REF:colors.success}}" + }, + "defaultTimeZoneSet": { + "description": "Successfully set your server's default timezone to **{{TIMEZONE}}**!", + "color": "{{REF:colors.success}}" + }, + "defaultTimeCleared": { + "description": "Successfully cleared your server's default timezone!", + "color": "{{REF:colors.success}}" + }, + "useTimeZoneSettingSet": { + "description": "Birthdays will now be celebrated based on the {{OPTION}}'s timezone!", + "color": "{{REF:colors.success}}" + }, + "addedTrustedRole": { + "description": "Successfully added {{ROLE}} as a trusted role!", + "color": "{{REF:colors.success}}" + }, + "addedMemberAnniversaryRole": { + "description": "Successfully added {{ROLE}} as a Year {{YEAR}} member anniversary reward!", + "color": "{{REF:colors.success}}" + }, + "clearedTrustedRole": { + "description": "Successfully cleared the trusted roles!", + "color": "{{REF:colors.success}}" + }, + "clearedMemberAnniversaryRole": { + "description": "Successfully cleared the member anniversary roles!", + "color": "{{REF:colors.success}}" + }, + "removedTrustedRole": { + "description": "Successfully removed the trusted role {{ROLE}}!", + "color": "{{REF:colors.success}}" + }, + "removedMemberAnniversaryRole": { + "description": "Successfully removed the member anniversary role {{ROLE}}!", + "color": "{{REF:colors.success}}" + }, + "trustedPreventsMessageYes": { + "description": "Trusted Role is now required for the birthday message!", + "color": "{{REF:colors.success}}" + }, + "trustedPreventsMessageNo": { + "description": "Trusted Role is now not required for the birthday message!", + "color": "{{REF:colors.success}}" + }, + "trustedPreventsRoleYes": { + "description": "Trusted Role is now required for the birthday role!", + "color": "{{REF:colors.success}}" + }, + "trustedPreventsRoleNo": { + "description": "Trusted Role is now not required for the birthday role!", + "color": "{{REF:colors.success}}" + }, + "requireAllTrustedYes": { + "description": "To have their birthday celebrated users now require all the trusted roles!", + "color": "{{REF:colors.success}}" + }, + "requireAllTrustedNo": { + "description": "To have their birthday celebrated users now require only one of the trusted roles!", + "color": "{{REF:colors.success}}" + }, + "setMessageMention": { + "description": "{{BOT}} will now mention {{MENTION}} with the {{DISPLAY_TYPE}}!", + "color": "{{REF:colors.success}}" + }, + "setMessageTime": { + "description": "Successfully set the {{DISPLAY_TYPE}} to send at **{{TIME}}**", + "color": "{{REF:colors.success}}" + }, + "viewBirthday": { + "description": "Your birthday is on **{{BIRTHDAY}}, {{TIMEZONE}}**!", + "footer": { + "text": "You currently have {{CHANGES_LEFT}} birthday attempt(s) left.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.default}}" + }, + "viewUserBirthday": { + "description": "{{USER}}'s birthday is on **{{BIRTHDAY}}, {{TIMEZONE}}**!", + "color": "{{REF:colors.default}}" + }, + "viewMemberAnniversary": { + "description": "Your member anniversary is on **{{DATE}}**!", + "color": "{{REF:colors.default}}" + }, + "viewUserMemberAnniversary": { + "description": "{{USER}}'s member anniversary is on **{{DATE}}**!", + "color": "{{REF:colors.default}}" + }, + "addCustomMessage": { + "title": "Add Custom Message", + "description": [ + "Successfully added the {{DISPLAY_TYPE}} message:", + "", + "{{MESSAGE}}", + "", + "{{HAS_PREMIUM}}", + "Embedded: `{{IS_EMBED}}`" + ], + "fields": [ + { + "name": "Actions", + "value": [ + "`{{REF:bot.prefix}} message {{REF:types.list}} {{TYPE}} [page]` - List all custom {{DISPLAY_TYPE}} messages.", + "`{{REF:bot.prefix}} message {{REF:types.test}} {{TYPE}} ` - Test a {{DISPLAY_TYPE}} message." + ] + } + ], + "footer": { + "text": "{{REF:emotes.confirm}} Message added.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}" + }, + "addCustomUserMessage": { + "title": "Add Custom Message", + "description": [ + "Successfully added the user {{DISPLAY_TYPE}} message for {{USER}}:", + "", + "{{MESSAGE}}", + "", + "{{HAS_PREMIUM}}", + "Embedded: `{{IS_EMBED}}`" + ], + "fields": [ + { + "name": "Actions", + "value": [ + "`{{REF:bot.prefix}} message {{REF:types.list}} {{TYPE}} [page]` - List all custom user specific {{DISPLAY_TYPE}} messages.", + "`{{REF:bot.prefix}} message {{REF:types.test}} {{TYPE}} ` - Test a user specific {{DISPLAY_TYPE}} message." + ] + } + ], + "footer": { + "text": "{{REF:emotes.confirm}} Message added.", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.success}}" + }, + "actionCanceled": { + "description": "{{REF:emotes.deny}} Action canceled.", + "color": "{{REF:colors.error}}" + }, + "actionFailed": { + "title": "{{TITLE}}", + "description": "{{REF:emotes.deny}} Action failed.", + "color": "{{REF:colors.error}}" + } + }, + "validation": { + "voteRequired": { + "title": "Vote Required!", + "thumbnail": "{{REF:links.voteImage}}", + "description": ["This command requires you to have voted in the past 24 hours!"], + "fields": [ + { + "name": "Last Vote", + "value": ["{{LAST_VOTE}}"], + "inline": true + }, + { + "name": "Vote Here", + "value": ["[Top.gg]({{REF:links.vote}})"], + "inline": true + } + ], + "footer": { + "text": "Don't want to vote? Disable voting for the whole server with {{REF:bot.name}} Premium!", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "notEnoughChannelPerms": { + "title": "Not Enough Permissions!", + "description": [ + "{{REF:bot.name}} needs more permissions in {{CHANNEL}}!", + "", + "__Permissions Needed__", + "View Channel", + "Send Messages", + "Embed Links", + "Add Reactions", + "", + "Note: These permissions are needed both in the server and in {{CHANNEL}}!" + ], + "footer": { + "text": "{{REF:footers.joinSupport}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "noPermToEdit": { + "description": "I do not have permission to edit that message!", + "color": "{{REF:colors.error}}" + }, + "setupRequired": { + "title": "Setup Required!", + "description": "You must run `{{REF:bot.prefix}} setup` before using this command!", + "color": "{{REF:colors.error}}" + }, + "noPermission": { + "title": "Permission Required!", + "description": "You don't have permission to run that command!", + "color": "{{REF:colors.error}}" + }, + "onlyStaff": { + "description": "This command can only be run by {{REF:bot.name}} Staff!", + "color": "{{REF:colors.error}}" + }, + "guildOnlyCommand": { + "description": "This command can only be used in a discord server!", + "color": "{{REF:colors.error}}" + }, + "noArgs": { + "title": "Invalid Usage!", + "description": ["Please specify an option!", "{{OPTIONS}}"], + "color": "{{REF:colors.error}}" + }, + "invalidNextArgs": { + "title": "Invalid Usage!", + "description": [ + "Please specify which you would like to view!", + "Next options: `{{REF:types.birthday}}`, `{{REF:types.memberAnniversary}}` or `{{REF:types.serverAnniversary}}`" + ], + "color": "{{REF:colors.error}}" + }, + "invalidListArgs": { + "title": "Invalid Usage!", + "description": [ + "Please specify which list you would like to view!", + "List options: `{{REF:types.birthday}}` or `{{REF:types.memberAnniversary}}`" + ], + "color": "{{REF:colors.error}}" + }, + "invalidViewArgs": { + "title": "Invalid Usage!", + "description": [ + "Please specify which type you would like to view!", + "View options: `{{REF:types.birthday}}` or `{{REF:types.memberAnniversary}}`" + ], + "color": "{{REF:colors.error}}" + }, + "invalidSetupArgs": { + "title": "Invalid Usage!", + "description": [ + "Please specify which setup you want to run!", + "Setup options: `anniversary` or `trusted`" + ], + "color": "{{REF:colors.error}}" + }, + "serverTimezoneNotSet": { + "description": "This server doesn't have a default timezone set! The anniversary system requires one to be set!", + "color": "{{REF:colors.error}}" + }, + "viewUserInDm": { + "description": "You cannot request another user's information in a DM!", + "color": "{{REF:colors.error}}" + }, + "birthdayNotSet": { + "description": "Your birthday isn't set!", + "color": "{{REF:colors.error}}" + }, + "userBirthdayNotSet": { + "description": ["{{USER}} hasn't set their birthday!"], + "color": "{{REF:colors.error}}" + }, + "noBlacklistArgs": { + "title": "Invalid Usage!", + "description": [ + "Please specify a sub command for the blacklist! [(?)]({{REF:docLinks.whatIsBirthdayBlacklist}})", + "Accepted Values: `{{REF:types.list}}`, `{{REF:types.add}} `, `{{REF:types.remove}} `, `{{REF:types.clear}}`" + ], + "color": "{{REF:colors.error}}" + }, + "noTrustedRoleArgs": { + "title": "Invalid Usage!", + "description": [ + "Please specify a sub command! [(?)]({{REF:docLinks.whatIsTrustedRole}})", + "Accepted Values: `{{REF:types.add}}`, `{{REF:types.remove}}`, `{{REF:types.clear}}`, `{{REF:types.list}}`, `{{REF:types.claim}}`" + ], + "color": "{{REF:colors.error}}" + }, + "noConfigArgs": { + "title": "Invalid Usage!", + "description": [ + "Please specify a config value to change!", + "Accepted Values: `{{REF:types.channel}}`, `{{REF:types.birthdayRole}}`, `{{REF:types.birthdayMasterRole}}`, `{{REF:types.nameFormat}}`, `{{REF:types.timezone}}`, `{{REF:types.useTimezone}}`, `{{REF:types.trustedPreventsMessage}}`, `{{REF:types.trustedPreventsRole}}`, `{{REF:types.requireAllTrustedRoles}}`" + ], + "color": "{{REF:colors.error}}" + }, + "noMemberAnniversaryRoleArgs": { + "title": "Invalid Usage!", + "description": [ + "Please specify a sub command!", + "Accepted Values: `{{REF:types.add}}`, `{{REF:types.remove}}`, `{{REF:types.clear}}`, `{{REF:types.list}}`" + ], + "color": "{{REF:colors.error}}" + }, + "noCustomMessageArgs": { + "title": "Invalid Usage!", + "description": [ + "Please specify a sub command!", + "Accepted Values: `{{REF:types.list}} `, `{{REF:types.add}} `, `{{REF:types.remove}} <#>`, `{{REF:types.clear}}`, `{{REF:types.time}} <0-23>`, and `{{REF:types.mention}} `" + ], + "color": "{{REF:colors.error}}" + }, + "roleHierarchyError": { + "title": "Role Hierarchy Error!", + "description": [ + "The birthday role must be below {{BOT}}'s role.", + "Additional Note: {{BOT}}'s role must be higher than the users it is assigning the birthday role to.", + "", + "Example Role Hierarchy:", + "`{{REF:bot.name}}'s Role`", + "`Birthday Role`", + "`Birthday User's Highest Role`", + "", + "Essentially the bot is unable to give a role to someone with a higher role than them or give a role that is higher than the bot's." + ], + "footer": { + "text": "{{REF:footers.joinSupport}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "noBirthdaysInServer": { + "description": "No one has set their birthday in this server! :(", + "color": "{{REF:colors.error}}" + }, + "noUpcomingBirthdays": { + "description": "There are no upcoming birthdays!", + "color": "{{REF:colors.error}}" + }, + "outOfAttempts": { + "description": "You are out of birthday attempts!", + "color": "{{REF:colors.error}}" + }, + "noMemberAnniversaryRolesToClaim": { + "description": "You aren't eligible for any Member Anniversary Roles!", + "color": "{{REF:colors.error}}" + }, + "invalidUsageMemberAnniversaryRoleAdd": { + "title": "Invalid Usage!", + "description": [ + "Please provide a year and a role!", + "Example Usage: `bday memberAnniversaryRole {{REF:types.add}} 5 @Role`" + ], + "color": "{{REF:colors.error}}" + }, + "needReactAndMessageHistoryPerms": { + "title": "Missing Permissions!", + "description": "I need permission to **Add Reactions** and **Read Message History** in this channel!", + "color": "{{REF:colors.error}}" + }, + "needManageChannelsAndRolesPerm": { + "title": "Missing Permissions!", + "description": "I need permission to **Manage Channels** and **Manage Roles**!", + "color": "{{REF:colors.error}}" + }, + "needManageRolesPerm": { + "title": "Missing Permissions!", + "description": "I need permission to **Manage Roles**!", + "color": "{{REF:colors.error}}" + }, + "memberNeedsMessageHistory": { + "description": "{{MEMBER}} needs the `READ_MESSAGE_HISTORY` permission in this channel!", + "color": "{{REF:colors.error}}" + }, + "noAccessToChannel": { + "description": [ + "I don't have permission to send messages in {{CHANNEL}}!", + "Please allow me to **View Channel**, **Send Messages**, **Embed Links**, and **Add Reactions** in {{CHANNEL}}." + ], + "color": "{{REF:colors.error}}" + }, + "cantSuggest": { + "description": "You do not have permission to suggest birthdays for other users!", + "color": "{{REF:colors.error}}" + }, + "cantSetBirthdayForBot": { + "description": "You can't set a birthday for a bot!", + "color": "{{REF:colors.error}}" + }, + "invalidBirthday": { + "title": "Birthday Setup - {{TARGET}}", + "description": "Invalid birthday!", + "color": "{{REF:colors.error}}" + }, + "invalidTimezone": { + "title": "Time Zone Setup - {{TARGET}}", + "description": [ + "Invalid time zone!", + "To find your time zone please use the [map time zone picker]({{REF:links.map}})!" + ], + "footer": { + "text": "{{REF:footers.tryAgain}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidTimeZoneAbbreviation": { + "title": "Time Zone Setup - {{TARGET}}", + "description": [ + "Invalid time zone! Do not use timezone abbreviations!", + "To find your time zone please use the [map time zone picker]({{REF:links.map}})!" + ], + "footer": { + "text": "{{REF:footers.tryAgain}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "addMessageInvalidType": { + "title": "Add Custom Message", + "description": "Please specify a message type! Accepted Values: `birthday`, `memberAnniversary`, `serverAnniversary`", + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "removeMessageInvalidType": { + "title": "Remove Custom Message", + "description": "Please specify a message type! Accepted Values: `birthday`, `memberAnniversary`, `serverAnniversary`, `userSpecificBirthday`, `userSpecificMemberAnniversary`", + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "clearMessageInvalidType": { + "title": "Clear Custom Message", + "description": "Please specify a message type! Accepted Values: `birthday`, `memberAnniversary`, `serverAnniversary`, `userSpecificBirthday`, `userSpecificMemberAnniversary`", + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidMessageType": { + "title": "Custom Message List", + "description": "Please specify a message type! Accepted Values: `birthday`, `memberAnniversary`, `serverAnniversary`, `userSpecificBirthday`, `userSpecificMemberAnniversary`", + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidCelebrationType": { + "title": "Celebration Test", + "description": "Please specify a celebration type! Accepted Values: `birthday`, `memberAnniversary`, `serverAnniversary`", + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidChannelType": { + "title": "Channel Selection", + "description": "Please specify a channel type! Accepted Values: `birthday`, `memberAnniversary`, `serverAnniversary`", + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidMentionSetting": { + "title": "Invalid Group/Role", + "description": "Accepted Values: `everyone`, `here`, `@role/role-name`, `none`", + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "birthdayRoleUsedWarning": { + "title": "Warning", + "description": "We have detected that __**{{AMOUNT}}**__ user{{S_VALUE}} already have that role!\nThe Birthday Role should ONLY be the role that users GET on their birthday!", + "footer": { + "text": "The Bot removes the Birthday Role from users whose birthday it isn't!", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.warn}}" + }, + "birthdayRoleUsedError": { + "title": "Error", + "description": "We have detected that __**{{AMOUNT}}**__ users already have that role!\nThe Birthday Role should ONLY be the role that users GET on their birthday!", + "footer": { + "text": "The Bot removes the Birthday Role from users whose birthday it isn't!", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidChannelAction": { + "title": "Channel Selection", + "description": [ + "Please specify an option!", + "", + "`{{REF:bot.prefix}} config {{REF:types.channel}} {{REF:types.create}}` - Creates the default channel of that type.", + "`{{REF:bot.prefix}} config {{REF:types.channel}} {{REF:types.clear}}` - Clears the channel of that type.", + "`{{REF:bot.prefix}} config {{REF:types.channel}} #channel` - Set the channel of that type." + ], + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidNameFormat": { + "title": "Invalid Name Format", + "description": [ + "Accepted Values", + "`mention (default)`: **{{MENTION}}**", + "`username`: **{{USERNAME}}**", + "`nickname`: **{{NICKNAME}}**", + "`tag:` **{{TAG}}**" + ], + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidMasterAction": { + "title": "Birthday Master Role", + "description": [ + "Please specify an option!", + "", + "`{{REF:bot.prefix}} config {{REF:types.birthdayMasterRole}} {{REF:types.create}}` - Creates the birthday master role.", + "`{{REF:bot.prefix}} config {{REF:types.birthdayMasterRole}} {{REF:types.clear}}` - Clears the birthday master role.", + "`{{REF:bot.prefix}} config {{REF:types.birthdayMasterRole}} @role` - Set the birthday master role." + ], + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidBirthdayRoleAction": { + "title": "Birthday Role", + "description": [ + "Please specify an option!", + "", + "`{{REF:bot.prefix}} config {{REF:types.birthdayRole}} {{REF:types.create}}` - Creates the default birthday role.", + "`{{REF:bot.prefix}} config {{REF:types.birthdayRole}} {{REF:types.clear}}` - Clears the birthday role.", + "`{{REF:bot.prefix}} config {{REF:types.birthdayRole}} @role` - Set the birthday role." + ], + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidUseTimezoneAction": { + "title": "Use Time Zone Setting", + "description": [ + "Please select an option:", + "`server` - Celebrate birthdays based on the server's timezone.", + "`user` - Celebrate birthdays based on the user's timezone." + ], + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidServerTimeZone": { + "title": "Default Server Timezone Selection", + "description": [ + "Invalid time zone!", + "To find your time zone please use the [map time zone picker]({{REF:links.map}})!" + ], + "footer": { + "text": "{{REF:footers.tryAgain}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidServerTimeZoneAbbreviation": { + "title": "Default Server Timezone Selection", + "description": [ + "Invalid time zone! Do not use timezone abbreviations!", + "To find your time zone please use the [map time zone picker]({{REF:links.map}})!" + ], + "footer": { + "text": "{{REF:footers.tryAgain}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidTrueFalseTrustedPreventsRole": { + "title": "Trusted Role Prevents Role", + "description": "Please provide a value! (True/False)", + "footer": { + "text": "{{REF:footers.tryAgain}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidTrueFalseTrustedPreventsMessage": { + "title": "Trusted Role Prevents Message", + "description": "Please provide a value! (True/False)", + "footer": { + "text": "{{REF:footers.tryAgain}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidTrueFalseRequireAllTrustedMessage": { + "title": "Require All Trusted Roles", + "description": "Please provide a value! (True/False)", + "footer": { + "text": "{{REF:footers.tryAgain}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "maxFreeTrustedRoles": { + "description": "Your server is limited to __**{{FREE_MAX}}**__ trusted role!", + "footer": { + "text": "To have up to {{PAID_MAX}} trusted roles get {{REF:bot.name}} Premium!", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "trustedRoleNoRoleOrPosition": { + "title": "Trusted Role Remove", + "description": "Please specify a position or role to remove!", + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "anniversaryRoleNoRoleOrPosition": { + "title": "Member Anniversary Role Remove", + "description": "Please specify a year or role to remove!", + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidTrustedRole": { + "title": "Trusted Role Remove - Not Found", + "description": [ + "Trusted Role does not exist!", + "View your server's trusted roles with `{{REF:bot.prefix}} trustedRole {{REF:types.list}}`!" + ], + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "invalidAnniversaryRole": { + "title": "Member Anniversary Role Remove - Not Found", + "description": [ + "Member Anniversary Role does not exist!", + "View your server's member anniversary roles with `{{REF:bot.prefix}} memberAnniversaryRole {{REF:types.list}}`!" + ], + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "customMessageInvalidMessageNumber": { + "title": "Remove Custom Message", + "description": [ + "Message number does not exist!", + "View your server's custom messages with `{{REF:bot.prefix}} message {{REF:types.list}} `!" + ], + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.error}}" + }, + "maxPaidTrustedRoles": { + "description": "Your server has reached the maximum trusted roles! (**{{PAID_MAX}}**)", + "color": "{{REF:colors.error}}" + }, + "maxPaidMemberAnniversaryRoles": { + "description": "Your server has reached the maximum member anniversary roles! (**{{PAID_MAX}}**)", + "color": "{{REF:colors.error}}" + }, + "maxFreeCustomMessages": { + "description": "Your server has reached the maximum amount of custom {{TYPE}} messages! (**{{FREE_MAX}}**)", + "footer": { + "text": "To have up to {{PAID_MAX}} custom {{TYPE}} messages try {{REF:bot.name}} Premium!", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.error}}" + }, + "maxPaidCustomMessages": { + "description": "Your server has reached the maximum amount of custom {{TYPE}} messages! (**{{PAID_MAX}}**)", + "color": "{{REF:colors.error}}" + }, + "maxCustomMessageSize": { + "description": "Custom Messages are maxed at {{MAX_SIZE}} characters!", + "color": "{{REF:colors.error}}" + }, + "noUserPlaceholder": { + "description": [ + "Please include the `{{REF:placeHolders.users}}` placeholder somewhere in the message. This indicates where the member names will appear.", + "Ex: `{{REF:bot.prefix}} message {{REF:types.add}} {{TYPE}} {{EXAMPLE_MESSAGE}}`" + ], + "color": "{{REF:colors.error}}" + }, + "noServerPlaceholder": { + "description": [ + "Please include the `{{REF:placeHolders.server}}` placeholder somewhere in the message. This indicates where the server name will appear.", + "Ex: `{{REF:bot.prefix}} message {{REF:types.add}} {{REF:types.serverAnniversary}} {{REF:defaults.serverAnniversaryMessage}}`", + "", + "Tip: You can also use the `{{REF:placeHolders.year}}` placeholder for the anniversary year." + ], + "color": "{{REF:colors.error}}" + }, + "warnBirthdayRoleSize": { + "title": "Birthday Role - Warning", + "description": [ + "We have detected that __**{{AMOUNT}}**__ users already have that role!", + "The Birthday Role should ONLY be the role that users GET on their birthday!" + ], + "footer": { + "text": "The Bot removes the Birthday Role from anyone whose birthday it isn't!", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.warn}}" + }, + "denyBirthdayRoleSize": { + "title": "Birthday Role - Error", + "description": [ + "We have detected that __**{{AMOUNT}}**__ users already have that role!", + "The Birthday Role should ONLY be the role that users GET on their birthday!" + ], + "footer": { + "text": "The Bot removes the Birthday Role from anyone whose birthday it isn't!", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "noTimeZone": { + "description": [ + "Please input a timezone!", + "To find your time zone please use the [map time zone picker]({{REF:links.map}})!" + ], + "color": "{{REF:colors.error}}" + }, + "messageDoesNotExist": { + "title": "Test Custom Message", + "description": [ + "Message does not exist!", + "View your server's custom messages with `{{REF:bot.prefix}} message {{REF:types.list}} `!" + ], + "footer": { + "text": "{{REF:footers.actionFailed}}", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.error}}" + }, + "duplicateTrustedRole": { + "description": "{{ROLE}} is already a trusted role!", + "color": "{{REF:colors.error}}" + }, + "duplicateYear": { + "description": "There is already a member anniversary role reward for year {{YEAR}}!", + "color": "{{REF:colors.error}}" + }, + "duplicateMessage": { + "description": "Duplicate message found for this server!", + "color": "{{REF:colors.error}}" + }, + "duplicateUserCustomMessage": { + "title": "Caution", + "description": [ + "There is already a {{TYPE}} custom message set for this user, would you like to overwrite it?", + "", + "**Current Message**: {{CURRENT_MESSAGE}}", + "", + "**New Message**: {{NEW_MESSAGE}}" + ], + "footer": { + "text": "{{REF:footers.irreversible}}", + "icon": "{{ICON}}" + }, + "color": "{{REF:colors.warn}}" + }, + "noTrueFalse": { + "description": "Please provide a value! (True/False)", + "color": "{{REF:colors.error}}" + }, + "noTime": { + "description": "Please provide a time! (0-23)", + "color": "{{REF:colors.error}}" + }, + "invalidTime": { + "description": "Invalid time! Accepted values: `0-23`", + "color": "{{REF:colors.error}}" + }, + "invalidRole": { + "description": "Invalid role! Please mention a role or input a role's name.", + "color": "{{REF:colors.error}}" + }, + "noTrustedRoles": { + "description": "You server has not set any trusted roles set!", + "color": "{{REF:colors.error}}" + }, + "noMemberAnniversaryRoles": { + "description": "You server has not set any member anniversary roles set!", + "color": "{{REF:colors.error}}" + }, + "noUserSpecified": { + "description": "Please specify a user!", + "color": "{{REF:colors.error}}" + }, + "noUserFound": { + "description": "Could not find that user!", + "color": "{{REF:colors.error}}" + }, + "noAmountGiven": { + "description": "Please specify an amount!", + "color": "{{REF:colors.error}}" + }, + "invalidNumber": { + "description": "Invalid Number!", + "color": "{{REF:colors.error}}" + }, + "amountTooLarge": { + "description": "Amount too large!", + "color": "{{REF:colors.error}}" + }, + "attemptsLeft": { + "description": "That user has not used any attempts!", + "color": "{{REF:colors.error}}" + }, + "noCustomMessagesGeneric": { + "description": "The {{DISPLAY_TYPE}} list is already empty!", + "color": "{{REF:colors.error}}" + }, + "noUserSpecificBirthdayMessages": { + "description": "This server doesn't have any user specific custom birthday messages!", + "color": "{{REF:colors.error}}" + }, + "noUserSpecificMemberAnniversaryMessages": { + "description": "This server doesn't have any user specific custom birthday messages!", + "color": "{{REF:colors.error}}" + }, + "cantBlacklistBot": { + "description": "You cannot blacklist a bot!", + "color": "{{REF:colors.error}}" + }, + "noUserMessageForBot": { + "description": "You cannot set a user-specific message for a bot!", + "color": "{{REF:colors.error}}" + }, + "noMessage": { + "description": "Please provide a message!", + "color": "{{REF:colors.error}}" + }, + "noMessageNumber": { + "description": [ + "Please provide a message number or user!", + "Find this using `{{REF:bot.prefix}} message {{REF:types.list}} `!" + ], + "color": "{{REF:colors.error}}" + }, + "userAlreadyInBlacklist": { + "description": "That user is already in the blacklist!", + "color": "{{REF:colors.error}}" + }, + "userNotInBlacklist": { + "description": "That user is not in the blacklist!", + "color": "{{REF:colors.error}}" + }, + "masterRoleManaged": { + "description": "Birthday Master Role cannot be managed by an external service!", + "color": "{{REF:colors.error}}" + }, + "birthdayRoleManaged": { + "description": "Birthday Role cannot be managed by an external service!", + "color": "{{REF:colors.error}}" + }, + "trustedRoleManaged": { + "description": "Trusted Role cannot be managed by an external service!", + "color": "{{REF:colors.error}}" + }, + "memberAnniversaryRoleManaged": { + "description": "Member Anniversary Role cannot be managed by an external service!", + "color": "{{REF:colors.error}}" + }, + "invalidMessageNumber": { + "description": [ + "Invalid message number!", + "Find this using `{{REF:bot.prefix}} message {{REF:types.list}} `!" + ], + "color": "{{REF:colors.error}}" + }, + "invalidChannel": { + "description": "Invalid channel! Please mention a channel or input a channel's name.", + "color": "{{REF:colors.error}}" + }, + "invalidYear": { + "description": "Invalid year! (1-1000)", + "color": "{{REF:colors.error}}" + }, + "invalidHour": { + "description": "Invalid hour!", + "color": "{{REF:colors.error}}" + }, + "invalidMention": { + "description": [ + "Please provide a value!", + "Accepted Values: `everyone`, `here`, `@role/role-name`, `none`" + ], + "color": "{{REF:colors.error}}" + }, + "invalidColor": { + "title": "Invalid Color", + "description": [ + "Please provide a valid hex color! Find hex colors [here]({{REF:links.colors}}).", + "", + "Example: `Orange` or `Crimson`", + "Example: `#4EEFFF` or `4EEFFF`" + ], + "color": "{{REF:colors.error}}" + }, + "emptyBlacklist": { + "description": "Your server has not blacklisted any users!", + "color": "{{REF:colors.error}}" + } + }, + "premiumPrompts": { + "subscriptionAdded": { + "title": "{{REF:bot.name}} Premium", + "description": "{{REF:emotes.party}} {[SERVER_NAME}}'s premium has been successfully activated! Enjoy the new features! {{REF:emotes.party}}", + "fields": [ + { + "name": "{{REF:bot.prefix}} premium", + "value": [ + "View details about your subscription.", + "", + "[Join Support Server]({{REF:links.support}})" + ] + } + ], + "footer": { + "text": "Thanks for support {{REF:bot.name}}!", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "subscriptionCanceled": { + "title": "{{REF:bot.name}} Premium", + "description": "{[SERVER_NAME}}'s premium has been canceled! :(", + "fields": [ + { + "name": "{{REF:bot.prefix}} premium", + "value": [ + "View details about your subscription.", + "", + "[Join Support Server]({{REF:links.support}})" + ] + } + ], + "footer": { + "text": "Consider letting us know how to improve premium!", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "subscriptionExpired": { + "title": "{{REF:bot.name}} Premium", + "description": "{[SERVER_NAME}}'s premium has expired! :(", + "fields": [ + { + "name": "{{REF:bot.prefix}} premium", + "value": [ + "Resubscribe to premium!", + "", + "[Join Support Server]({{REF:links.support}})" + ] + } + ], + "footer": { + "text": "Consider letting us know how to improve premium!", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "premiumDisabled": { + "title": "{{REF:bot.name}} Premium", + "description": "Premium subscriptions are currently disabled. Enjoy premium features for free! Woohoo!", + "timestamp": true + }, + "refuseNewTransactions": { + "title": "{{REF:bot.name}} Premium", + "description": [ + "New subscriptions are not being accept at this time. Please try again later.", + "[Join Support Server]({{REF:links.support}})" + ], + "timestamp": true + }, + "premiumAlreadyActive": { + "title": "{{REF:bot.name}} Premium", + "description": [ + "This server already has an active premium subscription or one that is currently processing.", + "", + "You cannot purchase premium for this server at this time. If you've recently checked out, you may need for the payment to be processed. If the subscriptions was recently cancelled you may need to wait until the paid time runs out before re-subscribing. As always, feel free to contact support at the link below with any questions.", + "", + "[Join Support Server]({{REF:links.support}})" + ], + "timestamp": true + }, + "subscriptionDMPrompt": { + "title": "{{REF:bot.name}} Premium", + "description": [ + "I have private messaged you with a PayPal subscription link!", + "If you did not receive a message, please make sure you have direct messages enabled for this server (under this servers Privacy Settings) and run this command again." + ], + "timestamp": true + }, + "subscriptionPM": { + "title": "{{REF:bot.name}} Premium", + "description": "Complete your servers subscription by checking out with PayPal at the link below.", + "fields": [ + { + "name": "Purchase Premium", + "value": ["[Checkout with PayPal Here!]({{SUB_LINK}})"] + }, + { + "name": "Premium Features", + "value": [ + "- No voting needed for **any** commands", + "- Up to **{{BIRTHDAY_MESSAGE_MAX_FREE}}** custom birthday messages *(vs **{{BIRTHDAY_MESSAGE_MAX_PAID}}** free)*", + "- Up to **{{MEMBER_ANNIVERSARY_MESSAGE_MAX_FREE}}** custom member anniversary messages *(vs **{{MEMBER_ANNIVERSARY_MAX_PAID}}** free)*", + "- Up to **{{MEMBER_ANNIVERSARY_MESSAGE_MAX_FREE}}** custom server anniversary messages *(vs **{{MEMBER_ANNIVERSARY_MESSAGE_MAX_PAID}}** free)*", + "- Access to Member Anniversary Roles! (Up to {{MAX_ANNIVERSARY_ROLES}}!)", + "- Access to multiple Trusted Roles! (Up to {{MAX_TRUSTED_ROLES}}!)", + "- Access to user-specific custom birthday messages", + "- Customize the color **each** birthday message embed", + "- Premium support", + "Features apply **server-wide** (this server only)." + ] + }, + { + "name": "Price", + "value": ["2.99 USD / Month"] + }, + { + "name": "Purchase Details", + "value": [ + "Your servers subscription will activate within 5 minutes of checking out with PayPal. You may cancel at any time on your [PayPal Automatic Payments]({{REF:links.autopay}}) page. Any paid time after cancelling will still count as premium service. As always, feel free to contact support at the link below with any questions.", + "", + "[Join Support Server]({{REF:links.support}})" + ] + } + ] + }, + "noSubscription": { + "title": "{{REF:bot.name}} Premium", + "description": "Subscribe to **{{REF:bot.name}} Premium** to give this server extra features!", + "fields": [ + { + "name": "Premium Features", + "value": [ + "- No voting needed for **any** commands", + "- Up to **{{BIRTHDAY_MESSAGE_MAX_FREE}}** custom birthday messages *(vs **{{BIRTHDAY_MESSAGE_MAX_PAID}}** free)*", + "- Up to **{{MEMBER_ANNIVERSARY_MESSAGE_MAX_FREE}}** custom member anniversary messages *(vs **{{MEMBER_ANNIVERSARY_MAX_PAID}}** free)*", + "- Up to **{{MEMBER_ANNIVERSARY_MESSAGE_MAX_FREE}}** custom server anniversary messages *(vs **{{MEMBER_ANNIVERSARY_MESSAGE_MAX_PAID}}** free)*", + "- Access to Member Anniversary Roles! (Up to {{MAX_ANNIVERSARY_ROLES}}!)", + "- Access to multiple Trusted Roles! (Up to {{MAX_TRUSTED_ROLES}}!)", + "- Access to user-specific custom birthday messages", + "- Customize the color **each** birthday message embed", + "- Premium support", + "Features apply **server-wide** (this server only)." + ] + }, + { + "name": "Price", + "value": ["2.99 USD / Month"] + }, + { + "name": "Purchase Details", + "value": [ + "Type `{{REF:bot.prefix}} subscribe` to purchase a subscription. You will then be direct messaged a PayPal link where you can checkout using your PayPal account or credit card. Your servers subscription will activate within 5 minutes of checking out with PayPal. You may cancel at any time on your [PayPal Automatic Payments]({{REF:links.autopay}}) page. Any paid time after cancelling will still count as premium service. As always, feel free to contact support at the link below with any questions.", + "", + "[Join Support Server]({{REF:links.support}})" + ] + } + ] + }, + "subscription": { + "title": "{{REF:bot.name}} Premium", + "description": "This servers subscription information.", + "fields": [ + { + "name": "Active", + "value": "{{IS_ACTIVE}}" + }, + { + "name": "Subscription ID", + "value": "{{SUBSCRIPTION_ID}}" + }, + { + "name": "Status", + "value": "{{STATUS}}" + }, + { + "name": "Last Payment", + "value": "{{LAST_PAYMENT}}" + }, + { + "name": "Paid Until", + "value": "{{PAID_UNTIL}}" + }, + { + "name": "Purchase Details", + "value": [ + "Type `{{REF:bot.prefix}} subscribe` to purchase a subscription. You will then be direct messaged a PayPal link where you can checkout using your PayPal account or credit card. Your servers subscription will activate within 5 minutes of checking out with PayPal. You may cancel at any time on your [PayPal Automatic Payments]({{REF:links.autopay}}) page. Any paid time after cancelling will still count as premium service. As always, feel free to contact support at the link below with any questions.", + "[Join Support Server]({{REF:links.support}})" + ] + } + ] + } + }, + "premiumRequired": { + "command": { + "title": "Premium Required!", + "description": ["This command requires this server to have premium!"], + "fields": [ + { + "name": "Premium Commands", + "value": [ + "Subscribe to **{{REF:bot.name}} Premium** for access to our premium features!", + "See `{{REF:bot.prefix}} premium` for more information." + ] + } + ], + "footer": { + "text": "{{REF:footers.whyPremium}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "userSpecificMessages": { + "title": "{{REF:terms.premiumRequired}}", + "description": "User-specific messages are a premium feature! View information about **{{REF:bot.name}} Premium** using `{{REF:bot.prefix}} premium`!", + "footer": { + "text": "{{REF:footers.whyPremium}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + }, + "anniversaryRoles": { + "title": "{{REF:terms.premiumRequired}}", + "description": "Member Anniversary Roles are a premium only feature! View information about **{{REF:bot.name}} Premium** using `{{REF:bot.prefix}} premium`!", + "footer": { + "text": "{{REF:footers.whyPremium}}", + "icon": "{{ICON}}" + }, + "timestamp": true, + "color": "{{REF:colors.error}}" + } + }, + "errors": { + "command": { + "description": "Something went wrong!", + "fields": [ + { + "name": "Error code", + "value": "{{ERROR_CODE}}" + }, + { + "name": "Contact support", + "value": "{{REF:links.support}}" + } + ], + "color": "{{REF:colors.error}}" + }, + "startupInProcess": { + "description": "{{REF:bot.name}} is still starting up. Try again later.", + "color": "{{REF:colors.warn}}" + } + }, + "info": { + "general": { + "title": "{{REF:bot.name}} - Info", + "fields": [ + { + "name": "Author", + "value": "{{REF:links.authorEmbed}}" + }, + { + "name": "Links", + "value": ["{{REF:links.support}}", "{{REF:links.invite}}"] + }, + { + "name": "Created With", + "value": "{{REF:links.template}}" + } + ] + }, + "update": { + "title": "{{REF:bot.name}} 3.0 Update Notes", + "description": [ + "For full break down of the update visit the [3.0 Update Guide]({{REF:recentUpdateDoc}}).", + "", + "When I started this Birthday Bot update I had no plans for it to be this large.", + "Fast forward almost **8 months** and it is finally ready to go!", + "", + "Thank you to everyone for all the support and for 180,000 servers! I never dreamed Birthday Bot would become this big.", + "I hope you enjoy the update!", + "", + "If you have any questions **find any bugs**, or have any suggestions **please** [Join Our Support Server]({{REF:links.support}})!" + ], + "fields": [ + { + "name": "Introducing Anniversaries!", + "value": [ + "There are two types of anniversaries: Member and Server.", + "", + "A Member Anniversary is a yearly celebration of when a user first joined a server.", + "A Server Anniversary is a yearly celebration of when the discord server was created.", + "", + "Features:", + "Added Member Anniversary Roles (premium, see below)", + "Added two new message types (there are now three total) which are sent on an anniversary", + " - All three message types can have their own separate channel, message time, and mention setting", + " - Like birthday messages, premium servers can set user specific member anniversary messages." + ] + } + ] + }, + "guildJoin": { + "title": "Thank you for using {{REF:bot.name}}!", + "description": [ + "To support the bot and unlock special features use `{{REF:bot.prefix}} premium` in your server.", + "", + "To view the commands of this bow use `{{REF:bot.prefix}} help`.", + "To setup the bot run `{{REF:bot.prefix}} setup` in your server.", + "To set your birthday use `{{REF:bot.prefix}} set`.", + "", + "View the {{REF:bot.name}} [Documentation]({{REF:links.docs}}) or [FAQ]({{REF:docLinks.faq}}).", + "For more support join the {{REF:bot.name}} support server [here]({{REF:links.support}})!" + ], + "footer": { + "text": "{{REF:footers.joinSupport}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "stats": { + "fields": [ + { + "name": "Total Birthdays", + "value": "{{TOTAL_BIRTHDAYS}}", + "inline": true + }, + { + "name": "Total Servers", + "value": "{{TOTAL_SERVERS}}", + "inline": true + }, + { + "name": "Shard ID", + "value": "{{SHARD_ID}}", + "inline": true + }, + { + "name": "Birthdays Today", + "value": "{{BIRTHDAYS_TODAY}}", + "inline": true + }, + { + "name": "Birthdays This Month", + "value": "{{BIRTHDAYS_THIS_MONTH}}", + "inline": true + } + ] + }, + "error": { + "description": "Something went wrong!", + "fields": [ + { + "name": "Error Code", + "value": "{{ERROR_CODE}}" + }, + { + "name": "Contact Support", + "value": "[Join Support Server]({{REF:links.support}})" + } + ], + "color": "{{REF:colors.error}}" + }, + "documentation": { + "description": "View our documentation for {{BOT}} [here]({{REF:links.docs}})!", + "color": "{{REF:colors.default}}" + }, + "vote": { + "description": "Support {{BOT}} by voting [here]({{REF:links.vote}})!", + "color": "{{REF:colors.default}}" + }, + "donate": { + "description": "You can support {{BOT}} by donating [here]({{REF:links.donate}})!", + "color": "{{REF:colors.default}}" + }, + "faq": { + "description": "View our FAQ for {{BOT}} [here]({{REF:links.docs}}/faq)!", + "color": "{{REF:colors.default}}" + }, + "invite": { + "description": "Invite {{BOT}} to your server [here]({{REF:links.invite}})!", + "color": "{{REF:colors.default}}" + }, + "support": { + "description": "For support join our discord server [here]({{REF:links.support}})!", + "color": "{{REF:colors.default}}" + }, + "map": { + "description": [ + "[Kevin Novak](https://github.com/KevinNovak) has created a handy [map time zone picker]({{REF:links.map}})!", + "", + "Simply click your location on the map and copy the name of the selected time zone. You can then use it in the `{{REF:bot.prefix}} set` command." + ], + "color": "{{REF:colors.default}}" + }, + "settingsGeneral": { + "title": "{{SERVER_NAME}}'s General Settings", + "fields": [ + { + "name": "{{REF:terms.birthdayRole}}", + "value": "{{BIRTHDAY_ROLE}}", + "inline": true + }, + { + "name": "{{REF:terms.nameFormat}}", + "value": "{{NAME_FORMAT}}", + "inline": true + }, + { + "name": "{{REF:terms.defaultTimezone}}", + "value": "{{DEFAULT_TIMEZONE}}", + "inline": true + }, + { + "name": "{{REF:terms.serverLanguage}}", + "value": "{{SERVER_LANGUAGE}}", + "inline": true + }, + { + "name": "Guild Id", + "value": "{{GUILD_ID}}", + "inline": true + }, + { + "name": "Premium", + "value": "{{HAS_PREMIUM}}", + "inline": true + } + ], + "footer": { + "text": "© {{DATE}} {{REF:bot.name}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "settingsAdvanced": { + "title": "{{SERVER_NAME}}'s Advanced Settings", + "fields": [ + { + "name": "{{REF:terms.birthdayMasterRole}}", + "value": "{{BIRTHDAY_MASTER_ROLE}}", + "inline": true + }, + { + "name": "{{REF:terms.trustedPreventsRole}}", + "value": "{{TRUSTED_PREVENTS_ROLE}}", + "inline": true + }, + { + "name": "{{REF:terms.trustedPreventsMessage}}", + "value": "{{TRUSTED_PREVENTS_MESSAGE}}", + "inline": true + }, + { + "name": "{{REF:terms.requireAllTrustedRoles}}", + "value": "{{REQUIRE_ALL_TRUSTED_ROLES}}", + "inline": true + }, + { + "name": "{{REF:terms.numberOfTrustedRoles}}", + "value": "{{TRUSTED_ROLE_COUNT}}", + "inline": true + }, + { + "name": "{{REF:terms.useTimezone}}", + "value": "{{USE_TIMEZONE}}", + "inline": true + }, + { + "name": "Guild Id", + "value": "{{GUILD_ID}}", + "inline": true + }, + { + "name": "Premium", + "value": "{{HAS_PREMIUM}}", + "inline": true + } + ], + "footer": { + "text": "© {{DATE}} {{REF:bot.name}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "settingsMessage": { + "title": "{{SERVER_NAME}}'s Message Settings", + "fields": [ + { + "name": "{{REF:terms.birthdayChannel}}", + "value": "{{BIRTHDAY_CHANNEL}}", + "inline": true + }, + { + "name": "{{REF:terms.memberAnniversaryChannel}}", + "value": "{{MEMBER_ANNIVERSARY_CHANNEL}}", + "inline": true + }, + { + "name": "{{REF:terms.serverAnniversaryChannel}}", + "value": "{{SERVER_ANNIVERSARY_CHANNEL}}", + "inline": true + }, + { + "name": "{{REF:terms.birthdayMessageTime}}", + "value": "{{BIRTHDAY_MESSAGE_TIME}}", + "inline": true + }, + { + "name": "{{REF:terms.memberAnniversaryMessageTime}}", + "value": "{{MEMBER_ANNIVERSARY_MESSAGE_TIME}}", + "inline": true + }, + { + "name": "{{REF:terms.serverAnniversaryMessageTime}}", + "value": "{{SERVER_ANNIVERSARY_MESSAGE_TIME}}", + "inline": true + }, + { + "name": "{{REF:terms.birthdayMention}}", + "value": "{{BIRTHDAY_MENTION}}", + "inline": true + }, + { + "name": "{{REF:terms.memberAnniversaryMention}}", + "value": "{{MEMBER_ANNIVERSARY_MENTION}}", + "inline": true + }, + { + "name": "{{REF:terms.serverAnniversaryMention}}", + "value": "{{SERVER_ANNIVERSARY_MENTION}}", + "inline": true + }, + { + "name": "Guild Id", + "value": "{{GUILD_ID}}", + "inline": true + }, + { + "name": "Premium", + "value": "{{HAS_PREMIUM}}", + "inline": true + } + ], + "footer": { + "text": "© {{DATE}} {{REF:bot.name}}", + "icon": "{{ICON}}" + }, + "timestamp": true + } + }, + "help": { + "general": { + "author": { + "name": "Birthday Bot General Help", + "icon": "{{ICON}}", + "url": "{{REF:docLinks.faq}}" + }, + "title": "{{REF:bot.name}} General Help", + "description": [ + "{{REF:bot.name}} helps your server celebrate birthdays with automatic birthday roles and announcements.", + "", + "**{{REF:bot.prefix}} premium** - See information about {{REF:bot.name}} Premium.", + "**{{REF:bot.prefix}} subscribe** - Subscribe to {{REF:bot.name}} Premium!", + "**{{REF:bot.prefix}} help {{REF:types.premium}}** - Help for {{REF:bot.name}} Premium.", + "", + "**{{REF:bot.prefix}} set** - Set your birthday.", + "**{{REF:bot.prefix}} view [type] [user]** - View your information or a user's information.", + "**{{REF:bot.prefix}} next [type]**** - View next birthday(s) in the server.", + "**{{REF:bot.prefix}} list [type] [page/date]**** - View the server birthday list.", + "**{{REF:bot.prefix}} map** - View the time zone map.", + "**{{REF:bot.prefix}} invite** - Invite {{REF:bot.name}} to a server.", + "**{{REF:bot.prefix}} support** - Join the support server.", + "**{{REF:bot.prefix}} docs** - View the documentation.", + "**{{REF:bot.prefix}} faq** - View the frequently asked questions.", + "**{{REF:bot.prefix}} vote** - Vote for {{REF:bot.name}}!", + "**{{REF:bot.prefix}} purge** - Remove your birthday data.", + "**{{REF:bot.prefix}} help {{REF:types.setup}}** - Help for server setup.", + "**{{REF:bot.prefix}} help {{REF:types.anniversary}}** - Help for the anniversary system.", + "**{{REF:bot.prefix}} help {{REF:types.message}}** - Help for the birthday message settings.", + "**{{REF:bot.prefix}} help {{REF:types.trusted}}** - Help for the trusted system.", + "**{{REF:bot.prefix}} help {{REF:types.blacklist}}** - Help for the birthday blacklist system.", + "**{{REF:bot.prefix}} premium** - See information about {{REF:bot.name}} Premium.", + "**{{REF:bot.prefix}} settings [{{REF:types.message}}/{{REF:types.advanced}}]**** - View server's settings.", + "**{{REF:bot.prefix}} test [user] [anniversary year]**** - Test the birthday event.", + "", + "**{{REF:bot.prefix}} donate** - Support developments by donating!", + "", + "If you have any question/problems please join our support server [here]({{REF:links.support}})." + ], + "fields": [ + { + "name": "Legend", + "value": "** = Server Only Command" + } + ] + }, + "setup": { + "author": { + "name": "Birthday Bot Setup Help", + "icon": "{{ICON}}", + "url": "{{REF:docLinks.faq}}" + }, + "title": "{{REF:bot.name}} Setup Help - Guild Only", + "description": [ + "**{{REF:bot.prefix}} setup** - Interactive guide for server setup.", + "**{{REF:bot.prefix}} setup {{REF:types.trusted}}** - Interactive guide for trusted system settings setup.", + "**{{REF:bot.prefix}} setup {{REF:types.anniversary}}** - Interactive guide anniversary settings setup.", + "", + "**{{REF:bot.prefix}} config {{REF:types.channel}} <{{REF:types.create}}/{{REF:types.clear}}/#channel> ** - Set a channel type.", + "**{{REF:bot.prefix}} config {{REF:types.birthdayRole}} <{{REF:types.create}}/{{REF:types.clear}}/#channel>** - Set the birthday role.", + "**{{REF:bot.prefix}} config {{REF:types.birthdayMasterRole}} <{{REF:types.create}}/{{REF:types.clear}}/#channel>** - Set the birthday master role.", + "**{{REF:bot.prefix}} config {{REF:types.timezone}} - Set the default server timezone.", + "**{{REF:bot.prefix}} config {{REF:types.useTimezone}} <{{REF:types.user}}/{{REF:types.server}}> - Set which timezone to use for celebrations.", + "**{{REF:bot.prefix}} config {{REF:types.nameFormat}} <{{REF:types.mention}}/{{REF:types.nickname}}/{{REF:types.username}}/{{REF:types.tag}}/{{REF:types.default}}>** - Decide how member's names will appear.", + "", + "If you have any question/problems please join our support server [here]({{REF:links.support}})." + ] + }, + "message": { + "author": { + "name": "Birthday Bot Message Help", + "icon": "{{ICON}}", + "url": "{{REF:docLinks.faq}}" + }, + "title": "{{REF:bot.name}} Message Help - Guild Only", + "description": [ + "**{{REF:bot.prefix}} message {{REF:types.list}} [page]** - List all messages for that type.", + "**{{REF:bot.prefix}} message {{REF:types.add}} ** - Add a message for that type.", + " - Example Usage: `{{REF:bot.prefix}} message {{REF:types.add}} {{REF:types.memberAnniversary}} have been in for (s)!`", + " - Placeholder for users: `{{REF:placeHolders.users}}`", + " - Placeholder for year: `{{REF:placeHolders.year}}`", + " - Placeholder for server name: `{{REF:placeHolders.server}}`", + "**{{REF:bot.prefix}} message {{REF:types.remove}} ** - Remove a certain message for that type.", + "**{{REF:bot.prefix}} message {{REF:types.clear}} ** - Clear all messages for that type.", + "**{{REF:bot.prefix}} message {{REF:types.time}} <0-23>** - Set the message time for that type", + "**{{REF:bot.prefix}} message {{REF:types.mention}} ** - Set the message mention setting for that type.", + "**{{REF:bot.prefix}} message {{REF:types.test}} [user count]** - Test a custom message.", + "", + "If you have any question/problems please join our support server [here]({{REF:links.support}})." + ] + }, + "trusted": { + "author": { + "name": "Birthday Bot Trusted Help", + "icon": "{{ICON}}", + "url": "{{REF:docLinks.faq}}" + }, + "title": "{{REF:bot.name}} Trusted System Help - Guild Only", + "description": [ + "**{{REF:bot.prefix}} setup {{REF:types.trusted}}** - Interactive guide for trusted system settings setup.", + "", + "**{{REF:bot.prefix}} trustedRole {{REF:types.add}} ** - Add a trusted role.", + "**{{REF:bot.prefix}} trustedRole {{REF:types.remove}} ** - Remove a trusted role", + "**{{REF:bot.prefix}} trustedRole {{REF:types.clear}}** - Clear all trusted roles.", + "**{{REF:bot.prefix}} trustedRole {{REF:types.list}} [page]** - List all trusted roles", + "**{{REF:bot.prefix}} config {{REF:types.requireAllTrustedRoles}} ** - If a user should need all the trusted roles.", + "**{{REF:bot.prefix}} config {{REF:types.trustedPreventsMessage}} ** - If trusted role is required for a birthday message.", + "**{{REF:bot.prefix}} config {{REF:types.trustedPreventsRole}} ** - If trusted role is required to get the birthday role.", + "", + "If you have any question/problems please join our support server [here]({{REF:links.support}})." + ] + }, + "blacklist": { + "author": { + "name": "Birthday Bot Blacklist Help", + "icon": "{{ICON}}", + "url": "{{REF:docLinks.faq}}" + }, + "title": "{{REF:bot.name}} Blacklist Help - Guild Only", + "description": [ + "**{{REF:bot.prefix}} blacklist {{REF:types.add}} ** - Add user to the blacklist.", + "**{{REF:bot.prefix}} blacklist {{REF:types.remove}} ** - Remove user from the blacklist.", + "**{{REF:bot.prefix}} blacklist {{REF:types.clear}}** - Clear the birthday blacklist.", + "**{{REF:bot.prefix}} blacklist {{REF:types.list}}** - View all blacklisted users in your server.", + "", + "If you have any question/problems please join our support server [here]({{REF:links.support}})." + ] + }, + "anniversary": { + "author": { + "name": "Birthday Bot Anniversary Help", + "icon": "{{ICON}}", + "url": "{{REF:docLinks.faq}}" + }, + "title": "{{REF:bot.name}} Anniversary Help - Guild Only", + "description": [ + "**{{REF:bot.prefix}} memberAnniversaryRole {{REF:types.add}} ** - Add a member anniversary role.", + "**{{REF:bot.prefix}} memberAnniversaryRole {{REF:types.remove}} ** - Remove a member anniversary role", + "**{{REF:bot.prefix}} memberAnniversaryRole {{REF:types.clear}}** - Clear all member anniversary roles.", + "**{{REF:bot.prefix}} memberAnniversaryRole {{REF:types.list}} [page]** - List all member anniversary roles.", + "", + "If you have any question/problems please join our support server [here]({{REF:links.support}})." + ] + }, + "premium": { + "author": { + "name": "Birthday Bot Premium Help", + "icon": "{{ICON}}", + "url": "{{REF:docLinks.faq}}" + }, + "title": "{{REF:bot.name}} Premium Help - Guild Only", + "description": [ + "**{{REF:bot.prefix}} premium** - View information about your server's premium.", + "**{{REF:bot.prefix}} message {{REF:types.add}} ** - Add a message for that type.", + " - Example Usage: `{{REF:bot.prefix}} message {{REF:types.add}} {{REF:types.birthday}} Happy Birthday @Stqlth!`", + " - Placeholder for year: `{{REF:placeHolders.year}}`", + " - Placeholder for server name: `{{REF:placeHolders.server}}`", + "**{{REF:bot.prefix}} message {{REF:types.remove}} <@user>** - Remove a user specific custom message.", + "**{{REF:bot.prefix}} message {{REF:types.list}} {{REF:types.userSpecificBirthday}} [page]** - List all user-specific birthday messages.", + "**{{REF:bot.prefix}} message {{REF:types.list}} {{REF:types.userSpecificMemberAnniversary}} [page]** - List all user-specific member anniversary messages.", + "**{{REF:bot.prefix}} memberAnniversaryRole {{REF:types.add}} ** - Add a member anniversary role.", + "**{{REF:bot.prefix}} memberAnniversaryRole {{REF:types.remove}} ** - Remove a member anniversary role", + "**{{REF:bot.prefix}} memberAnniversaryRole {{REF:types.clear}}** - Clear all member anniversary roles.", + "**{{REF:bot.prefix}} memberAnniversaryRole {{REF:types.list}} [page]** - List all member anniversary roles.", + "", + "If you have any question/problems please join our support server [here]({{REF:links.support}})." + ] + } + }, + "dev": { + "general": { + "title": "{{REF:bot.name}} - Developer Info", + "fields": [ + { + "name": "Versions", + "value": [ + "**Node.js**: {{NODE_VERSION}}", + "**TypeScript**: {{TS_VERSION}}", + "**ECMAScript**: {{ES_VERSION}}", + "**discord.js**: {{DJS_VERSION}}" + ] + }, + { + "name": "Stats", + "value": ["**Shards**: {{SHARD_COUNT}}", "**Servers**: {{SERVER_COUNT}}"] + }, + { + "name": "Memory", + "value": [ + "**RSS**: {{RSS_SIZE}} ({{RSS_SIZE_PER_SERVER}}/Server)", + "**Heap**: {{HEAP_TOTAL_SIZE}} ({{HEAP_TOTAL_SIZE_PER_SERVER}}/Server)", + "**Used**: {{HEAP_USED_SIZE}} ({{HEAP_USED_SIZE_PER_SERVER}}/Server)" + ] + }, + { + "name": "IDs", + "value": [ + "**Shard ID**: {{SHARD_ID}}", + "**Server ID**: {{SERVER_ID}}", + "**Bot ID**: {{BOT_ID}}", + "**User ID**: {{USER_ID}}" + ] + } + ] + } + }, + "list": { + "birthday": { + "title": "{{REF:terms.birthdayList}} | {{REF:list.pageTitle}}", + "description": [ + "*Birthdays are celebrated on the day (and __time zone__) of the birthday user. To set your birthday use `{{REF:bot.prefix}} set`!*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "Total Birthdays: {{TOTAL_BIRTHDAYS}} • {{PER_PAGE}} per page", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "memberAnniversary": { + "title": "{{REF:terms.memberAnniversaryList}} | {{REF:list.pageTitle}}", + "description": [ + "*Member Anniversaries are celebrated on the day of the user's server join date (in the timezone of the __server__).*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "Total Member Anniversaries: {{TOTAL_ANNIVERSARIES}} • {{PER_PAGE}} per page", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "birthdayMessageUnLocked": { + "title": "{{REF:list.birthdayMessagesTitle}}", + "description": [ + "*A random birthday message is chosen for each birthday message. If there are none, the default will be used. [(?)]({{REF:docLinks.whatIsCustomMessage}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.messagesFooter}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "birthdayMessageLocked": { + "title": "{{REF:list.birthdayMessagesTitle}}", + "description": [ + "*A random birthday message is chosen for each birthday message. If there are none, the default will be used. [(?)]({{REF:docLinks.whatIsCustomMessage}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.messagesFooter}}", + "icon": "{{ICON}}" + }, + "fields": [ + { + "name": "{{REF:list.messagePaidFieldTitle}}", + "value": "{{REF:list.birthdayMessageFieldText}}" + } + ], + "timestamp": true + }, + "memberAnniversaryMessageUnLocked": { + "title": "{{REF:list.memberAnniversaryMessagesTitle}}", + "description": [ + "*A random member anniversary message is chosen for each member anniversary. If there are none, the default will be used. [(?)]({{REF:docLinks.whatIsCustomMessage}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.messagesFooter}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "memberAnniversaryMessageLocked": { + "title": "{{REF:list.memberAnniversaryMessagesTitle}}", + "description": [ + "*A random member anniversary message is chosen for each member anniversary. If there are none, the default will be used. [(?)]({{REF:docLinks.whatIsCustomMessage}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.messagesFooter}}", + "icon": "{{ICON}}" + }, + "fields": [ + { + "name": "{{REF:list.messagePaidFieldTitle}}", + "value": "{{REF:list.memberAnniversaryMessageFieldText}}" + } + ], + "timestamp": true + }, + "serverAnniversaryMessageUnLocked": { + "title": "{{REF:list.serverAnniversaryMessagesTitle}}", + "description": [ + "*A random server anniversary message is chosen for each server anniversary. If there are none, the default will be used. [(?)]({{REF:docLinks.whatIsCustomMessage}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.messagesFooter}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "serverAnniversaryMessageLocked": { + "title": "{{REF:list.serverAnniversaryMessagesTitle}}", + "description": [ + "*A random server anniversary message is chosen for each server anniversary. If there are none, the default will be used. [(?)]({{REF:docLinks.whatIsCustomMessage}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.messagesFooter}}", + "icon": "{{ICON}}" + }, + "fields": [ + { + "name": "{{REF:list.messagePaidFieldTitle}}", + "value": "{{REF:list.serverAnniversaryMessageFieldText}}" + } + ], + "timestamp": true + }, + "userSpecificBirthdayMessageUnLocked": { + "title": "{{REF:list.userBirthdayMessagesTitle}}", + "description": [ + "*A user-specific birthday message is the birthday message sent to the designated user on their birthday. [(?)]({{REF:docLinks.whatIsUserSpecificCustomMessage}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.messagesFooter}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "userSpecificBirthdayMessageLocked": { + "title": "{{REF:list.userBirthdayMessagesTitle}}", + "description": [ + "*A user-specific birthday message is the birthday message sent to the designated user on their birthday. [(?)]({{REF:docLinks.whatIsUserSpecificCustomMessage}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.messagesFooter}}", + "icon": "{{ICON}}" + }, + "fields": [ + { + "name": "{{REF:list.userSpecificMessageFieldTitle}}", + "value": "{{REF:list.userSpecificMessageFieldText}}" + } + ], + "timestamp": true + }, + "userSpecificMemberAnniversaryMessageUnLocked": { + "title": "{{REF:list.userMemberAnniversaryMessagesTitle}}", + "description": [ + "*A user-specific member anniversary message is a message sent on that member's member anniversary. [(?)]({{REF:docLinks.whatIsUserSpecificCustomMessage}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.messagesFooter}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "userSpecificMemberAnniversaryMessageLocked": { + "title": "{{REF:list.userMemberAnniversaryMessagesTitle}}", + "description": [ + "*A user-specific member anniversary message is a message sent on that member's member anniversary. [(?)]({{REF:docLinks.whatIsUserSpecificCustomMessage}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.messagesFooter}}", + "icon": "{{ICON}}" + }, + "fields": [ + { + "name": "{{REF:list.userSpecificMessageFieldTitle}}", + "value": "{{REF:list.userSpecificMessageFieldText}}" + } + ], + "timestamp": true + }, + "trustedRoleFree": { + "title": "{{REF:list.trustedRolesTitle}}", + "description": [ + "*A trusted role decides which roles have their birthday celebrated. Edit how the trusted role(s) work with `{{REF:bot.prefix}} config` [(?)]({{REF:docLinks.whatIsTrustedRole}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.rolesFooter}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "trustedRolePaid": { + "title": "{{REF:list.trustedRolesTitle}}", + "description": [ + "*A trusted role decides which roles have their birthday celebrated. Edit how the trusted role(s) work with `{{REF:bot.prefix}} config` [(?)]({{REF:docLinks.whatIsTrustedRole}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.rolesFooter}}", + "icon": "{{ICON}}" + }, + "fields": [ + { + "name": "{{REF:list.trustedRoleFieldTitle}}", + "value": "{{REF:list.trustedRoleFieldText}}" + } + ], + "timestamp": true + }, + "memberAnniversaryRoleFree": { + "title": "{{REF:list.memberAnniversaryRolesTitle}}", + "description": [ + "*A member anniversary role is a given to a user when they hit the given amount of years apart of a discord. [(?)]({{REF:docLinks.whatIsAnniversaryRole}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.rolesFooter}}", + "icon": "{{ICON}}" + }, + "timestamp": true + }, + "memberAnniversaryRolePaid": { + "title": "{{REF:list.memberAnniversaryRolesTitle}}", + "description": [ + "*A member anniversary role is a given to a user when they hit the given amount of years apart of a discord. [(?)]({{REF:docLinks.whatIsAnniversaryRole}})*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "{{REF:list.rolesFooter}}", + "icon": "{{ICON}}" + }, + "fields": [ + { + "name": "{{REF:list.memberAnniversaryRoleFieldTitle}}", + "value": "{{REF:list.memberAnniversaryFieldText}}" + } + ], + "timestamp": true + }, + "blacklist": { + "title": "{{REF:terms.blacklistList}} | {{REF:list.pageTitle}}", + "description": [ + "*Users on this list will not have their birthdays celebrated no matter what. Edit this list with `{{REF:bot.prefix}} blacklist <{{REF:types.add}}/{{REF:types.remove}}> `!*", + "", + "{{LIST_DATA}}" + ], + "footer": { + "text": "Total Blacklisted Users: {{TOTAL_BLACKLIST}} • {{PER_PAGE}} per page", + "icon": "{{ICON}}" + }, + "timestamp": true + } + } + }, + "regexes": { + "meta": { + "language": "/\\b(en-US|en|en US|English)\\b/i" + } + }, + "refs": { + "meta": { + "language": "en-US", + "languageDisplay": "English" + }, + "bot": { + "name": "Birthday Bot", + "description": "{{REF:bots.name}} helps your server track and celebrate birthdays by automatically celebrating them with numerous fun and extremely customizable features.", + "prefix": "bday" + }, + "colors": { + "default": "#4eefff", + "success": "#1cfe86", + "warn": "#ffa255", + "error": "#ff0000", + "role": "#ac1cfe" + }, + "emotes": { + "confirm": "✅", + "deny": "❌", + "create": "🔨", + "select": "🖱", + "birthdayEmote": "🎂", + "party": "🎉" + }, + "links": { + "invite": "https://discord.com/api/oauth2/authorize?client_id=656621136808902656&permissions=268921936&scope=bot%20applications.commands", + "support": "https://discord.gg/9gUQFtz", + "docs": "https://birthdaybot.scottbucher.dev", + "vote": "https://top.gg/bot/656621136808902656/vote", + "donate": "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=PE97AGAPRX35Q¤cy_code=USD&source=url", + "map": "https://kevinnovak.github.io/Time-Zone-Picker/", + "colors": "https://codepen.io/meodai/full/pXNpXe", + "stream": "https://www.twitch.tv/stqlth", + "autopay": "https://www.sandbox.paypal.com/myaccount/autopay", + "inviteEmbed": "[Invite {{REF:bot.name}} to a Server!]({{REF:links.invite}})", + "supportEmbed": "[Join Support Server]({{REF:links.support}})", + "author": "https://github.com/scottbucher", + "authorEmbed": "[Scott Bucher]({{REF:links.author}})", + "template": "https://github.com/KevinNovak/Discord-Bot-TypeScript-Template", + "templateEmbed": "[Discord Bot TypeScript Template]({{REF:links.template}})", + "timeZonePicker": "https://kevinnovak.github.io/Time-Zone-Picker/", + "voteImage": "https://i.imgur.com/wak8g4V.png" + }, + "docLinks": { + "faq": "{{REF:links.docs}}/faq", + "setupRequiredGuide": "{{REF:links.docs}}/server-setup/required-setup", + "setupAnniversaryGuide": "{{REF:links.docs}}/", + "setupTrustedGuide": "{{REF:links.docs}}/server-setup/trusted-setup", + "setupBirthday": "{{REF:links.docs}}/setting-your-birthday", + "whyDoesItNeedMyTimezone": "{{REF:links.docs}}/faq-1/user#why-does-birthday-bot-need-my-timezone", + "whyDoesItNeedOnlyMonthAndDate": "{{REF:links.docs}}/faq-1/user#why-does-birthday-bot-only-need-my-birth-month-and-date", + "whatIsBirthdayRole": "{{REF:links.docs}}/faq-1/birthday-system#what-is-the-birthday-role", + "whatIsBirthdayChannel": "{{REF:links.docs}}/faq-1/birthday-system#what-is-the-birthday-channel", + "whatIsMemberAnniversaryChannel": "{{REF:links.docs}}/faq-1/member-anniversaries#what-is-the-member-anniversary-channel", + "whatIsServerAnniversaryChannel": "{{REF:links.docs}}/faq-1/server-anniversaries#what-is-the-server-anniversary-channel", + "whatIsMessageEmbedColor": "{{REF:links.docs}}/faq-1/custom-messages#what-is-a-message-embed-color", + "whatIsAnEmbed": "{{REF:links.docs}}/faq-1/custom-messages#what-is-a-message-embed-setting", + "whatIsTrustedRole": "{{REF:links.docs}}/faq-1/birthday-system/trusted-system#do-i-need-to-set-up-the-trusted-role", + "whatIsCustomMessage": "{{REF:links.docs}}/faq-1/custom-messages#what-are-the-different-types-of-custom-messages", + "whatIsUserSpecificCustomMessage": "{{REF:links.docs}}/premium-features#user-specific-custom-messages", + "whatIsAnniversaryRole": "{{REF:links.docs}}/premium-features#user-specific-custom-messages", + "trustedPreventsMessage": "{{REF:links.docs}}/faq-1/birthday-system/trusted-system#what-is-the-trusted-prevents-message-role-setting", + "trustedPreventsRole": "{{REF:links.docs}}/faq-1/birthday-system/trusted-system#what-is-the-trusted-prevents-message-role-setting", + "requireAllTrustedRoles": "{{REF:links.docs}}/faq-1/birthday-system/trusted-system#what-is-the-require-all-trusted-role-setting", + "whatIsBirthdayBlacklist": "{{REF:links.docs}}/faq-1/general#what-is-the-birthday-blacklist", + "recentUpdateDoc": "{{REF:links.docs}}/3.0-update" + }, + "footers": { + "expire": "This message expires in 2 minutes!", + "tryAgain": "Please check above and try again!", + "actionFailed": "{{REF:emotes.deny}} Action Failed.", + "irreversible": "This action is irreversible!", + "whyPremium": "Premium helps us support and maintain the bot!", + "joinSupport": "Please join our support server if you have any questions." + }, + "prompts": { + "setupForUser": "Setup For {{TARGET}} - {{VALUE}}", + "userSetup": "User Setup - {{VALUE}}", + "outOfAttempts": "**NOTE**: You do not have any birthday attempts left! Clearing your birthday will mean you can no longer set it!" + }, + "defaults": { + "birthdayMessage": "Happy Birthday {{REF:placeHolders.users}}!", + "memberAnniversaryMessage": "Congratulations on {{REF:placeHolders.year}} year(s) in the server {{REF:placeHolders.users}}!", + "serverAnniversaryMessage": "{{REF:placeHolders.server}} is now {{REF:placeHolders.year}} year(s) old!", + "birthdayChannelName": "{{REF:emotes.birthdayEmote}} birthdays", + "birthdayChannelTopic": "Birthdays Announcements!", + "memberAnniversaryChannelName": "{{REF:emotes.party}} member anniversaries", + "memberAnniversaryChannelTopic": "Member Anniversary Announcements!", + "serverAnniversaryChannelName": "{{REF:emotes.party}} server anniversaries", + "serverAnniversaryChannelTopic": "Server Anniversary Announcements!" + }, + "list": { + "birthdayMessagesTitle": "{{REF:terms.birthdayMessages}} | {{REF:list.pageTitle}}", + "userBirthdayMessagesTitle": "{{REF:terms.userBirthdayMessages}} | {{REF:list.pageTitle}}", + "userMemberAnniversaryMessagesTitle": "{{REF:terms.userMemberAnniversaryMessages}} | {{REF:list.pageTitle}}", + "memberAnniversaryMessagesTitle": "{{REF:terms.memberAnniversaryMessages}} | {{REF:list.pageTitle}}", + "serverAnniversaryMessagesTitle": "{{REF:terms.serverAnniversaryMessages}} {{REF:terms.messages}} | {{REF:list.pageTitle}}", + "trustedRolesTitle": "{{REF:terms.memberAnniversaryRoles}} | {{REF:list.pageTitle}}", + "memberAnniversaryRolesTitle": "{{REF:terms.trustedRoles}} | {{REF:list.pageTitle}}", + "messagesFooter": "Total Message: {{TOTAL_MESSAGES}} • {{PER_PAGE}} per page", + "rolesFooter": "Total Roles: {{TOTAL_ROLES}} • {{PER_PAGE}} per page", + "pageTitle": "Page {{PAGE}}/{{TOTAL_PAGES}}", + "noTrustedRoles": "**No Trusted Roles!**", + "noMemberAnniversaryRoles": "**No MemberAnniversary Roles!**", + "noBirthdays": "**No Birthdays in this server!**", + "noMemberAnniversaries": "**No Member Anniversaries in this server!**", + "emptyBlacklist": "**The Birthday Blacklist is empty!**", + "noCustomBirthdayMessages": "**No Custom Birthday Messages!**", + "noCustomUserSpecificBirthdayMessages": "**No User-Specific Birthday Messages!**", + "noCustomUserSpecificMemberAnnivesaryMessages": "**No User-Specific Member Anniversary Messages!**", + "noCustomMemberAnniversaryMessages": "**No Custom Member Anniversary Messages!**", + "noCustomServerAnniversaryMessages": "**No Custom Server Anniversary Messages!**", + "messagePaidFieldTitle": "Message Limit", + "birthdayMessageFieldText": "The free version of {{REF:bot.name}} can only have up to **{{MAX_FREE}}** custom birthday messages. Unlock up to **{{MAX_PAID}}** with `{{REF:bot.prefix}} premium`!", + "memberAnniversaryMessageFieldText": "The free version of {{REF:bot.name}} can only have up to **{{MAX_FREE}}** custom member anniversary messages. Unlock up to **{{MAX_PAID}}** with `{{REF:bot.prefix}} premium`!", + "serverAnniversaryMessageFieldText": "The free version of {{REF:bot.name}} can only have up to **{{MAX_FREE}}** custom server anniversary messages. Unlock up to **{{MAX_PAID}}** with `{{REF:bot.prefix}} premium`!", + "userSpecificMessageFieldTitle": "Locked Feature", + "userSpecificMessageFieldText": "User-specific messages are a premium only feature. Unlock them with `{{REF:bot.prefix}} premium`!", + "trustedRoleFieldTitle": "Trusted Role Limit", + "trustedRoleFieldText": "The free version of {{REF:bot.name}} can only have up to **{{MAX_FREE}}** trusted roles. Unlock up to **{{MAX_PAID}}** with `{{REF:bot.prefix}} premium`!", + "memberAnniversaryRoleFieldTitle": "Member Anniversary Roles Unavailable", + "memberAnniversaryRoleFieldText": "Member anniversary roles are a premium feature **only**. Unlock up to **{{MAX_PAID}}** with `{{REF:bot.prefix}} premium`!" + }, + "conditionals": { + "needColorForPremium": "Customize the message color with `{{REF:bot.prefix}} premium`!", + "colorForPremium": "Color Hex: `{{COLOR_HEX}}`!" + }, + "terms": { + "timeZone": "Time Zone", + "birthday": "Birthday", + "birthdays": "Birthdays", + "user": "User", + "users": "Users", + "list": "List", + "total": "Total", + "messages": "Messages", + "message": "Message", + "general": "General", + "advanced": "Advanced", + "memberAnniversary": "Member Anniversary", + "serverAnniversary": "Server Anniversary", + "blacklist": "Blacklist", + "blacklisted": "Blacklisted", + "correctlySet": "Correctly Set", + "notSet": "Not Set", + "notSetOrIncorrect": "Not Set (or Incorrectly Set)", + "memberInBlacklist": "Member In Birthday Blacklist", + "inBlacklist": "In Blacklist", + "notInBlacklist": "Not In Blacklist", + "trustedPreventMsg": "Trusted Prevented Message", + "trustedPreventRole": "Trusted Prevented Role", + "didPreventMsg": "Didn't Prevent Message", + "didntPreventMsg": "Prevented Message", + "didPreventRole": "Didn't Prevent Role", + "didntPreventRole": "Prevented Role", + "canBeGiven": "Can be given", + "cantBeGivenPermIssue": "Can't be given (Permission Issue)", + "unknownMember": "Unknown Member", + "unknownChannel": "Unknown Channel", + "unknownRole": "Unknown Role", + "none": "None", + "and": "and", + "deletedRole": "Deleted Role", + "deletedChannel": "Deleted Channel", + "birthdayList": "Birthday List", + "memberAnniversaryList": "Member Anniversary List", + "birthdayMessages": "Birthday Messages", + "userBirthdayMessages": "User-Specific Birthday Messages", + "userMemberAnniversaryMessages": "User-Specific Member Anniversary Messages", + "serverAnniversaryMessages": "Server Anniversary Messages", + "memberAnniversaryMessages": "Member Anniversary Messages", + "birthdayMessage": "Birthday Message", + "userBirthdayMessage": "User-Specific Birthday Message", + "userMemberAnniversaryMessage": "User-Specific Member Anniversary Message", + "serverAnniversaryMessage": "Server Anniversary Message", + "memberAnniversaryMessage": "Member Anniversary Message", + "trustedRoles": "Trusted Roles", + "memberAnniversaryRoles": "Member Anniversary Roles", + "blacklistList": "Blacklist List", + "birthdayChannelTitle": "{{REF:emotes.birthdayEmote}} birthdays", + "birthdayChannelTopic": "Birthday Announcements!", + "memberAnniversaryChannelTitle": "{{REF:emotes.party}} member anniversaries", + "memberAnniversaryChannelTopic": "Member Anniversary Announcements!", + "serverAnniversaryChannelTitle": "{{REF:emotes.party}} server anniversaries", + "serverAnniversaryChannelTopic": "Server Anniversary Announcements!", + "premiumRequired": "Premium Required!", + "active": "Active", + "notActive": "Not Active", + "amTime": "AM", + "pmTime": "PM", + "noOne": "no one", + "na": "N/A", + "change": "Change", + "suggest": "Suggest", + "server": "Server", + "birthdayChannel": "Birthday Channel", + "birthdayRole": "Birthday Role", + "birthdayMasterRole": "Birthday Master Role", + "trustedPreventsRole": "Trusted Prevents Role", + "trustedPreventsMessage": "Trusted Prevents Message", + "requireAllTrustedRoles": "Require All Trusted Roles", + "defaultTimezone": "Default Timezone", + "numberOfTrustedRoles": "Number Of Trusted Roles", + "useTimezone": "Use Timezone", + "nameFormat": "Name Format", + "memberAnniversaryChannel": "Member Anniversary Channel", + "serverAnniversaryChannel": "Server Anniversary Channel", + "birthdayMessageTime": "Birthday Message Time", + "memberAnniversaryMessageTime": "Member Anniversary Time", + "serverAnniversaryMessageTime": "Server Anniversary Time", + "birthdayMention": "Birthday Mention Setting", + "memberAnniversaryMention": "Member Anniversary Mention Setting", + "serverAnniversaryMention": "Server Anniversary Mention Setting", + "serverLanguage": "Server Language", + "never": "Never" + }, + "types": { + "birthday": "birthday", + "memberAnniversary": "memberAnniversary", + "serverAnniversary": "serverAnniversary", + "userSpecificBirthday": "userSpecificBirthday", + "userSpecificMemberAnniversary": "userSpecificMemberAnniversary", + "channel": "channel", + "birthdayRole": "role", + "birthdayMasterRole": "birthdayMasterRole", + "nameFormat": "nameFormat", + "timezone": "timezone", + "useTimezone": "useTimezone", + "trustedPreventsRole": "trustedPreventsRole", + "trustedPreventsMessage": "trustedPreventsMessage", + "requireAllTrustedRoles": "requireAllTrustedRoles", + "alternatives": { + "birthday": ["birthdays"], + "memberAnniversary": ["member"], + "serverAnniversary": ["server"], + "userSpecificBirthday": ["specificBirthday", "userBirthday"], + "userSpecificMemberAnniversary": ["specificAnniversary", "userAnniversary"] + }, + "mention": "mention", + "nickname": "nickname", + "username": "username", + "tag": "tag", + "default": "default", + "add": "add", + "remove": "remove", + "clear": "clear", + "list": "list", + "time": "time", + "useEmbed": "useEmbed", + "help": "help", + "setup": "setup", + "trusted": "trusted", + "anniversary": "anniversary", + "message": "message", + "blacklist": "blacklist", + "advanced": "advanced", + "premium": "premium", + "test": "test", + "create": "create", + "user": "user", + "server": "server", + "claim": "claim" + }, + "placeHolders": { + "users": "", + "usersRegex": "/@users?||{users?}/gi", + "year": "", + "yearRegex": "/@year?||{year?}/gi", + "server": "", + "serverRegex": "/@server?||{server?}/gi" + }, + "permissions": { + "manageRoles": "Manage Roles", + "manageChannels": "Manage Channels" + }, + "boolean": { + "yes": "Yes", + "no": "No", + "true": "True", + "false": "False" + }, + "months": { + "jan": "January", + "feb": "February", + "mar": "March", + "apr": "April", + "may": "May", + "jun": "June", + "july": "July", + "aug": "August", + "sep": "September", + "oct": "October", + "nov": "November", + "dec": "December", + "invalid": "Invalid Month" + }, + "messages": { + "missingEmbedPerms": [ + "I don't have all permissions required to send messages here!", + "", + "Please allow me to **View Channel**, **Send Messages**, **Embed Links**, and **Add Reactions** in this channel." + ] + } + } +} diff --git a/lang/logs.json b/lang/logs.json index 0727f70e..778074ce 100644 --- a/lang/logs.json +++ b/lang/logs.json @@ -9,6 +9,7 @@ "jobCompleted": "Job '{JOB}' completed.", "startedVotingApi": "Started the voting api.", "login": "Logged in as '{USER_TAG}'!", + "allShardsSpawned": "All shards have been spawned.", "updatedServerCount": "Updated server count. Connected to {SERVER_COUNT} total servers.", "updateServerCountSite": "Updated server count on '{BOT_SITE}'.", "guildJoined": "Guild '{GUILD_NAME}' ({GUILD_ID}) joined.", @@ -36,15 +37,17 @@ "job": "An error occurred while running the '{JOB}' job.", "resolveGuild": "Guild '{GUILD_NAME}' ({GUILD_ID}) could not be resolved even though it is in the cache.", "message": "An error occurred while processing a message.", + "reaction": "An error occurred while processing a reaction.", "rateLimit": "A rate limit was hit while making a request.", "commandDm": "[{MESSAGE_ID}] An error occurred while executing the '{COMMAND_NAME}' command for user '{SENDER_TAG}' ({SENDER_ID}) in direct message.", "birthdayJob": "An error occurred while running the birthday job.", - "celebrateBirthday": "An error occurred while trying to celebrate birthdays for guild '{GUILD_NAME}' ({GUILD_ID}).", "messagePartial": "An error occurred while trying to fetch a message partial.", "userFetch": "An error occurred while trying to fetch the users of a reaction.", "fetchMembers": "An error occurred while trying to fetch the members for the guild '{GUILD_NAME}' ({GUILD_ID}).", - "birthdayService": "An error occurred while trying run the birthday service for '{GUILD_NAME}' ({GUILD_ID}) with {MEMBER_COUNT} members and {MEMBER_CACHE_BEFORE_COUNT} in the cache before and {MEMBER_CACHE_AFTER_COUNT} after the fetch.", + "fetchMemberError": "An error occurred while trying to fetch the members for '{GUILD_NAME}' ({GUILD_ID}) with {MEMBER_COUNT} members and {MEMBER_CACHE_BEFORE_COUNT} in the cache before and {MEMBER_CACHE_AFTER_COUNT} after the fetch.", "commandGuild": "[{MESSAGE_ID}] An error occurred while executing the '{COMMAND_KEYWORD}' command for user '{SENDER_TAG}' ({SENDER_ID}) in channel '{CHANNEL_NAME}' ({CHANNEL_ID}) in guild '{GUILD_NAME}' ({GUILD_ID}).", - "apiRequest": "An error occurred while processing a '{HTTP_METHOD}' request to '{URL}'." + "apiRequest": "An error occurred while processing a '{HTTP_METHOD}' request to '{URL}'.", + "roleServiceFailedForGuild": "An error occurred while running the role service for '{GUILD_NAME}' ({GUILD_ID})!", + "messageServiceFailedForGuild": "An error occurred while running the message service for '{GUILD_NAME}' ({GUILD_ID})!" } } diff --git a/misc/Birthday Bot Cluster API.postman_collection.json b/misc/Birthday Bot Cluster API.postman_collection.json index ae8e96b4..ea44c6a5 100644 --- a/misc/Birthday Bot Cluster API.postman_collection.json +++ b/misc/Birthday Bot Cluster API.postman_collection.json @@ -15,12 +15,8 @@ "header": [], "url": { "raw": "{{BASE_URL}}/shards", - "host": [ - "{{BASE_URL}}" - ], - "path": [ - "shards" - ] + "host": ["{{BASE_URL}}"], + "path": ["shards"] } }, "response": [] @@ -41,13 +37,8 @@ }, "url": { "raw": "{{BASE_URL}}/shards/presence", - "host": [ - "{{BASE_URL}}" - ], - "path": [ - "shards", - "presence" - ] + "host": ["{{BASE_URL}}"], + "path": ["shards", "presence"] } }, "response": [] @@ -64,12 +55,8 @@ "header": [], "url": { "raw": "{{BASE_URL}}/guilds", - "host": [ - "{{BASE_URL}}" - ], - "path": [ - "guilds" - ] + "host": ["{{BASE_URL}}"], + "path": ["guilds"] } }, "response": [] @@ -83,9 +70,7 @@ "header": [], "url": { "raw": "{{BASE_URL}}", - "host": [ - "{{BASE_URL}}" - ] + "host": ["{{BASE_URL}}"] } }, "response": [] @@ -111,18 +96,14 @@ "listen": "prerequest", "script": { "type": "text/javascript", - "exec": [ - "" - ] + "exec": [""] } }, { "listen": "test", "script": { "type": "text/javascript", - "exec": [ - "" - ] + "exec": [""] } } ], @@ -132,4 +113,4 @@ "value": "localhost:8080" } ] -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 243e7244..267382e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,61 +8,74 @@ "name": "birthday-bot", "version": "1.0.0", "dependencies": { - "chrono-node": "2.2.5", + "chrono-node": "2.3.0", "class-transformer": "^0.4.0", "class-validator": "^0.13.1", - "color-name-list": "7.37.0", - "discord.js": "12.5.1", - "discord.js-collector-utils": "1.0.24", - "discord.js-rate-limiter": "^1.0.5", + "color-name-list": "8.13.0", + "discord.js": "12.5.3", + "discord.js-collector-utils": "1.1.0", + "discord.js-multilingual-utils": "^1.5.0", + "discord.js-rate-limiter": "^1.1.0", "express": "4.17.1", "express-promise-router": "^4.1.0", - "limiter": "1.1.5", + "filesize": "7.0.0", "moment-timezone": "0.5.33", "mysql": "2.18.1", "node-fetch": "2.6.1", "node-schedule": "2.0.0", - "pm2": "^4.5.6", - "reflect-metadata": "^0.1.13" + "pm2": "^5.1.0", + "reflect-metadata": "^0.1.13", + "remove-markdown": "0.3.0" }, "devDependencies": { - "@types/express": "4.17.11", - "@types/mysql": "2.15.18", - "@types/node": "^14.14.35", - "@types/node-fetch": "2.5.8", - "@types/node-schedule": "1.3.1", - "prettier": "^2.2.1", + "@types/express": "4.17.13", + "@types/mysql": "2.15.19", + "@types/node": "^16.3.1", + "@types/node-fetch": "2.5.11", + "@types/node-schedule": "1.3.2", + "@types/remove-markdown": "^0.3.1", + "prettier": "^2.3.2", + "ts-node-dev": "^1.1.8", "tslint": "^6.1.3", - "typescript": "^4.2.3" + "typescript": "^4.3.5" }, "engines": { - "node": ">=12.14.0" + "node": ">=15.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "dependencies": { - "@babel/highlight": "^7.10.4" + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/highlight/node_modules/ansi-styles": { @@ -213,22 +226,23 @@ } }, "node_modules/@pm2/agent": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-1.0.8.tgz", - "integrity": "sha512-r8mud8BhBz+a2yjlgtk+PBXUR5EQ9UKSJCs232OxfCmuBr1MZw0Mo+Kfog6WJ8OmVk99r1so9yTUK4IyrgGcMQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.0.tgz", + "integrity": "sha512-W1LvdyF1tGaVU5f/hV8DjpI5joI7MEgXiQMLZnTwZlFwDVP00O9s86571Q8xSiweTcFZFyye0F4wORN/PjSgGA==", "dependencies": { "async": "~3.2.0", "chalk": "~3.0.0", "dayjs": "~1.8.24", "debug": "~4.3.1", "eventemitter2": "~5.0.1", + "fast-json-patch": "^3.0.0-1", "fclone": "~1.0.11", "nssocket": "0.6.0", "pm2-axon": "~4.0.1", "pm2-axon-rpc": "~0.7.0", "proxy-agent": "~4.0.1", "semver": "~7.2.0", - "ws": "~7.2.0" + "ws": "~7.4.0" } }, "node_modules/@pm2/agent/node_modules/dayjs": { @@ -264,9 +278,9 @@ } }, "node_modules/@pm2/agent/node_modules/ws": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", - "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==", + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", "engines": { "node": ">=8.3.0" } @@ -405,9 +419,9 @@ } }, "node_modules/@types/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", "dev": true, "dependencies": { "@types/connect": "*", @@ -415,18 +429,18 @@ } }, "node_modules/@types/connect": { - "version": "3.4.34", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", - "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/express": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", - "integrity": "sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==", + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", "dev": true, "dependencies": { "@types/body-parser": "*", @@ -436,9 +450,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz", - "integrity": "sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", "dev": true, "dependencies": { "@types/node": "*", @@ -447,30 +461,30 @@ } }, "node_modules/@types/mime": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", - "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", "dev": true }, "node_modules/@types/mysql": { - "version": "2.15.18", - "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.18.tgz", - "integrity": "sha512-JW74Nh3P/RDAnaP8uXe1qmRpoFBO84SiWvWoSju/F5+2S1kVBi1FbbDoqK/sTZrCCxySaOJnRATvWD+bLcJjAg==", + "version": "2.15.19", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.19.tgz", + "integrity": "sha512-wSRg2QZv14CWcZXkgdvHbbV2ACufNy5EgI8mBBxnJIptchv7DBy/h53VMa2jDhyo0C9MO4iowE6z9vF8Ja1DkQ==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/node": { - "version": "14.14.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz", - "integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.1.tgz", + "integrity": "sha512-N87VuQi7HEeRJkhzovao/JviiqKjDKMVKxKMfUvSKw+MbkbW8R0nA3fi/MQhhlxV2fQ+2ReM+/Nt4efdrJx3zA==", "dev": true }, "node_modules/@types/node-fetch": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz", - "integrity": "sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==", + "version": "2.5.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.11.tgz", + "integrity": "sha512-2upCKaqVZETDRb8A2VTaRymqFBEgH8u6yr96b/u3+1uQEPDRo3mJLEiPk7vdXBHRtjwkjqzFYMJXrt0Z9QsYjQ==", "dev": true, "dependencies": { "@types/node": "*", @@ -478,40 +492,58 @@ } }, "node_modules/@types/node-schedule": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-1.3.1.tgz", - "integrity": "sha512-xAY/ZATrThUkMElSDfOk+5uXprCrV6c6GQ5gTw3U04qPS6NofE1dhOUW+yrOF2UyrUiAax/Zc4WtagrbPAN3Tw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-1.3.2.tgz", + "integrity": "sha512-Y0CqdAr+lCpArT8CJJjJq4U2v8Bb5e7ru2nV/NhDdaptCMCRdOL3Y7tAhen39HluQMaIKWvPbDuiFBUQpg7Srw==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==", + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, "node_modules/@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "node_modules/@types/remove-markdown": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@types/remove-markdown/-/remove-markdown-0.3.1.tgz", + "integrity": "sha512-JpJNEJEsmmltyL2LdE8KRjJ0L2ad5vgLibqNj85clohT9AyTrfN6jvHxStPshDkmtcL/ShFu0p2tbY7DBS1mqQ==", "dev": true }, "node_modules/@types/serve-static": { - "version": "1.13.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.8.tgz", - "integrity": "sha512-MoJhSQreaVoL+/hurAZzIm8wafFR6ajiTM1m4A0kv6AGeVBl4r4pOV8bGFrjjq1sGxDTnCoF8i22o0/aE5XCyA==", + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", "dev": true, "dependencies": { - "@types/mime": "*", + "@types/mime": "^1", "@types/node": "*" } }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", + "dev": true + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, "node_modules/@types/validator": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.1.3.tgz", - "integrity": "sha512-DaOWN1zf7j+8nHhqXhIgNmS+ltAC53NXqGxYuBhWqWgqolRhddKzfZU814lkHQSTG0IUfQxU7Cg0gb8fFWo2mA==" + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.6.0.tgz", + "integrity": "sha512-Cnhn4IPT9lme5SVVT5fvXsgXAer9AoQR3miFgoM541snaeLbQ31zQ37wmR+GJVHrLZDB24GcO2mLQtdVrkMawg==" }, "node_modules/abort-controller": { "version": "3.0.0", @@ -596,9 +628,9 @@ } }, "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -607,6 +639,12 @@ "node": ">= 8" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -637,9 +675,9 @@ } }, "node_modules/ast-types/node_modules/tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" }, "node_modules/async": { "version": "3.2.0", @@ -680,9 +718,9 @@ } }, "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/bignumber.js": { "version": "9.0.0", @@ -736,6 +774,11 @@ "node": ">= 0.8" } }, + "node_modules/boolean": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.2.tgz", + "integrity": "sha512-YN6UmV0FfLlBVvRvNPx3pz5W/mUoYB24J4WSXOKP/OOJpi+Oq6WYqPaNTHzjI0QzwWtnvEd5CGYyQPgp1jFxnw==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -805,29 +848,30 @@ "integrity": "sha1-BsIe7RobBq62dVPNxT4jJ0usIpY=" }, "node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", "dependencies": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "glob-parent": "~5.1.0", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" }, "engines": { "node": ">= 8.10.0" }, "optionalDependencies": { - "fsevents": "~2.3.1" + "fsevents": "~2.3.2" } }, "node_modules/chrono-node": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.2.5.tgz", - "integrity": "sha512-ZXkMRKIncvidcLxD07yYpTWBP1J7bWjM57JmrTVd7KGgNXmsFb+LtDvHTSWd61zJMlicIqYka3c/clOqqEWRng==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.3.0.tgz", + "integrity": "sha512-70DhMEY3RiwB/9+Fstpu9cbI+ycO/WNHxCOI1Lvo+LLiVdhMVOJ8B3jzXgZHCiGbZc7SKHFsP7apeeuP/EAw+Q==", "dependencies": { "dayjs": "^1.10.0" } @@ -875,9 +919,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color-name-list": { - "version": "7.37.0", - "resolved": "https://registry.npmjs.org/color-name-list/-/color-name-list-7.37.0.tgz", - "integrity": "sha512-XZoS2zvIl42u9Suck7VinrDWdUqndnYgs+eEaPFs8YKf8vXDsdbya3q6VSyP8Tm4QnVNKq7nowOx7ngi6HFucg==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/color-name-list/-/color-name-list-8.13.0.tgz", + "integrity": "sha512-phn/kxG/XEX4GajQoIyXOZVI7Z5edByyryefqS0NmuQecQb52tpD3H60nDEBTP3uylpKrroJ0etHWZmgBN/U0Q==", "engines": { "node": ">=8", "npm": ">=5" @@ -950,6 +994,12 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cron": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", @@ -959,12 +1009,12 @@ } }, "node_modules/cron-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.3.0.tgz", - "integrity": "sha512-Tz5WxSrDVhR7YVuLuUxhXoMGHCWoe8I7g8APkyRZNaINXHQ3zcfK97av6hBYy9ue4sOLI7ZH7SeQqi/t4jR+5A==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.5.0.tgz", + "integrity": "sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==", "dependencies": { - "is-nan": "^1.3.0", - "luxon": "^1.25.0" + "is-nan": "^1.3.2", + "luxon": "^1.26.0" }, "engines": { "node": ">=0.8" @@ -984,9 +1034,9 @@ } }, "node_modules/dayjs": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.3.tgz", - "integrity": "sha512-/2fdLN987N8Ki7Id8BUN2nhuiRyxTLumQnSQf9CNncFCyqFsSKb9TNhzRYcC8K8eJSJOKvbvkImo/MKKhNi4iw==" + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.5.tgz", + "integrity": "sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g==" }, "node_modules/debug": { "version": "2.6.9", @@ -1056,40 +1106,50 @@ } }, "node_modules/discord.js": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", - "integrity": "sha512-VwZkVaUAIOB9mKdca0I5MefPMTQJTNg0qdgi1huF3iwsFwJ0L5s/Y69AQe+iPmjuV6j9rtKoG0Ta0n9vgEIL6w==", + "version": "12.5.3", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", + "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", "dependencies": { "@discordjs/collection": "^0.1.6", "@discordjs/form-data": "^3.0.1", "abort-controller": "^3.0.0", "node-fetch": "^2.6.1", - "prism-media": "^1.2.2", + "prism-media": "^1.2.9", "setimmediate": "^1.0.5", "tweetnacl": "^1.0.3", - "ws": "^7.3.1" + "ws": "^7.4.4" }, "engines": { "node": ">=12.0.0" } }, "node_modules/discord.js-collector-utils": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/discord.js-collector-utils/-/discord.js-collector-utils-1.0.24.tgz", - "integrity": "sha512-SAGdRwKb6V/0GVXuvNmPAKjJD6YHAXj6X0uTvpjsmmYGoucAE49Pq+uYrOmMdy3D0o2l+ncNzFnRxkmFXwkAXw==", - "dependencies": { - "discord.js": "12.5.1" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/discord.js-collector-utils/-/discord.js-collector-utils-1.1.0.tgz", + "integrity": "sha512-42iL8v3a+m/mcgwDJdamvRYMDJ0DDcfB9Xkydli5KeZMiwWfCHPrf9zXbzPzmPOtCOh0JvJpi8ZJEVRBNDjgwQ==" + }, + "node_modules/discord.js-multilingual-utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/discord.js-multilingual-utils/-/discord.js-multilingual-utils-1.5.0.tgz", + "integrity": "sha512-GZVMovYZazbrmQVDyL27aK6dVUXNqOfI3PKGW6in/ZX9NtephbvEW/5NTW9vgIC2PAcK2ZTkxQEaeP45b3rebA==" }, "node_modules/discord.js-rate-limiter": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/discord.js-rate-limiter/-/discord.js-rate-limiter-1.0.5.tgz", - "integrity": "sha512-f50AADgJ2vYDs/RHOvAZSC7V3rWmZtf3JuYZ4U7lVY4/ud/EILa7UaPnrOp/T8iPTLKgdpQw8lpX/F/HPImgIA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/discord.js-rate-limiter/-/discord.js-rate-limiter-1.1.0.tgz", + "integrity": "sha512-Mo+wj6Y+PHlCUCtcOIt2Aoph0xcPFND9XxzJyFbDKTLfJOoxDZVckrWEu9Y7bRkHmAUElgz2oq3vDDUbWbIUWA==", "dependencies": { - "discord.js": "12.5.1", "limiter": "1.1.5" } }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", + "dev": true, + "dependencies": { + "xtend": "^4.0.0" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1256,11 +1316,27 @@ "node": ">=10" } }, + "node_modules/fast-json-patch": { + "version": "3.0.0-1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.0.0-1.tgz", + "integrity": "sha512-6pdFb07cknxvPzCeLsFHStEy+MysPJPgZQ9LbQ/2O67unQF93SNqfdSqnPPl71YMHX+AD8gbl7iuoGFzHEdDuw==" + }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "node_modules/fast-printf": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.5.tgz", + "integrity": "sha512-0+bBTCT4SOmHg3NFsPO39s+EwUdQiKig4MMye7fM0ea24YprUW35AZuWVQdE0SMBRI/GIQldj8ydQGuVRE95UQ==", + "dependencies": { + "boolean": "^3.0.2" + }, + "engines": { + "node": ">=10.0" + } + }, "node_modules/fclone": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", @@ -1274,6 +1350,14 @@ "node": ">= 6" } }, + "node_modules/filesize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-7.0.0.tgz", + "integrity": "sha512-Wsstw+O1lZ9gVmOI1thyeQvODsaoId2qw14lCqIzUhoHKXX7T2hVpB7BR6SvgodMBgWccrx/y2eyV8L7tDmY6A==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -1303,17 +1387,17 @@ } }, "node_modules/follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", "engines": { "node": ">=4.0" } }, "node_modules/form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", "dev": true, "dependencies": { "asynckit": "^0.4.0", @@ -1325,9 +1409,9 @@ } }, "node_modules/forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "engines": { "node": ">= 0.6" } @@ -1461,9 +1545,9 @@ "integrity": "sha1-WZrBkrcYdYJeE6RF86bgURjC90U=" }, "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1646,9 +1730,9 @@ } }, "node_modules/is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", + "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", "dependencies": { "has": "^1.0.3" } @@ -1732,18 +1816,11 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/js-yaml/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "optional": true }, "node_modules/jsonfile": { "version": "4.0.0", @@ -1774,9 +1851,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.9.16", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.16.tgz", - "integrity": "sha512-PaHT7nTtnejZ0HHekAaA0olv6BUTKZGtKM4SCQS0yE3XjFuVo/tjePMHUAr32FKwIZfyPky1ExMUuaiBAUmV6w==" + "version": "1.9.20", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.20.tgz", + "integrity": "sha512-fZw/XGE19SdRtcmAa5mEUjhGuFrVsT6x3V07TLt36psLbJnZiAdI0i/h4JSufKGXFCyreYM7gP98julXfO1XNA==" }, "node_modules/limiter": { "version": "1.1.5", @@ -1815,13 +1892,19 @@ } }, "node_modules/luxon": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.26.0.tgz", - "integrity": "sha512-+V5QIQ5f6CDXQpWNICELwjwuHdqeJM1UenlZWx5ujcRMc9venvluCjFb4t5NYLhb6IhkbMVOxzVuOqkgMxee2A==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.27.0.tgz", + "integrity": "sha512-VKsFsPggTA0DvnxtJdiExAucKdAnwbCCNlMM5ENvHlxubqWd0xhZcdb4XgZ7QFNhaRhilXCFxHuoObP5BNA4PA==", "engines": { "node": "*" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1855,19 +1938,19 @@ } }, "node_modules/mime-db": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", - "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.28", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", - "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", "dependencies": { - "mime-db": "1.45.0" + "mime-db": "1.48.0" }, "engines": { "node": ">= 0.6" @@ -2153,9 +2236,9 @@ } }, "node_modules/path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { "version": "0.1.7", @@ -2163,9 +2246,9 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "engines": { "node": ">=8.6" } @@ -2187,11 +2270,11 @@ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "node_modules/pm2": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/pm2/-/pm2-4.5.6.tgz", - "integrity": "sha512-4J5q704Xl6VmpmQhXFGMJL4kXyyQw3AZM1FE9vRxhS3LiDI/+WVBtOM6pqJ4g/RKW+AUjEkc23i/DCC4BVenDA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-5.1.0.tgz", + "integrity": "sha512-reJ35NOxM4+g7H0enW47HJsp32CszKkseCojAuUMUkffyXsGDKBMnDqhxAZMZKtHUUjl0cWFEqKehqB0ODH8Kw==", "dependencies": { - "@pm2/agent": "~1.0.8", + "@pm2/agent": "~2.0.0", "@pm2/io": "~5.0.0", "@pm2/js-api": "~0.6.7", "@pm2/pm2-version-check": "^1.0.4", @@ -2206,20 +2289,20 @@ "debug": "^4.3.1", "enquirer": "2.3.6", "eventemitter2": "5.0.1", + "fast-printf": "^1.3.0", "fclone": "1.0.11", "mkdirp": "1.0.4", "needle": "2.4.0", "pidusage": "2.0.21", "pm2-axon": "~4.0.1", - "pm2-axon-rpc": "~0.7.0", + "pm2-axon-rpc": "~0.7.1", "pm2-deploy": "~1.0.2", "pm2-multimeter": "^0.1.2", + "pm2-sysmonit": "^1.2.8", "promptly": "^2", - "ps-list": "6.3.0", "semver": "^7.2", "source-map-support": "0.5.19", - "sprintf-js": "1.1.2", - "vizion": "2.2.1", + "vizion": "~2.2.1", "yamljs": "0.3.0" }, "bin": { @@ -2229,7 +2312,10 @@ "pm2-runtime": "bin/pm2-runtime" }, "engines": { - "node": ">=8.10.0" + "node": ">=10.0.0" + }, + "optionalDependencies": { + "pm2-sysmonit": "^1.2.8" } }, "node_modules/pm2-axon": { @@ -2309,6 +2395,37 @@ "charm": "~0.1.1" } }, + "node_modules/pm2-sysmonit": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", + "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", + "optional": true, + "dependencies": { + "async": "^3.2.0", + "debug": "^4.3.1", + "pidusage": "^2.0.21", + "systeminformation": "^5.7", + "tx2": "~1.0.4" + } + }, + "node_modules/pm2-sysmonit/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "optional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/pm2-sysmonit/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + }, "node_modules/pm2/node_modules/dayjs": { "version": "1.8.36", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", @@ -2339,9 +2456,9 @@ } }, "node_modules/prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", "dev": true, "bin": { "prettier": "bin-prettier.js" @@ -2351,9 +2468,9 @@ } }, "node_modules/prism-media": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", - "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.1.tgz", + "integrity": "sha512-nyYAa3KB4qteJIqdguKmwxTJgy55xxUtkJ3uRnOvO5jO+frci+9zpRXw6QZVcfDeva3S654fU9+26P2OSTzjHw==" }, "node_modules/process-nextick-args": { "version": "2.0.1", @@ -2369,11 +2486,11 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dependencies": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" }, "engines": { @@ -2419,14 +2536,6 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "node_modules/ps-list": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-6.3.0.tgz", - "integrity": "sha512-qau0czUSB0fzSlBOQt0bo+I2v6R+xiQdj78e1BR/Qjfl5OHWJ/urXi8+ilw1eHe+5hSeDI1wrwVTgDp2wst4oA==", - "engines": { - "node": ">=8" - } - }, "node_modules/qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -2483,9 +2592,9 @@ } }, "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dependencies": { "picomatch": "^2.2.1" }, @@ -2498,6 +2607,11 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" }, + "node_modules/remove-markdown": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.3.0.tgz", + "integrity": "sha1-XktmdJOpNXlyjz1S7MHbnKUF3Jg=" + }, "node_modules/require-in-the-middle": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.1.0.tgz", @@ -2525,14 +2639,26 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dependencies": { - "is-core-module": "^2.1.0", + "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/run-series": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", @@ -2655,9 +2781,9 @@ } }, "node_modules/socks": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.0.tgz", - "integrity": "sha512-mNmr9owlinMplev0Wd7UHFlqI4ofnBnNzFuzrm63PPaHgbkqCFe4T5LzwKmtQ/f2tX0NTpcdVLyD/FHxFBstYw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", "dependencies": { "ip": "^1.1.5", "smart-buffer": "^4.1.0" @@ -2668,11 +2794,11 @@ } }, "node_modules/socks-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz", - "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz", + "integrity": "sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==", "dependencies": { - "agent-base": "6", + "agent-base": "^6.0.2", "debug": "4", "socks": "^2.3.3" }, @@ -2718,11 +2844,6 @@ "source-map": "^0.6.0" } }, - "node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" - }, "node_modules/sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", @@ -2747,6 +2868,24 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2758,6 +2897,27 @@ "node": ">=8" } }, + "node_modules/systeminformation": { + "version": "5.7.7", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.7.7.tgz", + "integrity": "sha512-aQ7MBeVI2MKPYOi3YJAoZ45JVlRkBA7IXoqGgtVBamvtE0I6JLOyJzD/VVc9pnMXDb3yqaMwssAjhwtJax4/Rw==", + "optional": true, + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2777,6 +2937,75 @@ "node": ">=0.6" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "dependencies": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ts-node-dev": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.1.8.tgz", + "integrity": "sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.5", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^9.0.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, "node_modules/tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -2929,6 +3158,15 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, + "node_modules/tx2": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.4.tgz", + "integrity": "sha512-rU+y30nUY3PyIi+znvv74HzxlpULKwMPAyRK+YiCjvGkk3rY3fic3D6Z+avLpun3V5A6HFwPQ9JrBTMNEV/dxg==", + "optional": true, + "dependencies": { + "json-stringify-safe": "^5.0.1" + } + }, "node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -2953,9 +3191,9 @@ } }, "node_modules/typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -3003,9 +3241,9 @@ } }, "node_modules/validator": { - "version": "13.5.2", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.5.2.tgz", - "integrity": "sha512-mD45p0rvHVBlY2Zuy3F3ESIe1h5X58GPfAtslBjY7EtTqGquZTj+VX/J4RnHWN8FKq0C9WRVt1oWAcytWRuYLQ==", + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", + "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==", "engines": { "node": ">= 0.10" } @@ -3054,9 +3292,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/ws": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", - "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.1.tgz", + "integrity": "sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==", "engines": { "node": ">=8.3.0" } @@ -3066,6 +3304,15 @@ "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -3083,31 +3330,40 @@ "json2yaml": "bin/json2yaml", "yaml2json": "bin/yaml2json" } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } } }, "dependencies": { "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.10.4" + "@babel/highlight": "^7.14.5" } }, "@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", + "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", "dev": true }, "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -3233,22 +3489,23 @@ } }, "@pm2/agent": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-1.0.8.tgz", - "integrity": "sha512-r8mud8BhBz+a2yjlgtk+PBXUR5EQ9UKSJCs232OxfCmuBr1MZw0Mo+Kfog6WJ8OmVk99r1so9yTUK4IyrgGcMQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.0.0.tgz", + "integrity": "sha512-W1LvdyF1tGaVU5f/hV8DjpI5joI7MEgXiQMLZnTwZlFwDVP00O9s86571Q8xSiweTcFZFyye0F4wORN/PjSgGA==", "requires": { "async": "~3.2.0", "chalk": "~3.0.0", "dayjs": "~1.8.24", "debug": "~4.3.1", "eventemitter2": "~5.0.1", + "fast-json-patch": "^3.0.0-1", "fclone": "~1.0.11", "nssocket": "0.6.0", "pm2-axon": "~4.0.1", "pm2-axon-rpc": "~0.7.0", "proxy-agent": "~4.0.1", "semver": "~7.2.0", - "ws": "~7.2.0" + "ws": "~7.4.0" }, "dependencies": { "dayjs": { @@ -3275,9 +3532,9 @@ "integrity": "sha512-utbW9Z7ZxVvwiIWkdOMLOR9G/NFXh2aRucghkVrEMJWuC++r3lCkBC3LwqBinyHzGMAJxY5tn6VakZGHObq5ig==" }, "ws": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.5.tgz", - "integrity": "sha512-C34cIU4+DB2vMyAbmEKossWq2ZQDr6QEyuuCzWrM9zfw1sGc0mYiJ0UnG9zzNykt49C2Fi34hvr2vssFQRS6EA==" + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==" } } }, @@ -3400,9 +3657,9 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==" }, "@types/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", "dev": true, "requires": { "@types/connect": "*", @@ -3410,18 +3667,18 @@ } }, "@types/connect": { - "version": "3.4.34", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", - "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", "dev": true, "requires": { "@types/node": "*" } }, "@types/express": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", - "integrity": "sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==", + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", "dev": true, "requires": { "@types/body-parser": "*", @@ -3431,9 +3688,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz", - "integrity": "sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", "dev": true, "requires": { "@types/node": "*", @@ -3442,30 +3699,30 @@ } }, "@types/mime": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", - "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", "dev": true }, "@types/mysql": { - "version": "2.15.18", - "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.18.tgz", - "integrity": "sha512-JW74Nh3P/RDAnaP8uXe1qmRpoFBO84SiWvWoSju/F5+2S1kVBi1FbbDoqK/sTZrCCxySaOJnRATvWD+bLcJjAg==", + "version": "2.15.19", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.19.tgz", + "integrity": "sha512-wSRg2QZv14CWcZXkgdvHbbV2ACufNy5EgI8mBBxnJIptchv7DBy/h53VMa2jDhyo0C9MO4iowE6z9vF8Ja1DkQ==", "dev": true, "requires": { "@types/node": "*" } }, "@types/node": { - "version": "14.14.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz", - "integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.3.1.tgz", + "integrity": "sha512-N87VuQi7HEeRJkhzovao/JviiqKjDKMVKxKMfUvSKw+MbkbW8R0nA3fi/MQhhlxV2fQ+2ReM+/Nt4efdrJx3zA==", "dev": true }, "@types/node-fetch": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz", - "integrity": "sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==", + "version": "2.5.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.11.tgz", + "integrity": "sha512-2upCKaqVZETDRb8A2VTaRymqFBEgH8u6yr96b/u3+1uQEPDRo3mJLEiPk7vdXBHRtjwkjqzFYMJXrt0Z9QsYjQ==", "dev": true, "requires": { "@types/node": "*", @@ -3473,40 +3730,58 @@ } }, "@types/node-schedule": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-1.3.1.tgz", - "integrity": "sha512-xAY/ZATrThUkMElSDfOk+5uXprCrV6c6GQ5gTw3U04qPS6NofE1dhOUW+yrOF2UyrUiAax/Zc4WtagrbPAN3Tw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-1.3.2.tgz", + "integrity": "sha512-Y0CqdAr+lCpArT8CJJjJq4U2v8Bb5e7ru2nV/NhDdaptCMCRdOL3Y7tAhen39HluQMaIKWvPbDuiFBUQpg7Srw==", "dev": true, "requires": { "@types/node": "*" } }, "@types/qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==", + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, "@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/remove-markdown": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@types/remove-markdown/-/remove-markdown-0.3.1.tgz", + "integrity": "sha512-JpJNEJEsmmltyL2LdE8KRjJ0L2ad5vgLibqNj85clohT9AyTrfN6jvHxStPshDkmtcL/ShFu0p2tbY7DBS1mqQ==", "dev": true }, "@types/serve-static": { - "version": "1.13.8", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.8.tgz", - "integrity": "sha512-MoJhSQreaVoL+/hurAZzIm8wafFR6ajiTM1m4A0kv6AGeVBl4r4pOV8bGFrjjq1sGxDTnCoF8i22o0/aE5XCyA==", + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", "dev": true, "requires": { - "@types/mime": "*", + "@types/mime": "^1", "@types/node": "*" } }, + "@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", + "dev": true + }, + "@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, "@types/validator": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.1.3.tgz", - "integrity": "sha512-DaOWN1zf7j+8nHhqXhIgNmS+ltAC53NXqGxYuBhWqWgqolRhddKzfZU814lkHQSTG0IUfQxU7Cg0gb8fFWo2mA==" + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.6.0.tgz", + "integrity": "sha512-Cnhn4IPT9lme5SVVT5fvXsgXAer9AoQR3miFgoM541snaeLbQ31zQ37wmR+GJVHrLZDB24GcO2mLQtdVrkMawg==" }, "abort-controller": { "version": "3.0.0", @@ -3575,14 +3850,20 @@ } }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3612,9 +3893,9 @@ }, "dependencies": { "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } } }, @@ -3653,9 +3934,9 @@ } }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "bignumber.js": { "version": "9.0.0", @@ -3694,6 +3975,11 @@ "type-is": "~1.6.17" } }, + "boolean": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.2.tgz", + "integrity": "sha512-YN6UmV0FfLlBVvRvNPx3pz5W/mUoYB24J4WSXOKP/OOJpi+Oq6WYqPaNTHzjI0QzwWtnvEd5CGYyQPgp1jFxnw==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3751,24 +4037,24 @@ "integrity": "sha1-BsIe7RobBq62dVPNxT4jJ0usIpY=" }, "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", "requires": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" } }, "chrono-node": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.2.5.tgz", - "integrity": "sha512-ZXkMRKIncvidcLxD07yYpTWBP1J7bWjM57JmrTVd7KGgNXmsFb+LtDvHTSWd61zJMlicIqYka3c/clOqqEWRng==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chrono-node/-/chrono-node-2.3.0.tgz", + "integrity": "sha512-70DhMEY3RiwB/9+Fstpu9cbI+ycO/WNHxCOI1Lvo+LLiVdhMVOJ8B3jzXgZHCiGbZc7SKHFsP7apeeuP/EAw+Q==", "requires": { "dayjs": "^1.10.0" } @@ -3810,9 +4096,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "color-name-list": { - "version": "7.37.0", - "resolved": "https://registry.npmjs.org/color-name-list/-/color-name-list-7.37.0.tgz", - "integrity": "sha512-XZoS2zvIl42u9Suck7VinrDWdUqndnYgs+eEaPFs8YKf8vXDsdbya3q6VSyP8Tm4QnVNKq7nowOx7ngi6HFucg==" + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/color-name-list/-/color-name-list-8.13.0.tgz", + "integrity": "sha512-phn/kxG/XEX4GajQoIyXOZVI7Z5edByyryefqS0NmuQecQb52tpD3H60nDEBTP3uylpKrroJ0etHWZmgBN/U0Q==" }, "combined-stream": { "version": "1.0.8", @@ -3869,6 +4155,12 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cron": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", @@ -3878,12 +4170,12 @@ } }, "cron-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.3.0.tgz", - "integrity": "sha512-Tz5WxSrDVhR7YVuLuUxhXoMGHCWoe8I7g8APkyRZNaINXHQ3zcfK97av6hBYy9ue4sOLI7ZH7SeQqi/t4jR+5A==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.5.0.tgz", + "integrity": "sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==", "requires": { - "is-nan": "^1.3.0", - "luxon": "^1.25.0" + "is-nan": "^1.3.2", + "luxon": "^1.26.0" } }, "culvert": { @@ -3897,9 +4189,9 @@ "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==" }, "dayjs": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.3.tgz", - "integrity": "sha512-/2fdLN987N8Ki7Id8BUN2nhuiRyxTLumQnSQf9CNncFCyqFsSKb9TNhzRYcC8K8eJSJOKvbvkImo/MKKhNi4iw==" + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.5.tgz", + "integrity": "sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g==" }, "debug": { "version": "2.6.9", @@ -3954,37 +4246,47 @@ "dev": true }, "discord.js": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", - "integrity": "sha512-VwZkVaUAIOB9mKdca0I5MefPMTQJTNg0qdgi1huF3iwsFwJ0L5s/Y69AQe+iPmjuV6j9rtKoG0Ta0n9vgEIL6w==", + "version": "12.5.3", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", + "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", "requires": { "@discordjs/collection": "^0.1.6", "@discordjs/form-data": "^3.0.1", "abort-controller": "^3.0.0", "node-fetch": "^2.6.1", - "prism-media": "^1.2.2", + "prism-media": "^1.2.9", "setimmediate": "^1.0.5", "tweetnacl": "^1.0.3", - "ws": "^7.3.1" + "ws": "^7.4.4" } }, "discord.js-collector-utils": { - "version": "1.0.24", - "resolved": "https://registry.npmjs.org/discord.js-collector-utils/-/discord.js-collector-utils-1.0.24.tgz", - "integrity": "sha512-SAGdRwKb6V/0GVXuvNmPAKjJD6YHAXj6X0uTvpjsmmYGoucAE49Pq+uYrOmMdy3D0o2l+ncNzFnRxkmFXwkAXw==", - "requires": { - "discord.js": "12.5.1" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/discord.js-collector-utils/-/discord.js-collector-utils-1.1.0.tgz", + "integrity": "sha512-42iL8v3a+m/mcgwDJdamvRYMDJ0DDcfB9Xkydli5KeZMiwWfCHPrf9zXbzPzmPOtCOh0JvJpi8ZJEVRBNDjgwQ==" + }, + "discord.js-multilingual-utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/discord.js-multilingual-utils/-/discord.js-multilingual-utils-1.5.0.tgz", + "integrity": "sha512-GZVMovYZazbrmQVDyL27aK6dVUXNqOfI3PKGW6in/ZX9NtephbvEW/5NTW9vgIC2PAcK2ZTkxQEaeP45b3rebA==" }, "discord.js-rate-limiter": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/discord.js-rate-limiter/-/discord.js-rate-limiter-1.0.5.tgz", - "integrity": "sha512-f50AADgJ2vYDs/RHOvAZSC7V3rWmZtf3JuYZ4U7lVY4/ud/EILa7UaPnrOp/T8iPTLKgdpQw8lpX/F/HPImgIA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/discord.js-rate-limiter/-/discord.js-rate-limiter-1.1.0.tgz", + "integrity": "sha512-Mo+wj6Y+PHlCUCtcOIt2Aoph0xcPFND9XxzJyFbDKTLfJOoxDZVckrWEu9Y7bRkHmAUElgz2oq3vDDUbWbIUWA==", "requires": { - "discord.js": "12.5.1", "limiter": "1.1.5" } }, + "dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", + "dev": true, + "requires": { + "xtend": "^4.0.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4110,11 +4412,24 @@ "methods": "^1.0.0" } }, + "fast-json-patch": { + "version": "3.0.0-1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.0.0-1.tgz", + "integrity": "sha512-6pdFb07cknxvPzCeLsFHStEy+MysPJPgZQ9LbQ/2O67unQF93SNqfdSqnPPl71YMHX+AD8gbl7iuoGFzHEdDuw==" + }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fast-printf": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.5.tgz", + "integrity": "sha512-0+bBTCT4SOmHg3NFsPO39s+EwUdQiKig4MMye7fM0ea24YprUW35AZuWVQdE0SMBRI/GIQldj8ydQGuVRE95UQ==", + "requires": { + "boolean": "^3.0.2" + } + }, "fclone": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", @@ -4125,6 +4440,11 @@ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz", "integrity": "sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg==" }, + "filesize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-7.0.0.tgz", + "integrity": "sha512-Wsstw+O1lZ9gVmOI1thyeQvODsaoId2qw14lCqIzUhoHKXX7T2hVpB7BR6SvgodMBgWccrx/y2eyV8L7tDmY6A==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4148,14 +4468,14 @@ } }, "follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" }, "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", "dev": true, "requires": { "asynckit": "^0.4.0", @@ -4164,9 +4484,9 @@ } }, "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fresh": { "version": "0.5.2", @@ -4280,9 +4600,9 @@ "integrity": "sha1-WZrBkrcYdYJeE6RF86bgURjC90U=" }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4430,9 +4750,9 @@ } }, "is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", + "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", "requires": { "has": "^1.0.3" } @@ -4499,16 +4819,14 @@ "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - } } }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "optional": true + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -4532,9 +4850,9 @@ } }, "libphonenumber-js": { - "version": "1.9.16", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.16.tgz", - "integrity": "sha512-PaHT7nTtnejZ0HHekAaA0olv6BUTKZGtKM4SCQS0yE3XjFuVo/tjePMHUAr32FKwIZfyPky1ExMUuaiBAUmV6w==" + "version": "1.9.20", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.9.20.tgz", + "integrity": "sha512-fZw/XGE19SdRtcmAa5mEUjhGuFrVsT6x3V07TLt36psLbJnZiAdI0i/h4JSufKGXFCyreYM7gP98julXfO1XNA==" }, "limiter": { "version": "1.1.5", @@ -4570,9 +4888,15 @@ } }, "luxon": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.26.0.tgz", - "integrity": "sha512-+V5QIQ5f6CDXQpWNICELwjwuHdqeJM1UenlZWx5ujcRMc9venvluCjFb4t5NYLhb6IhkbMVOxzVuOqkgMxee2A==" + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.27.0.tgz", + "integrity": "sha512-VKsFsPggTA0DvnxtJdiExAucKdAnwbCCNlMM5ENvHlxubqWd0xhZcdb4XgZ7QFNhaRhilXCFxHuoObP5BNA4PA==" + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true }, "media-typer": { "version": "0.3.0", @@ -4595,16 +4919,16 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", - "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==" }, "mime-types": { - "version": "2.1.28", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", - "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", "requires": { - "mime-db": "1.45.0" + "mime-db": "1.48.0" } }, "minimatch": { @@ -4827,9 +5151,9 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-to-regexp": { "version": "0.1.7", @@ -4837,9 +5161,9 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" }, "pidusage": { "version": "2.0.21", @@ -4857,11 +5181,11 @@ } }, "pm2": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/pm2/-/pm2-4.5.6.tgz", - "integrity": "sha512-4J5q704Xl6VmpmQhXFGMJL4kXyyQw3AZM1FE9vRxhS3LiDI/+WVBtOM6pqJ4g/RKW+AUjEkc23i/DCC4BVenDA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-5.1.0.tgz", + "integrity": "sha512-reJ35NOxM4+g7H0enW47HJsp32CszKkseCojAuUMUkffyXsGDKBMnDqhxAZMZKtHUUjl0cWFEqKehqB0ODH8Kw==", "requires": { - "@pm2/agent": "~1.0.8", + "@pm2/agent": "~2.0.0", "@pm2/io": "~5.0.0", "@pm2/js-api": "~0.6.7", "@pm2/pm2-version-check": "^1.0.4", @@ -4876,20 +5200,20 @@ "debug": "^4.3.1", "enquirer": "2.3.6", "eventemitter2": "5.0.1", + "fast-printf": "^1.3.0", "fclone": "1.0.11", "mkdirp": "1.0.4", "needle": "2.4.0", "pidusage": "2.0.21", "pm2-axon": "~4.0.1", - "pm2-axon-rpc": "~0.7.0", + "pm2-axon-rpc": "~0.7.1", "pm2-deploy": "~1.0.2", "pm2-multimeter": "^0.1.2", + "pm2-sysmonit": "^1.2.8", "promptly": "^2", - "ps-list": "6.3.0", "semver": "^7.2", "source-map-support": "0.5.19", - "sprintf-js": "1.1.2", - "vizion": "2.2.1", + "vizion": "~2.2.1", "yamljs": "0.3.0" }, "dependencies": { @@ -4979,21 +5303,51 @@ "charm": "~0.1.1" } }, + "pm2-sysmonit": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", + "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", + "optional": true, + "requires": { + "async": "^3.2.0", + "debug": "^4.3.1", + "pidusage": "^2.0.21", + "systeminformation": "^5.7", + "tx2": "~1.0.4" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "optional": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "optional": true + } + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, "prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", + "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", "dev": true }, "prism-media": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.3.tgz", - "integrity": "sha512-fSrR66n0l6roW9Rx4rSLMyTPTjRTiXy5RVqDOurACQ6si1rKHHKDU5gwBJoCsIV0R3o9gi+K50akl/qyw1C74A==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.1.tgz", + "integrity": "sha512-nyYAa3KB4qteJIqdguKmwxTJgy55xxUtkJ3uRnOvO5jO+frci+9zpRXw6QZVcfDeva3S654fU9+26P2OSTzjHw==" }, "process-nextick-args": { "version": "2.0.1", @@ -5009,11 +5363,11 @@ } }, "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "requires": { - "forwarded": "~0.1.2", + "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, @@ -5052,11 +5406,6 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, - "ps-list": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/ps-list/-/ps-list-6.3.0.tgz", - "integrity": "sha512-qau0czUSB0fzSlBOQt0bo+I2v6R+xiQdj78e1BR/Qjfl5OHWJ/urXi8+ilw1eHe+5hSeDI1wrwVTgDp2wst4oA==" - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -5101,9 +5450,9 @@ } }, "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "requires": { "picomatch": "^2.2.1" } @@ -5113,6 +5462,11 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" }, + "remove-markdown": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.3.0.tgz", + "integrity": "sha1-XktmdJOpNXlyjz1S7MHbnKUF3Jg=" + }, "require-in-the-middle": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.1.0.tgz", @@ -5139,14 +5493,23 @@ } }, "resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "requires": { - "is-core-module": "^2.1.0", + "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "run-series": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", @@ -5254,20 +5617,20 @@ "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==" }, "socks": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.0.tgz", - "integrity": "sha512-mNmr9owlinMplev0Wd7UHFlqI4ofnBnNzFuzrm63PPaHgbkqCFe4T5LzwKmtQ/f2tX0NTpcdVLyD/FHxFBstYw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", + "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", "requires": { "ip": "^1.1.5", "smart-buffer": "^4.1.0" } }, "socks-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz", - "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz", + "integrity": "sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==", "requires": { - "agent-base": "6", + "agent-base": "^6.0.2", "debug": "4", "socks": "^2.3.3" }, @@ -5306,11 +5669,6 @@ "source-map": "^0.6.0" } }, - "sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" - }, "sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", @@ -5329,6 +5687,18 @@ "safe-buffer": "~5.1.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5337,6 +5707,12 @@ "has-flag": "^4.0.0" } }, + "systeminformation": { + "version": "5.7.7", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.7.7.tgz", + "integrity": "sha512-aQ7MBeVI2MKPYOi3YJAoZ45JVlRkBA7IXoqGgtVBamvtE0I6JLOyJzD/VVc9pnMXDb3yqaMwssAjhwtJax4/Rw==", + "optional": true + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5350,6 +5726,56 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + }, + "ts-node-dev": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.1.8.tgz", + "integrity": "sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg==", + "dev": true, + "requires": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.5", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^9.0.0", + "tsconfig": "^7.0.0" + } + }, + "tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "requires": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -5474,6 +5900,15 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, + "tx2": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.4.tgz", + "integrity": "sha512-rU+y30nUY3PyIi+znvv74HzxlpULKwMPAyRK+YiCjvGkk3rY3fic3D6Z+avLpun3V5A6HFwPQ9JrBTMNEV/dxg==", + "optional": true, + "requires": { + "json-stringify-safe": "^5.0.1" + } + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -5492,9 +5927,9 @@ } }, "typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true }, "universalify": { @@ -5523,9 +5958,9 @@ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, "validator": { - "version": "13.5.2", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.5.2.tgz", - "integrity": "sha512-mD45p0rvHVBlY2Zuy3F3ESIe1h5X58GPfAtslBjY7EtTqGquZTj+VX/J4RnHWN8FKq0C9WRVt1oWAcytWRuYLQ==" + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.6.0.tgz", + "integrity": "sha512-gVgKbdbHgtxpRyR8K0O6oFZPhhB5tT1jeEHZR0Znr9Svg03U0+r9DXWMrnRAB+HtCStDQKlaIZm42tVsVjqtjg==" }, "vary": { "version": "1.1.2", @@ -5564,15 +5999,21 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", - "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.1.tgz", + "integrity": "sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==" }, "xregexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=" }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -5586,6 +6027,12 @@ "argparse": "^1.0.7", "glob": "^7.0.5" } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/package.json b/package.json index 20a12acf..3d86ff56 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "Celebrate birthdays with automatic birthday roles and announcements! Highly customizable and easy to use!", "private": true, "engines": { - "node": ">=12.14.0" + "node": ">=15.0.0" }, "main": "dist/start.js", "scripts": { @@ -14,6 +14,7 @@ "format": "prettier --check .", "format:fix": "prettier --write .", "build": "tsc", + "start:dev": "ts-node-dev --watch src/**/*.ts,config/**/*.json,lang/**/*.json src/start.ts", "start": "npm run build && node --enable-source-maps dist/start.js", "start:shard": "npm run build && node --enable-source-maps dist/app.js", "start:pm2": "npm run build && npm run pm2:start", @@ -22,31 +23,35 @@ "pm2:delete": "pm2 delete process.json" }, "dependencies": { - "chrono-node": "2.2.5", + "chrono-node": "2.3.0", "class-transformer": "^0.4.0", "class-validator": "^0.13.1", - "color-name-list": "7.37.0", - "discord.js": "12.5.1", - "discord.js-collector-utils": "1.0.24", - "discord.js-rate-limiter": "^1.0.5", + "color-name-list": "8.13.0", + "discord.js": "12.5.3", + "discord.js-collector-utils": "1.1.0", + "discord.js-multilingual-utils": "^1.5.0", + "discord.js-rate-limiter": "^1.1.0", "express": "4.17.1", "express-promise-router": "^4.1.0", - "limiter": "1.1.5", + "filesize": "7.0.0", "moment-timezone": "0.5.33", "mysql": "2.18.1", "node-fetch": "2.6.1", "node-schedule": "2.0.0", - "pm2": "^4.5.6", - "reflect-metadata": "^0.1.13" + "pm2": "^5.1.0", + "reflect-metadata": "^0.1.13", + "remove-markdown": "0.3.0" }, "devDependencies": { - "@types/express": "4.17.11", - "@types/mysql": "2.15.18", - "@types/node": "^14.14.35", - "@types/node-fetch": "2.5.8", - "@types/node-schedule": "1.3.1", - "prettier": "^2.2.1", + "@types/express": "4.17.13", + "@types/mysql": "2.15.19", + "@types/node": "^16.3.1", + "@types/node-fetch": "2.5.11", + "@types/node-schedule": "1.3.2", + "@types/remove-markdown": "^0.3.1", + "prettier": "^2.3.2", + "ts-node-dev": "^1.1.8", "tslint": "^6.1.3", - "typescript": "^4.2.3" + "typescript": "^4.3.5" } } diff --git a/scripts/create-database.sql b/scripts/create-database.sql deleted file mode 100644 index 4e8210ac..00000000 --- a/scripts/create-database.sql +++ /dev/null @@ -1,1060 +0,0 @@ --- phpMyAdmin SQL Dump --- version 4.9.0.1 --- https://www.phpmyadmin.net/ --- --- Host: localhost --- Generation Time: Oct 19, 2020 at 12:46 AM --- Server version: 10.3.22-MariaDB-0+deb10u1 --- PHP Version: 7.3.14-1~deb10u1 - -SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; -SET AUTOCOMMIT = 0; -START TRANSACTION; -SET time_zone = "+00:00"; - - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8mb4 */; - --- --- Database: `birthdaybotproddev` --- - -DELIMITER $$ --- --- Procedures --- -CREATE DEFINER=`admin`@`localhost` PROCEDURE `Blacklist_Add` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_UserDiscordId` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; -SET @MessageId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -INSERT IGNORE INTO `blacklist` ( - GuildId, - UserDiscordId -) VALUES ( - @GuildId, - IN_UserDiscordId -); -END$$ - -CREATE DEFINER=`admin`@`localhost` PROCEDURE `Blacklist_Clear` (IN `IN_GuildDiscordId` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -DELETE -FROM `blacklist` -WHERE - GuildId = @GuildId; -END$$ - -CREATE DEFINER=`admin`@`localhost` PROCEDURE `Blacklist_Get` (IN `IN_GuildDiscordId` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -SET @Row_Number = 0; - -SELECT - *, - ROW_NUMBER() OVER ( - ORDER BY BlacklistId - ) AS Position - FROM `blacklist` - WHERE GuildId = @GuildId -ORDER BY BlacklistId; - -END$$ - -CREATE DEFINER=`admin`@`localhost` PROCEDURE `Blacklist_GetList` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_PageSize` INT, IN `IN_Page` INT) BEGIN - -SET @GuildId = NULL; -SET @TotalPages = NULL; -SET @TotalItems = NULL; -SET @StartRow = NULL; -SET @EndRow = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -SELECT COUNT(*) -INTO @TotalItems -FROM `blacklist` -WHERE GuildId = @GuildId; - -SELECT CEILING(@TotalItems/IN_PageSize) INTO @TotalPages; - -IF (IN_Page < 0) THEN - SET IN_Page = 1; -ELSEIF (IN_Page > @TotalPages) THEN - SET IN_Page = @TotalPages; -END IF; - -SET @StartRow = ((IN_Page - 1) * IN_PageSize) + 1; -SET @EndRow = IN_Page * IN_PageSize; - -SELECT * -FROM ( - SELECT - *, - ROW_NUMBER() OVER ( - ORDER BY BlacklistId - ) AS Position - FROM `blacklist` - WHERE GuildId = @GuildId - ORDER BY BlacklistId -) AS Blacklisted -WHERE - Blacklisted.Position >= @StartRow AND - Blacklisted.Position <= @EndRow; - -SELECT - @TotalItems AS 'TotalItems', - @TotalPages as 'TotalPages'; - -DROP TEMPORARY TABLE IF EXISTS temp; -END$$ - -CREATE DEFINER=`admin`@`localhost` PROCEDURE `Blacklist_Remove` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_UserDiscordId` VARCHAR(20)) NO SQL -BEGIN - -SET @GuildId = NULL; -SET @MessageId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -DELETE -FROM `blacklist` -WHERE - GuildId = @GuildId AND - UserDiscordId = IN_UserDiscordId; -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `CustomMessages_Add` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_Message` VARCHAR(2000), IN `IN_UserDiscordId` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; -SET @MessageId = NULL; -SET @UserMessage = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -SELECT MessageId -INTO @UserMessage -FROM `messages` -WHERE GuildId = @GuildId AND UserDiscordId = IN_UserDiscordId AND UserDiscordId <> '0'; - -IF @UserMessage IS NULL THEN - INSERT INTO `messages` ( - GuildId, - Message, - UserDiscordId - ) VALUES ( - @GuildId, - IN_Message, - IN_UserDiscordId - ); -ELSE - UPDATE `messages` - SET - Message = IN_Message - WHERE MessageId = @UserMessage; -END IF; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `CustomMessages_Clear` (IN `IN_GuildDiscordId` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -DELETE -FROM `messages` -WHERE - GuildId = @GuildId; -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `CustomMessages_Get` (IN `IN_GuildDiscordId` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -SET @Row_Number = 0; - -SELECT - *, - ROW_NUMBER() OVER ( - ORDER BY MessageId - ) AS Position - FROM `messages` - WHERE GuildId = @GuildId AND UserDiscordId = '0' -ORDER BY MessageId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `CustomMessages_GetList` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_PageSize` INT, IN `IN_Page` INT) BEGIN - -SET @GuildId = NULL; -SET @TotalPages = NULL; -SET @TotalItems = NULL; -SET @StartRow = NULL; -SET @EndRow = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -SELECT COUNT(*) -INTO @TotalItems -FROM `messages` -WHERE GuildId = @GuildId AND UserDiscordId = '0'; - -SELECT CEILING(@TotalItems/IN_PageSize) INTO @TotalPages; - -IF (IN_Page < 0) THEN - SET IN_Page = 1; -ELSEIF (IN_Page > @TotalPages) THEN - SET IN_Page = @TotalPages; -END IF; - -SET @StartRow = ((IN_Page - 1) * IN_PageSize) + 1; -SET @EndRow = IN_Page * IN_PageSize; - -SELECT * -FROM ( - SELECT - *, - ROW_NUMBER() OVER ( - ORDER BY MessageId - ) AS Position - FROM `messages` - WHERE GuildId = @GuildId AND UserDiscordId = '0' - ORDER BY MessageId -) AS CustomMessage -WHERE - CustomMessage.Position >= @StartRow AND - CustomMessage.Position <= @EndRow; - -SELECT - @TotalItems AS 'TotalItems', - @TotalPages as 'TotalPages'; - -DROP TEMPORARY TABLE IF EXISTS temp; -END$$ - -CREATE DEFINER=`admin`@`localhost` PROCEDURE `CustomMessages_GetUser` (IN `IN_GuildDiscordId` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -SET @Row_Number = 0; - -SELECT - *, - ROW_NUMBER() OVER ( - ORDER BY MessageId - ) AS Position - FROM `messages` - WHERE GuildId = @GuildId AND UserDiscordId <> '0' -ORDER BY MessageId; - -END$$ - -CREATE DEFINER=`admin`@`localhost` PROCEDURE `CustomMessages_GetUserList` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_PageSize` INT, IN `IN_Page` INT) NO SQL -BEGIN - -SET @GuildId = NULL; -SET @TotalPages = NULL; -SET @TotalItems = NULL; -SET @StartRow = NULL; -SET @EndRow = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -SELECT COUNT(*) -INTO @TotalItems -FROM `messages` -WHERE GuildId = @GuildId AND UserDiscordId <> '0'; - -SELECT CEILING(@TotalItems/IN_PageSize) INTO @TotalPages; - -IF (IN_Page < 0) THEN - SET IN_Page = 1; -ELSEIF (IN_Page > @TotalPages) THEN - SET IN_Page = @TotalPages; -END IF; - -SET @StartRow = ((IN_Page - 1) * IN_PageSize) + 1; -SET @EndRow = IN_Page * IN_PageSize; - -SELECT * -FROM ( - SELECT - *, - ROW_NUMBER() OVER ( - ORDER BY MessageId - ) AS Position - FROM `messages` - WHERE GuildId = @GuildId AND UserDiscordId <> '0' - ORDER BY MessageId -) AS CustomMessage -WHERE - CustomMessage.Position >= @StartRow AND - CustomMessage.Position <= @EndRow; - -SELECT - @TotalItems AS 'TotalItems', - @TotalPages as 'TotalPages'; - -DROP TEMPORARY TABLE IF EXISTS temp; -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `CustomMessages_Remove` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_Position` INT) BEGIN - -SET @GuildId = NULL; -SET @MessageId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -SELECT M.MessageId -INTO @MessageId -FROM ( - SELECT - *, - ROW_NUMBER() OVER ( - ORDER BY MessageId - ) AS Position - FROM `messages` - WHERE GuildId = @GuildId AND UserDiscordId = '0' -) AS M -WHERE M.Position = IN_Position; - -DELETE -FROM `messages` -WHERE - MessageId = @MessageId; -END$$ - -CREATE DEFINER=`admin`@`localhost` PROCEDURE `CustomMessages_RemoveUser` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_Position` INT) BEGIN - -SET @GuildId = NULL; -SET @MessageId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -SELECT M.MessageId -INTO @MessageId -FROM ( - SELECT - *, - ROW_NUMBER() OVER ( - ORDER BY MessageId - ) AS Position - FROM `messages` - WHERE GuildId = @GuildId AND UserDiscordId <> '0' -) AS M -WHERE M.Position = IN_Position; - -DELETE -FROM `messages` -WHERE - MessageId = @MessageId; -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_AddOrUpdate` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_BirthdayChannelDiscordId` VARCHAR(20), IN `IN_BirthdayRoleDiscordId` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -IF @GuildId IS NULL THEN - INSERT INTO `guild` ( - GuildDiscordId, - BirthdayChannelDiscordId, - BirthdayRoleDiscordId - ) VALUES ( - IN_GuildDiscordId, - IN_BirthdayChannelDiscordId, - IN_BirthdayRoleDiscordId - ); -ELSE - UPDATE `guild` - SET - GuildDiscordId = IN_GuildDiscordId, - BirthdayChannelDiscordId = IN_BirthdayChannelDiscordId, - BirthdayRoleDiscordId = IN_BirthdayRoleDiscordId - WHERE GuildId = @GuildId; -END IF; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_Get` (IN `IN_GuildDiscordId` VARCHAR(20)) BEGIN - -SELECT * -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_GetAll` (IN `IN_GuildDiscordIds` MEDIUMTEXT) BEGIN - -SELECT * -FROM `guild` -WHERE FIND_IN_SET(GuildDiscordId, IN_GuildDiscordIds) > 0; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_SetupMessage` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_MessageTime` TINYINT, IN `IN_MentionSetting` VARCHAR(20), IN `IN_UseEmbed` TINYINT(1)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET - GuildDiscordId = IN_GuildDiscordId, - MessageTime = IN_MessageTime, - MentionSetting = IN_MentionSetting, - UseEmbed = IN_UseEmbed -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_SetupTrusted` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_TrustedRoleDiscordId` VARCHAR(20), IN `IN_TrustedPreventsRole` TINYINT(1), IN `IN_TrustedPreventsMessage` TINYINT(1)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET - GuildDiscordId = IN_GuildDiscordId, - TrustedRoleDiscordId = IN_TrustedRoleDiscordId, - TrustedPreventsRole = IN_TrustedPreventsRole, - TrustedPreventsMessage = IN_TrustedPreventsMessage -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_UpdateBirthdayChannel` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_BirthdayChannelDiscordId` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET BirthdayChannelDiscordId = IN_BirthdayChannelDiscordId -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`admin`@`localhost` PROCEDURE `Guild_UpdateBirthdayMasterRole` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_BirthdayMasterRoleDiscordId` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET BirthdayMasterRoleDiscordId = IN_BirthdayMasterRoleDiscordId -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_UpdateBirthdayRole` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_BirthdayRoleDiscordId` VARCHAR(20)) NO SQL -BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET BirthdayRoleDiscordId = IN_BirthdayRoleDiscordId -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_UpdateMentionSetting` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_MentionSetting` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET MentionSetting = IN_MentionSetting -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`admin`@`localhost` PROCEDURE `Guild_UpdateMessageEmbedColor` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_HexColor` VARCHAR(6)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET MessageEmbedColor = IN_HexColor -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_UpdateMessageTime` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_Time` TINYINT) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET MessageTime = IN_Time -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_UpdateTrustedPreventsMessage` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_Value` TINYINT(1)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET TrustedPreventsMessage = IN_Value -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_UpdateTrustedPreventsRole` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_Value` TINYINT(1)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET TrustedPreventsRole = IN_Value -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_UpdateTrustedRole` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_TrustedRoleDiscordId` VARCHAR(20)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET TrustedRoleDiscordId = IN_TrustedRoleDiscordId -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `Guild_UpdateUseEmbed` (IN `IN_GuildDiscordId` VARCHAR(20), IN `IN_Value` TINYINT(1)) BEGIN - -SET @GuildId = NULL; - -SELECT GuildId -INTO @GuildId -FROM `guild` -WHERE GuildDiscordId = IN_GuildDiscordId; - -UPDATE `guild` -SET UseEmbed = IN_Value -WHERE GuildId = @GuildId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `User_AddOrUpdate` (IN `IN_UserDiscordId` VARCHAR(20), IN `IN_Birthday` DATE, IN `IN_Timezone` VARCHAR(100), IN `IN_ChangesLeft` TINYINT) BEGIN - -SET @UserId = NULL; - -SELECT UserId -INTO @UserId -FROM `user` -WHERE UserDiscordId = IN_UserDiscordId; - -IF @UserId IS NULL THEN - INSERT INTO `user` ( - UserDiscordId, - Birthday, - TimeZone, - ChangesLeft - ) VALUES ( - IN_UserDiscordId, - IN_Birthday, - IN_Timezone, - IN_ChangesLeft - ); -ELSE - UPDATE `user` - SET - UserDiscordId = IN_UserDiscordId, - Birthday = IN_Birthday, - TimeZone = IN_TimeZone, - ChangesLeft = IN_ChangesLeft - WHERE UserId = @UserId; -END IF; - -END$$ - -CREATE DEFINER=`admin`@`%` PROCEDURE `User_AddVote` (IN `IN_BotSiteName` VARCHAR(50), IN `IN_UserDiscordId` VARCHAR(20)) MODIFIES SQL DATA -BEGIN - -INSERT INTO `vote` ( - BotSiteName, - UserDiscordId -) VALUES ( - IN_BotSiteName, - IN_UserDiscordId -); - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `User_Get` (IN `IN_UserDiscordId` VARCHAR(20)) BEGIN - -SELECT * -FROM `user` -WHERE UserDiscordId = IN_UserDiscordId; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `User_GetAll` (IN `IN_UserDiscordIds` MEDIUMTEXT) BEGIN - -DROP TEMPORARY TABLE IF EXISTS temp; -CREATE TEMPORARY TABLE temp( val VARCHAR(20), INDEX(val) ); -SET @SQL = CONCAT("INSERT INTO temp (val) values ('", REPLACE(IN_UserDiscordIds, ",", "'),('"),"');"); - -PREPARE stmt1 FROM @sql; -EXECUTE stmt1; - -SELECT * -FROM ( - SELECT * - FROM temp AS T - JOIN `user`AS U - ON U.UserDiscordId = T.val - WHERE U.Birthday IS NOT NULL AND U.Timezone IS NOT NULL - ORDER BY U.Birthday -) AS UserData; - -DROP TEMPORARY TABLE IF EXISTS temp; -END$$ - -CREATE DEFINER=`admin`@`%` PROCEDURE `User_GetBirthdays` (IN `IN_Birthday` VARCHAR(10)) BEGIN - -SELECT * -FROM `user` -WHERE DATE_FORMAT(`user`.Birthday, '%m-%d') = IN_Birthday; - -END$$ - -CREATE DEFINER=`admin`@`%` PROCEDURE `User_GetBirthdaysThisMonthCount` (IN `IN_Birthday` VARCHAR(10)) READS SQL DATA -BEGIN - -SELECT COUNT(*) AS Total -FROM `user` -WHERE DATE_FORMAT(`user`.Birthday, '%m') = IN_Birthday; - -END$$ - -CREATE DEFINER=`admin`@`%` PROCEDURE `User_GetBirthdaysTodayCount` (IN `IN_Birthday` VARCHAR(10)) READS SQL DATA -BEGIN - -SELECT COUNT(*) AS Total -FROM `user` -WHERE DATE_FORMAT(`user`.Birthday, '%m-%d') = IN_Birthday; - -END$$ - -CREATE DEFINER=`root`@`localhost` PROCEDURE `User_GetFullList` (IN `IN_UserDiscordIds` MEDIUMTEXT, IN `IN_PageSize` INT, IN `IN_Page` INT) BEGIN -DROP TEMPORARY TABLE IF EXISTS temp; - -SET @TotalPages = NULL; -SET @TotalItems = NULL; -SET @StartRow = NULL; -SET @EndRow = NULL; - -CREATE TEMPORARY TABLE temp( val VARCHAR(20), INDEX(val) ); -SET @SQL = CONCAT("INSERT INTO temp (val) values ('", REPLACE(IN_UserDiscordIds, ",", "'),('"),"');"); -PREPARE stmt1 FROM @sql; -EXECUTE stmt1; - -SELECT COUNT(*) INTO @TotalItems -FROM temp AS T -JOIN `user`AS U - ON U.UserDiscordId = T.val -WHERE - U.Birthday IS NOT NULL AND - U.Timezone IS NOT NULL; - -SELECT CEILING(@TotalItems / IN_PageSize) INTO @TotalPages; - -IF (IN_Page < 0) THEN - SET IN_Page = 1; -ELSEIF (IN_Page > @TotalPages) THEN - SET IN_Page = @TotalPages; -END IF; - -SET @StartRow = ((IN_Page - 1) * IN_PageSize) + 1; -SET @EndRow = IN_Page * IN_PageSize; - -SELECT * -FROM ( - SELECT - *, - ROW_NUMBER() OVER ( - ORDER BY U.Birthday - ) AS 'Position' - FROM temp AS T - JOIN `user`AS U - ON U.UserDiscordId = T.val - WHERE - U.Birthday IS NOT NULL AND - U.Timezone IS NOT NULL -) AS UserData -WHERE - UserData.Position >= @StartRow AND - UserData.Position <= @EndRow; - -SELECT - @TotalItems AS 'TotalItems', - @TotalPages as 'TotalPages', - IN_Page as 'Page'; - -DROP TEMPORARY TABLE IF EXISTS temp; -END$$ - -CREATE DEFINER=`admin`@`%` PROCEDURE `User_GetFullListFromDate` (IN `IN_UserDiscordIds` MEDIUMTEXT, IN `IN_PageSize` INT, IN `IN_Date` VARCHAR(10)) READS SQL DATA -BEGIN -DROP TEMPORARY TABLE IF EXISTS temp; -DROP TEMPORARY TABLE IF EXISTS birthdays; - -SET @TotalPages = NULL; -SET @TotalItems = NULL; -SET @StartRow = NULL; -SET @EndRow = NULL; -SET @Position = NULL; -SET @Page = NULL; - -CREATE TEMPORARY TABLE temp( val VARCHAR(20), INDEX(val) ); -SET @SQL = CONCAT("INSERT INTO temp (val) values ('", REPLACE(IN_UserDiscordIds, ",", "'),('"),"');"); -PREPARE stmt1 FROM @sql; -EXECUTE stmt1; - -SELECT COUNT(*) INTO @TotalItems -FROM temp AS T -JOIN `user`AS U - ON U.UserDiscordId = T.val -WHERE - U.Birthday IS NOT NULL AND - U.Timezone IS NOT NULL; - -CREATE TEMPORARY TABLE birthdays -SELECT * -FROM ( - SELECT - *, - ROW_NUMBER() OVER ( - ORDER BY U.Birthday - ) AS 'Position' - FROM temp AS T - JOIN `user`AS U - ON U.UserDiscordId = T.val - WHERE - U.Birthday IS NOT NULL AND - U.Timezone IS NOT NULL -) as birthdays; - -SELECT Position -INTO @Position -FROM birthdays -WHERE DATE_FORMAT(birthdays.Birthday, '%m-%d') >= IN_DATE -LIMIT 1; - -SELECT CEILING(@TotalItems / IN_PageSize) INTO @TotalPages; - -SELECT CEILING(@Position / IN_PageSize) INTO @Page; - -SET @StartRow = ((@Page - 1) * IN_PageSize) + 1; -SET @EndRow = @Page * IN_PageSize; - -SELECT * FROM birthdays -AS UserData -WHERE - UserData.Position >= @StartRow AND - UserData.Position <= @EndRow; - -SELECT - @TotalItems AS 'TotalItems', - @TotalPages as 'TotalPages', - @Page as 'Page'; - -DROP TEMPORARY TABLE IF EXISTS temp; -DROP TEMPORARY TABLE IF EXISTS birthdays; -END$$ - -CREATE DEFINER=`admin`@`%` PROCEDURE `User_GetLastVote` (IN `IN_UserDiscordId` VARCHAR(20)) READS SQL DATA -BEGIN - -SELECT * -FROM `vote` -WHERE UserDiscordId = IN_UserDiscordId -ORDER BY VoteId DESC LIMIT 1; - -END$$ - -CREATE DEFINER=`admin`@`%` PROCEDURE `User_GetTotalCount` () READS SQL DATA -BEGIN - -SELECT COUNT(*) AS Total FROM `user`; - -END$$ - -DELIMITER ; - --- -------------------------------------------------------- - --- --- Table structure for table `blacklist` --- - -CREATE TABLE `blacklist` ( - `BlacklistId` int(11) NOT NULL, - `GuildId` int(11) NOT NULL, - `UserDiscordId` varchar(20) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- -------------------------------------------------------- - --- --- Table structure for table `guild` --- - -CREATE TABLE `guild` ( - `GuildId` int(11) NOT NULL, - `GuildDiscordId` varchar(20) NOT NULL, - `BirthdayChannelDiscordId` varchar(20) NOT NULL DEFAULT '0', - `BirthdayRoleDiscordId` varchar(20) DEFAULT '0', - `TrustedRoleDiscordId` varchar(20) DEFAULT '0', - `BirthdayMasterRoleDiscordId` varchar(20) DEFAULT '0', - `MentionSetting` varchar(20) DEFAULT '0', - `MessageTime` tinyint(4) NOT NULL DEFAULT 0, - `TrustedPreventsRole` tinyint(1) NOT NULL DEFAULT 1, - `TrustedPreventsMessage` tinyint(1) NOT NULL DEFAULT 1, - `UseEmbed` tinyint(1) NOT NULL DEFAULT 1, - `MessageEmbedColor` varchar(6) DEFAULT '0' -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- -------------------------------------------------------- - --- --- Table structure for table `messages` --- - -CREATE TABLE `messages` ( - `MessageId` int(11) NOT NULL, - `GuildId` int(11) DEFAULT NULL, - `Message` varchar(500) CHARACTER SET utf8mb4 NOT NULL, - `UserDiscordId` varchar(20) DEFAULT '0' -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- -------------------------------------------------------- - --- --- Table structure for table `user` --- - -CREATE TABLE `user` ( - `UserId` int(11) NOT NULL, - `UserDiscordId` varchar(20) NOT NULL, - `Birthday` date DEFAULT NULL, - `TimeZone` varchar(100) CHARACTER SET utf32 DEFAULT NULL, - `ChangesLeft` tinyint(4) DEFAULT 5 -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- -------------------------------------------------------- - --- --- Table structure for table `vote` --- - -CREATE TABLE `vote` ( - `VoteId` int(11) NOT NULL, - `BotSiteName` varchar(50) NOT NULL, - `UserDiscordId` varchar(20) NOT NULL, - `VoteTime` timestamp NOT NULL DEFAULT current_timestamp() -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- --- Indexes for dumped tables --- - --- --- Indexes for table `blacklist` --- -ALTER TABLE `blacklist` - ADD PRIMARY KEY (`BlacklistId`), - ADD UNIQUE KEY `UQ_GuildIdUserDiscordId` (`GuildId`,`UserDiscordId`) USING BTREE, - ADD KEY `UserId` (`UserDiscordId`); - --- --- Indexes for table `guild` --- -ALTER TABLE `guild` - ADD PRIMARY KEY (`GuildId`), - ADD UNIQUE KEY `DiscordId` (`GuildDiscordId`), - ADD KEY `GuildDiscordId` (`GuildDiscordId`), - ADD KEY `BirthdayChannelDiscordId` (`BirthdayChannelDiscordId`), - ADD KEY `BirthdayRoleDiscordId` (`BirthdayRoleDiscordId`), - ADD KEY `TrustedRoleDiscordId` (`TrustedRoleDiscordId`), - ADD KEY `BirthdayMasterRoleDiscordId` (`BirthdayMasterRoleDiscordId`); - --- --- Indexes for table `messages` --- -ALTER TABLE `messages` - ADD PRIMARY KEY (`MessageId`), - ADD KEY `FK_Messages_GuildId` (`GuildId`), - ADD KEY `UserDiscordId` (`UserDiscordId`); - --- --- Indexes for table `user` --- -ALTER TABLE `user` - ADD PRIMARY KEY (`UserId`), - ADD UNIQUE KEY `UserDiscordId` (`UserDiscordId`), - ADD KEY `UserDiscordId_2` (`UserDiscordId`); - --- --- Indexes for table `vote` --- -ALTER TABLE `vote` - ADD PRIMARY KEY (`VoteId`), - ADD UNIQUE KEY `UQ_BotSiteName_UserDiscordId_VoteTime` (`BotSiteName`,`UserDiscordId`,`VoteTime`) USING BTREE, - ADD KEY `UserDiscordId` (`UserDiscordId`); - --- --- AUTO_INCREMENT for dumped tables --- - --- --- AUTO_INCREMENT for table `blacklist` --- -ALTER TABLE `blacklist` - MODIFY `BlacklistId` int(11) NOT NULL AUTO_INCREMENT; - --- --- AUTO_INCREMENT for table `guild` --- -ALTER TABLE `guild` - MODIFY `GuildId` int(11) NOT NULL AUTO_INCREMENT; - --- --- AUTO_INCREMENT for table `messages` --- -ALTER TABLE `messages` - MODIFY `MessageId` int(11) NOT NULL AUTO_INCREMENT; - --- --- AUTO_INCREMENT for table `user` --- -ALTER TABLE `user` - MODIFY `UserId` int(11) NOT NULL AUTO_INCREMENT; - --- --- AUTO_INCREMENT for table `vote` --- -ALTER TABLE `vote` - MODIFY `VoteId` int(11) NOT NULL AUTO_INCREMENT; - --- --- Constraints for dumped tables --- - --- --- Constraints for table `blacklist` --- -ALTER TABLE `blacklist` - ADD CONSTRAINT `blacklist_ibfk_1` FOREIGN KEY (`GuildId`) REFERENCES `guild` (`GuildId`); -COMMIT; - -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; \ No newline at end of file diff --git a/scripts/migrate-database-v1-to-v2.sql b/scripts/migrate-database-v1-to-v2.sql deleted file mode 100644 index 9ec9d957..00000000 --- a/scripts/migrate-database-v1-to-v2.sql +++ /dev/null @@ -1,63 +0,0 @@ -INSERT INTO birthdaybot.user ( - UserId, - UserDiscordId, - Birthday, - TimeZone, - ChangesLeft -) -SELECT - UserId, - UserDiscordId, - Birthday, - ZoneId, - ChangesLeft -FROM oldbirthdaybot.users; - -DROP TEMPORARY TABLE IF EXISTS birthdaybot.GuildData; -CREATE TEMPORARY TABLE IF NOT EXISTS birthdaybot.GuildData AS - SELECT - G.DiscordId, - GS.* - FROM oldbirthdaybot.`guildsettings` AS GS - JOIN oldbirthdaybot.`guild` AS G - ON GS.GuildSettingsId = G.GuildSettingsId; - -INSERT INTO birthdaybot.guild ( - GuildDiscordId, - BirthdayChannelDiscordId, - BirthdayRoleDiscordId, - TrustedRoleDiscordId, - MentionSetting, - MessageTime, - TrustedPreventsRole, - TrustedPreventsMessage, - UseEmbed -) SELECT - DiscordId, - BirthdayChannel, - BirthdayRole, - TrustedRole, - MentionSetting, - MessageTime, - TrustedPreventsRole, - TrustedPreventsMessage, - UseEmbed -FROM birthdaybot.GuildData; - -INSERT INTO birthdaybot.messages ( - GuildId, - Message -) SELECT - GuildId, - CustomMessage -FROM ( - SELECT - G.GuildId, - LEFT(GD.CustomMessage, 300) AS CustomMessage - FROM birthdaybot.GuildData AS GD - JOIN birthdaybot.guild AS G - ON GD.DiscordId = G.GuildDiscordId - WHERE GD.CustomMessage <> '0' -) AS NewData; - -DROP TEMPORARY TABLE IF EXISTS birthdaybot.GuildData; \ No newline at end of file diff --git a/src/bot.ts b/src/bot.ts index fb5392bd..e73e4e10 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,10 +1,17 @@ -import { Client, Guild, Message, MessageReaction, RateLimitData, User } from 'discord.js'; +import { + Client, + Constants, + Guild, + Message, + MessageReaction, + RateLimitData, + User, +} from 'discord.js'; import { GuildJoinHandler, GuildLeaveHandler, MessageHandler, ReactionAddHandler } from './events'; - -import { Job } from './jobs'; import { JobService, Logger } from './services'; let Config = require('../config/config.json'); +let Debug = require('../config/debug.json'); let Logs = require('../lang/logs.json'); export class Bot { @@ -26,15 +33,19 @@ export class Bot { } private registerListeners(): void { - this.client.on('ready', () => this.onReady()); - this.client.on('shardReady', (shardId: number) => this.onShardReady(shardId)); - this.client.on('guildCreate', (guild: Guild) => this.onGuildJoin(guild)); - this.client.on('guildDelete', (guild: Guild) => this.onGuildLeave(guild)); - this.client.on('message', (msg: Message) => this.onMessage(msg)); - this.client.on('messageReactionAdd', (msgReaction: MessageReaction, reactor: User) => - this.onReactionAdd(msgReaction, reactor) + this.client.on(Constants.Events.CLIENT_READY, () => this.onReady()); + this.client.on(Constants.Events.SHARD_READY, (shardId: number) => + this.onShardReady(shardId) + ); + this.client.on(Constants.Events.GUILD_CREATE, (guild: Guild) => this.onGuildJoin(guild)); + this.client.on(Constants.Events.GUILD_DELETE, (guild: Guild) => this.onGuildLeave(guild)); + this.client.on(Constants.Events.MESSAGE_CREATE, (msg: Message) => this.onMessage(msg)); + this.client.on( + Constants.Events.MESSAGE_REACTION_ADD, + (msgReaction: MessageReaction, reactor: User) => + this.onReactionAdd(msgReaction, reactor) ); - this.client.on('rateLimit', (rateLimitData: RateLimitData) => + this.client.on(Constants.Events.RATE_LIMIT, (rateLimitData: RateLimitData) => this.onRateLimit(rateLimitData) ); } @@ -52,7 +63,10 @@ export class Bot { let userTag = this.client.user.tag; Logger.info(Logs.info.login.replace('{USER_TAG}', userTag)); - this.jobService.start(); + if (!Debug.dummyMode.enabled) { + this.jobService.start(); + Logger.info(Logs.info.startedJobs); + } this.ready = true; } @@ -62,7 +76,7 @@ export class Bot { } private async onGuildJoin(guild: Guild): Promise { - if (!this.ready) { + if (!this.ready || Debug.dummyMode.enabled) { return; } @@ -74,7 +88,7 @@ export class Bot { } private async onGuildLeave(guild: Guild): Promise { - if (!this.ready) { + if (!this.ready || Debug.dummyMode.enabled) { return; } @@ -86,12 +100,25 @@ export class Bot { } private async onReactionAdd(msgReaction: any, reactor: User): Promise { - if (!this.ready) return; - this.reactionAddHandler.process(msgReaction, reactor); + if ( + !this.ready || + (Debug.dummyMode.enabled && !Debug.dummyMode.whitelist.includes(reactor.id)) + ) { + return; + } + + try { + this.reactionAddHandler.process(msgReaction, reactor); + } catch (error) { + Logger.error(Logs.error.reaction, error); + } } private async onMessage(msg: Message): Promise { - if (!this.ready) { + if ( + !this.ready || + (Debug.dummyMode.enabled && !Debug.dummyMode.whitelist.includes(msg.author.id)) + ) { return; } diff --git a/src/commands/blacklist-command.ts b/src/commands/blacklist-command.ts index 81369713..09ab215b 100644 --- a/src/commands/blacklist-command.ts +++ b/src/commands/blacklist-command.ts @@ -1,13 +1,15 @@ -import { Message, MessageEmbed, TextChannel } from 'discord.js'; +import { + BlacklistAddSubCommand, + BlacklistClearSubCommand, + BlacklistListSubCommand, + BlacklistRemoveSubCommand, +} from './blacklist'; +import { Message, TextChannel } from 'discord.js'; -import { BlacklistAddSubCommand } from './blacklist/blacklist-add-sub-command'; -import { BlacklistClearSubCommand } from './blacklist/blacklist-clear-sub-command'; -import { BlacklistListSubCommand } from './blacklist'; -import { BlacklistRemoveSubCommand } from './blacklist/blacklist-remove-sub-command'; import { Command } from './command'; -import { MessageUtils } from '../utils'; - -let Config = require('../../config/config.json'); +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { MessageUtils, FormatUtils } from '../utils'; export class BlacklistCommand implements Command { public name: string = 'blacklist'; @@ -27,34 +29,30 @@ export class BlacklistCommand implements Command { private blacklistListSubCommand: BlacklistListSubCommand ) {} - public async execute(args: string[], msg: Message, channel: TextChannel) { + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { if (args.length === 2) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription( - `Please specify a sub command for the blacklist! [(?)](${Config.links.docs}/faq#what-is-the-birthday-blacklist)\nAccepted Values: \`list\`, \`add \`, \`remove \`, \`clear\`` - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noBlacklistArgs', LangCode.EN_US) + ); return; } - if (args[2].toLowerCase() === 'add') { + let action = FormatUtils.extractMiscActionType(args[2].toLowerCase())?.toLowerCase() ?? ''; + + if (action === 'add') { this.blacklistAddSubCommand.execute(args, msg, channel); - } else if (args[2].toLowerCase() === 'remove') { + } else if (action === 'remove') { this.blacklistRemoveSubCommand.execute(args, msg, channel); - } else if (args[2].toLowerCase() === 'list') { + } else if (action === 'list') { this.blacklistListSubCommand.execute(args, msg, channel); - } else if (args[2].toLowerCase() === 'clear') { + } else if (action === 'clear') { this.blacklistClearSubCommand.execute(args, msg, channel); } else { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription( - `Please specify a sub command for the blacklist! [(?)](${Config.links.docs}/faq#what-is-the-birthday-blacklist)\nAccepted Values: \`list\`, \`add \`, \`remove \`, \`clear\`` - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noBlacklistArgs', LangCode.EN_US) + ); return; } } diff --git a/src/commands/blacklist/blacklist-add-sub-command.ts b/src/commands/blacklist/blacklist-add-sub-command.ts index a3d24908..6a179682 100644 --- a/src/commands/blacklist/blacklist-add-sub-command.ts +++ b/src/commands/blacklist/blacklist-add-sub-command.ts @@ -1,20 +1,19 @@ import { GuildUtils, MessageUtils } from '../../utils'; -import { Message, MessageEmbed, TextChannel } from 'discord.js'; +import { Message, TextChannel } from 'discord.js'; import { BlacklistRepo } from '../../services/database/repos'; - -let Config = require('../../../config/config.json'); +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; export class BlacklistAddSubCommand { constructor(private blacklistRepo: BlacklistRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel) { + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { if (args.length === 3) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription('Please specify a user!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noUserSpecified', LangCode.EN_US) + ); return; } @@ -24,38 +23,38 @@ export class BlacklistAddSubCommand { // Did we find a user? if (!target) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription('Could not find that user!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noUserFound', LangCode.EN_US) + ); return; } if (target.bot) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription('You cannot blacklist a bot!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.cantBlacklistBot', LangCode.EN_US) + ); return; } let blacklist = await this.blacklistRepo.getBlacklist(msg.guild.id); if (blacklist.blacklist.map(entry => entry.UserDiscordId).includes(target.id)) { - let embed = new MessageEmbed() - .setDescription('This user is already in the blacklist!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.userAlreadyInBlacklist', LangCode.EN_US) + ); return; } await this.blacklistRepo.addBlacklist(msg.guild.id, target.id); - let embed = new MessageEmbed() - .setDescription(`Successfully added ${target.toString()} to the birthday blacklist!`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('results.blacklistAddSuccess', LangCode.EN_US, { + TARGET: target.toString(), + }) + ); } } diff --git a/src/commands/blacklist/blacklist-clear-sub-command.ts b/src/commands/blacklist/blacklist-clear-sub-command.ts index e5f6ff15..135a7476 100644 --- a/src/commands/blacklist/blacklist-clear-sub-command.ts +++ b/src/commands/blacklist/blacklist-clear-sub-command.ts @@ -1,13 +1,15 @@ -import { ActionUtils, MessageUtils } from '../../utils'; import { CollectOptions, CollectorUtils, ExpireFunction, MessageFilter, } from 'discord.js-collector-utils'; -import { Message, MessageEmbed, MessageReaction, TextChannel, User } from 'discord.js'; +import { Message, MessageReaction, TextChannel, User } from 'discord.js'; import { BlacklistRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MessageUtils } from '../../utils'; let Config = require('../../../config/config.json'); @@ -19,46 +21,35 @@ const COLLECT_OPTIONS: CollectOptions = { export class BlacklistClearSubCommand { constructor(private blacklistRepo: BlacklistRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel) { + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { let stopFilter: MessageFilter = (nextMsg: Message) => nextMsg.author.id === msg.author.id && [Config.prefix, ...Config.stopCommands].includes( nextMsg.content.split(/\s+/)[0].toLowerCase() ); let expireFunction: ExpireFunction = async () => { - await MessageUtils.send( - channel, - new MessageEmbed() - .setTitle('Birthday Message Clear - Expired') - .setDescription('Type `bday blacklist clear` to clear the birthday blacklist.') - .setColor(Config.colors.error) - ); + await MessageUtils.reply(msg, Lang.getEmbed('results.promptExpired', LangCode.EN_US)); }; let blacklisted = await this.blacklistRepo.getBlacklist(msg.guild.id); - let confirmationEmbed = new MessageEmbed(); - if (blacklisted.blacklist.length === 0) { - confirmationEmbed - .setDescription('You server has not blacklisted any users!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, confirmationEmbed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.emptyBlacklist', LangCode.EN_US) + ); return; } let trueFalseOptions = [Config.emotes.confirm, Config.emotes.deny]; - confirmationEmbed - .setDescription( - `Are you sure you want to clear __**${ - blacklisted.blacklist.length - }**__ blacklisted user${blacklisted.blacklist.length === 1 ? '' : 's'}?` - ) - .setFooter('This action is irreversible!', msg.client.user.avatarURL()) - .setColor(Config.colors.warning); - - let confirmationMessage = await MessageUtils.send(channel, confirmationEmbed); // Send confirmation and emotes + let confirmationMessage = await MessageUtils.send( + channel, + Lang.getEmbed('serverPrompts.blacklistClearConfirmation', LangCode.EN_US, { + TOTAL: blacklisted.blacklist.length.toString(), + ICON: msg.client.user.avatarURL(), + }) + ); // Send confirmation and emotes for (let option of trueFalseOptions) { await MessageUtils.react(confirmationMessage, option); } @@ -85,15 +76,15 @@ export class BlacklistClearSubCommand { // Confirm await this.blacklistRepo.clearBlacklist(msg.guild.id); - let embed = new MessageEmbed() - .setDescription(`Successfully cleared the birthday blacklist!`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('results.blacklistClearSuccess', LangCode.EN_US) + ); } else { - let embed = new MessageEmbed() - .setDescription(`Action canceled.`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('results.actionCanceled', LangCode.EN_US) + ); } } } diff --git a/src/commands/blacklist/blacklist-list-sub-command.ts b/src/commands/blacklist/blacklist-list-sub-command.ts index 8e572e48..8ee2f8c1 100644 --- a/src/commands/blacklist/blacklist-list-sub-command.ts +++ b/src/commands/blacklist/blacklist-list-sub-command.ts @@ -2,21 +2,19 @@ import { FormatUtils, MessageUtils, ParseUtils } from '../../utils'; import { Message, TextChannel } from 'discord.js'; import { BlacklistRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; let Config = require('../../../config/config.json'); export class BlacklistListSubCommand { constructor(private blacklistRepo: BlacklistRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel) { + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { let page = 1; if (args[3]) { - try { - page = ParseUtils.parseInt(args[3]); - } catch (error) { - // Not A Number - } + page = ParseUtils.parseInt(args[3]); if (!page || page <= 0 || page > 100000) page = 1; } @@ -39,7 +37,7 @@ export class BlacklistListSubCommand { let message = await MessageUtils.send(channel, embed); - if (embed.description === '**The blacklist is empty!**') return; + if (embed.description === Lang.getRef('list.emptyBlacklist', LangCode.EN_US)) return; await MessageUtils.react(message, Config.emotes.previousPage); await MessageUtils.react(message, Config.emotes.jumpToPage); diff --git a/src/commands/blacklist/blacklist-remove-sub-command.ts b/src/commands/blacklist/blacklist-remove-sub-command.ts index 6f0550fe..0ce730e7 100644 --- a/src/commands/blacklist/blacklist-remove-sub-command.ts +++ b/src/commands/blacklist/blacklist-remove-sub-command.ts @@ -1,20 +1,19 @@ import { GuildUtils, MessageUtils } from '../../utils'; -import { Message, MessageEmbed, TextChannel } from 'discord.js'; +import { Message, TextChannel } from 'discord.js'; import { BlacklistRepo } from '../../services/database/repos'; - -let Config = require('../../../config/config.json'); +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; export class BlacklistRemoveSubCommand { constructor(private blacklistRepo: BlacklistRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel) { + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { if (args.length === 3) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription('Please specify a user!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noUserSpecified', LangCode.EN_US) + ); return; } @@ -24,48 +23,38 @@ export class BlacklistRemoveSubCommand { // Did we find a user? if (!target) { - let embed = new MessageEmbed() - .setDescription('Could not find that user!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - if (!target) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription('Could not find that user!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noUserFound', LangCode.EN_US) + ); return; } if (target.bot) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription('You cannot blacklist a bot!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.cantBlacklistBot', LangCode.EN_US) + ); return; } let blacklist = await this.blacklistRepo.getBlacklist(msg.guild.id); if (!blacklist.blacklist.map(entry => entry.UserDiscordId).includes(target.id)) { - let embed = new MessageEmbed() - .setDescription(`This user isn't in the blacklist!`) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.userNotInBlacklist', LangCode.EN_US) + ); return; } await this.blacklistRepo.removeBlacklist(msg.guild.id, target.id); - let embed = new MessageEmbed() - .setDescription( - `Successfully removed ${target.toString()} from the birthday blacklist!` - ) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('results.blacklistAddSuccess', LangCode.EN_US, { + TARGET: target.toString(), + }) + ); } } diff --git a/src/commands/clear-command.ts b/src/commands/clear-command.ts deleted file mode 100644 index f6f5c5fb..00000000 --- a/src/commands/clear-command.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Message, MessageEmbed, TextChannel } from 'discord.js'; - -import { Command } from './command'; -import { GuildRepo } from '../services/database/repos'; -import { MessageUtils } from '../utils'; - -let Config = require('../../config/config.json'); - -export class ClearCommand implements Command { - public name: string = 'clear'; - public aliases = ['remove']; - public requireSetup = true; - public guildOnly = true; - public adminOnly = true; - public ownerOnly = false; - public voteOnly = false; - public requirePremium = false; - public getPremium = false; - - constructor(private guildRepo: GuildRepo) {} - - public async execute(args: string[], msg: Message, channel: TextChannel) { - if (args.length === 2) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription( - 'Please specify what to clear!\nAccepted Values: `channel`, `role`, `trustedRole`, `birthdayMasterRole`' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - if (args[2].toLowerCase() === 'channel') { - await this.guildRepo.updateBirthdayChannel(msg.guild.id, '0'); - - let embed = new MessageEmbed() - .setDescription(`Successfully cleared the birthday channel!`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } else if (args[2].toLowerCase() === 'role') { - await this.guildRepo.updateBirthdayRole(msg.guild.id, '0'); - - let embed = new MessageEmbed() - .setDescription(`Successfully cleared the birthday role!`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } else if (args[2].toLowerCase() === 'trustedrole') { - await this.guildRepo.updateTrustedRole(msg.guild.id, '0'); - - let embed = new MessageEmbed() - .setDescription(`Successfully cleared the trusted role!`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } else if ( - args[2].toLowerCase() === 'birthdaymaster' || - args[2].toLowerCase() === 'birthdaymasterrole' - ) { - await this.guildRepo.updateBirthdayMasterRole(msg.guild.id, '0'); - - let embed = new MessageEmbed() - .setDescription(`Successfully cleared the birthday master role!`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } - } -} diff --git a/src/commands/config-command.ts b/src/commands/config-command.ts new file mode 100644 index 00000000..d06a0ddd --- /dev/null +++ b/src/commands/config-command.ts @@ -0,0 +1,91 @@ +import { + ConfigBirthdayMasterRoleSubCommand, + ConfigChannelSubCommand, + ConfigNameFormatSubCommand, + ConfigRequireAllTrustedRolesSubCommand, + ConfigRoleSubCommand, + ConfigTimezoneSubCommand, + ConfigTrustedPreventsMsgSubCommand, + ConfigTrustedPreventsRoleSubCommand, + ConfigUseTimezoneSubCommand, +} from './config'; +import { Message, TextChannel } from 'discord.js'; + +import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { MessageUtils, FormatUtils } from '../utils'; + +export class ConfigCommand implements Command { + public name: string = 'config'; + public aliases = ['cf', 'setting']; + public requireSetup = true; + public guildOnly = true; + public adminOnly = true; + public ownerOnly = false; + public voteOnly = false; + public requirePremium = false; + public getPremium = false; + + constructor( + private configBirthdayMasterRole: ConfigBirthdayMasterRoleSubCommand, + private configChannelSubCommand: ConfigChannelSubCommand, + private configRoleSubCommand: ConfigRoleSubCommand, + private configNameFormatSubCommand: ConfigNameFormatSubCommand, + private configTrustedPreventsMsgSubCommand: ConfigTrustedPreventsMsgSubCommand, + private configTrustedPreventsRoleSubCommand: ConfigTrustedPreventsRoleSubCommand, + private configTimezoneSubCommand: ConfigTimezoneSubCommand, + private configUseTimezoneSubCommand: ConfigUseTimezoneSubCommand, + private configRequireAllTrustedRolesSubCommand: ConfigRequireAllTrustedRolesSubCommand + ) {} + + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { + if (args.length === 2) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noConfigArgs', LangCode.EN_US) + ); + return; + } + let subCommand = FormatUtils.extractConfigType(args[2].toLowerCase())?.toLowerCase(); + + if (!subCommand) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noConfigArgs', LangCode.EN_US) + ); + return; + } + + if (subCommand === 'birthdaymasterrole') { + this.configBirthdayMasterRole.execute(args, msg, channel); + } else if (subCommand === 'channel') { + this.configChannelSubCommand.execute(args, msg, channel); + } else if (subCommand === 'role') { + this.configRoleSubCommand.execute(args, msg, channel); + } else if (subCommand === 'nameformat') { + this.configNameFormatSubCommand.execute(args, msg, channel); + } else if (subCommand === 'timezone') { + this.configTimezoneSubCommand.execute(args, msg, channel); + } else if (subCommand === 'usetimezone') { + this.configUseTimezoneSubCommand.execute(args, msg, channel); + } else if (subCommand === 'trustedpreventsmessage') { + this.configTrustedPreventsMsgSubCommand.execute(args, msg, channel); + } else if (subCommand === 'trustedpreventsrole') { + this.configTrustedPreventsRoleSubCommand.execute(args, msg, channel); + } else if (subCommand === 'requirealltrustedroles') { + this.configRequireAllTrustedRolesSubCommand.execute(args, msg, channel); + } else { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noConfigArgs', LangCode.EN_US) + ); + return; + } + } +} diff --git a/src/commands/config/config-birthday-master-role-sub-command.ts b/src/commands/config/config-birthday-master-role-sub-command.ts new file mode 100644 index 00000000..61916dd5 --- /dev/null +++ b/src/commands/config/config-birthday-master-role-sub-command.ts @@ -0,0 +1,94 @@ +import { Message, Role, TextChannel } from 'discord.js'; + +import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MessageUtils, FormatUtils } from '../../utils'; + +const errorEmbed = Lang.getEmbed('validation.invalidMasterAction', LangCode.EN_US); + +export class ConfigBirthdayMasterRoleSubCommand { + constructor(private guildRepo: GuildRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + if (args.length === 3) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + let action = FormatUtils.extractMiscActionType(args[3].toLowerCase())?.toLowerCase() ?? ''; + + if (action === 'create') { + // User wants to create the default birthday master role + if (!msg.guild.me.hasPermission('MANAGE_ROLES')) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.needsManageRoles', LangCode.EN_US) + ); + return; + } + + // Create role with desired attributes + let birthdayMasterRole = await msg.guild.roles.create({ + data: { + name: 'BirthdayMaster', + }, + }); + + await this.guildRepo.updateBirthdayMasterRole(msg.guild.id, birthdayMasterRole?.id); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.masterRoleCreated', LangCode.EN_US, { + ROLE: birthdayMasterRole.toString(), + }) + ); + } else if (action === 'clear') { + // User wants to clear the birthday master role + await this.guildRepo.updateBirthdayMasterRole(msg.guild.id, '0'); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.masterRoleCleared', LangCode.EN_US) + ); + } else { + // See if a role was specified + let birthdayMasterRole: Role = msg.mentions.roles.first(); + + if (!birthdayMasterRole) { + birthdayMasterRole = msg.guild.roles.cache.find(role => + role.name.toLowerCase().includes(args[3].toLowerCase()) + ); + } + + if ( + !birthdayMasterRole || + birthdayMasterRole.id === msg.guild.id || + args[3].toLowerCase() === 'everyone' + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidRole', LangCode.EN_US) + ); + return; + } + + if (birthdayMasterRole.managed) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.masterRoleManaged', LangCode.EN_US) + ); + return; + } + + await this.guildRepo.updateBirthdayMasterRole(msg.guild.id, birthdayMasterRole?.id); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.masterRoleSet', LangCode.EN_US, { + ROLE: birthdayMasterRole.toString(), + }) + ); + } + } +} diff --git a/src/commands/config/config-channel-sub-command.ts b/src/commands/config/config-channel-sub-command.ts new file mode 100644 index 00000000..a3bc3c9b --- /dev/null +++ b/src/commands/config/config-channel-sub-command.ts @@ -0,0 +1,143 @@ +import { FormatUtils, MessageUtils, PermissionUtils } from '../../utils'; +import { Message, TextChannel } from 'discord.js'; + +import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; + +const errorEmbed = Lang.getEmbed('validation.invalidChannelAction', LangCode.EN_US); + +export class ConfigChannelSubCommand { + constructor(private guildRepo: GuildRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + let type = FormatUtils.extractCelebrationType(args[3]?.toLowerCase()); + + if ( + !type || + (type !== 'birthday' && type !== 'memberanniversary' && type !== 'serveranniversary') + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidChannelType', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + if (args.length === 4) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + let channelId = '0'; + + let refType = + (type === 'memberanniversary' + ? 'memberAnniversary' + : type === 'serveranniversary' + ? 'serverAnniversary' + : 'birthday') + 'Channel'; + + let action = FormatUtils.extractMiscActionType(args[4].toLowerCase())?.toLowerCase() ?? ''; + + if (action === 'create') { + // User wants to create the default birthday channel + if (!msg.guild.me.hasPermission('MANAGE_CHANNELS')) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.needsManageChannels', LangCode.EN_US) + ); + return; + } + + // Create channel with desired attributes + let newChannel = await msg.guild.channels.create( + Lang.getRef('terms.' + refType + 'Title', LangCode.EN_US), + { + type: 'text', + topic: Lang.getRef('terms.' + refType + 'Topic', LangCode.EN_US), + permissionOverwrites: [ + { + id: msg.guild.id, + deny: ['SEND_MESSAGES'], + allow: ['VIEW_CHANNEL'], + }, + { + id: msg.guild.me.roles.cache.filter(role => role.managed).first(), + allow: [ + 'VIEW_CHANNEL', + 'SEND_MESSAGES', + 'EMBED_LINKS', + 'ADD_REACTIONS', + 'READ_MESSAGE_HISTORY', + ], + }, + ], + } + ); + + channelId = newChannel ? newChannel.id : '0'; + await MessageUtils.send( + channel, + Lang.getEmbed('results.' + refType + 'Created', LangCode.EN_US, { + CHANNEL: newChannel.toString(), + }) + ); + } else if (action === 'clear') { + // User wants to clear the birthday channel + + await MessageUtils.send( + channel, + Lang.getEmbed('results.' + refType + 'Cleared', LangCode.EN_US) + ); + } else { + // See if a channel was specified + + let newChannel: TextChannel = msg.mentions.channels.first(); + + // If could not find in mention check, try to find by name + if (!newChannel) { + newChannel = msg.guild.channels.cache + .filter(channel => channel instanceof TextChannel) + .map(channel => channel as TextChannel) + .find(channel => channel.name.toLowerCase().includes(args[4].toLowerCase())); + } + + // Could it find the channel in either check? + if (!newChannel) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + // Bot needs to be able to message in the desired channel + if (!PermissionUtils.canSend(channel)) { + await MessageUtils.send( + msg.channel as TextChannel, + Lang.getEmbed('validation.notEnoughChannelPerms', LangCode.EN_US, { + CHANNEL: channel.toString(), + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + channelId = newChannel ? newChannel.id : '0'; + + await MessageUtils.send( + channel, + Lang.getEmbed('results.' + refType + 'Set', LangCode.EN_US, { + CHANNEL: newChannel.toString(), + }) + ); + } + + // Update the database with the channel + type === 'birthday' + ? await this.guildRepo.updateBirthdayChannel(msg.guild.id, channelId) + : type === 'memberanniversary' + ? await this.guildRepo.updateMemberAnniversaryChannel(msg.guild.id, channelId) + : await this.guildRepo.updateServerAnniversaryChannel(msg.guild.id, channelId); + } +} diff --git a/src/commands/config/config-name-format-sub-command.ts b/src/commands/config/config-name-format-sub-command.ts new file mode 100644 index 00000000..dd5c2417 --- /dev/null +++ b/src/commands/config/config-name-format-sub-command.ts @@ -0,0 +1,46 @@ +import { Message, TextChannel } from 'discord.js'; + +import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MessageUtils, FormatUtils } from '../../utils'; + +export class ConfigNameFormatSubCommand { + constructor(private guildRepo: GuildRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + let setting = FormatUtils.extractNameFormatType(args[3]?.toLowerCase())?.toLowerCase(); + if (!setting) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidNameFormat', LangCode.EN_US, { + MENTION: msg.author.toString(), + USERNAME: msg.author.username, + NICKNAME: msg.member.displayName, + TAG: `${msg.author.username}#${msg.author.discriminator}`, + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + if (setting === 'default') setting = 'mention'; + + await this.guildRepo.updateNameFormat(msg.guild.id, setting); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.nameFormatSet', LangCode.EN_US, { + SETTING: setting, + FORMAT: + setting === 'mention' + ? msg.author.toString() + : setting === 'nickname' + ? msg.member.displayName + : setting === 'username' + ? msg.author.username + : `${msg.author.username}#${msg.author.discriminator}`, + }) + ); + } +} diff --git a/src/commands/config/config-require-all-trusted-roles-sub-command.ts b/src/commands/config/config-require-all-trusted-roles-sub-command.ts new file mode 100644 index 00000000..df0ca463 --- /dev/null +++ b/src/commands/config/config-require-all-trusted-roles-sub-command.ts @@ -0,0 +1,36 @@ +import { FormatUtils, MessageUtils } from '../../utils'; +import { Message, TextChannel } from 'discord.js'; + +import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; + +const errorEmbed = Lang.getEmbed('validation.noTrueFalse', LangCode.EN_US); + +export class ConfigRequireAllTrustedRolesSubCommand { + constructor(private guildRepo: GuildRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + if (args.length === 3) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + let requireAll = FormatUtils.findBoolean(args[3]); + + if (requireAll === undefined || requireAll === null) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidTrueFalseRequireAllTrustedMessage', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + await this.guildRepo.updateRequireAllTrustedRoles(msg.guild.id, requireAll ? 1 : 0); + + let value = requireAll ? 'results.requireAllTrustedYes' : 'results.requireAllTrustedNo'; + await MessageUtils.send(channel, Lang.getEmbed(value, LangCode.EN_US)); + } +} diff --git a/src/commands/config/config-role-sub-command.ts b/src/commands/config/config-role-sub-command.ts new file mode 100644 index 00000000..db47a49f --- /dev/null +++ b/src/commands/config/config-role-sub-command.ts @@ -0,0 +1,142 @@ +import { Message, Role, TextChannel } from 'discord.js'; + +import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MessageUtils, FormatUtils } from '../../utils'; + +let Config = require('../../../config/config.json'); + +const errorEmbed = Lang.getEmbed('validation.invalidBirthdayRoleAction', LangCode.EN_US); + +export class ConfigRoleSubCommand { + constructor(private guildRepo: GuildRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + if (args.length === 3) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + let action = FormatUtils.extractMiscActionType(args[3].toLowerCase())?.toLowerCase() ?? ''; + + if (action === 'create') { + // User wants to create the default birthday role + if (!msg.guild.me.hasPermission('MANAGE_ROLES')) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.needsManageChannels', LangCode.EN_US) + ); + return; + } + + // Create role with desired attributes + let birthdayRole = await msg.guild.roles.create({ + data: { + name: Config.emotes.birthday, + color: Config.colors.role, + hoist: true, + mentionable: true, + }, + }); + + await this.guildRepo.updateBirthdayRole(msg.guild.id, birthdayRole?.id); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.birthdayRoleCreated', LangCode.EN_US, { + ROLE: birthdayRole.toString(), + }) + ); + } else if (action === 'clear') { + // User wants to clear the birthday role + await this.guildRepo.updateBirthdayRole(msg.guild.id, '0'); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.birthdayRoleCleared', LangCode.EN_US) + ); + } else { + // See if a role was specified + if (!msg.guild.me.hasPermission('MANAGE_ROLES')) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.needsManageChannels', LangCode.EN_US) + ); + return; + } + + // Find role with desired attributes + let birthdayRole: Role = msg.mentions.roles.first(); + + if (!birthdayRole) { + birthdayRole = msg.guild.roles.cache.find(role => + role.name.toLowerCase().includes(args[3].toLowerCase()) + ); + } + + if ( + !birthdayRole || + birthdayRole.id === msg.guild.id || + args[3].toLowerCase() === 'everyone' + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidRole', LangCode.EN_US) + ); + return; + } + + if ( + birthdayRole.position > + msg.guild.members.resolve(msg.client.user).roles.highest.position + ) { + await MessageUtils.send( + msg.channel as TextChannel, + Lang.getEmbed('validation.roleHierarchyError', LangCode.EN_US, { + BOT: msg.client.user.toString(), + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + if (birthdayRole.managed) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.birthdayRoleManaged', LangCode.EN_US) + ); + return; + } + + let membersWithRole = birthdayRole.members.size; + + if (membersWithRole > 0 && membersWithRole < 100) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.warnBirthdayRoleSize', LangCode.EN_US, { + AMOUNT: membersWithRole.toString(), + ICON: msg.client.user.avatarURL(), + }) + ); + } else if (membersWithRole > 100) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.denyBirthdayRoleSize', LangCode.EN_US, { + AMOUNT: membersWithRole.toString(), + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + await this.guildRepo.updateBirthdayRole(msg.guild.id, birthdayRole?.id); + await MessageUtils.send( + channel, + Lang.getEmbed('results.birthdayRoleSet', LangCode.EN_US, { + ROLE: birthdayRole.toString(), + }) + ); + } + } +} diff --git a/src/commands/config/config-timezone-sub-command.ts b/src/commands/config/config-timezone-sub-command.ts new file mode 100644 index 00000000..7eab939c --- /dev/null +++ b/src/commands/config/config-timezone-sub-command.ts @@ -0,0 +1,56 @@ +import { FormatUtils, MessageUtils } from '../../utils'; +import { Message, TextChannel } from 'discord.js'; + +import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; + +const errorEmbed = Lang.getEmbed('validation.noTimeZone', LangCode.EN_US); +export class ConfigTimezoneSubCommand { + constructor(private guildRepo: GuildRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + if (args.length === 3) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + if (FormatUtils.extractMiscActionType(args[3])) { + await this.guildRepo.updateDefaultTimezone(msg.guild.id, '0'); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.defaultTimeCleared', LangCode.EN_US) + ); + return; + } + + if (FormatUtils.checkAbbreviation(args[3])) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidServerTimeZoneAbbreviation', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + let timezone = FormatUtils.findZone(args[3]); // Try and get the time zone + if (!timezone) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidServerTimeZone', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + await this.guildRepo.updateDefaultTimezone(msg.guild.id, timezone); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.defaultTimeZoneSet', LangCode.EN_US, { TIMEZONE: timezone }) + ); + } +} diff --git a/src/commands/config/config-trusted-prevents-msg-sub-command.ts b/src/commands/config/config-trusted-prevents-msg-sub-command.ts new file mode 100644 index 00000000..091e487d --- /dev/null +++ b/src/commands/config/config-trusted-prevents-msg-sub-command.ts @@ -0,0 +1,38 @@ +import { FormatUtils, MessageUtils } from '../../utils'; +import { Message, TextChannel } from 'discord.js'; + +import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; + +const errorEmbed = Lang.getEmbed('validation.noTrueFalse', LangCode.EN_US); + +export class ConfigTrustedPreventsMsgSubCommand { + constructor(private guildRepo: GuildRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + if (args.length === 3) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + let preventMessage = FormatUtils.findBoolean(args[3]); + + if (preventMessage === undefined || preventMessage === null) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidTrueFalseTrustedPreventsMessage', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + await this.guildRepo.updateTrustedPreventsMessage(msg.guild.id, preventMessage ? 1 : 0); + + let value = preventMessage + ? 'results.trustedPreventsMessageYes' + : 'results.trustedPreventsMessageNo'; + await MessageUtils.send(channel, Lang.getEmbed(value, LangCode.EN_US)); + } +} diff --git a/src/commands/config/config-trusted-prevents-role-sub-command.ts b/src/commands/config/config-trusted-prevents-role-sub-command.ts new file mode 100644 index 00000000..56a6f73b --- /dev/null +++ b/src/commands/config/config-trusted-prevents-role-sub-command.ts @@ -0,0 +1,38 @@ +import { FormatUtils, MessageUtils } from '../../utils'; +import { Message, TextChannel } from 'discord.js'; + +import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; + +const errorEmbed = Lang.getEmbed('validation.noTrueFalse', LangCode.EN_US); + +export class ConfigTrustedPreventsRoleSubCommand { + constructor(private guildRepo: GuildRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + if (args.length === 3) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + let preventRole = FormatUtils.findBoolean(args[3]); + + if (preventRole === undefined || preventRole === null) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidTrueFalseTrustedPreventsRole', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + await this.guildRepo.updateTrustedPreventsRole(msg.guild.id, preventRole ? 1 : 0); + + let value = preventRole + ? 'results.trustedPreventsRoleYes' + : 'results.trustedPreventsRoleNo'; + await MessageUtils.send(channel, Lang.getEmbed(value, LangCode.EN_US)); + } +} diff --git a/src/commands/config/config-use-timezone-sub-command.ts b/src/commands/config/config-use-timezone-sub-command.ts new file mode 100644 index 00000000..e9eea81c --- /dev/null +++ b/src/commands/config/config-use-timezone-sub-command.ts @@ -0,0 +1,32 @@ +import { Message, TextChannel } from 'discord.js'; + +import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MessageUtils, FormatUtils } from '../../utils'; + +const errorEmbed = Lang.getEmbed('validation.invalidUseTimezoneAction', LangCode.EN_US); + +export class ConfigUseTimezoneSubCommand { + constructor(private guildRepo: GuildRepo) {} + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + if (args.length === 3) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + let option = FormatUtils.extractMiscActionType(args[3].toLowerCase())?.toLowerCase() ?? ''; + + if (option !== 'user' && option !== 'server') { + await MessageUtils.send(channel, errorEmbed); + return; + } + + await this.guildRepo.updateUseTimezone(msg.guild.id, option); + await MessageUtils.send( + channel, + Lang.getEmbed('results.useTimeZoneSettingSet', LangCode.EN_US, { OPTION: option }) + ); + } +} diff --git a/src/commands/config/index.ts b/src/commands/config/index.ts new file mode 100644 index 00000000..9a462575 --- /dev/null +++ b/src/commands/config/index.ts @@ -0,0 +1,9 @@ +export { ConfigChannelSubCommand } from './config-channel-sub-command'; +export { ConfigRoleSubCommand } from './config-role-sub-command'; +export { ConfigBirthdayMasterRoleSubCommand } from './config-birthday-master-role-sub-command'; +export { ConfigNameFormatSubCommand } from './config-name-format-sub-command'; +export { ConfigTrustedPreventsMsgSubCommand } from './config-trusted-prevents-msg-sub-command'; +export { ConfigTrustedPreventsRoleSubCommand } from './config-trusted-prevents-role-sub-command'; +export { ConfigTimezoneSubCommand } from './config-timezone-sub-command'; +export { ConfigUseTimezoneSubCommand } from './config-use-timezone-sub-command'; +export { ConfigRequireAllTrustedRolesSubCommand } from './config-require-all-trusted-roles-sub-command'; diff --git a/src/commands/create-command.ts b/src/commands/create-command.ts deleted file mode 100644 index 7f0513a8..00000000 --- a/src/commands/create-command.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { Message, MessageEmbed, TextChannel } from 'discord.js'; - -import { Command } from './command'; -import { GuildRepo } from '../services/database/repos'; -import { MessageUtils } from '../utils'; - -let Config = require('../../config/config.json'); - -export class CreateCommand implements Command { - public name: string = 'create'; - public aliases = []; - public requireSetup = true; - public guildOnly = true; - public adminOnly = true; - public ownerOnly = false; - public voteOnly = false; - public requirePremium = false; - public getPremium = false; - - constructor(private guildRepo: GuildRepo) {} - - public async execute(args: string[], msg: Message, channel: TextChannel) { - if (args.length === 2) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription( - 'Please specify what to create!\nAccepted Values: `channel`, `role`, `trustedRole`, `birthdayMasterRole`' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - if (args[2].toLowerCase() === 'channel') { - if (!msg.guild.me.hasPermission('MANAGE_CHANNELS')) { - let embed = new MessageEmbed() - .setTitle('Not Enough Permissions!') - .setDescription('The bot must have permission to manage channel!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - // Create channel with desired attributes - let birthdayChannel = await msg.guild.channels.create( - `${Config.emotes.birthday} birthdays`, - { - type: 'text', - topic: 'Birthday Announcements!', - permissionOverwrites: [ - { - id: msg.guild.id, - deny: ['SEND_MESSAGES'], - allow: ['VIEW_CHANNEL'], - }, - { - id: msg.guild.me.roles.cache.filter(role => role.managed).first(), - allow: ['VIEW_CHANNEL', 'SEND_MESSAGES'], - }, - ], - } - ); - - await this.guildRepo.updateBirthdayChannel(msg.guild.id, birthdayChannel?.id); - - let embed = new MessageEmbed() - .setDescription( - `Successfully created the birthday channel ${birthdayChannel.toString()}!` - ) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } else if (args[2].toLowerCase() === 'role') { - if (!msg.guild.me.hasPermission('MANAGE_ROLES')) { - let embed = new MessageEmbed() - .setTitle('Not Enough Permissions!') - .setDescription('The bot must have permission to manage roles!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - // Create role with desired attributes - let birthdayRole = await msg.guild.roles.create({ - data: { - name: Config.emotes.birthday, - color: Config.colors.role, - hoist: true, - mentionable: true, - }, - }); - - await this.guildRepo.updateBirthdayRole(msg.guild.id, birthdayRole?.id); - - let embed = new MessageEmbed() - .setDescription( - `Successfully created the birthday role ${birthdayRole.toString()}!` - ) - .setFooter(`This role is actively removed from those whose birthday it isn't.`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } else if (args[2].toLowerCase() === 'trustedrole') { - if (!msg.guild.me.hasPermission('MANAGE_ROLES')) { - let embed = new MessageEmbed() - .setTitle('Not Enough Permissions!') - .setDescription('The bot must have permission to manage roles!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - // Create role with desired attributes - let trustedRole = await msg.guild.roles.create({ - data: { - name: 'BirthdayTrusted', - }, - }); - - await this.guildRepo.updateTrustedRole(msg.guild.id, trustedRole?.id); - - let embed = new MessageEmbed() - .setDescription(`Successfully created the trusted role ${trustedRole.toString()}!`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } else if ( - args[2].toLowerCase() === 'birthdaymaster' || - args[2].toLowerCase() === 'birthdaymasterrole' - ) { - if (!msg.guild.me.hasPermission('MANAGE_ROLES')) { - let embed = new MessageEmbed() - .setTitle('Not Enough Permissions!') - .setDescription('The bot must have permission to manage roles!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - // Create role with desired attributes - let birthdayMasterRole = await msg.guild.roles.create({ - data: { - name: 'BirthdayMaster', - }, - }); - - await this.guildRepo.updateBirthdayMasterRole(msg.guild.id, birthdayMasterRole?.id); - - let embed = new MessageEmbed() - .setDescription( - `Successfully created the birthday master role ${birthdayMasterRole.toString()}!` - ) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } - } -} diff --git a/src/commands/dev-command.ts b/src/commands/dev-command.ts new file mode 100644 index 00000000..ce8cb3ed --- /dev/null +++ b/src/commands/dev-command.ts @@ -0,0 +1,81 @@ +import { MessageUtils, ShardUtils } from '../utils'; +import djs, { DMChannel, Message, TextChannel } from 'discord.js'; + +import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import fileSize from 'filesize'; +import typescript from 'typescript'; + +let TsConfig = require('../../tsconfig.json'); + +export class DevCommand implements Command { + public name: string = 'dev'; + public aliases = ['developer', 'usage', 'memory', 'mem']; + public requireSetup = false; + public guildOnly = false; + public adminOnly = false; + public ownerOnly = false; + public voteOnly = false; + public requirePremium = false; + public getPremium = false; + + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + let shardCount = msg.client.shard?.count ?? 1; + let serverCount: number; + if (msg.client.shard) { + try { + serverCount = await ShardUtils.serverCount(msg.client.shard); + } catch (error) { + // SHARDING_IN_PROCESS: Shards are still being spawned. + if (error.name.includes('SHARDING_IN_PROCESS')) { + await MessageUtils.send( + msg.channel, + Lang.getEmbed('errors.startupInProcess', LangCode.EN_US) + ); + return; + } else { + throw error; + } + } + } else { + serverCount = msg.client.guilds.cache.size; + } + + let memory = process.memoryUsage(); + await MessageUtils.send( + msg.channel, + Lang.getEmbed('dev.general', LangCode.EN_US, { + NODE_VERSION: process.version, + TS_VERSION: `v${typescript.version}`, + ES_VERSION: TsConfig.compilerOptions.target, + DJS_VERSION: `v${djs.version}`, + SHARD_COUNT: shardCount.toLocaleString(), + SERVER_COUNT: serverCount.toLocaleString(), + RSS_SIZE: fileSize(memory.rss), + RSS_SIZE_PER_SERVER: + serverCount > 0 + ? fileSize(memory.rss / serverCount) + : Lang.getRef('terms.na', LangCode.EN_US), + HEAP_TOTAL_SIZE: fileSize(memory.heapTotal), + HEAP_TOTAL_SIZE_PER_SERVER: + serverCount > 0 + ? fileSize(memory.heapTotal / serverCount) + : Lang.getRef('terms.na', LangCode.EN_US), + HEAP_USED_SIZE: fileSize(memory.heapUsed), + HEAP_USED_SIZE_PER_SERVER: + serverCount > 0 + ? fileSize(memory.heapUsed / serverCount) + : Lang.getRef('terms.na', LangCode.EN_US), + SHARD_ID: (msg.guild?.shardID ?? 0).toString(), + SERVER_ID: msg.guild?.id ?? Lang.getRef('other.na', LangCode.EN_US), + BOT_ID: msg.client.user.id, + USER_ID: msg.author.id, + }) + ); + } +} diff --git a/src/commands/documentation-command.ts b/src/commands/documentation-command.ts index f01c4dbc..172f1b2f 100644 --- a/src/commands/documentation-command.ts +++ b/src/commands/documentation-command.ts @@ -1,10 +1,10 @@ -import { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; +import { DMChannel, Message, TextChannel } from 'discord.js'; import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; import { MessageUtils } from '../utils'; -let Config = require('../../config/config.json'); - export class DocumentationCommand implements Command { public name: string = 'documentation'; public aliases = ['docs', 'doc', 'wiki']; @@ -16,15 +16,14 @@ export class DocumentationCommand implements Command { public requirePremium = false; public getPremium = false; - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { - let embed = new MessageEmbed() - .setDescription( - `View our Documentation for ${msg.client.user.toString()} [here](${ - Config.links.docs - })!` - ) - .setColor(Config.colors.default); - - await MessageUtils.send(channel, embed); + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + await MessageUtils.send( + channel, + Lang.getEmbed('info.documentation', LangCode.EN_US, { BOT: msg.client.user.toString() }) + ); } } diff --git a/src/commands/donate-command.ts b/src/commands/donate-command.ts index d62d8965..2da3f6e2 100644 --- a/src/commands/donate-command.ts +++ b/src/commands/donate-command.ts @@ -1,10 +1,10 @@ -import { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; +import { DMChannel, Message, TextChannel } from 'discord.js'; import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; import { MessageUtils } from '../utils'; -let Config = require('../../config/config.json'); - export class DonateCommand implements Command { public name: string = 'donate'; public aliases = ['donations', 'contribute']; @@ -16,15 +16,14 @@ export class DonateCommand implements Command { public requirePremium = false; public getPremium = false; - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { - let embed = new MessageEmbed() - .setDescription( - `You can support ${msg.client.user.toString()} by donating [here](${ - Config.links.donate - })!` - ) - .setColor(Config.colors.default); - - await MessageUtils.send(channel, embed); + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + await MessageUtils.send( + channel, + Lang.getEmbed('info.donate', LangCode.EN_US, { BOT: msg.client.user.toString() }) + ); } } diff --git a/src/commands/faq-command.ts b/src/commands/faq-command.ts index 60ce756b..03091286 100644 --- a/src/commands/faq-command.ts +++ b/src/commands/faq-command.ts @@ -1,11 +1,11 @@ -import { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; +import { DMChannel, Message, TextChannel } from 'discord.js'; import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; import { MessageUtils } from '../utils'; -let Config = require('../../config/config.json'); - -export class FAQCommand implements Command { +export class FaqCommand implements Command { public name: string = 'faq'; public aliases = []; public requireSetup = false; @@ -16,13 +16,14 @@ export class FAQCommand implements Command { public requirePremium = false; public getPremium = false; - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { - let embed = new MessageEmbed() - .setDescription( - `View our FAQ for ${msg.client.user.toString()} [here](${Config.links.docs}/faq)!` - ) - .setColor(Config.colors.default); - - await MessageUtils.send(channel, embed); + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + await MessageUtils.send( + channel, + Lang.getEmbed('info.faq', LangCode.EN_US, { BOT: msg.client.user.toString() }) + ); } } diff --git a/src/commands/help-command.ts b/src/commands/help-command.ts index 00277639..c5d67ae7 100644 --- a/src/commands/help-command.ts +++ b/src/commands/help-command.ts @@ -1,9 +1,9 @@ -import { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; +import { DMChannel, Message, TextChannel } from 'discord.js'; +import { FormatUtils, MessageUtils } from '../utils'; import { Command } from './command'; -import { MessageUtils } from '../utils'; - -let Config = require('../../config/config.json'); // Possible support for server-specific prefixes? +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; export class HelpCommand implements Command { public name: string = 'help'; @@ -16,128 +16,70 @@ export class HelpCommand implements Command { public requirePremium = false; public getPremium = false; - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { - let embed = new MessageEmbed(); + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { let clientAvatarUrl = msg.client.user.avatarURL(); - let option = args[2]?.toLowerCase(); + let option = FormatUtils.extractMiscActionType(args[2]?.toLowerCase())?.toLowerCase(); if (!option) { - embed - .setAuthor(HELP_GENERAL_TITLE, clientAvatarUrl) - .setDescription(HELP_GENERAL_DESC) - .addField('Legend', HELP_GENERAL_LEGEND) - .setColor(Config.colors.default); + await MessageUtils.send( + channel, + Lang.getEmbed('help.general', LangCode.EN_US, { + ICON: clientAvatarUrl, + }) + ); } else if (option === 'setup') { - embed - .setAuthor(HELP_SETUP_TITLE, clientAvatarUrl) - .setDescription(HELP_SETUP_DESC) - .setColor(Config.colors.default); + await MessageUtils.send( + channel, + Lang.getEmbed('help.setup', LangCode.EN_US, { + ICON: clientAvatarUrl, + }) + ); } else if (option === 'message') { - embed - .setAuthor(HELP_MESSAGE_TITLE, clientAvatarUrl) - .setDescription(HELP_MESSAGE_DESC) - .setColor(Config.colors.default); + await MessageUtils.send( + channel, + Lang.getEmbed('help.message', LangCode.EN_US, { + ICON: clientAvatarUrl, + }) + ); } else if (option === 'trusted') { - embed - .setAuthor(HELP_TRUSTED_TITLE, clientAvatarUrl) - .setDescription(HELP_TRUSTED_DESC) - .setColor(Config.colors.default); - } else if (option === 'permissions') { - embed - .setAuthor(HELP_PERM_TITLE, clientAvatarUrl) - .setDescription(HELP_PERM_DESC) - .setColor(Config.colors.default); + await MessageUtils.send( + channel, + Lang.getEmbed('help.trusted', LangCode.EN_US, { + ICON: clientAvatarUrl, + }) + ); + } else if (option === 'blacklist') { + await MessageUtils.send( + channel, + Lang.getEmbed('help.blacklist', LangCode.EN_US, { + ICON: clientAvatarUrl, + }) + ); + } else if (option === 'anniversary') { + await MessageUtils.send( + channel, + Lang.getEmbed('help.anniversary', LangCode.EN_US, { + ICON: clientAvatarUrl, + }) + ); } else if (option === 'premium') { - embed - .setAuthor(HELP_PREMIUM_TITLE, clientAvatarUrl) - .setDescription(HELP_PREMIUM_DESC) - .setColor(Config.colors.default); + await MessageUtils.send( + channel, + Lang.getEmbed('help.premium', LangCode.EN_US, { + ICON: clientAvatarUrl, + }) + ); } else { - embed - .setAuthor(HELP_GENERAL_TITLE, clientAvatarUrl) - .setDescription(HELP_GENERAL_DESC) - .addField('Legend', HELP_GENERAL_LEGEND) - .setColor(Config.colors.default); + await MessageUtils.send( + channel, + Lang.getEmbed('help.general', LangCode.EN_US, { + ICON: clientAvatarUrl, + }) + ); } - - await MessageUtils.send(channel, embed); } } - -const HELP_GENERAL_TITLE = 'Birthday Bot General Help'; -const HELP_GENERAL_DESC = - `Birthday Bot helps your server celebrate birthdays with automatic birthday roles and announcements.` + - '\n' + - `\n**bday premium** - See information about Birthday Bot Premium.` + - `\n**bday help premium** - Help for Birthday Bot Premium.` + - '\n' + - `\n**bday set** - Set your birthday.` + - `\n**bday view [user]** - View your birthday or a users birthday.` + - `\n**bday next**\*\* - View next birthday(s) in the server.` + - `\n**bday list [page/date]**\*\* - View the server birthday list.` + - `\n**bday map** - View the time zone map.` + - `\n**bday invite** - Invite Birthday Bot to a server.` + - `\n**bday support** - Join the support server.` + - `\n**bday docs** - View the documentation.` + - `\n**bday faq** - View the frequently asked questions.` + - `\n**bday purge** - Remove your birthday data.` + - `\n**bday help setup** - Help for server setup.` + - `\n**bday help message** - Help for the birthday message settings.` + - `\n**bday help trusted** - Help for the trusted system.` + - `\n**bday help permissions** - Help for permissions.` + - `\n**bday settings**\*\* - View server's settings.` + - `\n**bday test [user]**\*\* - Test the birthday event.` + - `\n` + - `\n**bday donate** - Support developments by donating!` + - `\n` + - `\nIf you have any question/problems please join our support server [here](${Config.links.support}).`; -const HELP_GENERAL_LEGEND = '** = Server Only Command'; - -const HELP_SETUP_TITLE = 'Birthday Bot Setup Help - Guild Only'; -const HELP_SETUP_DESC = - `\n**bday setup** - Interactive guide for server setup.` + - '\n' + - `\n**bday create ** - Create the default birthday role/channel.` + - `\n**bday update <#channel/@role>** - Update the birthday role/channel.` + - `\n**bday clear ** - Clear the birthday role/channel.`; - -const HELP_MESSAGE_TITLE = 'Birthday Bot Message Help - Guild Only'; -const HELP_MESSAGE_DESC = - `\n**bday setup message** - Interactive guide for message settings setup.` + - '\n' + - `\n**bday message list [page]** - List all custom birthday messages.` + - `\n**bday message add ** - Add a custom birthday message.\n - Placeholder for users: \`\`` + - `\n**bday message remove ** - Remove a certain birthday message.` + - `\n**bday message clear** - Clear all custom birthday messages.` + - `\n**bday message time <0-23>** - Set the birthday message time.` + - `\n**bday message mention ** - Set the birthday message mention setting.` + - `\n**bday message embed ** - Should the birthday message be embedded.` + - `\n**bday message test [user count]** - Test a birthday message.`; - -const HELP_TRUSTED_TITLE = 'Birthday Bot Trusted System Help - Guild Only'; -const HELP_TRUSTED_DESC = - `\n**bday setup trusted** - Interactive guide for trusted system settings setup.` + - '\n' + - `\n**bday create trustedRole** - Create the default trusted role.` + - `\n**bday update trustedRole ** - Update the trusted role.` + - `\n**bday clear trustedRole ** - Clear the trusted role.` + - `\n**bday trusted preventMsg ** - If trusted role is required for a birthday message.` + - `\n**bday trusted preventRole ** - If trusted role is required to get the birthday role.`; - -const HELP_PERM_TITLE = 'Birthday Bot Permissions Help - Guild Only'; -const HELP_PERM_DESC = - `\n**bday create birthdayMasterRole** - Create the default birthday master role.` + - `\n**bday update birthdayMasterRole ** - Update the birthday master role.` + - `\n**bday clear birthdayMasterRole** - Clear the birthday master role.` + - `\n**bday blacklist add ** - Add user to the blacklist.` + - `\n**bday blacklist remove ** - Remove user from the blacklist.` + - `\n**bday blacklist clear** - Clear the birthday blacklist.` + - `\n**bday blacklist list** - View all blacklisted users in your server.`; - -const HELP_PREMIUM_TITLE = 'Birthday Bot Premium Help - Guild Only'; -const HELP_PREMIUM_DESC = - `\n**bday premium** - View information about your server's premium.` + - `\n**bday message add ** - Add a user-specific birthday message.\n - Placeholder for users: \`\`\n- Example Usage: \`bday message add @Scott Happy Birthday !\`` + - `\n**bday message remove ** - Remove a certain birthday message.` + - `\n**bday message list user [page]** - List all user-specific birthday messages.` + - `\n**bday message color ** - Set the color of the birthday message embed.`; diff --git a/src/commands/index.ts b/src/commands/index.ts index 7d24b857..e012a69a 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,25 +1,29 @@ +export { BlacklistCommand } from './blacklist-command'; export { Command } from './command'; +export { ConfigCommand } from './config-command'; +export { DevCommand } from './dev-command'; +export { DocumentationCommand } from './documentation-command'; +export { DonateCommand } from './donate-command'; +export { FaqCommand } from './faq-command'; export { HelpCommand } from './help-command'; -export { SetCommand } from './set-command'; -export { SetupCommand } from './setup-command'; -export { CreateCommand } from './create-command'; -export { UpdateCommand } from './update-command'; -export { ClearCommand } from './clear-command'; -export { MessageCommand } from './message-command'; -export { ListCommand } from './list-command'; +export { InfoCommand } from './info-command'; export { InviteCommand } from './invite-command'; -export { SupportCommand } from './support-command'; +export { ListCommand } from './list-command'; +export { MapCommand } from './map-command'; +export { MemberAnniversaryRoleCommand } from './member-anniversary-role-command'; +export { MessageCommand } from './message-command'; +export { NextCommand } from './next-command'; +export { PremiumCommand } from './premium-command'; export { PurgeCommand } from './purge-command'; -export { ViewCommand } from './view-command'; -export { TrustedCommand } from './trusted-command'; export { SetAttemptsCommand } from './set-attempts-command'; +export { SetCommand } from './set-command'; export { SettingsCommand } from './settings-command'; -export { NextCommand } from './next-command'; -export { MapCommand } from './map-command'; -export { TestCommand } from './test-command'; -export { FAQCommand } from './faq-command'; -export { DocumentationCommand } from './documentation-command'; -export { DonateCommand } from './donate-command'; -export { BlacklistCommand } from './blacklist-command'; -export { PremiumCommand } from './premium-commands'; +export { SetupCommand } from './setup-command'; +export { StatsCommand } from './stats-command'; export { SubscribeCommand } from './subscribe-command'; +export { SupportCommand } from './support-command'; +export { TestCommand } from './test-command'; +export { TrustedRoleCommand } from './trusted-role-command'; +export { ViewCommand } from './view-command'; +export { VoteCommand } from './vote-command'; +export { UpdateCommand } from './update-command'; diff --git a/src/commands/info-command.ts b/src/commands/info-command.ts new file mode 100644 index 00000000..303c59be --- /dev/null +++ b/src/commands/info-command.ts @@ -0,0 +1,26 @@ +import { DMChannel, Message, TextChannel } from 'discord.js'; + +import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { MessageUtils } from '../utils'; + +export class InfoCommand implements Command { + public name: string = 'info'; + public aliases = ['information']; + public requireSetup = false; + public guildOnly = false; + public adminOnly = false; + public ownerOnly = false; + public voteOnly = false; + public requirePremium = false; + public getPremium = false; + + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + await MessageUtils.send(msg.channel, Lang.getEmbed('info.general', LangCode.EN_US)); + } +} diff --git a/src/commands/invite-command.ts b/src/commands/invite-command.ts index 7f4047ba..80986704 100644 --- a/src/commands/invite-command.ts +++ b/src/commands/invite-command.ts @@ -1,10 +1,10 @@ -import { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; +import { DMChannel, Message, TextChannel } from 'discord.js'; import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; import { MessageUtils } from '../utils'; -let Config = require('../../config/config.json'); - export class InviteCommand implements Command { public name: string = 'invite'; public aliases = ['inv']; @@ -16,15 +16,14 @@ export class InviteCommand implements Command { public requirePremium = false; public getPremium = false; - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { - let embed = new MessageEmbed() - .setDescription( - `Invite ${msg.client.user.toString()} to your server [here](${ - Config.links.invite - })!` - ) - .setColor(Config.colors.default); - - await MessageUtils.send(channel, embed); + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + await MessageUtils.send( + channel, + Lang.getEmbed('info.invite', LangCode.EN_US, { BOT: msg.client.user.toString() }) + ); } } diff --git a/src/commands/list-command.ts b/src/commands/list-command.ts index 97d5cb11..e709e066 100644 --- a/src/commands/list-command.ts +++ b/src/commands/list-command.ts @@ -1,11 +1,13 @@ import * as Chrono from 'chrono-node'; import { FormatUtils, MessageUtils, ParseUtils } from '../utils'; -import { Message, TextChannel } from 'discord.js'; +import { GuildRepo, UserRepo } from '../services/database/repos'; +import { Message, MessageEmbed, TextChannel } from 'discord.js'; import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; import { UserDataResults } from '../models/database'; -import { UserRepo } from '../services/database/repos'; import moment from 'moment'; let Config = require('../../config/config.json'); @@ -21,53 +23,127 @@ export class ListCommand implements Command { public requirePremium = false; public getPremium = false; - constructor(private userRepo: UserRepo) {} + constructor(private userRepo: UserRepo, private guildRepo: GuildRepo) {} public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + let guildData = await this.guildRepo.getGuild(msg.guild.id); + + let type: string; + + if (args.length > 2) { + type = FormatUtils.extractCelebrationType(args[2].toLowerCase())?.toLowerCase() ?? ''; + if (type !== 'birthday' && type !== 'memberanniversary') { + // Lang part not implemented + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidListArgs', LangCode.EN_US) + ); + return; + } + } else { + type = 'birthday'; + } + let page = 1; - let date; + let date: moment.MomentInput; - let input = args.slice(2).join(' '); + let input = args.length > 3 ? args.slice(3).join(' ') : args.slice(2).join(' '); if (input) { - try { - page = ParseUtils.parseInt(input); - } catch (error) { - // Not A Number - } + page = ParseUtils.parseInt(input); if (!page) date = Chrono.parseDate(input); // Try an parse a date if (!page || page <= 0 || page > 100000) page = 1; } - let pageSize = Config.experience.birthdayListSize; + // TODO: Add config option for the size of the memberAnniversaryList + let pageSize = + type === 'birthday' + ? Config.experience.birthdayListSize + : Config.experience.birthdayListSize; - let users = msg.guild.members.cache.filter(member => !member.user.bot).keyArray(); + let embed: MessageEmbed; - let userDataResults: UserDataResults; + let guildMembers = msg.guild.members.cache; - if (date) { - let input = moment(date).format('MM-DD'); - userDataResults = await this.userRepo.getBirthdayListFullFromDate( - users, - pageSize, - input + if (msg.guild.memberCount - guildMembers.size > 5) { + guildMembers = await msg.guild.members.fetch(); + } + + if (type === 'birthday') { + // Birthday List + let users = guildMembers.filter(member => !member.user.bot).keyArray(); + + let userDataResults: UserDataResults; + + if (date) { + let input = moment(date).format('MM-DD'); + userDataResults = await this.userRepo.getBirthdayListFullFromDate( + users, + pageSize, + input + ); + } else { + userDataResults = await this.userRepo.getBirthdayListFull(users, pageSize, page); + } + + embed = await FormatUtils.getBirthdayListFullEmbed( + msg.guild, + userDataResults, + guildData, + userDataResults.stats.Page, + pageSize ); } else { - userDataResults = await this.userRepo.getBirthdayListFull(users, pageSize, page); - } + // Member Anniversary List + let memberList = guildMembers.filter(member => !member.user.bot).map(member => member); + + let totalMembers = memberList.length; + + memberList = memberList.sort( + (first, second) => + 0 - + (moment(first.joinedAt).format('MM-DD') > + moment(second.joinedAt).format('MM-DD') + ? -1 + : 1) + ); + + let totalPages = Math.ceil(memberList.length / pageSize); + + let startMember: number; + + if (date) { + startMember = memberList.indexOf( + memberList.find( + m => moment(m.joinedAt).format('MM') === moment(date).format('MM') + ) + ); + } else { + startMember = (page - 1) * pageSize; + } + + memberList = memberList.slice(startMember, startMember + pageSize); - let embed = await FormatUtils.getBirthdayListFullEmbed( - msg.guild, - userDataResults, - userDataResults.stats.Page, - pageSize - ); + embed = await FormatUtils.getMemberAnniversaryListFullEmbed( + msg.guild, + memberList, + guildData, + page, + pageSize, + totalPages, + totalMembers + ); + } let message = await MessageUtils.send(channel, embed); - if (embed.description === '**No Birthdays in this server!**') return; + if ( + embed.description === Lang.getRef('list.noBirthdays', LangCode.EN_US) || + embed.description === Lang.getRef('list.noMemberAnniversaries', LangCode.EN_US) + ) + return; await MessageUtils.react(message, Config.emotes.previousPage); await MessageUtils.react(message, Config.emotes.jumpToPage); diff --git a/src/commands/map-command.ts b/src/commands/map-command.ts index 5406220c..89a3651d 100644 --- a/src/commands/map-command.ts +++ b/src/commands/map-command.ts @@ -1,10 +1,10 @@ -import { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; +import { DMChannel, Message, TextChannel } from 'discord.js'; import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; import { MessageUtils } from '../utils'; -let Config = require('../../config/config.json'); - export class MapCommand implements Command { public name: string = 'map'; public aliases = [ @@ -24,15 +24,11 @@ export class MapCommand implements Command { public requirePremium = false; public getPremium = false; - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { - let embed = new MessageEmbed() - .setDescription( - `[Kevin Novak](https://github.com/KevinNovak) has created a handy [map time zone picker](${Config.links.map})!` + - '\n' + - '\nSimply click your location on the map and copy the name of the selected time zone. You can then use it in the `bday set` command.' - ) - .setColor(Config.colors.default); - - await MessageUtils.send(channel, embed); + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + await MessageUtils.send(channel, Lang.getEmbed('info.map', LangCode.EN_US)); } } diff --git a/src/commands/member-anniversary-role-command.ts b/src/commands/member-anniversary-role-command.ts new file mode 100644 index 00000000..422828cc --- /dev/null +++ b/src/commands/member-anniversary-role-command.ts @@ -0,0 +1,90 @@ +import { FormatUtils, MessageUtils, PermissionUtils } from '../utils'; +import { + MemberAnniversaryRoleAddSubCommand, + MemberAnniversaryRoleClaimSubCommand, + MemberAnniversaryRoleClearSubCommand, + MemberAnniversaryRoleListSubCommand, + MemberAnniversaryRoleRemoveSubCommand, +} from './memberAnniversaryRole'; +import { Message, TextChannel } from 'discord.js'; + +import { Command } from './command'; +import { GuildRepo } from '../services/database/repos'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; + +export class MemberAnniversaryRoleCommand implements Command { + public name: string = 'memberanniversaryrole'; + public aliases = ['mar', 'memberanniversary', 'anniversaryrole']; + public requireSetup = true; + public guildOnly = true; + public adminOnly = false; + public ownerOnly = false; + public voteOnly = false; + public requirePremium = true; + public getPremium = true; + + constructor( + private guildRepo: GuildRepo, + private memberAnniversaryRoleAddSubCommand: MemberAnniversaryRoleAddSubCommand, + private memberAnniversaryRoleRemoveSubCommand: MemberAnniversaryRoleRemoveSubCommand, + private memberAnniversaryRoleClearSubCommand: MemberAnniversaryRoleClearSubCommand, + private memberAnniversaryRoleListSubCommand: MemberAnniversaryRoleListSubCommand, + private memberAnniversaryRoleClaimSubCommand: MemberAnniversaryRoleClaimSubCommand + ) {} + + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { + if (args.length === 2) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noMemberAnniversaryRoleArgs', LangCode.EN_US) + ); + return; + } + + let type = FormatUtils.extractMiscActionType(args[2]?.toLowerCase())?.toLowerCase() ?? ''; + + let guildData = await this.guildRepo.getGuild(msg.guild.id); + + if (type === 'claim') { + this.memberAnniversaryRoleClaimSubCommand.execute( + args, + msg, + channel, + hasPremium, + guildData + ); + return; + } + + // Check if user has permission + if (!PermissionUtils.hasSubCommandPermission(msg.member, guildData)) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noPermission', LangCode.EN_US) + ); + return; + } + + if (type === 'add') { + this.memberAnniversaryRoleAddSubCommand.execute(args, msg, channel, hasPremium); + } else if (type === 'remove') { + this.memberAnniversaryRoleRemoveSubCommand.execute(args, msg, channel); + } else if (type === 'clear') { + this.memberAnniversaryRoleClearSubCommand.execute(args, msg, channel); + } else if (type === 'list') { + this.memberAnniversaryRoleListSubCommand.execute(args, msg, channel, hasPremium); + } else { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noMemberAnniversaryRoleArgs', LangCode.EN_US) + ); + return; + } + } +} diff --git a/src/commands/memberAnniversaryRole/index.ts b/src/commands/memberAnniversaryRole/index.ts new file mode 100644 index 00000000..9c5b390b --- /dev/null +++ b/src/commands/memberAnniversaryRole/index.ts @@ -0,0 +1,5 @@ +export { MemberAnniversaryRoleAddSubCommand } from './member-anniversary-role-add-sub-command'; +export { MemberAnniversaryRoleRemoveSubCommand } from './member-anniversary-role-remove-sub-command'; +export { MemberAnniversaryRoleClearSubCommand } from './member-anniversary-role-clear-sub-command'; +export { MemberAnniversaryRoleListSubCommand } from './member-anniversary-role-list-sub-command'; +export { MemberAnniversaryRoleClaimSubCommand } from './member-anniversary-role-claim-command'; diff --git a/src/commands/memberAnniversaryRole/member-anniversary-role-add-sub-command.ts b/src/commands/memberAnniversaryRole/member-anniversary-role-add-sub-command.ts new file mode 100644 index 00000000..2650bcc8 --- /dev/null +++ b/src/commands/memberAnniversaryRole/member-anniversary-role-add-sub-command.ts @@ -0,0 +1,117 @@ +import { Message, Role, TextChannel } from 'discord.js'; +import { MessageUtils, ParseUtils } from '../../utils'; + +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MemberAnniversaryRoleRepo } from '../../services/database/repos'; + +let Config = require('../../../config/config.json'); + +const errorEmbed = Lang.getEmbed('validation.invalidUsageMemberAnniversaryRoleAdd', LangCode.EN_US); + +export class MemberAnniversaryRoleAddSubCommand { + constructor(private memberAnniversaryRoleRepo: MemberAnniversaryRoleRepo) { } + + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { + // bday mar add year role + if (!hasPremium) { + MessageUtils.send( + channel, + Lang.getEmbed('premiumRequired.anniversaryRoles', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + if (args.length < 5) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + let year = ParseUtils.parseInt(args[3]); + + if (!year || year > 1000 || year < 0) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidYear', LangCode.EN_US) + ); + return; + } + + // See if a role was specified + let memberAnniversaryRole: Role = msg.mentions.roles.first(); + + if (!memberAnniversaryRole) { + memberAnniversaryRole = msg.guild.roles.cache.find( + role => + role.name.toLowerCase().includes(args[4].toLowerCase()) || + role.id === args[4].toLowerCase() + ); + } + + if ( + !memberAnniversaryRole || + memberAnniversaryRole.id === msg.guild.id || + args[3].toLowerCase() === 'everyone' + ) { + MessageUtils.send(channel, Lang.getEmbed('validation.invalidRole', LangCode.EN_US)); + return; + } + + if (memberAnniversaryRole.managed) { + MessageUtils.send( + channel, + Lang.getEmbed('validation.memberAnniversaryRoleManaged', LangCode.EN_US) + ); + return; + } + + let memberAnniversaryRoles = await this.memberAnniversaryRoleRepo.getMemberAnniversaryRoles( + msg.guild.id + ); + + if (memberAnniversaryRoles.memberAnniversaryRoles.find(role => role.Year === year)) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.duplicateYear', LangCode.EN_US, { + YEAR: year.toString(), + }) + ); + return; + return; + } + + if ( + memberAnniversaryRoles && + memberAnniversaryRoles.memberAnniversaryRoles.length >= + Config.validation.memberAnniversaryRoles.maxCount.paid + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.maxPaidMemberAnniversaryRoles', LangCode.EN_US, { + PAID_MAX: Config.validation.trustedRoles.maxCount.paid.toString(), + }) + ); + return; + } + + await this.memberAnniversaryRoleRepo.addMemberAnniversaryRole( + msg.guild.id, + memberAnniversaryRole?.id, + year + ); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.addedMemberAnniversaryRole', LangCode.EN_US, { + ROLE: memberAnniversaryRole.toString(), + YEAR: year.toString(), + }) + ); + } +} diff --git a/src/commands/memberAnniversaryRole/member-anniversary-role-claim-command.ts b/src/commands/memberAnniversaryRole/member-anniversary-role-claim-command.ts new file mode 100644 index 00000000..4b09dee6 --- /dev/null +++ b/src/commands/memberAnniversaryRole/member-anniversary-role-claim-command.ts @@ -0,0 +1,99 @@ +import { ActionUtils, CelebrationUtils, MessageUtils } from '../../utils'; +import { Message, TextChannel } from 'discord.js'; + +import { GuildData } from '../../models/database'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MemberAnniversaryRoleRepo } from '../../services/database/repos'; +import moment from 'moment'; + +let Config = require('../../../config/config.json'); + +export class MemberAnniversaryRoleClaimSubCommand { + constructor(private memberAnniversaryRoleRepo: MemberAnniversaryRoleRepo) { } + + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean, + guildData: GuildData + ): Promise { + if (!hasPremium) { + MessageUtils.send( + channel, + Lang.getEmbed('premiumRequired.anniversaryRoles', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + let memberAnniversaryRoleData = + await this.memberAnniversaryRoleRepo.getMemberAnniversaryRoles(msg.guild.id); + + if ( + !memberAnniversaryRoleData || + memberAnniversaryRoleData.memberAnniversaryRoles.length === 0 + ) { + // No roles to claim + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noMemberAnniversaryRoles', LangCode.EN_US) + ); + return; + } + let timezone = guildData?.DefaultTimezone; + + if (!timezone || timezone === '0') { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.serverTimezoneNotSet', LangCode.EN_US) + ); + return; + } + + let memberJoinedAt = moment(msg.member.joinedAt).tz(timezone); + let now = moment.tz(timezone); + let yearsOldRoundedDown = now.year() - memberJoinedAt.year(); + + // If the date hasn't passed yet this year then we decrease the year + // Unlike bday next we don't want to round up for this + if ( + moment(memberJoinedAt.format('MM-DD'), 'MM-DD').diff( + moment(now.format('MM-DD'), 'MM-DD'), + 'days' + ) > 0 + ) + yearsOldRoundedDown--; + + let roleData = memberAnniversaryRoleData.memberAnniversaryRoles.filter( + data => data.Year <= yearsOldRoundedDown + ); + + if (roleData.length === 0) { + // No roles to claim + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noMemberAnniversaryRolesToClaim', LangCode.EN_US) + ); + return; + } + + let roles = await CelebrationUtils.getMemberAnniversaryRoleList(msg.guild, roleData); + + // Give the roles they are owed + for (let role of roles) { + if (!msg.member.roles.cache.has(role.id)) { + await ActionUtils.giveRole(msg.member, role, Config.delays.roles); + } + } + + await MessageUtils.send( + channel, + Lang.getEmbed('results.memberAnniversaryRolesClaimed', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + } +} diff --git a/src/commands/memberAnniversaryRole/member-anniversary-role-clear-sub-command.ts b/src/commands/memberAnniversaryRole/member-anniversary-role-clear-sub-command.ts new file mode 100644 index 00000000..2b223cf5 --- /dev/null +++ b/src/commands/memberAnniversaryRole/member-anniversary-role-clear-sub-command.ts @@ -0,0 +1,91 @@ +import { + CollectOptions, + CollectorUtils, + ExpireFunction, + MessageFilter, +} from 'discord.js-collector-utils'; +import { Message, MessageReaction, TextChannel, User } from 'discord.js'; + +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MemberAnniversaryRoleRepo } from '../../services/database/repos'; +import { MessageUtils } from '../../utils'; + +let Config = require('../../../config/config.json'); + +const COLLECT_OPTIONS: CollectOptions = { + time: Config.experience.promptExpireTime * 1000, + reset: true, +}; + +export class MemberAnniversaryRoleClearSubCommand { + constructor(private memberAnniversaryRoleRepo: MemberAnniversaryRoleRepo) {} + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + let stopFilter: MessageFilter = (nextMsg: Message) => + nextMsg.author.id === msg.author.id && + [Config.prefix, ...Config.stopCommands].includes( + nextMsg.content.split(/\s+/)[0].toLowerCase() + ); + let expireFunction: ExpireFunction = async () => { + await MessageUtils.reply(msg, Lang.getEmbed('results.promptExpired', LangCode.EN_US)); + }; + + let memberAnniversaryRoles = await this.memberAnniversaryRoleRepo.getMemberAnniversaryRoles( + msg.guild.id + ); + + if (memberAnniversaryRoles.memberAnniversaryRoles.length === 0) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noMemberAnniversaryRoles', LangCode.EN_US) + ); + return; + } + + let trueFalseOptions = [Config.emotes.confirm, Config.emotes.deny]; + + let confirmationMessage = await MessageUtils.send( + channel, + Lang.getEmbed('serverPrompts.memberAnniversaryRoleClearConfirmation', LangCode.EN_US, { + TOTAL: memberAnniversaryRoles.memberAnniversaryRoles.length.toString(), + ICON: msg.client.user.avatarURL(), + }) + ); // Send confirmation and emotes + for (let option of trueFalseOptions) { + await MessageUtils.react(confirmationMessage, option); + } + + let confirmation: string = await CollectorUtils.collectByReaction( + confirmationMessage, + // Collect Filter + (msgReaction: MessageReaction, reactor: User) => + reactor.id === msg.author.id && trueFalseOptions.includes(msgReaction.emoji.name), + stopFilter, + // Retrieve Result + async (msgReaction: MessageReaction, reactor: User) => { + return msgReaction.emoji.name; + }, + expireFunction, + COLLECT_OPTIONS + ); + + MessageUtils.delete(confirmationMessage); + + if (confirmation === undefined) return; + + if (confirmation === Config.emotes.confirm) { + // Confirm + await this.memberAnniversaryRoleRepo.clearMemberAnniversaryRoles(msg.guild.id); + await MessageUtils.send( + channel, + Lang.getEmbed('results.clearedMemberAnniversaryRole', LangCode.EN_US) + ); + } else { + await MessageUtils.send( + channel, + Lang.getEmbed('results.actionCanceled', LangCode.EN_US) + ); + } + } +} diff --git a/src/commands/memberAnniversaryRole/member-anniversary-role-list-sub-command.ts b/src/commands/memberAnniversaryRole/member-anniversary-role-list-sub-command.ts new file mode 100644 index 00000000..47ef05b1 --- /dev/null +++ b/src/commands/memberAnniversaryRole/member-anniversary-role-list-sub-command.ts @@ -0,0 +1,54 @@ +import { FormatUtils, MessageUtils, ParseUtils } from '../../utils'; +import { Message, TextChannel } from 'discord.js'; + +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MemberAnniversaryRoleRepo } from '../../services/database/repos'; + +let Config = require('../../../config/config.json'); + +export class MemberAnniversaryRoleListSubCommand { + constructor(private memberAnniversaryRoleRepo: MemberAnniversaryRoleRepo) {} + + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { + let page = 1; + + if (args[3]) { + page = ParseUtils.parseInt(args[4]); + if (!page || page <= 0 || page > 100000) page = 1; + } + + let pageSize = Config.experience.birthdayMessageListSize; + + let memberAnniversaryRoleResults = await this.memberAnniversaryRoleRepo.getMemberAnniversaryRoleList( + msg.guild.id, + pageSize, + page + ); + + if (page > memberAnniversaryRoleResults.stats.TotalPages) + page = memberAnniversaryRoleResults.stats.TotalPages; + + let embed = await FormatUtils.getMemberAnniversaryRoleList( + msg.guild, + memberAnniversaryRoleResults, + page, + pageSize, + hasPremium + ); + + let message = await MessageUtils.send(channel, embed); + + if (embed.description === Lang.getRef('list.noMemberAnniversaryRoles', LangCode.EN_US)) + return; + + await MessageUtils.react(message, Config.emotes.previousPage); + await MessageUtils.react(message, Config.emotes.jumpToPage); + await MessageUtils.react(message, Config.emotes.nextPage); + } +} diff --git a/src/commands/memberAnniversaryRole/member-anniversary-role-remove-sub-command.ts b/src/commands/memberAnniversaryRole/member-anniversary-role-remove-sub-command.ts new file mode 100644 index 00000000..f13b895d --- /dev/null +++ b/src/commands/memberAnniversaryRole/member-anniversary-role-remove-sub-command.ts @@ -0,0 +1,90 @@ +import { Message, Role, TextChannel } from 'discord.js'; +import { MessageUtils, ParseUtils } from '../../utils'; + +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MemberAnniversaryRoleRepo } from '../../services/database/repos'; + +const errorEmbed = Lang.getEmbed('validation.anniversaryRoleNoRoleOrPosition', LangCode.EN_US); + +export class MemberAnniversaryRoleRemoveSubCommand { + constructor(private memberAnniversaryRoleRepo: MemberAnniversaryRoleRepo) {} + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + let anniversaryRole: Role = msg.mentions.roles.first(); + let year: number; + //bday mar remove role + if (args.length <= 3) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + if (!anniversaryRole) { + anniversaryRole = msg.guild.roles.cache.find( + role => + role.name.toLowerCase().includes(args[3].toLowerCase()) || + role.id === args[3].toLowerCase() + ); + } + + if ( + anniversaryRole && + (anniversaryRole.id === msg.guild.id || args[3].toLowerCase() === 'everyone') + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidRole', LangCode.EN_US) + ); + return; + } + + let memberAnniversaryRoles = await this.memberAnniversaryRoleRepo.getMemberAnniversaryRoles( + msg.guild.id + ); + + if (anniversaryRole) { + let role = memberAnniversaryRoles.memberAnniversaryRoles.filter( + r => r.MemberAnniversaryRoleDiscordId === anniversaryRole.id + ); + + if (role.length > 0) year = role[0].Year; + } + + if (!year) { + year = ParseUtils.parseInt(args[3]); + } + + if (!year) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidAnniversaryRole', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + let role = memberAnniversaryRoles.memberAnniversaryRoles.find(r => r.Year === year); + + if (!role) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidAnniversaryRole', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + await this.memberAnniversaryRoleRepo.removeMemberAnniversaryRole(msg.guild.id, year); + + let r = msg.guild.roles.resolve(role.MemberAnniversaryRoleDiscordId); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.removedMemberAnniversaryRole', LangCode.EN_US, { + ROLE: r ? r.toString() : '**Deleted Role**', + }) + ); + } +} diff --git a/src/commands/message-command.ts b/src/commands/message-command.ts index 4cde997e..7ef8dfde 100644 --- a/src/commands/message-command.ts +++ b/src/commands/message-command.ts @@ -1,8 +1,7 @@ -import { Message, MessageEmbed, TextChannel } from 'discord.js'; +import { Message, TextChannel } from 'discord.js'; import { MessageAddSubCommand, MessageClearSubCommand, - MessageEmbedSubCommand, MessageListSubCommand, MessageMentionSubCommand, MessageRemoveSubCommand, @@ -11,11 +10,9 @@ import { } from './message'; import { Command } from './command'; -import { MessageColorSubCommand } from './message/message-color-sub-command'; -import { MessageUserListSubCommand } from './message/message-user-list-sub-command'; -import { MessageUtils } from '../utils'; - -let Config = require('../../config/config.json'); +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { MessageUtils, FormatUtils } from '../utils'; export class MessageCommand implements Command { public name: string = 'message'; @@ -35,67 +32,44 @@ export class MessageCommand implements Command { private messageRemoveSubCommand: MessageRemoveSubCommand, private messageTimeSubCommand: MessageTimeSubCommand, private messageMentionSubCommand: MessageMentionSubCommand, - private messageEmbedSubCommand: MessageEmbedSubCommand, - private messageTestSubCommand: MessageTestSubCommand, - private messageColorSubCommand: MessageColorSubCommand, - private messageUserListSubCommand: MessageUserListSubCommand + private messageTestSubCommand: MessageTestSubCommand ) {} - public async execute(args: string[], msg: Message, channel: TextChannel, hasPremium: boolean) { + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { if (args.length === 2) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription( - `Please specify a sub command for the custom birthday message! [(?)](${Config.links.docs}/faq#what-is-a-custom-birthday-message)\nAccepted Values: \`list\`, \`add \`, \`remove <#>\`, \`clear\`, \`time <0-23>\`, \`mention \`, \`useEmbed \`,` - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noCustomMessageArgs', LangCode.EN_US) + ); return; } - if (args[2].toLowerCase() === 'list' && args[3]?.toLowerCase() !== 'user') { + + let type = FormatUtils.extractMiscActionType(args[2]?.toLowerCase())?.toLowerCase() ?? ''; + + if (type === 'list') { this.messageListSubCommand.execute(args, msg, channel, hasPremium); - } else if (args[2].toLowerCase() === 'list' && args[3]?.toLowerCase() === 'user') { - this.messageUserListSubCommand.execute(args, msg, channel, hasPremium); - } else if (args[2].toLowerCase() === 'clear') { + } else if (type === 'clear') { this.messageClearSubCommand.execute(args, msg, channel); - } else if (args[2].toLowerCase() === 'add' || args[2].toLowerCase() === 'create') { + } else if (type === 'add' || args[2].toLowerCase() === 'create') { this.messageAddSubCommand.execute(args, msg, channel, hasPremium); - } else if (args[2].toLowerCase() === 'remove' || args[2].toLowerCase() === 'delete') { + } else if (type === 'remove' || args[2].toLowerCase() === 'delete') { this.messageRemoveSubCommand.execute(args, msg, channel); - } else if (args[2].toLowerCase() === 'time') { + } else if (type === 'time') { this.messageTimeSubCommand.execute(args, msg, channel); - } else if (args[2].toLowerCase() === 'mention' || args[2].toLowerCase() === 'role') { + } else if (type === 'mention' || args[2].toLowerCase() === 'role') { this.messageMentionSubCommand.execute(args, msg, channel); - } else if (args[2].toLowerCase() === 'embed') { - this.messageEmbedSubCommand.execute(args, msg, channel); - } else if (args[2].toLowerCase() === 'test') { + } else if (type === 'test') { this.messageTestSubCommand.execute(args, msg, channel); - } else if (args[2].toLowerCase() === 'color') { - if (hasPremium) { - this.messageColorSubCommand.execute(args, msg, channel); - } else { - let embed = new MessageEmbed() - .setTitle('Premium Required!') - .setDescription( - `Custom birthday message color is a premium feature! View information about **Birthday Bot Premium** using \`bday premium\`!` - ) - .setFooter( - 'Premium helps us support and maintain the bot!', - msg.client.user.avatarURL() - ) - .setTimestamp() - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); - return; - } } else { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription( - `Please specify a sub command for the custom birthday message! [(?)](${Config.links.docs}/faq#what-is-a-custom-birthday-message)\nAccepted Values: \`list\`, \`add \`, \`remove <#>\`, \`clear\`, \`time <0-23>\`, \`mention \`, \`useEmbed \`,` - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noCustomMessageArgs', LangCode.EN_US) + ); return; } } diff --git a/src/commands/message/index.ts b/src/commands/message/index.ts index d642cbbd..db547116 100644 --- a/src/commands/message/index.ts +++ b/src/commands/message/index.ts @@ -1,10 +1,7 @@ export { MessageAddSubCommand } from './message-add-sub-command'; export { MessageClearSubCommand } from './message-clear-sub-command'; -export { MessageEmbedSubCommand } from './message-embed-sub-command'; export { MessageListSubCommand } from './message-list-sub-command'; -export { MessageMentionSubCommand } from './message-mention-sub-commands'; +export { MessageMentionSubCommand } from './message-mention-sub-command'; export { MessageRemoveSubCommand } from './message-remove-sub-command'; export { MessageTestSubCommand } from './message-test-sub-command'; export { MessageTimeSubCommand } from './message-time-sub-command'; -export { MessageColorSubCommand } from './message-color-sub-command'; -export { MessageUserListSubCommand } from './message-user-list-sub-command'; diff --git a/src/commands/message/message-add-sub-command.ts b/src/commands/message/message-add-sub-command.ts index 362b608b..701879bd 100644 --- a/src/commands/message/message-add-sub-command.ts +++ b/src/commands/message/message-add-sub-command.ts @@ -1,13 +1,15 @@ -import { ActionUtils, MessageUtils } from '../../utils'; +import { CelebrationUtils, ColorUtils, FormatUtils, MessageUtils } from '../../utils'; import { CollectOptions, CollectorUtils, ExpireFunction, MessageFilter, } from 'discord.js-collector-utils'; -import { Message, MessageEmbed, MessageReaction, TextChannel, User } from 'discord.js'; +import { GuildMember, Message, MessageReaction, TextChannel, User } from 'discord.js'; import { CustomMessageRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; let Config = require('../../../config/config.json'); @@ -16,158 +18,215 @@ const COLLECT_OPTIONS: CollectOptions = { reset: true, }; +const trueFalseOptions = [Config.emotes.confirm, Config.emotes.deny]; + export class MessageAddSubCommand { constructor(private customMessageRepo: CustomMessageRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel, hasPremium: boolean) { + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { let stopFilter: MessageFilter = (nextMsg: Message) => nextMsg.author.id === msg.author.id && [Config.prefix, ...Config.stopCommands].includes( nextMsg.content.split(/\s+/)[0].toLowerCase() ); let expireFunction: ExpireFunction = async () => { + await MessageUtils.reply(msg, Lang.getEmbed('results.promptExpired', LangCode.EN_US)); + }; + // get the english types based on their inputted types, if it equals none set to nothing so it fails on the next check + let type = FormatUtils.extractCelebrationType(args[3]?.toLowerCase()); + if ( + !type || + (type !== 'birthday' && type !== 'memberanniversary' && type !== 'serveranniversary') + ) { await MessageUtils.send( channel, - new MessageEmbed() - .setTitle('Birthday Message Add - Expired') - .setDescription( - 'Type `bday message add ` to add a custom birthday message.' - ) - .setColor(Config.colors.error) + Lang.getEmbed('validation.addMessageInvalidType', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) ); - }; - - // Variable that decides if we should overwrite an already set user message (default true as not all messages are user messages) - let overwrite = true; + return; + } - if (args.length < 4) { - let embed = new MessageEmbed() - .setDescription('Please provide a message!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + if (args.length < 5) { + await MessageUtils.send(channel, Lang.getEmbed('validation.noMessage', LangCode.EN_US)); return; } + let message: string; + let colorHex = '0'; + let embedChoice: number; + let target: GuildMember; + let userId = '0'; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Try and find someone they are mentioning - let target = msg.mentions.members.first()?.user; + target = msg.mentions.members.first(); // Did we find a user? if (target) { - if (target.bot) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription('You cannot set a user-specific message for a bot!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + if (target.user.bot) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noUserMessageForBot', LangCode.EN_US) + ); return; } else if (!hasPremium) { - let embed = new MessageEmbed() - .setTitle('Premium Required!') - .setDescription( - 'User-specific birthday messages are a premium feature! View information about **Birthday Bot Premium** using `bday premium`!' - ) - .setFooter( - 'Premium helps us support and maintain the bot!', - msg.client.user.avatarURL() - ) - .setTimestamp() - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('premiumRequired.userSpecificMessages', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); return; } } + /** + * The reason for this ugly code is due to the fact that in a message, if a user mentions someone + * who either has or has had a nickname, their string format of the mention is <@!USERID> + * so, we remove the ! if it is there, and replace for both that format, and the original format + * given by target.toString() + */ + let mentionWithNickNameFormat = + target?.toString().substring(0, 2) + '!' + target?.toString().substring(2); + // Compile the message + message = msg.content + .substring(msg.content.toLowerCase().indexOf(type) + type.length + 1) + .replace( + target && type !== 'serveranniversary' + ? mentionWithNickNameFormat + : Lang.getRef('placeHolders.usersRegex', LangCode.EN_US), + '' + ) + .replace( + target && type !== 'serveranniversary' + ? target?.toString() + : Lang.getRef('placeHolders.usersRegex', LangCode.EN_US), + '' + ) + .replace(Lang.getRef('placeHolders.serverRegex', LangCode.EN_US), '') + .replace(Lang.getRef('placeHolders.yearRegex', LangCode.EN_US), ''); - // Get Message - let birthdayMessage: string; - - // Compile the birthday message - if (target) { - // If the input of the target WASN'T a @mention, replace it with the <@USER_ID> format so the substring works universally - birthdayMessage = msg.content - .replace(args[3], target.toString() + ' ') - .substring(msg.content.indexOf('add') + 27) - .replace(/@users?||{users?}/gi, ''); - } else { - // Basic non user-specific custom message - birthdayMessage = msg.content - .substring(msg.content.indexOf('add') + 4) - .replace(/@users?||{users?}/gi, ''); - } - - if (birthdayMessage.length > Config.validation.message.maxLength) { - let embed = new MessageEmbed() - .setDescription( - `Custom Messages are maxed at ${Config.validation.message.maxLength.toLocaleString()} characters!` - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + if (message.length > Config.validation.message.maxLength) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.maxCustomMessageSize', LangCode.EN_US, { + MAX_SIZE: Config.validation.message.maxLength.toString(), + }) + ); return; } - if (!birthdayMessage.includes('')) { - let embed = new MessageEmbed() - .setDescription( - '' + - 'Please include the `` placeholder somewhere in the message. This indicates where birthday usernames will appear.' + - '\n' + - '\nEx: `bday message add Happy Birthday !`' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; + let typeDisplayName = + type === 'birthday' + ? Lang.getRef('terms.birthday', LangCode.EN_US).toLowerCase() + : type === 'memberanniversary' + ? Lang.getRef('terms.memberAnniversary', LangCode.EN_US).toLowerCase() + : Lang.getRef('terms.serverAnniversary', LangCode.EN_US).toLowerCase(); + + if (type === 'birthday' || type === 'memberanniversary') { + // Can also use year and server name placeholder + if (!message.includes('')) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noUserPlaceholder', LangCode.EN_US, { + TYPE: type, + EXAMPLE_MESSAGE: Lang.getRef( + type === 'birthday' + ? 'defaults.birthdayMessage' + : 'defaults.memberAnniversaryMessage', + LangCode.EN_US + ), + }) + ); + return; + } + } else { + if (!message.includes('')) { + // NO SERVER PLACEHOLDER (can also use year placeholder) + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noServerPlaceholder', LangCode.EN_US) + ); + return; + } } - let customMessages = await this.customMessageRepo.getCustomMessages(msg.guild.id); + let customMessages = await this.customMessageRepo.getCustomMessages(msg.guild.id, type); + + let messages = customMessages.customMessages.filter(message => message.Type === type); - let globalMessageCount = customMessages.customMessages.filter( - message => message.UserDiscordId === '0' + let globalMessageCount = messages.filter( + message => message.UserDiscordId === '0' && message.Type === type ).length; + let maxMessageCountFree = + type === 'birthday' + ? Config.validation.message.maxCount.birthday.free + : type === 'memberanniversary' + ? Config.validation.message.maxCount.memberAnniversary.free + : Config.validation.message.maxCount.serverAnniversary.free; + let maxMessageCountPaid = + type === 'birthday' + ? Config.validation.message.maxCount.birthday.paid + : type === 'memberanniversary' + ? Config.validation.message.maxCount.memberAnniversary.paid + : Config.validation.message.maxCount.serverAnniversary.paid; + if (customMessages) { - if (globalMessageCount >= Config.validation.message.maxCount.free && !hasPremium) { - let embed = new MessageEmbed() - .setDescription( - `Your server has reached the maximum custom messages! (${Config.validation.message.maxCount.free.toLocaleString()})` - ) - .setFooter( - `To have up to ${Config.validation.message.maxCount.paid.toLocaleString()} custom birthday messages get **Birthday Bot Premium**!`, - msg.client.user.avatarURL() - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + if (globalMessageCount >= maxMessageCountFree && !hasPremium) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.maxFreeCustomMessages', LangCode.EN_US, { + TYPE: typeDisplayName, + FREE_MAX: maxMessageCountFree, + PAID_MAX: maxMessageCountPaid, + ICON: msg.client.user.avatarURL(), + }) + ); return; - } else if (globalMessageCount >= Config.validation.message.maxCount.paid) { - let embed = new MessageEmbed() - .setDescription( - `Your server has reached the maximum custom messages! (${Config.validation.message.maxCount.paid.toLocaleString()})` - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + } else if (globalMessageCount >= maxMessageCountPaid) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.maxPaidCustomMessages', LangCode.EN_US, { + TYPE: typeDisplayName, + PAID_MAX: maxMessageCountPaid, + }) + ); return; } // If there is a target, begin the checks if there is a user custom message already for the target - if (target) { - let userMessage = customMessages.customMessages.filter( - message => message.UserDiscordId === target.id - ); + if (target && type !== 'serveranniversary') { + let userMessage = messages.filter(message => message.UserDiscordId === target.id); // if it found a message for this user if (userMessage.length > 0) { // There is already a message for this user should they overwrite it? let trueFalseOptions = [Config.emotes.confirm, Config.emotes.deny]; - let confirmationEmbed = new MessageEmbed() - .setTitle('Caution') - .setDescription( - `There is already a custom message set for this user, would you like to overwrite it?` + - `\n\n**Current Message**: ${userMessage[0].Message}` + - `\n\n**New Message**: ${birthdayMessage}` - ) - .setFooter('This action is irreversible!', msg.client.user.avatarURL()) - .setColor(Config.colors.warning); - - let confirmationMessage = await MessageUtils.send(channel, confirmationEmbed); // Send confirmation and emotes + let confirmationMessage = await MessageUtils.send( + channel, + Lang.getEmbed('validation.duplicateUserCustomMessage', LangCode.EN_US, { + TYPE: typeDisplayName, + CURRENT_MESSAGE: userMessage[0].Message.replace( + '', + target.toString() + ), + NEW_MESSAGE: CelebrationUtils.replaceLangPlaceHolders( + message, + msg.guild, + type, + target?.toString() + ), + ICON: msg.client.user.avatarURL(), + }) + ); // Send confirmation and emotes for (let option of trueFalseOptions) { await MessageUtils.react(confirmationMessage, option); } @@ -191,60 +250,150 @@ export class MessageAddSubCommand { if (confirmation === undefined) return; - // set the overwrite value - overwrite = confirmation === Config.emotes.confirm ? true : false; + if (confirmation === Config.emotes.deny) { + await MessageUtils.send( + channel, + Lang.getEmbed('results.actionCanceled', LangCode.EN_US) + ); + return; + } } } else { // Don't allow duplicate birthday messages for non user messages - let duplicateMessage = customMessages.customMessages - .map(message => message.Message) - .includes(birthdayMessage); + let duplicateMessage = messages.map(message => message.Message).includes(message); if (duplicateMessage) { - let embed = new MessageEmbed() - .setDescription('Duplicate message found for this server!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.duplicateMessage', LangCode.EN_US) + ); return; } } } - let userId = target ? target.id : '0'; + // we can let there be an @ in the server anniversary message we just won't consider it a user specific messages + userId = target && type !== 'serveranniversary' ? target.id : '0'; - if (overwrite) { - await this.customMessageRepo.addCustomMessage(msg.guild.id, birthdayMessage, userId); - } else { - let embed = new MessageEmbed() - .setDescription('Action Canceled.') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; + if (hasPremium) { + let selectMessage = await MessageUtils.send( + channel, + Lang.getEmbed('serverPrompts.customMessageColorSelection', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + + colorHex = await CollectorUtils.collectByMessage( + msg.channel, + // Collect Filter + (nextMsg: Message) => nextMsg.author.id === msg.author.id, + stopFilter, + // Retrieve Result + async (nextMsg: Message) => { + let check = ColorUtils.findHex(nextMsg.content); + + if (!check) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidColor', LangCode.EN_US) + ); + return; + } + + return check; + }, + expireFunction, + COLLECT_OPTIONS + ); + + MessageUtils.delete(selectMessage); + + if (colorHex === undefined) { + return; + } } - let embed = new MessageEmbed().setColor(Config.colors.success); - if (!target) { - embed - .setDescription( - `Successfully added the birthday message:\n\n\`${birthdayMessage}\`\n\u200b` - ) - .addField( - 'Actions', - '' + - '`bday message list [page]` - List all custom birthday messages.' + - '\n`bday message test [user count]` - Test a birthday message.' - ); - } else { - embed - .setDescription( - `Successfully added the user birthday message for ${target.toString()}:\n\n\`${birthdayMessage}\`\n\u200b` - ) - .addField( - 'Actions', - '' + - '`bday message list user [page]` - List all user birthday messages.' + - '\n`bday message test ` - Test a user birthday message.' - ); + let settingRole = await MessageUtils.send( + channel, + Lang.getEmbed('serverPrompts.customMessageEmbedSelection', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); // Send confirmation and emotes + for (let option of trueFalseOptions) { + await settingRole.react(option); } - await MessageUtils.send(channel, embed); + + let option: string = await CollectorUtils.collectByReaction( + settingRole, + // Collect Filter + (msgReaction: MessageReaction, reactor: User) => + reactor.id === msg.author.id && trueFalseOptions.includes(msgReaction.emoji.name), + stopFilter, + // Retrieve Result + async (msgReaction: MessageReaction, reactor: User) => { + return msgReaction.emoji.name; + }, + expireFunction, + COLLECT_OPTIONS + ); + + MessageUtils.delete(settingRole); + + if (option === undefined) return; + + embedChoice = option === Config.emotes.confirm ? 1 : 0; + + await this.customMessageRepo.addCustomMessage( + msg.guild.id, + message, + userId, + type, + colorHex, + embedChoice + ); + + await MessageUtils.send( + channel, + userId === '0' + ? Lang.getEmbed('results.addCustomMessage', LangCode.EN_US, { + DISPLAY_TYPE: typeDisplayName, + MESSAGE: CelebrationUtils.replaceLangPlaceHolders( + message, + msg.guild, + type, + null + ), + IS_EMBED: embedChoice === 1 ? 'True' : 'False', + HAS_PREMIUM: !hasPremium + ? Lang.getRef('conditionals.needColorForPremium', LangCode.EN_US) + : Lang.getRef('conditionals.colorForPremium', LangCode.EN_US, { + COLOR_HEX: colorHex, + }), + TYPE: type, + ICON: msg.client.user.avatarURL(), + }) + : Lang.getEmbed('results.addCustomUserMessage', LangCode.EN_US, { + DISPLAY_TYPE: typeDisplayName, + MESSAGE: CelebrationUtils.replaceLangPlaceHolders( + message, + msg.guild, + type, + target?.toString() + ), + IS_EMBED: embedChoice === 1 ? 'True' : 'False', + HAS_PREMIUM: !hasPremium + ? Lang.getRef('conditionals.colorForPremium', LangCode.EN_US) + : Lang.getRef('conditionals.colorForPremium', LangCode.EN_US, { + COLOR_HEX: colorHex, + }), + TYPE: + type === 'birthday' + ? 'userSpecificBirthday' + : 'userSpecificMemberAnniversary', + USER: target.toString(), + ICON: msg.client.user.avatarURL(), + }) + ); + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } } diff --git a/src/commands/message/message-clear-sub-command.ts b/src/commands/message/message-clear-sub-command.ts index bba7f135..cf06344c 100644 --- a/src/commands/message/message-clear-sub-command.ts +++ b/src/commands/message/message-clear-sub-command.ts @@ -1,13 +1,15 @@ -import { ActionUtils, MessageUtils } from '../../utils'; import { CollectOptions, CollectorUtils, ExpireFunction, MessageFilter, } from 'discord.js-collector-utils'; -import { Message, MessageEmbed, MessageReaction, TextChannel, User } from 'discord.js'; +import { FormatUtils, MessageUtils } from '../../utils'; +import { Message, MessageReaction, TextChannel, User } from 'discord.js'; import { CustomMessageRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; let Config = require('../../../config/config.json'); @@ -19,46 +21,73 @@ const COLLECT_OPTIONS: CollectOptions = { export class MessageClearSubCommand { constructor(private customMessageRepo: CustomMessageRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel) { + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { let stopFilter: MessageFilter = (nextMsg: Message) => nextMsg.author.id === msg.author.id && [Config.prefix, ...Config.stopCommands].includes( nextMsg.content.split(/\s+/)[0].toLowerCase() ); let expireFunction: ExpireFunction = async () => { - await MessageUtils.send( - channel, - new MessageEmbed() - .setTitle('Birthday Message Clear - Expired') - .setDescription('Type `bday message clear` to clear the birthday messages.') - .setColor(Config.colors.error) - ); + await MessageUtils.reply(msg, Lang.getEmbed('results.promptExpired', LangCode.EN_US)); }; - let customMessages = await this.customMessageRepo.getCustomMessages(msg.guild.id); + let type = FormatUtils.extractCelebrationType(args[3]?.toLowerCase()); - let confirmationEmbed = new MessageEmbed(); + if ( + type !== 'birthday' && + type !== 'memberanniversary' && + type !== 'serveranniversary' && + type !== 'userspecificbirthday' && + type !== 'userspecificmemberanniversary' + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.clearMessageInvalidType', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } - if (customMessages.customMessages.length === 0) { - confirmationEmbed - .setDescription('You server has not set any custom birthday messages!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, confirmationEmbed); + let customMessages = type.includes('user') + ? await this.customMessageRepo.getCustomUserMessages( + msg.guild.id, + type.includes('birthday') ? 'birthday' : 'memberanniversary' + ) + : await this.customMessageRepo.getCustomMessages(msg.guild.id, type); + + let totalMessages = customMessages.customMessages.length; + // If it is a 0 the custom message technically needs a plural + let displayType: string = FormatUtils.getCelebrationDisplayType( + type, + totalMessages !== 1 + ).toLowerCase(); + + if (totalMessages === 0) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noCustomMessagesGeneric', LangCode.EN_US, { + DISPLAY_TYPE: displayType, + }) + ); return; } let trueFalseOptions = [Config.emotes.confirm, Config.emotes.deny]; - confirmationEmbed - .setDescription( - `Are you sure you want to clear __**${ - customMessages.customMessages.length - }**__ custom message${customMessages.customMessages.length === 1 ? '' : 's'}?` - ) - .setFooter('This action is irreversible!', msg.client.user.avatarURL()) - .setColor(Config.colors.warning); + let confirmationMessage = await MessageUtils.send( + channel, + Lang.getEmbed('serverPrompts.confirmClearMessages', LangCode.EN_US, { + MESSAGE_COUNT: totalMessages.toString(), + DISPLAY_TYPE: FormatUtils.getCelebrationDisplayType( + type, + totalMessages > 1 + ).toLowerCase(), + ICON: msg.client.user.avatarURL(), + }) + ); - let confirmationMessage = await MessageUtils.send(channel, confirmationEmbed); // Send confirmation and emotes + // Send confirmation and emotes for (let option of trueFalseOptions) { await MessageUtils.react(confirmationMessage, option); } @@ -83,17 +112,25 @@ export class MessageClearSubCommand { if (confirmation === Config.emotes.confirm) { // Confirm - await this.customMessageRepo.clearCustomMessages(msg.guild.id); - let embed = new MessageEmbed() - .setDescription(`Successfully cleared all birthday messages from the database!`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); + type.includes('user') + ? await this.customMessageRepo.clearCustomUserMessages( + msg.guild.id, + type.includes('birthday') ? 'birthday' : 'memberanniversary' + ) + : await this.customMessageRepo.clearCustomMessages(msg.guild.id, type); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.customMessagesCleared', LangCode.EN_US, { + DISPLAY_TYPE: displayType, + }) + ); } else { - let embed = new MessageEmbed() - .setDescription(`Action canceled.`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('results.actionCanceled', LangCode.EN_US) + ); } } } diff --git a/src/commands/message/message-color-sub-command.ts b/src/commands/message/message-color-sub-command.ts deleted file mode 100644 index c4343a70..00000000 --- a/src/commands/message/message-color-sub-command.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { ActionUtils, MessageUtils } from '../../utils'; -import { - CollectOptions, - CollectorUtils, - ExpireFunction, - MessageFilter, -} from 'discord.js-collector-utils'; -import { Message, MessageEmbed, MessageReaction, TextChannel, User } from 'discord.js'; - -import { ColorUtils } from '../../utils/color-utils'; -import { GuildRepo } from '../../services/database/repos'; - -let Config = require('../../../config/config.json'); - -const COLLECT_OPTIONS: CollectOptions = { - time: Config.experience.promptExpireTime * 1000, - reset: true, -}; - -export class MessageColorSubCommand { - constructor(private guildRepo: GuildRepo) {} - - public async execute(args: string[], msg: Message, channel: TextChannel) { - let stopFilter: MessageFilter = (nextMsg: Message) => - nextMsg.author.id === msg.author.id && - [Config.prefix, ...Config.stopCommands].includes( - nextMsg.content.split(/\s+/)[0].toLowerCase() - ); - let expireFunction: ExpireFunction = async () => { - await MessageUtils.send( - channel, - new MessageEmbed() - .setTitle('Birthday Message Color Selection - Expired') - .setDescription( - 'Type `bday message color` to set a custom birthday message color.' - ) - .setColor(Config.colors.error) - ); - }; - let colorHex: string; - - if (args.length < 4) { - let colorOptionsText = ''; - for (const [colorName, colorEmoji] of Object.entries(Config.emotes.colors)) { - colorOptionsText += `${colorEmoji} ${ - colorName.charAt(0).toUpperCase() + colorName.slice(1) - }\n`; - } - colorOptionsText += `${Config.emotes.custom} Custom Color`; - - let colorEmbed = new MessageEmbed() - .setAuthor(`${msg.guild.name}`, msg.guild.iconURL()) - .setTitle('Birthday Message Color Selection') - .setDescription( - `Please select a color or input a custom one. [(?)](${Config.links.docs}/faq#what-is-the-birthday-message-embed-color)` - ) - .addField(colorOptionsText, '\u200b') - .setFooter(`This message expires in 2 minutes!`, msg.client.user.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); - - let colorMessage = await MessageUtils.send(channel, colorEmbed); // Send confirmation and emotes - - let emotes = [...Object.values(Config.emotes.colors), Config.emotes.custom]; - for (let emote of emotes) { - await MessageUtils.react(colorMessage, emote); - } - - let colorChoice: string = await CollectorUtils.collectByReaction( - colorMessage, - // Collect Filter - (msgReaction: MessageReaction, reactor: User) => - reactor.id === msg.author.id && emotes.includes(msgReaction.emoji.name), - stopFilter, - // Retrieve Result - async (msgReaction: MessageReaction, reactor: User) => { - return msgReaction.emoji.name; - }, - expireFunction, - COLLECT_OPTIONS - ); - - MessageUtils.delete(colorMessage); - - if (colorChoice === undefined) return; - - for (const [colorName, colorEmoji] of Object.entries(Config.emotes.colors)) { - if (colorChoice === colorEmoji) { - colorHex = ColorUtils.findHex(colorName); - break; - } - } - - if (colorChoice === Config.emotes.custom) { - let inputColorEmbed = new MessageEmbed() - .setDescription( - `Please input what color you would like! (name, hex code, etc.)` - ) - .setFooter(`This message expires in 2 minutes!`, msg.client.user.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); - - let selectMessage = await MessageUtils.send(channel, inputColorEmbed); - - colorHex = await CollectorUtils.collectByMessage( - msg.channel, - // Collect Filter - (nextMsg: Message) => nextMsg.author.id === msg.author.id, - stopFilter, - // Retrieve Result - async (nextMsg: Message) => { - let check = ColorUtils.findHex(nextMsg.content); - - if (!check) { - let embed = new MessageEmbed() - .setTitle('Invalid Color') - .setDescription( - `Please provide a valid hex color! Find hex colors [here](${Config.links.colors}).` + - '\n\nExample: `Orange` or `Crimson`' + - '\nExample: `#4EEFFF` or `4EEFFF`' - ) - .setTimestamp() - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - return check; - }, - expireFunction, - COLLECT_OPTIONS - ); - - MessageUtils.delete(selectMessage); - - if (colorHex === undefined) { - return; - } - } - } else { - colorHex = ColorUtils.findHex(args[3]); - } - - if (!colorHex) { - let embed = new MessageEmbed() - .setTitle('Invalid Color') - .setDescription( - `Please provide a valid hex color! Find hex colors [here](${Config.links.colors}).` + - '\n\nExample: `Orange` or `Crimson`' + - '\nExample: `#4EEFFF` or `4EEFFF`' - ) - .setTimestamp() - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - let colorName = ColorUtils.findName(colorHex); - - let embed = new MessageEmbed() - .setDescription( - `${msg.client.user.toString()} will now use the hex color **#${ - colorName ? `${colorHex} (${colorName})` : colorHex - }** in birthday messages!` + - `\n\nHint: You can see an example of the color on the left side of this embed!` - ) - .setColor(colorHex); - - await this.guildRepo.updateMessageEmbedColor(msg.guild.id, colorHex); - - await MessageUtils.send(channel, embed); - } -} diff --git a/src/commands/message/message-embed-sub-command.ts b/src/commands/message/message-embed-sub-command.ts deleted file mode 100644 index 51e29d86..00000000 --- a/src/commands/message/message-embed-sub-command.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { FormatUtils, MessageUtils } from '../../utils'; -import { Message, MessageEmbed, TextChannel } from 'discord.js'; - -import { GuildRepo } from '../../services/database/repos'; - -let Config = require('../../../config/config.json'); - -export class MessageEmbedSubCommand { - constructor(private guildRepo: GuildRepo) {} - - public async execute(args: string[], msg: Message, channel: TextChannel) { - if (args.length < 4) { - let embed = new MessageEmbed() - .setDescription('Please provide a value! (True/False)') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - let useEmbed = FormatUtils.findBoolean(args[3]); - - if (useEmbed === undefined || useEmbed === null) { - let embed = new MessageEmbed() - .setTitle('Invalid Value!') - .setDescription('Accepted Values: `True/False`') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - await this.guildRepo.updateUseEmbed(msg.guild.id, useEmbed ? 1 : 0); - - let embed = new MessageEmbed() - .setDescription( - useEmbed - ? 'The birthday message will now be embedded!' - : 'The birthday message will no longer be embedded!' - ) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } -} diff --git a/src/commands/message/message-list-sub-command.ts b/src/commands/message/message-list-sub-command.ts index 7cd1ea41..4684f057 100644 --- a/src/commands/message/message-list-sub-command.ts +++ b/src/commands/message/message-list-sub-command.ts @@ -2,46 +2,99 @@ import { FormatUtils, MessageUtils, ParseUtils } from '../../utils'; import { Message, TextChannel } from 'discord.js'; import { CustomMessageRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; let Config = require('../../../config/config.json'); export class MessageListSubCommand { - constructor(private customMessageRepo: CustomMessageRepo) {} + constructor(private customMessageRepo: CustomMessageRepo) { } - public async execute(args: string[], msg: Message, channel: TextChannel, hasPremium: boolean) { + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { let page = 1; if (args[3]) { - try { - page = ParseUtils.parseInt(args[3]); - } catch (error) { - // Not A Number - } + page = ParseUtils.parseInt(args[4]); if (!page || page <= 0 || page > 100000) page = 1; } + let type = FormatUtils.extractCelebrationType(args[3]?.toLowerCase()); + + if ( + type !== 'birthday' && + type !== 'memberanniversary' && + type !== 'serveranniversary' && + type !== 'userspecificbirthday' && + type !== 'userspecificmemberanniversary' + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidMessageType', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + let pageSize = Config.experience.birthdayMessageListSize; - let customMessageResults = await this.customMessageRepo.getCustomMessageList( - msg.guild.id, - pageSize, - page - ); + // Get the correct message list using logic based on the given type + let customMessageResults = + type === 'userspecificbirthday' || type === 'userspecificmemberanniversary' + ? await this.customMessageRepo.getCustomMessageUserList( + msg.guild.id, + pageSize, + page, + type === 'userspecificbirthday' ? 'birthday' : 'memberanniversary' + ) + : await this.customMessageRepo.getCustomMessageList( + msg.guild.id, + pageSize, + page, + type + ); if (page > customMessageResults.stats.TotalPages) page = customMessageResults.stats.TotalPages; - let embed = await FormatUtils.getCustomMessageListEmbed( - msg.guild, - customMessageResults, - page, - pageSize, - hasPremium - ); + let embed = + type === 'userspecificbirthday' || type === 'userspecificmemberanniversary' + ? await FormatUtils.getCustomUserMessageListEmbed( + msg.guild, + customMessageResults, + page, + pageSize, + hasPremium, + type === 'userspecificbirthday' ? 'birthday' : 'memberanniversary' + ) + : await FormatUtils.getCustomMessageListEmbed( + msg.guild, + customMessageResults, + page, + pageSize, + hasPremium, + type + ); let message = await MessageUtils.send(channel, embed); - if (embed.description === '**No Custom Birthday Messages!**') return; + if ( + embed.description === Lang.getRef('list.noCustomBirthdayMessages', LangCode.EN_US) || + embed.description === + Lang.getRef('list.noCustomMemberAnniversaryMessages', LangCode.EN_US) || + embed.description === + Lang.getRef('list.noCustomServerAnniversaryMessages', LangCode.EN_US) || + embed.description === + Lang.getRef('list.noCustomUserSpecificBirthdayMessages', LangCode.EN_US) || + embed.description === + Lang.getRef('list.noCustomUserSpecificMemberAnnivesaryMessages', LangCode.EN_US) + ) + return; await MessageUtils.react(message, Config.emotes.previousPage); await MessageUtils.react(message, Config.emotes.jumpToPage); diff --git a/src/commands/message/message-mention-sub-command.ts b/src/commands/message/message-mention-sub-command.ts new file mode 100644 index 00000000..04f12521 --- /dev/null +++ b/src/commands/message/message-mention-sub-command.ts @@ -0,0 +1,105 @@ +import { FormatUtils, MessageUtils } from '../../utils'; +import { Message, Role, TextChannel } from 'discord.js'; + +import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; + +export class MessageMentionSubCommand { + constructor(private guildRepo: GuildRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + // bday message mention + let type = FormatUtils.extractCelebrationType(args[3]?.toLowerCase())?.toLowerCase(); + + if ( + !type || + (type !== 'birthday' && type !== 'memberanniversary' && type !== 'serveranniversary') + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidMessageType', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + if (args.length < 5) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidMention', LangCode.EN_US) + ); + return; + } + + let mention: string; + + // Find mentioned role + let roleInput: Role = msg.mentions.roles.first(); + + if (!roleInput) { + roleInput = msg.guild.roles.cache.find(role => + role.name.toLowerCase().includes(args[4].toLowerCase()) + ); + } + + if (!roleInput || roleInput.guild.id !== msg.guild.id) { + // if there is no roles then check for other accepted values + if ( + args[4].toLowerCase() !== 'everyone' && + args[4].toLowerCase() !== 'here' && + args[4].toLowerCase() !== '@here' && + args[4].toLowerCase() !== 'none' + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidMentionSetting', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } else { + if (args[4].toLowerCase() === '@here') { + // Support for the @here input + mention = `here`; + } else { + mention = args[4]; // Else it is either here, everyone, or none + } + } + } else { + mention = roleInput?.id; // If roleInput does exists then get the role Id + } + + let mentionOutput: string; + + if (!roleInput || roleInput.guild.id !== msg.guild.id) { + if (mention.toLowerCase() === 'everyone' || mention.toLowerCase() === 'here') { + mentionOutput = '@' + mention; + } else if (mention.toLowerCase() === 'none') { + mentionOutput = Lang.getRef('terms.noOne', LangCode.EN_US); + } + } else { + mentionOutput = roleInput.toString(); + } + + let displayType = FormatUtils.getCelebrationDisplayType(type, false); + + if (mention === 'none') mention = '0'; + + type === 'birthday' + ? await this.guildRepo.updateBirthdayMentionSetting(msg.guild.id, mention) + : type === 'memberanniversary' + ? await this.guildRepo.updateMemberAnniversaryMentionSetting(msg.guild.id, mention) + : await this.guildRepo.updateServerAnniversaryMentionSetting(msg.guild.id, mention); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.setMessageMention', LangCode.EN_US, { + BOT: msg.client.user.toString(), + MENTION: mentionOutput, + DISPLAY_TYPE: displayType, + }) + ); + } +} diff --git a/src/commands/message/message-mention-sub-commands.ts b/src/commands/message/message-mention-sub-commands.ts deleted file mode 100644 index 5e17bfbb..00000000 --- a/src/commands/message/message-mention-sub-commands.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Message, MessageEmbed, Role, TextChannel } from 'discord.js'; - -import { GuildRepo } from '../../services/database/repos'; -import { MessageUtils } from '../../utils'; - -let Config = require('../../../config/config.json'); - -export class MessageMentionSubCommand { - constructor(private guildRepo: GuildRepo) {} - - public async execute(args: string[], msg: Message, channel: TextChannel) { - if (args.length < 4) { - let embed = new MessageEmbed() - .setDescription( - 'Please provide a value!\nAccepted Values: `everyone`, `here`, `@role/role-name`, `none`' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - let mention: string; - - // Find mentioned role - let roleInput: Role = msg.mentions.roles.first(); - - if (!roleInput) { - roleInput = msg.guild.roles.cache.find(role => - role.name.toLowerCase().includes(args[3].toLowerCase()) - ); - } - - if (!roleInput || roleInput.guild.id !== msg.guild.id) { - // if there is no roles then check for other accepted values - if ( - args[3].toLowerCase() !== 'everyone' && - args[3].toLowerCase() !== 'here' && - args[3].toLowerCase() !== '@here' && - args[3].toLowerCase() !== 'none' - ) { - let embed = new MessageEmbed() - .setTitle('Invalid Group/Role') - .setDescription( - 'Accepted Values: `everyone`, `here`, `@role/role-name`, `none`' - ) - .setTimestamp() - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } else { - if (args[3].toLowerCase() === '@here') { - // Support for the @here input - mention = `here`; - } else { - mention = args[3]; // Else it is either here, everyone, or none - } - } - } else { - mention = roleInput?.id; // If roleInput does exists then get the role Id - } - - let mentionOutput: string; - - if (!roleInput || roleInput.guild.id !== msg.guild.id) { - if (mention.toLowerCase() === 'everyone' || mention.toLowerCase() === 'here') { - mentionOutput = '@' + mention; - } else if (mention.toLowerCase() === 'none') { - mentionOutput = `no one`; - } - } else { - mentionOutput = roleInput.toString(); - } - - let embed = new MessageEmbed() - .setDescription( - `${msg.client.user.toString()} will now mention ${mentionOutput} with the birthday message!` - ) - .setColor(Config.colors.success); - - if (mention === 'none') mention = '0'; - - await this.guildRepo.updateMentionSetting(msg.guild.id, mention); - - await MessageUtils.send(channel, embed); - } -} diff --git a/src/commands/message/message-remove-sub-command.ts b/src/commands/message/message-remove-sub-command.ts index d6f735d2..1819a106 100644 --- a/src/commands/message/message-remove-sub-command.ts +++ b/src/commands/message/message-remove-sub-command.ts @@ -1,77 +1,91 @@ -import { MessageUtils } from '../../utils'; -import { Message, MessageEmbed, TextChannel } from 'discord.js'; +import { CelebrationUtils, FormatUtils, MessageUtils, ParseUtils } from '../../utils'; +import { CustomMessage, CustomMessages } from '../../models/database'; +import { Message, TextChannel } from 'discord.js'; -import { CustomMessage } from '../../models/database'; import { CustomMessageRepo } from '../../services/database/repos'; - -let Config = require('../../../config/config.json'); +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; export class MessageRemoveSubCommand { - constructor(private customMessageRepo: CustomMessageRepo) {} - - public async execute(args: string[], msg: Message, channel: TextChannel) { - if (args.length < 4) { - let embed = new MessageEmbed() - .setDescription( - 'Please provide a message number!\nFind this using `bday message list`!' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + constructor(private customMessageRepo: CustomMessageRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + let type = FormatUtils.extractCelebrationType(args[3]?.toLowerCase()); + + if ( + type !== 'birthday' && + type !== 'memberanniversary' && + type !== 'serveranniversary' && + type !== 'userspecificbirthday' && + type !== 'userspecificmemberanniversary' + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.removeMessageInvalidType', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); return; } - // Retrieve message to remove - let customMessages = await this.customMessageRepo.getCustomMessages(msg.guild.id); - - let userMessages = await this.customMessageRepo.getCustomUserMessages(msg.guild.id); - - if (!customMessages && !userMessages) { - let embed = new MessageEmbed() - .setDescription(`This server doesn't have any custom messages!`) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + if (args.length < 5) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noMessageNumber', LangCode.EN_US) + ); return; } - // Try and get the position - let position: number; - // Try and find someone they are mentioning let target = msg.mentions.members.first()?.user; - // Did we find a user? - if (target) { - let userMessage = userMessages.customMessages.filter( - message => message.UserDiscordId === target.id - ); + // Try and get the position + let position: number; - if (userMessage.length > 0) position = userMessage[0].Position; + // Retrieve message to remove + let customMessages = await this.customMessageRepo.getCustomMessages(msg.guild.id, type); + let userMessages: CustomMessages; + + if (type === 'userspecificbirthday' || type === 'userspecificmemberanniversary') { + if (target) { + type = type === 'userspecificbirthday' ? 'birthday' : 'memberanniversary'; + userMessages = await this.customMessageRepo.getCustomUserMessages( + msg.guild.id, + type + ); + + if (!userMessages) { + await MessageUtils.send( + channel, + Lang.getEmbed( + 'validation.' + type === 'birthday' + ? 'noUserSpecificBirthdayMessages' + : 'noUserSpecificMemberAnniversaryMessages', + LangCode.EN_US + ) + ); + return; + } + + let userMessage = userMessages.customMessages.filter( + message => message.UserDiscordId === target.id + ); + + if (userMessage.length > 0) position = userMessage[0].Position; + } } if (!position) { - try { - position = parseInt(args[3]); - } catch (error) { - let embed = new MessageEmbed() - .setTitle('Invalid position!') - .setDescription( - `Use \`bday message list\` to view your server's custom messages!` - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } + position = ParseUtils.parseInt(args[4]); } if (!position) { - let embed = new MessageEmbed() - .setTitle('Remove Custom Message') - .setDescription( - `Message number does not exist!\nView your server's custom messages with \`bday message list\`!` - ) - .setFooter(`${Config.emotes.deny} Action Failed.`, msg.client.user.avatarURL()) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.customMessageInvalidMessageNumber', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); return; } @@ -80,35 +94,45 @@ export class MessageRemoveSubCommand { // find the position based on if it is a user or global message target ? (message = userMessages.customMessages.find( - question => question.Position === position - )) + question => question.Position === position + )) : (message = customMessages.customMessages.find( - question => question.Position === position - )); + question => question.Position === position + )); if (!message) { - let embed = new MessageEmbed() - .setTitle('Remove Custom Message') - .setDescription( - `Message number does not exist!\nView your server's custom messages with \`bday message list\`!` - ) - .setFooter(`${Config.emotes.deny} Action Failed.`, msg.client.user.avatarURL()) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validiation.customMessageInvalidMessageNumber', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); return; } // Remove the question base on if it is a user or global message target - ? await this.customMessageRepo.removeCustomMessageUser(msg.guild.id, position) - : await this.customMessageRepo.removeCustomMessage(msg.guild.id, position); - - let embed = new MessageEmbed() - .setTitle('Remove Custom Message') - .setDescription(message.Message) - .setFooter(`${Config.emotes.confirm} Message removed.`, msg.client.user.avatarURL()) - .setTimestamp() - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); + ? await this.customMessageRepo.removeCustomMessageUser(msg.guild.id, position, type) + : await this.customMessageRepo.removeCustomMessage(msg.guild.id, position, type); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.removeMessage', LangCode.EN_US, { + MESSAGE: target + ? CelebrationUtils.replaceLangPlaceHolders( + message.Message, + msg.guild, + type, + target.toString() + ) + : CelebrationUtils.replaceLangPlaceHolders( + message.Message, + msg.guild, + type, + null + ), + ICON: msg.client.user.avatarURL(), + }) + ); } } diff --git a/src/commands/message/message-test-sub-command.ts b/src/commands/message/message-test-sub-command.ts index e4ae4417..16877d5c 100644 --- a/src/commands/message/message-test-sub-command.ts +++ b/src/commands/message/message-test-sub-command.ts @@ -1,117 +1,141 @@ +import { CelebrationUtils, FormatUtils, MessageUtils, ParseUtils } from '../../utils'; import { CustomMessageRepo, GuildRepo } from '../../services/database/repos'; -import { FormatUtils, MessageUtils } from '../../utils'; -import { Message, MessageEmbed, TextChannel } from 'discord.js'; +import { GuildMember, Message, MessageEmbed, TextChannel } from 'discord.js'; + +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import moment from 'moment'; let Config = require('../../../config/config.json'); export class MessageTestSubCommand { - constructor(private guildRepo: GuildRepo, private customMessageRepo: CustomMessageRepo) {} + constructor(private guildRepo: GuildRepo, private customMessageRepo: CustomMessageRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + // bday message test [user count] + let type = FormatUtils.extractCelebrationType(args[3]?.toLowerCase())?.toLowerCase(); + + if ( + !type || + (type !== 'birthday' && type !== 'memberanniversary' && type !== 'serveranniversary') + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidMessageType', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } - public async execute(args: string[], msg: Message, channel: TextChannel) { let userCount = 1; - if (args.length < 4) { - let embed = new MessageEmbed() - .setDescription( - 'Please provide a message number or user!\nFind this using `bday message list`!' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + if (args.length < 5) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noMessageNumber', LangCode.EN_US) + ); return; - } else if (args.length >= 5) { - try { - userCount = parseInt(args[4]); - } catch (error) { - userCount = 1; - } + } else if (args.length >= 6) { + userCount = ParseUtils.parseInt(args[5]); } - // Try and find someone they are mentioning - let target = msg.mentions.members.first()?.user; + let target = msg.mentions.members.first(); let position: number; if (!target) { userCount = userCount > 5 ? 5 : userCount; // Try and get the position - try { - position = parseInt(args[3]); - } catch (error) { - let embed = new MessageEmbed() - .setDescription('Invalid message number!\nFind this using `bday message list`!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } + position = ParseUtils.parseInt(args[4]); if (!position) { - let embed = new MessageEmbed() - .setTitle('Test Custom Message') - .setDescription( - `Message number does not exist!\nView your server's custom messages with \`bday message list\`!` - ) - .setFooter(`${Config.emotes.deny} Action Failed.`, msg.client.user.avatarURL()) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidMessageNumber', LangCode.EN_US) + ); return; } } else { userCount = 1; } - let users: string[] = []; + let botMember = msg.guild.members.resolve(msg.client.user); + + let memberList: GuildMember[] = []; for (let i = 0; i < userCount; i++) { - target ? users.push(target.toString()) : users.push(msg.author.toString()); + memberList.push(target ? target : botMember); } - let userList = userCount > 1 ? FormatUtils.joinWithAnd(users) : msg.author.toString(); - - // Get guild data let guildData = await this.guildRepo.getGuild(msg.guild.id); + let userList = CelebrationUtils.getUserListString(guildData, memberList); + // Retrieve message to remove let messages = target - ? await this.customMessageRepo.getCustomUserMessages(msg.guild.id) - : await this.customMessageRepo.getCustomMessages(msg.guild.id); + ? await this.customMessageRepo.getCustomUserMessages(msg.guild.id, type) + : await this.customMessageRepo.getCustomMessages(msg.guild.id, type); + + let year = + type === 'memberanniversary' + ? moment().diff( + target + ? target.joinedAt + : msg.guild.members.resolve(msg.client.user).joinedAt, + 'years' + ) + 1 + : type === 'serveranniversary' + ? moment().diff(msg.guild.createdAt, 'years') + 1 + : null; if (!messages) { - let defaultMessage = `Happy Birthday ${userList}!`; - if (guildData.UseEmbed) { - let embed = new MessageEmbed() - .setDescription(defaultMessage) - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); - return; - } else { - await MessageUtils.send(channel, defaultMessage); - return; - } + let defaultMessage = + type === 'memberanniversary' + ? Lang.getRef('defaults.memberAnniversaryMessage', LangCode.EN_US) + : type === 'serveranniversary' + ? Lang.getRef('defaults.serverAnniversaryMessage', LangCode.EN_US) + : Lang.getRef('defaults.birthdayMessage', LangCode.EN_US); + + await MessageUtils.send( + channel, + new MessageEmbed() + .setDescription( + CelebrationUtils.replacePlaceHolders( + defaultMessage, + msg.guild, + type, + userList, + year + ) + ) + .setColor(Config.colors.default) + ); } - let customMessage: string = target - ? messages.customMessages - .find(message => message.UserDiscordId === target.id) - ?.Message.replace(/@Users/g, userList) - .replace(//g, userList) - : messages.customMessages - .find(message => message.Position === position) - ?.Message.replace(/@Users/g, userList) - .replace(//g, userList); + let chosenMessage = target + ? messages.customMessages.find(message => message.UserDiscordId === target.id) + : messages.customMessages.find(message => message.Position === position); + + let customMessage = CelebrationUtils.replacePlaceHolders( + chosenMessage?.Message, + msg.guild, + type, + userList, + year + ); if (!customMessage) { - let embed = new MessageEmbed() - .setTitle('Test Custom Message') - .setDescription( - `Message does not exist!\nView your server's custom messages with \`bday message list\`!` - ) - .setFooter(`${Config.emotes.deny} Action Failed.`, msg.client.user.avatarURL()) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.messageDoesNotExist', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); return; } - if (guildData.UseEmbed) { + if (chosenMessage.Embed) { let embed = new MessageEmbed() .setDescription(customMessage) .setColor(Config.colors.default); diff --git a/src/commands/message/message-time-sub-command.ts b/src/commands/message/message-time-sub-command.ts index 21d77282..4f4ca6c7 100644 --- a/src/commands/message/message-time-sub-command.ts +++ b/src/commands/message/message-time-sub-command.ts @@ -1,54 +1,67 @@ -import { Message, MessageEmbed, TextChannel } from 'discord.js'; +import { FormatUtils, MessageUtils, ParseUtils } from '../../utils'; +import { Message, TextChannel } from 'discord.js'; import { GuildRepo } from '../../services/database/repos'; -import { MessageUtils } from '../../utils'; - -let Config = require('../../../config/config.json'); +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; export class MessageTimeSubCommand { - constructor(private guildRepo: GuildRepo) {} - - public async execute(args: string[], msg: Message, channel: TextChannel) { - if (args.length < 4) { - let embed = new MessageEmbed() - .setDescription('Please provide a time! (0-23)') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + constructor(private guildRepo: GuildRepo) { } + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + // bday message time <0-23> + let type = FormatUtils.extractCelebrationType(args[3]?.toLowerCase())?.toLowerCase(); + + if ( + !type || + (type !== 'birthday' && type !== 'memberanniversary' && type !== 'serveranniversary') + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidMessageType', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); return; } - // Try and get the time - let messageTime: number; - try { - messageTime = parseInt(args[3]); - } catch (error) { - let embed = new MessageEmbed() - .setTitle('Invalid time!') - .setDescription('Accepted Values: `0-23`') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + if (args.length < 5) { + await MessageUtils.send(channel, Lang.getEmbed('validation.noTime', LangCode.EN_US)); return; } + // Try and get the time + let messageTime = ParseUtils.parseInt(args[4]); + if (messageTime !== 0 && (messageTime < 0 || messageTime > 23 || !messageTime)) { - let embed = new MessageEmbed() - .setTitle('Invalid time!') - .setDescription('Accepted Values: `0-23`') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidTime', LangCode.EN_US) + ); return; } - await this.guildRepo.updateMessageTime(msg.guild.id, messageTime); let timeOutput: string; - if (messageTime === 0) timeOutput = '12:00 AM'; - else if (messageTime === 12) timeOutput = '12:00 PM'; - else if (messageTime < 12) timeOutput = messageTime + ':00 AM'; - else timeOutput = messageTime - 12 + ':00 PM'; - - let embed = new MessageEmbed() - .setDescription(`Successfully set the birthday message to send at **${timeOutput}**!`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); + if (messageTime === 0) timeOutput = '12:00 ' + Lang.getRef('terms.amTime', LangCode.EN_US); + else if (messageTime === 12) + timeOutput = '12:00 ' + Lang.getRef('terms.pmTime', LangCode.EN_US); + else if (messageTime < 12) + timeOutput = messageTime + ':00 ' + Lang.getRef('terms.amTime', LangCode.EN_US); + else timeOutput = messageTime - 12 + ':00 ' + Lang.getRef('terms.pmTime', LangCode.EN_US); + + let displayType = FormatUtils.getCelebrationDisplayType(type, false).toLowerCase(); + + type === 'birthday' + ? await this.guildRepo.updateBirthdayMessageTime(msg.guild.id, messageTime) + : type === 'memberanniversary' + ? await this.guildRepo.updateMemberAnniversaryMessageTime(msg.guild.id, messageTime) + : await this.guildRepo.updateServerAnniversaryMessageTime(msg.guild.id, messageTime); + await MessageUtils.send( + channel, + Lang.getEmbed('results.setMessageTime', LangCode.EN_US, { + DISPLAY_TYPE: displayType, + TIME: timeOutput, + }) + ); } } diff --git a/src/commands/next-command.ts b/src/commands/next-command.ts index 8acd7063..b49a9055 100644 --- a/src/commands/next-command.ts +++ b/src/commands/next-command.ts @@ -1,12 +1,12 @@ -import { BdayUtils, FormatUtils, MessageUtils } from '../utils'; -import { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; +import { CelebrationUtils, FormatUtils, MessageUtils, TimeUtils } from '../utils'; +import { DMChannel, Message, TextChannel } from 'discord.js'; +import { GuildRepo, UserRepo } from '../services/database/repos'; import { Command } from './command'; -import { UserRepo } from '../services/database/repos'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; import moment from 'moment'; -let Config = require('../../config/config.json'); - export class NextCommand implements Command { public name: string = 'next'; public aliases = ['upcoming']; @@ -18,41 +18,171 @@ export class NextCommand implements Command { public requirePremium = false; public getPremium = false; - constructor(private userRepo: UserRepo) {} + constructor(private userRepo: UserRepo, private guildRepo: GuildRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { - let users = msg.guild.members.cache.filter(member => !member.user.bot).keyArray(); + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + let guildData = await this.guildRepo.getGuild(msg.guild.id); + let timezone = guildData?.DefaultTimezone; - let userDatas = await this.userRepo.getAllUsers(users); + let type: string; - if (!userDatas) { - let embed = new MessageEmbed() - .setDescription('No one has set their birthday in this server! :(') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; + if (args.length > 2) { + type = FormatUtils.extractCelebrationType(args[2].toLowerCase())?.toLowerCase() ?? ''; + if ( + type !== 'birthday' && + type !== 'memberanniversary' && + type !== 'serveranniversary' + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidNextArgs', LangCode.EN_US) + ); + return; + } } - let commandUser = userDatas.find(user => user.UserDiscordId === msg.author.id); + if (args.length === 2 || type === 'birthday') { + // Next birthday + let users = msg.guild.members.cache.filter(member => !member.user.bot).keyArray(); - let nextBirthdayUsers = BdayUtils.getNextUsers(userDatas, commandUser?.TimeZone); + let userDatas = await this.userRepo.getAllUsers(users); - if (!nextBirthdayUsers) { - let embed = new MessageEmbed() - .setDescription('There are no upcoming birthdays!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } + if (!userDatas) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noBirthdaysInServer', LangCode.EN_US) + ); + return; + } + + let commandUser = userDatas.find(user => user.UserDiscordId === msg.author.id); + + timezone = + timezone && timezone !== '0' && guildData?.UseTimezone === 'server' + ? timezone + : commandUser?.TimeZone; + + let nextBirthdayUsers = CelebrationUtils.getNextUsers(userDatas, timezone); + + if (!nextBirthdayUsers) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noUpcomingBirthdays', LangCode.EN_US) + ); + return; + } + + let userList = nextBirthdayUsers.map(user => + msg.guild.members.resolve(user.UserDiscordId) + ); + + let userStringList = CelebrationUtils.getUserListString(guildData, userList); + let nextBirthday = moment(nextBirthdayUsers[0].Birthday).format('MMMM Do'); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.nextBirthday', LangCode.EN_US, { + USERS: userStringList, + BIRTHDAY: nextBirthday, + }) + ); + } else { + if (!timezone || timezone === '0') { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.serverTimezoneNotSet', LangCode.EN_US) + ); + return; + } + if (type === 'memberanniversary') { + // TODO: fetch members? + // Next member anniversary + let guildMembers = msg.guild.members.cache + .filter(member => !member.user.bot) + .map(member => member); + let closestMonthDay: string; + let now = moment.tz(timezone); + let nowMonthDay = now.format('MM-DD'); - let userList = nextBirthdayUsers.map(user => msg.guild.members.resolve(user.UserDiscordId)); + for (let member of guildMembers) { + let memberMonthDay = moment(member.joinedAt).format('MM-DD'); - let userStringList = FormatUtils.joinWithAnd(userList.map(user => user.toString())); - let nextBirthday = moment(nextBirthdayUsers[0].Birthday).format('MMMM Do'); + // If this is the first run through + if (!closestMonthDay) { + closestMonthDay = memberMonthDay; + continue; + } - let embed = new MessageEmbed() - .setDescription(`${userStringList}'s birthday is next on **${nextBirthday}**!`) - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); + let memberDiff = moment(memberMonthDay, 'MM-DD').diff( + moment(nowMonthDay, 'MM-DD'), + 'days' + ); + let closestDiff = moment(closestMonthDay, 'MM-DD').diff( + moment(nowMonthDay, 'MM-DD'), + 'days' + ); + + // Basically if the diff is negative then that date has passed this year + // So we need to subtract it from 365 to get days until (366 if next year is a leap year) + memberDiff = + memberDiff < 0 + ? (TimeUtils.isLeap(now.year() + 1) ? 366 : 365) + memberDiff + : memberDiff; + closestDiff = + closestDiff < 0 + ? (TimeUtils.isLeap(now.year() + 1) ? 366 : 365) + closestDiff + : closestDiff; + + if (memberDiff < closestDiff && memberDiff !== 0) + closestMonthDay = memberMonthDay; + } + + guildMembers = guildMembers.filter( + member => moment(member.joinedAt).format('MM-DD') === closestMonthDay + ); + + let userList = CelebrationUtils.getUserListString(guildData, guildMembers); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.nextMemberAnniversary', LangCode.EN_US, { + USERS: userList, + DATE: moment(closestMonthDay, 'MM-DD').format('MMMM Do'), + }) + ); + } else { + // Next server anniversary + let serverCreatedAt = moment(msg.guild.createdAt).tz(timezone); + let anniversaryFormatted = serverCreatedAt.format('MMMM Do'); + let now = moment.tz(timezone); + let yearsOldRoundedUp = now.year() - serverCreatedAt.year(); + + // If the diff is negative that date has already passed so we need to increase the year (this is how we round up) + // This is confusing but we are looking for the NEXT year, so if this isn't met we technically already have the next year + // For instance, if a server was created on August 28th 2001, and we are checking the next anniversary on June 28th 2021, + // Subtracting those years would give you 20 years, but the server is still only 19, so 20 is correct for the upcoming year + // Likewise if it was September 1st when checking, it would be 20 years old but we need to increase it since the server is TURNING 21 next + if ( + moment(serverCreatedAt.format('MM-DD'), 'MM-DD').diff( + moment(now.format('MM-DD'), 'MM-DD'), + 'days' + ) < 0 + ) + yearsOldRoundedUp++; + + await MessageUtils.send( + channel, + Lang.getEmbed('results.nextServerAnniversary', LangCode.EN_US, { + SERVER: msg.guild.name, + DATE: anniversaryFormatted, + YEARS: yearsOldRoundedUp.toString(), + }) + ); + } + } } } diff --git a/src/commands/premium-command.ts b/src/commands/premium-command.ts new file mode 100644 index 00000000..4b41758d --- /dev/null +++ b/src/commands/premium-command.ts @@ -0,0 +1,92 @@ +import { DMChannel, Message, TextChannel } from 'discord.js'; +import { Lang, Logger, SubscriptionService } from '../services'; +import { MessageUtils, TimeUtils } from '../utils'; + +import { Command } from './command'; +import { LangCode } from '../models/enums'; +import { PlanName } from '../models/subscription-models'; + +let Config = require('../../config/config.json'); +let Logs = require('../../lang/logs.json'); + +export class PremiumCommand implements Command { + public name: string = 'premium'; + public aliases = ['inv']; + public requireSetup = true; + public guildOnly = true; + public adminOnly = false; + public ownerOnly = false; + public voteOnly = false; + public requirePremium = false; + public getPremium = false; + + constructor(private subscriptionService: SubscriptionService) {} + + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + if (!Config.payments.enabled) { + await MessageUtils.send( + channel, + Lang.getEmbed('premiumPrompts.premiumDisabled', LangCode.EN_US) + ); + return; + } + + let subStatus = await this.subscriptionService.getSubscription( + PlanName.premium1, + msg.guild.id + ); + + if (!subStatus || !subStatus.service) { + await MessageUtils.send( + channel, + Lang.getEmbed('premiumPrompts.noSubscription', LangCode.EN_US, { + BIRTHDAY_MESSAGE_MAX_FREE: Config.validation.message.maxCount.birthday.free.toLocaleString(), + BIRTHDAY_MESSAGE_MAX_PAID: Config.validation.message.maxCount.birthday.paid.free.toLocaleString(), + MEMBER_ANNIVERSARY_MESSAGE_MAX_FREE: Config.validation.message.maxCount.memberAnniversary.paid.free.toLocaleString(), + MEMBER_ANNIVERSARY_MESSAGE_MAX_PAID: Config.validation.message.maxCount.memberAnniversary.paid.free.toLocaleString(), + SERVER_ANNIVERSARY_MESSAGE_MAX_FREE: Config.validation.message.maxCount.serverAnniversary.paid.free.toLocaleString(), + SERVER_ANNIVERSARY_MESSAGE_MAX_PAID: Config.validation.message.maxCount.serverAnniversary.paid.free.toLocaleString(), + MAX_ANNIVERSARY_ROLES: Config.validation.trustedRoles.maxCount.paid.free.toLocaleString(), + MAX_TRUSTED_ROLES: Config.validation.maxCount.paid.free.toLocaleString(), + }) + ); + + Logger.info( + Logs.info.unsubRanPremiumCmd + .replace('{SENDER_TAG}', msg.author.tag) + .replace('{SENDER_ID}', msg.author.id) + .replace('{GUILD_NAME}', msg.guild.name) + .replace('{GUILD_ID}', msg.guild.id) + ); + return; + } + + let lastPayment = TimeUtils.getMoment(subStatus.subscription.times.lastPayment); + let paidUntil = TimeUtils.getMoment(subStatus.subscription.times.paidUntil); + + let na = Lang.getRef('terms.na', LangCode.EN_US); + await MessageUtils.send( + channel, + Lang.getEmbed('premiumPrompts.subscription', LangCode.EN_US, { + IS_ACTIVE: Lang.getRef( + 'boolean.' + subStatus.service ? 'yes' : 'no', + LangCode.EN_US + ), + SUBSCRIPTION_ID: subStatus.subscription.id + ? `[${subStatus.subscription.id}](${Lang.getRef( + 'links.autopay', + LangCode.EN_US + )}/connect/${subStatus.subscription.id})` + : na, + STATUS: subStatus.subscription.status ?? na, + LAST_PAYMENT: lastPayment?.format('MMMM DD, YYYY, HH:mm UTC') ?? na, + PAID_UNTIL: paidUntil?.format('MMMM DD, YYYY, HH:mm UTC') ?? na, + }) + ); + return; + } +} diff --git a/src/commands/premium-commands.ts b/src/commands/premium-commands.ts deleted file mode 100644 index af562e09..00000000 --- a/src/commands/premium-commands.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; -import { Logger, SubscriptionService } from '../services'; -import { MessageUtils, TimeUtils } from '../utils'; - -import { Command } from './command'; -import { PlanName } from '../models/subscription-models'; - -let Config = require('../../config/config.json'); -let Logs = require('../../lang/logs.json'); - -export class PremiumCommand implements Command { - public name: string = 'premium'; - public aliases = ['inv']; - public requireSetup = true; - public guildOnly = true; - public adminOnly = false; - public ownerOnly = false; - public voteOnly = false; - public requirePremium = false; - public getPremium = false; - - constructor(private subscriptionService: SubscriptionService) {} - - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { - if (!Config.payments.enabled) { - let embed = new MessageEmbed() - .setTitle('Birthday Bot Premium') - .setDescription( - `Premium subscriptions are currently disabled. Enjoy premium features for free! Woohoo!` - ) - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); - return; - } - - let messagesLimitFree = Config.validation.message.maxCount.free; - let messagesLimitPremium = Config.validation.message.maxCount.paid; - - let subStatus = await this.subscriptionService.getSubscription( - PlanName.premium1, - msg.guild.id - ); - - if (!subStatus || !subStatus.service) { - let embed = new MessageEmbed() - .setAuthor(msg.guild.name, msg.guild.iconURL()) - .setTitle('Birthday Bot Premium') - .setDescription( - 'Subscribe to **Birthday Bot Premium** to give this server extra features!' - ) - .addField( - 'Premium Features', - `- No voting needed for commands\n- Up to **${messagesLimitPremium.toLocaleString()}** custom birthday messages *(vs **${messagesLimitFree.toLocaleString()}** free)*\n- Access to user-specific custom birthday messages\n- Customize the color of the birthday message embed\n- Premium support\nFeatures apply **server-wide** (this server only).` - ) - .addField('Price', '$2.99 USD / Month') - .addField( - 'Purchase Details', - `Type \`bday subscribe\` to purchase a subscription. You will then be direct messaged a PayPal link where you can checkout using your PayPal account or credit card. Your servers subscription will activate within 5 minutes of checking out with PayPal. You may cancel at any time on your [PayPal Automatic Payments](${Config.links.autopay}) page. Any paid time after cancelling will still count as premium service. As always, feel free to contact support at the link below with any questions.\n\n[Join Support Server](${Config.links.support})` - ) - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); - - Logger.info( - Logs.info.unsubRanPremiumCmd - .replace('{SENDER_TAG}', msg.author.tag) - .replace('{SENDER_ID}', msg.author.id) - .replace('{GUILD_NAME}', msg.guild.name) - .replace('{GUILD_ID}', msg.guild.id) - ); - return; - } - - let lastPayment = TimeUtils.getMoment(subStatus.subscription.times.lastPayment); - let paidUntil = TimeUtils.getMoment(subStatus.subscription.times.paidUntil); - - let embed = new MessageEmbed() - .setAuthor(msg.guild.name, msg.guild.iconURL()) - .setTitle('Birthday Bot Premium') - .setDescription(`This servers subscription information.`) - .addField('Active', subStatus.service ? 'Yes' : 'No') - .addField( - 'Subscription ID', - subStatus.subscription.id - ? `[${subStatus.subscription.id}](${Config.links.autopay}/connect/${subStatus.subscription.id})` - : 'N/A' - ) - .addField('Status', subStatus.subscription.status ?? 'N/A') - .addField('Last Payment', lastPayment?.format('MMMM DD, YYYY, HH:mm UTC') ?? 'N/A') - .addField('Paid Until', paidUntil?.format('MMMM DD, YYYY, HH:mm UTC') ?? 'N/A') - .addField( - 'Purchase Details', - `Type \`bday subscribe\` to purchase a subscription. You will then be direct messaged a PayPal link where you can checkout using your PayPal account or credit card. Your servers subscription will activate within 5 minutes of checking out with PayPal. You may cancel at any time on your [PayPal Automatic Payments](${Config.links.autopay}) page. Any paid time after cancelling will still count as premium service. As always, feel free to contact support at the link below with any questions.\n\n[Join Support Server](${Config.links.support})` - ) - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); - return; - } -} diff --git a/src/commands/purge-command.ts b/src/commands/purge-command.ts index f1fb73d9..2be5cfa6 100644 --- a/src/commands/purge-command.ts +++ b/src/commands/purge-command.ts @@ -1,13 +1,15 @@ -import { ActionUtils, MessageUtils } from '../utils'; import { CollectOptions, CollectorUtils, ExpireFunction, MessageFilter, } from 'discord.js-collector-utils'; -import { DMChannel, Message, MessageEmbed, MessageReaction, TextChannel, User } from 'discord.js'; +import { DMChannel, Message, MessageReaction, TextChannel, User } from 'discord.js'; import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { MessageUtils } from '../utils'; import { UserRepo } from '../services/database/repos'; let Config = require('../../config/config.json'); @@ -33,56 +35,36 @@ export class PurgeCommand implements Command { async execute(args: string[], msg: Message, channel: TextChannel | DMChannel): Promise { let target = msg.author; let userData = await this.userRepo.getUser(target.id); // Try and get their data - let confirmEmbed = new MessageEmbed(); let changesLeft = 0; let stopFilter: MessageFilter = (nextMsg: Message) => nextMsg.author.id === msg.author.id && nextMsg.content.split(/\s+/)[0].toLowerCase() === Config.prefix; let expireFunction: ExpireFunction = async () => { - await MessageUtils.send( - channel, - new MessageEmbed() - .setTitle('Birthday Purge - Expired') - .setDescription('Type `bday purge` to rerun the purge.') - .setColor(Config.colors.error) - ); + await MessageUtils.reply(msg, Lang.getEmbed('results.promptExpired', LangCode.EN_US)); }; if (!userData || !(userData.Birthday && userData.TimeZone)) { // Are they in the database? - let embed = new MessageEmbed() - .setDescription('You do not have data in the database.') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.birthdayNotSet', LangCode.EN_US) + ); return; } else { changesLeft = userData.ChangesLeft; - confirmEmbed - .setTitle('Clear User Data') - .addField( - 'Actions', - `${Config.emotes.confirm} Confirm\n${Config.emotes.deny} Cancel` - ) - .setFooter(`This message expires in 2 minutes!`, msg.client.user.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp() - .setAuthor(target.tag, target.avatarURL()); - - let description = - 'This command will remove both your time zone and your birthday from the database. [(?)](${Config.links.docs}/faq#why-does-birthday-bot-need-my-timezone)' + - `\n\nThis will not reset your birthday attempts. (You have ${changesLeft} left) [(?)](${Config.links.docs}/faq#how-many-times-can-i-set-my-birthday)`; - - if (changesLeft === 0) { - // Out of changes? - description += - '\n\n**NOTE**: You do not have any birthday attempts left! Clearing your birthday will mean you can no longer set it!'; - } - confirmEmbed.setDescription(description); } let trueFalseOptions = [Config.emotes.confirm, Config.emotes.deny]; - let confirmationMessage = await MessageUtils.send(channel, confirmEmbed); // Send confirmation and emotes + let confirmationMessage = await MessageUtils.send( + channel, + Lang.getEmbed('userPrompts.birthdayConfirmPurge', LangCode.EN_US, { + CHANGES_LEFT: changesLeft.toString(), + APPEND: + changesLeft === 0 ? Lang.getRef('prompts.outOfAttemtps', LangCode.EN_US) : '', + ICON: msg.client.user.avatarURL(), + }) + ); // Send confirmation and emotes for (let option of trueFalseOptions) { await MessageUtils.react(confirmationMessage, option); } @@ -109,16 +91,16 @@ export class PurgeCommand implements Command { // Confirm await this.userRepo.addOrUpdateUser(target.id, null, null, changesLeft); // Add or update user - let embed = new MessageEmbed() - .setDescription('Successfully purged your data from the database.') - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('results.purgeSuccessful', LangCode.EN_US) + ); } else if (confirmation === Config.emotes.deny) { // Cancel - let embed = new MessageEmbed() - .setDescription('Request Canceled.') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('results.actionCanceled', LangCode.EN_US) + ); } } } diff --git a/src/commands/set-attempts-command.ts b/src/commands/set-attempts-command.ts index 53502959..766f0e70 100644 --- a/src/commands/set-attempts-command.ts +++ b/src/commands/set-attempts-command.ts @@ -1,11 +1,11 @@ import { GuildUtils, MathUtils, MessageUtils, ParseUtils } from '../utils'; -import { Message, MessageEmbed, TextChannel, User } from 'discord.js'; +import { Message, TextChannel, User } from 'discord.js'; import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; import { UserRepo } from '../services/database/repos'; -let Config = require('../../config/config.json'); - export class SetAttemptsCommand implements Command { public name: string = 'setattempts'; public aliases = ['setchangesleft']; @@ -19,14 +19,14 @@ export class SetAttemptsCommand implements Command { constructor(private userRepo: UserRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel) { + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { let target: User; if (args.length < 3) { - let embed = new MessageEmbed() - .setDescription('Please specify a user!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noUserSpecified', LangCode.EN_US) + ); return; } // Get who they are mentioning @@ -35,57 +35,46 @@ export class SetAttemptsCommand implements Command { // Did we find a user? if (!target) { - let embed = new MessageEmbed() - .setDescription('Could not find that user!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noUserFound', LangCode.EN_US) + ); return; } if (args.length < 4) { - let embed = new MessageEmbed() - .setDescription('Please specify an amount!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noAmountGiven', LangCode.EN_US) + ); return; } - let amount: number; - - try { - amount = MathUtils.clamp(ParseUtils.parseInt(args[3]), 0, 127); - } catch (error) { - let embed = new MessageEmbed() - .setDescription('Invalid Number!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } + let amount = MathUtils.clamp(ParseUtils.parseInt(args[3]), 0, 127); if (!(typeof amount === 'number') || !amount) { - let embed = new MessageEmbed() - .setDescription('Invalid Number!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidNumber', LangCode.EN_US) + ); return; } if (amount > 127) { - let embed = new MessageEmbed() - .setDescription('Amount too large!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.amountTooLarge', LangCode.EN_US) + ); return; } let userData = await this.userRepo.getUser(target.id); if (!userData) { - let embed = new MessageEmbed().setColor(Config.colors.error); - if (target !== msg.author) { - embed.setDescription('That user has not used any attempts'); - } - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.attemptsLeft', LangCode.EN_US) + ); return; } @@ -96,12 +85,13 @@ export class SetAttemptsCommand implements Command { amount ); - let embed = new MessageEmbed() - .setDescription( - `Successfully set ${target.toString()}'s birthday attempts to ${amount}!` - ) - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('results.setAttempts', LangCode.EN_US, { + USER: target.toString(), + AMOUNT: amount.toString(), + }) + ); return; } } diff --git a/src/commands/set-command.ts b/src/commands/set-command.ts index c1e4fc92..ab41fcce 100644 --- a/src/commands/set-command.ts +++ b/src/commands/set-command.ts @@ -1,6 +1,5 @@ import * as Chrono from 'chrono-node'; -import { ActionUtils, FormatUtils, GuildUtils, MessageUtils, PermissionUtils } from '../utils'; import { CollectOptions, CollectorUtils, @@ -16,9 +15,13 @@ import { TextChannel, User, } from 'discord.js'; +import { FormatUtils, GuildUtils, MessageUtils, PermissionUtils } from '../utils'; import { GuildRepo, UserRepo } from '../services/database/repos'; import { Command } from './command'; +import { GuildData } from '../models/database'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; let Config = require('../../config/config.json'); @@ -27,6 +30,8 @@ const COLLECT_OPTIONS: CollectOptions = { reset: true, }; +const trueFalseOptions = [Config.emotes.confirm, Config.emotes.deny]; + export class SetCommand implements Command { public name: string = 'set'; public aliases = ['add', 'suggest']; @@ -38,104 +43,61 @@ export class SetCommand implements Command { public requirePremium = false; public getPremium = false; - constructor(private guilRepo: GuildRepo, private userRepo: UserRepo) {} + constructor(private guildRepo: GuildRepo, private userRepo: UserRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { let stopFilter: MessageFilter = (nextMsg: Message) => nextMsg.author.id === msg.author.id && [Config.prefix, ...Config.stopCommands].includes( nextMsg.content.split(/\s+/)[0].toLowerCase() ); let expireFunction: ExpireFunction = async () => { - await MessageUtils.send( - channel, - new MessageEmbed() - .setTitle('Birthday Set - Expired') - .setDescription('Type `bday set` to rerun the birthday set.') - .setColor(Config.colors.error) - ); + await MessageUtils.reply(msg, Lang.getEmbed('results.promptExpired', LangCode.EN_US)); }; let target: User; - let birthday: string; + let birthday = FormatUtils.getBirthday(args.slice(2).join(' ')); let timeZone: string; let dm = channel instanceof DMChannel; + let guildData: GuildData; target = msg.mentions.members?.first()?.user; - if (args.length >= 3) { - // Check the third arg for inputs - let suggestCheck = false; // This could be removed if I did it as I did it in the subsequent args check, but this is technically more efficient - if (!dm && !target) { - target = FormatUtils.getUser(msg, args[2]); - if (target) suggestCheck = true; - } - - if (!suggestCheck) { - birthday = FormatUtils.getBirthday(args[2]); - } - - if (!birthday) { - if (!FormatUtils.checkAbbreviation(args[2])) { - timeZone = FormatUtils.findZone(args[2]); // Try and get the time zone - } - } - } - - if (args.length >= 4) { - // Check the fourth arg for inputs - if (!dm && !target) { - target = msg.mentions.members.first()?.user; - } - - if (!birthday) { - birthday = FormatUtils.getBirthday(args[3]); - } - - if (!timeZone) { - if (!FormatUtils.checkAbbreviation(args[3])) { - timeZone = FormatUtils.findZone(args[3]); // Try and get the time zone - } - } + for (let i = 2; i < args.length; i++) { + if (!FormatUtils.checkAbbreviation(args[i])) timeZone = FormatUtils.findZone(args[i]); + if (timeZone) break; } - if (args.length >= 5) { - // Check the fifth arg for inputs - if (!dm && !target) { - target = msg.mentions.members.first()?.user; - } - - if (!birthday) { - birthday = FormatUtils.getBirthday(args[4]); - } - - if (!timeZone) { - if (!FormatUtils.checkAbbreviation(args[4])) { - timeZone = FormatUtils.findZone(args[4]); // Try and get the time zone - } - } - } - - if (!target) { + if (!target || dm) { target = msg.author; } else { - let guildData = await this.guilRepo.getGuild(msg.guild.id); + guildData = await this.guildRepo.getGuild(msg.guild.id); if (guildData && !PermissionUtils.hasPermission(msg.member, guildData)) { - let embed = new MessageEmbed() - .setDescription( - 'You do not have permission to suggest birthdays for other users!' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.cantSuggest', LangCode.EN_US) + ); return; } // Get who they are mentioning let member = msg.mentions.members?.first() || - GuildUtils.findMember(msg.guild, args[2]) || - GuildUtils.findMember(msg.guild, args[3]) || - GuildUtils.findMember(msg.guild, args[4]); + (args.length > 2 && GuildUtils.findMember(msg.guild, args[2])) || + (args.length > 3 && GuildUtils.findMember(msg.guild, args[3])) || + (args.length > 4 && GuildUtils.findMember(msg.guild, args[4])); + + if (member.user.bot) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.cantSetBirthdayForBot', LangCode.EN_US) + ); + return; + } if ( member && @@ -143,24 +105,15 @@ export class SetCommand implements Command { .permissionsFor(member) .has([Permissions.FLAGS.READ_MESSAGE_HISTORY]) ) { - let embed = new MessageEmbed() - .setDescription( - 'That user needs the `READ_MESSAGE_HISTORY` permission in this channel!' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.memberNeedsMessageHistory', LangCode.EN_US, { + MEMBER: member.toString(), + }) + ); return; } } - - if (target.bot) { - let embed = new MessageEmbed() - .setDescription(`You can't set a birthday for a bot!`) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - let suggest = target !== msg.author; let userData = await this.userRepo.getUser(target.id); // Try and get their data @@ -170,45 +123,77 @@ export class SetCommand implements Command { // Are they in the database? if (userData.ChangesLeft === 0) { // Out of changes? - let embed = new MessageEmbed() - .setDescription('You are out of birthday attempts!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.outOfAttempts', LangCode.EN_US) + ); return; } else { changesLeft = userData.ChangesLeft; } } + if (!(channel instanceof DMChannel) && !guildData) + guildData = await this.guildRepo.getGuild(msg.guild.id); + + // if the guild has a timezone, and their inputted timezone isn't already the guild's timezone + if ( + guildData?.DefaultTimezone !== '0' && + (!timeZone || timeZone !== guildData?.DefaultTimezone) + ) { + let confirmationMessage = await MessageUtils.send( + channel, + Lang.getEmbed( + 'userPrompts.defaultTimeZoneAvailable' + (timeZone ? 'Override' : ''), + LangCode.EN_US, + { + SERVER_TIMEZONE: guildData.DefaultTimezone, + INPUTTED_TIMEZONE: timeZone, + TARGET: target.username, + } + ) + ); // Send confirmation and emotes + for (let option of trueFalseOptions) { + await MessageUtils.react(confirmationMessage, option); + } - let override = - userData && userData.Birthday && userData.TimeZone - ? '__**Note**__: Your birthday is already set, continue to change it.\n\n' - : ''; + let confirmation: string = await CollectorUtils.collectByReaction( + confirmationMessage, + // Collect Filter + (msgReaction: MessageReaction, reactor: User) => + reactor.id === msg.author.id && + trueFalseOptions.includes(msgReaction.emoji.name), + stopFilter, + // Retrieve Result + async (msgReaction: MessageReaction, reactor: User) => { + return msgReaction.emoji.name; + }, + expireFunction, + COLLECT_OPTIONS + ); - if (!timeZone) { - let timeZoneEmbed = new MessageEmbed() - .setDescription( - `${override}**Important**: By submitting this information you agree it can be shown to anyone.` + - '\n' + - `\nFirst, please enter your time zone. [(?)](${Config.links.docs}/faq#why-does-birthday-bot-need-my-timezone)` + - '\n' + - `\nTo find your time zone please use the [map time zone picker](${Config.links.map})!` + - '\n' + - '\nSimply click your location on the map and copy the name of the selected time zone. You can then enter it below.' + - '\n' + - '\n**Example Usage:** `America/New_York`' + - '\n' + - `\n**Info**: Birthdays are stored globally, meaning you only have to set your birthday once!` - ) - .setFooter(`This message expires in 2 minutes!`, msg.client.user.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp() - .setAuthor(target.tag, target.avatarURL()); + MessageUtils.delete(confirmationMessage); - if (suggest) timeZoneEmbed.setTitle(`Setup For ${target.username} - Time Zone`); - else timeZoneEmbed.setTitle('User Setup - Time Zone'); + if (confirmation === undefined) return; - let timezoneMessage = await MessageUtils.send(channel, timeZoneEmbed); + if (confirmation === Config.emotes.deny) { + // deny + timeZone = timeZone ? guildData.DefaultTimezone : null; + } else { + // Confirm + timeZone = timeZone ?? guildData.DefaultTimezone; + } + } + + if (!timeZone) { + let timezoneMessage = await MessageUtils.send( + channel, + Lang.getEmbed('userPrompts.birthdaySetupTimeZone', LangCode.EN_US, { + TARGET: target.username, + AUTHOR_ICON: target.avatarURL(), + ICON: msg.client.user.avatarURL(), + TAG: target.tag, + }) + ); timeZone = await CollectorUtils.collectByMessage( msg.channel, @@ -218,33 +203,29 @@ export class SetCommand implements Command { // Retrieve Result async (nextMsg: Message) => { if (FormatUtils.checkAbbreviation(nextMsg.content)) { - let embed = new MessageEmbed() - .setDescription('Invalid time zone! Do not use timezone abbreviations!') - .setFooter( - `Please check above and try again!`, - msg.client.user.avatarURL() + await MessageUtils.send( + channel, + Lang.getEmbed( + 'validation.invalidTimeZoneAbbreviation', + LangCode.EN_US, + { + TARGET: target.username, + ICON: msg.client.user.avatarURL(), + } ) - .setTimestamp() - .setColor(Config.colors.error); - if (suggest) embed.setTitle(`Setup For ${target.username} - Time Zone`); - else embed.setTitle('User Setup - Time Zone'); - await MessageUtils.send(channel, embed); + ); return; } let input = FormatUtils.findZone(nextMsg.content); // Try and get the time zone if (!input) { - let embed = new MessageEmbed() - .setDescription('Invalid time zone!') - .setFooter( - `Please check above and try again!`, - msg.client.user.avatarURL() - ) - .setTimestamp() - .setColor(Config.colors.error); - if (suggest) embed.setTitle(`Setup For ${target.username} - Time Zone`); - else embed.setTitle('User Setup - Time Zone'); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidTimezone', LangCode.EN_US, { + TARGET: target.username, + ICON: msg.client.user.avatarURL(), + }) + ); return; } @@ -262,25 +243,15 @@ export class SetCommand implements Command { } if (!birthday) { - let birthdayEmbed = new MessageEmbed() - .setDescription( - `${override}**Important**: By submitting this information you agree it can be shown to anyone.` + - '\n' + - `\nPlease enter your birth month and day. [(?)](${Config.links.docs}/faq#why-does-birthday-bot-only-need-my-birth-month-and-date)` + - '\n' + - '\n**Example Usage:** `08/28` (MM/DD)' + - '\n' + - `\n**Info**: Birthdays are stored globally, meaning you only have to set your birthday once!` - ) - .setFooter(`This message expires in 2 minutes!`, msg.client.user.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp() - .setAuthor(target.tag, target.avatarURL()); - - if (suggest) birthdayEmbed.setTitle(`Setup For ${target.username} - Birthday`); - else birthdayEmbed.setTitle('User Setup - Birthday'); - - let birthdayMessage = await MessageUtils.send(channel, birthdayEmbed); + let birthdayMessage = await MessageUtils.send( + channel, + Lang.getEmbed('userPrompts.birthdaySetupBirthday', LangCode.EN_US, { + TARGET: target.username, + AUTHOR_ICON: target.avatarURL(), + ICON: msg.client.user.avatarURL(), + TAG: target.tag, + }).setAuthor(target.tag, target.avatarURL()) + ); birthday = await CollectorUtils.collectByMessage( msg.channel, @@ -293,17 +264,12 @@ export class SetCommand implements Command { // Don't laugh at my double check it prevents the dates chrono misses on the first input if (!result) { - let embed = new MessageEmbed() - .setDescription('Invalid birthday!') - .setFooter( - `Please check above and try again!`, - msg.client.user.avatarURL() - ) - .setTimestamp() - .setColor(Config.colors.error); - if (suggest) embed.setTitle(`Setup For ${target.username} - Birthday`); - else embed.setTitle('User Setup - Birthday'); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidBirthday', LangCode.EN_US, { + TARGET: target.username, + }) + ); return; } @@ -325,33 +291,22 @@ export class SetCommand implements Command { let month = birthDate.getMonth() + 1; let day = birthDate.getDate(); - let confirmationEmbed = new MessageEmbed().setColor(Config.colors.warning); - - if (suggest) { - confirmationEmbed - .setDescription( - `${target.toString()}, please confirm this information is correct: **${FormatUtils.getMonth( - month - )} ${day}, ${timeZone}**` - ) - .setFooter( - `${target.username} has ${changesLeft} attempts left. By clicking confirm they will use one of them.`, - msg.client.user.avatarURL() - ); - } else { - confirmationEmbed - .setDescription( - `Please confirm this information is correct: **${FormatUtils.getMonth( - month - )} ${day}, ${timeZone}**` - ) - .setFooter( - `You have ${changesLeft} attempts left. By clicking confirm you will use one of them.`, - msg.client.user.avatarURL() - ); - } + let confirmationEmbed: MessageEmbed; - let trueFalseOptions = [Config.emotes.confirm, Config.emotes.deny]; + let confirmationEmbedChoice = + userData && userData.Birthday && userData.TimeZone + ? 'userPrompts.confirmChangeBirthday' + : 'userPrompts.confirmFirstBirthday'; + confirmationEmbedChoice += suggest ? 'Suggest' : ''; + + confirmationEmbed = Lang.getEmbed(confirmationEmbedChoice, LangCode.EN_US, { + TARGET: target.toString(), + BIRTHDAY: `${FormatUtils.getMonth(month)} ${day}`, + TIMEZONE: timeZone, + TARGET_FOOTER: target.username, + CHANGES_LEFT: `${changesLeft}`, + ICON: msg.client.user.avatarURL(), + }); let confirmationMessage = await MessageUtils.send(channel, confirmationEmbed); // Send confirmation and emotes for (let option of trueFalseOptions) { @@ -378,25 +333,24 @@ export class SetCommand implements Command { if (confirmation === Config.emotes.confirm) { // Confirm - await this.userRepo.addOrUpdateUser(target.id, birthday, timeZone, changesLeft - 1); // Add or update user - let embed = new MessageEmbed() - .setDescription( - `Successfully set your birthday to **${FormatUtils.getMonth( - month - )} ${day}, ${timeZone}**` - ) - .setFooter(`You now have ${changesLeft - 1} birthday attempts left.`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('results.setBirthday', LangCode.EN_US, { + BIRTHDAY: `${FormatUtils.getMonth(month)} ${day}`, + TIMEZONE: timeZone, + AMOUNT: `${changesLeft - 1}`, + ICON: msg.client.user.avatarURL(), + }) + ); return; } else if (confirmation === Config.emotes.deny) { // Cancel - let embed = new MessageEmbed() - .setDescription('Your birthday has not been set.') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('results.actionCanceled', LangCode.EN_US) + ); return; } } diff --git a/src/commands/settings-command.ts b/src/commands/settings-command.ts index 339a374d..e4f0f623 100644 --- a/src/commands/settings-command.ts +++ b/src/commands/settings-command.ts @@ -1,11 +1,10 @@ -import { Message, MessageEmbed, Role, TextChannel } from 'discord.js'; +import { FormatUtils, MessageUtils } from '../utils'; +import { GuildRepo, TrustedRoleRepo } from '../services/database/repos'; +import { LangCode, Language } from '../models/enums'; +import { Message, TextChannel } from 'discord.js'; -import { ColorUtils } from '../utils/color-utils'; import { Command } from './command'; -import { GuildRepo } from '../services/database/repos'; -import { MessageUtils } from '../utils'; - -let Config = require('../../config/config.json'); +import { Lang } from '../services'; export class SettingsCommand implements Command { public name: string = 'settings'; @@ -18,7 +17,7 @@ export class SettingsCommand implements Command { public requirePremium = false; public getPremium = true; - constructor(private guildRepo: GuildRepo) {} + constructor(private guildRepo: GuildRepo, private trustedRoleRepo: TrustedRoleRepo) { } async execute( args: string[], @@ -29,91 +28,175 @@ export class SettingsCommand implements Command { let guild = msg.guild; let guildData = await this.guildRepo.getGuild(guild.id); - let settingsEmbed = new MessageEmbed() - .setAuthor(`${guild.name}'s Settings`, guild.iconURL()) - .setFooter(`© ${new Date().getFullYear()} Birthday Bot`, msg.client.user.avatarURL()) - .setTimestamp(); - - let birthdayChannel: string; - let birthdayRole: string; - let mentionSetting = 'None'; - let messageTime: string; - let trustedRole: string; - let birthdayMasterRole: string; - let preventsRole = guildData.TrustedPreventsRole ? 'True' : 'False'; - let preventsMessage = guildData.TrustedPreventsMessage ? 'True' : 'False'; - let useEmbed = guildData.UseEmbed ? 'True' : 'False'; - - // Get Message Time - if (guildData.MessageTime === 0) messageTime = '12:00 AM'; - else if (guildData.MessageTime === 12) messageTime = '12:00 PM'; - else if (guildData.MessageTime < 12) messageTime = guildData.MessageTime + ':00 AM'; - else messageTime = guildData.MessageTime - 12 + ':00 PM'; - - // Find mentioned role - let roleInput: Role = guild.roles.resolve(guildData.MentionSetting); - - if (!roleInput || roleInput.guild.id !== msg.guild.id) { - if ( - guildData.MentionSetting.toLowerCase() === 'everyone' || - guildData.MentionSetting.toLowerCase() === 'here' - ) { - mentionSetting = '@' + guildData.MentionSetting; - } + let type = + args.length > 2 + ? FormatUtils.extractMiscActionType(args[2].toLowerCase())?.toLowerCase() ?? + 'general' + : 'general'; + + // split settings into general settings, message settings, advanced settings + // bday settings [option] + if (type === 'message') { + // message settings + let birthdayChannel: string; + let memberAnniversaryChannel: string; + let serverAnniversaryChannel: string; + let birthdayMessageTime: string; + let memberAnniversaryMessageTime: string; + let serverAnniversaryMessageTime: string; + let birthdayMentionSetting: string; + let memberAnniversaryMentionSetting: string; + let serverAnniversaryMentionSetting: string; + birthdayChannel = + guildData.BirthdayChannelDiscordId === '0' + ? Lang.getRef('terms.notSet', LangCode.EN_US) + : guild.channels.resolve(guildData.BirthdayChannelDiscordId)?.toString() || + `**${Lang.getRef('terms.deletedChannel', LangCode.EN_US)}**`; + memberAnniversaryChannel = + guildData.MemberAnniversaryChannelDiscordId === '0' + ? Lang.getRef('terms.notSet', LangCode.EN_US) + : guild.channels + .resolve(guildData.MemberAnniversaryChannelDiscordId) + ?.toString() || + `**${Lang.getRef('terms.deletedChannel', LangCode.EN_US)}**`; + serverAnniversaryChannel = + guildData.ServerAnniversaryChannelDiscordId === '0' + ? Lang.getRef('terms.notSet', LangCode.EN_US) + : guild.channels + .resolve(guildData.ServerAnniversaryChannelDiscordId) + ?.toString() || + `**${Lang.getRef('terms.deletedChannel', LangCode.EN_US)}**`; + + // Get our mention settings + birthdayMentionSetting = FormatUtils.getMentionSetting( + guildData.BirthdayMentionSetting, + guild + ); + if (birthdayMentionSetting === 'none') + birthdayMentionSetting = Lang.getRef('terms.notSet', LangCode.EN_US); + memberAnniversaryMentionSetting = FormatUtils.getMentionSetting( + guildData.MemberAnniversaryMentionSetting, + guild + ); + if (memberAnniversaryMentionSetting === 'none') + memberAnniversaryMentionSetting = Lang.getRef('terms.notSet', LangCode.EN_US); + serverAnniversaryMentionSetting = FormatUtils.getMentionSetting( + guildData.ServerAnniversaryMentionSetting, + guild + ); + if (serverAnniversaryMentionSetting === 'none') + serverAnniversaryMentionSetting = Lang.getRef('terms.notSet', LangCode.EN_US); + + // Get Message Time + birthdayMessageTime = FormatUtils.getMessageTime(guildData.BirthdayMessageTime); + memberAnniversaryMessageTime = FormatUtils.getMessageTime( + guildData.MemberAnniversaryMessageTime + ); + serverAnniversaryMessageTime = FormatUtils.getMessageTime( + guildData.ServerAnniversaryMessageTime + ); + + await MessageUtils.send( + channel, + Lang.getEmbed('info.settingsMessage', LangCode.EN_US, { + SERVER_NAME: guild.name, + BIRTHDAY_CHANNEL: birthdayChannel, + BIRTHDAY_MESSAGE_TIME: birthdayMessageTime, + BIRTHDAY_MENTION: birthdayMentionSetting, + MEMBER_ANNIVERSARY_CHANNEL: memberAnniversaryChannel, + MEMBER_ANNIVERSARY_MENTION: memberAnniversaryMentionSetting, + MEMBER_ANNIVERSARY_MESSAGE_TIME: memberAnniversaryMessageTime, + SERVER_ANNIVERSARY_CHANNEL: serverAnniversaryChannel, + SERVER_ANNIVERSARY_MENTION: serverAnniversaryMentionSetting, + SERVER_ANNIVERSARY_MESSAGE_TIME: serverAnniversaryMessageTime, + GUILD_ID: guild.id, + HAS_PREMIUM: Lang.getRef( + 'terms.' + (hasPremium ? 'active' : 'notActive'), + LangCode.EN_US + ), + DATE: new Date().getFullYear().toString(), + ICON: msg.client.user.avatarURL(), + }) + ); + } else if (type === 'advanced') { + // advanced settings + let birthdayMasterRole: string; + let preventsRole = Lang.getRef( + 'boolean.' + (guildData.TrustedPreventsRole ? 'true' : 'false'), + LangCode.EN_US + ); + let preventsMessage = Lang.getRef( + 'boolean.' + (guildData.TrustedPreventsMessage ? 'true' : 'false'), + LangCode.EN_US + ); + let requireAllTrustedRoles = Lang.getRef( + 'boolean.' + (guildData.RequireAllTrustedRoles ? 'true' : 'false'), + LangCode.EN_US + ); + let useTimezone = Lang.getRef('terms.' + guildData.UseTimezone, LangCode.EN_US); + birthdayMasterRole = + guildData.BirthdayMasterRoleDiscordId === '0' + ? Lang.getRef('terms.notSet', LangCode.EN_US) + : guild.roles.resolve(guildData.BirthdayMasterRoleDiscordId)?.toString() || + `**${Lang.getRef('terms.deletedRole', LangCode.EN_US)}**`; + + let trustedRoleCount = + (await this.trustedRoleRepo.getTrustedRoles(msg.guild.id))?.trustedRoles.length ?? + 0; + + await MessageUtils.send( + channel, + Lang.getEmbed('info.settingsAdvanced', LangCode.EN_US, { + SERVER_NAME: guild.name, + BIRTHDAY_MASTER_ROLE: birthdayMasterRole, + TRUSTED_PREVENTS_ROLE: preventsRole, + TRUSTED_PREVENTS_MESSAGE: preventsMessage, + REQUIRE_ALL_TRUSTED_ROLES: requireAllTrustedRoles, + TRUSTED_ROLE_COUNT: trustedRoleCount.toString(), + USE_TIMEZONE: useTimezone, + GUILD_ID: guild.id, + HAS_PREMIUM: Lang.getRef( + 'terms.' + (hasPremium ? 'active' : 'notActive'), + LangCode.EN_US + ), + DATE: new Date().getFullYear().toString(), + ICON: msg.client.user.avatarURL(), + }) + ); } else { - mentionSetting = roleInput.toString(); + // general settings + let birthdayRole: string; + birthdayRole = + guildData.BirthdayRoleDiscordId === '0' + ? Lang.getRef('terms.notSet', LangCode.EN_US) + : guild.roles.resolve(guildData.BirthdayRoleDiscordId)?.toString() || + `**${Lang.getRef('terms.deletedRole', LangCode.EN_US)}**`; + + let nameFormat = + guildData.NameFormat.charAt(0).toUpperCase() + guildData.NameFormat.slice(1); + let defaultTimezone = + guildData.DefaultTimezone === '0' + ? Lang.getRef('terms.notSet', LangCode.EN_US) + : guildData.DefaultTimezone; + let serverLanguage = Language.displayName(LangCode.EN_US); + + await MessageUtils.send( + channel, + Lang.getEmbed('info.settingsGeneral', LangCode.EN_US, { + SERVER_NAME: guild.name, + BIRTHDAY_ROLE: birthdayRole, + NAME_FORMAT: nameFormat, + DEFAULT_TIMEZONE: defaultTimezone, + SERVER_LANGUAGE: serverLanguage, + GUILD_ID: guild.id, + HAS_PREMIUM: Lang.getRef( + 'terms.' + (hasPremium ? 'active' : 'notActive'), + LangCode.EN_US + ), + DATE: new Date().getFullYear().toString(), + ICON: msg.client.user.avatarURL(), + }) + ); } - - birthdayChannel = - guildData.BirthdayChannelDiscordId === '0' - ? 'Not Set' - : guild.channels.resolve(guildData.BirthdayChannelDiscordId)?.toString() || - '**Deleted Channel**'; - birthdayRole = - guildData.BirthdayRoleDiscordId === '0' - ? 'Not Set' - : guild.roles.resolve(guildData.BirthdayRoleDiscordId)?.toString() || - '**Deleted Role**'; - trustedRole = - guildData.TrustedRoleDiscordId === '0' - ? 'Not Set' - : guild.roles.resolve(guildData.TrustedRoleDiscordId)?.toString() || - '**Deleted Role**'; - birthdayMasterRole = - guildData.BirthdayMasterRoleDiscordId === '0' - ? 'Not Set' - : guild.roles.resolve(guildData.BirthdayMasterRoleDiscordId)?.toString() || - '**Deleted Role**'; - - let colorHex = guildData.MessageEmbedColor === '0' ? Config.colors.default : null; - - colorHex = !colorHex - ? '#' + ColorUtils.findHex(guildData.MessageEmbedColor) ?? Config.colors.default - : Config.colors.default; - - let colorName = ColorUtils.findName(colorHex); - - settingsEmbed - .setColor(hasPremium ? colorHex : Config.colors.default) - .addField('Birthday Channel', birthdayChannel, true) - .addField('Birthday Role', birthdayRole, true) - .addField('Birthday Master Role', birthdayMasterRole, true) - .addField('Mention Setting', mentionSetting, true) - .addField('Message Time', messageTime, true) - .addField('Trusted Role', trustedRole, true) - .addField('Trusted Prevents Role', preventsRole, true) - .addField('Trusted Prevents Message', preventsMessage, true) - .addField('Embed Birthday Message', useEmbed, true) - .addField( - 'Birthday Message Color', - hasPremium || colorHex === Config.colors.default - ? `${colorName ? `${colorHex} (${colorName})` : colorHex}` - : `~~${colorName ? `${colorHex} (${colorName})` : colorHex}~~`, - true - ) - .addField('Guild Id', guild.id, true) - .addField('Premium', hasPremium ? 'Active' : 'Not Active', true); - - await MessageUtils.send(channel, settingsEmbed); } } diff --git a/src/commands/setup-command.ts b/src/commands/setup-command.ts index 11e79481..3673ff77 100644 --- a/src/commands/setup-command.ts +++ b/src/commands/setup-command.ts @@ -1,11 +1,12 @@ -import { Message, MessageEmbed, TextChannel } from 'discord.js'; -import { MessageUtils, PermissionUtils } from '../utils'; -import { SetupMessage, SetupRequired, SetupTrusted } from './setup'; +import { Message, TextChannel } from 'discord.js'; +import { MessageUtils, PermissionUtils, FormatUtils } from '../utils'; +import { SetupRequired, SetupTrusted } from './setup'; import { Command } from '.'; import { GuildRepo } from '../services/database/repos'; - -let Config = require('../../config/config.json'); +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { SetupAnniversary } from './setup/setup-anniversary'; export class SetupCommand implements Command { public name: string = 'setup'; @@ -16,25 +17,27 @@ export class SetupCommand implements Command { public ownerOnly = false; public voteOnly = false; public requirePremium = false; - public getPremium = false; + public getPremium = true; constructor( private guildRepo: GuildRepo, private setupRequired: SetupRequired, - private setupMessage: SetupMessage, - private setupTrusted: SetupTrusted - ) {} + private setupTrusted: SetupTrusted, + private setupAnniversary: SetupAnniversary + ) { } - public async execute(args: string[], msg: Message, channel: TextChannel) { + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { // Check for permissions if (!PermissionUtils.canReact(channel)) { - let embed = new MessageEmbed() - .setTitle('Missing Permissions!') - .setDescription( - 'I need permission to **Add Reactions** and **Read Message History** in this channel!' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.needReactAndMessageHistoryPerms', LangCode.EN_US) + ); return; } @@ -44,13 +47,10 @@ export class SetupCommand implements Command { !msg.guild.me.hasPermission('MANAGE_CHANNELS') || !msg.guild.me.hasPermission('MANAGE_ROLES') ) { - let embed = new MessageEmbed() - .setTitle('Missing Permissions!') - .setDescription( - 'I need permission to **Manage Channels** and **Manage Roles**!' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.needManageChannelsAndRolesPerm', LangCode.EN_US) + ); return; } @@ -61,39 +61,35 @@ export class SetupCommand implements Command { // Required setup is needed to run any specific setup let guildData = await this.guildRepo.getGuild(msg.guild.id); if (!guildData) { - let embed = new MessageEmbed() - .setTitle('Setup Required!') - .setDescription('You must run `bday setup` before using this command!') - .setColor(Config.colors.error); - - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.setupRequired', LangCode.EN_US) + ); return; } + let type = FormatUtils.extractMiscActionType(args[2].toLowerCase())?.toLowerCase() ?? ''; + // Run the appropriate setup - switch (args[2].toLowerCase()) { - case 'message': - await this.setupMessage.execute(args, msg, channel); + switch (type) { + case 'anniversary': + await this.setupAnniversary.execute(args, msg, channel); return; case 'trusted': if (!msg.guild.me.hasPermission('MANAGE_ROLES')) { - let embed = new MessageEmbed() - .setTitle('Not Enough Permissions!') - .setDescription('The bot must have permission to manage roles!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.needManageRolesPerm', LangCode.EN_US) + ); return; } - await this.setupTrusted.execute(args, msg, channel); + await this.setupTrusted.execute(args, msg, channel, hasPremium); return; default: - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription( - `Please specify which setup you'd like to run!\n\nSetup options: \`message\` or \`trusted\`` - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidSetupArgs', LangCode.EN_US) + ); return; } } diff --git a/src/commands/setup/index.ts b/src/commands/setup/index.ts index 959f16b2..353e544c 100644 --- a/src/commands/setup/index.ts +++ b/src/commands/setup/index.ts @@ -1,3 +1,3 @@ export { SetupRequired } from './setup-required'; export { SetupTrusted } from './setup-trusted'; -export { SetupMessage } from './setup-message'; +export { SetupAnniversary } from './setup-anniversary'; diff --git a/src/commands/setup/setup-anniversary.ts b/src/commands/setup/setup-anniversary.ts new file mode 100644 index 00000000..02ec4bc5 --- /dev/null +++ b/src/commands/setup/setup-anniversary.ts @@ -0,0 +1,337 @@ +import { + CollectOptions, + CollectorUtils, + ExpireFunction, + MessageFilter, +} from 'discord.js-collector-utils'; +import { Message, MessageReaction, TextChannel, User } from 'discord.js'; +import { MessageUtils, PermissionUtils } from '../../utils'; + +import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; + +let Config = require('../../../config/config.json'); + +const COLLECT_OPTIONS: CollectOptions = { + time: Config.experience.promptExpireTime * 1000, + reset: true, +}; + +export class SetupAnniversary { + constructor(private guildRepo: GuildRepo) {} + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + let guild = channel.guild; + // let botUser = guild.client.user; + let stopFilter: MessageFilter = (nextMsg: Message) => + nextMsg.author.id === msg.author.id && + [Config.prefix, ...Config.stopCommands].includes( + nextMsg.content.split(/\s+/)[0].toLowerCase() + ); + let expireFunction: ExpireFunction = async () => { + await MessageUtils.reply(msg, Lang.getEmbed('results.promptExpired', LangCode.EN_US)); + }; + + let memberAnniversaryChannel: string; + let serverAnniversaryChannel: string; + + // Member Anniversary Channel Setup + + let memberAnniversaryChannelEmbed = Lang.getEmbed( + 'serverPrompts.anniversarySetupMemberChannel', + LangCode.EN_US, + { + ICON: msg.client.user.avatarURL(), + } + ); + + let reactOptions = [Config.emotes.create, Config.emotes.select, Config.emotes.deny]; + + let memberAnniversaryChannelMessage = await MessageUtils.send( + channel, + memberAnniversaryChannelEmbed + ); + for (let reactOption of reactOptions) { + await MessageUtils.react(memberAnniversaryChannelMessage, reactOption); + } + + let memberAnniversaryChannelOption: string = await CollectorUtils.collectByReaction( + memberAnniversaryChannelMessage, + // Collect Filter + (msgReaction: MessageReaction, reactor: User) => + reactor.id === msg.author.id && reactOptions.includes(msgReaction.emoji.name), + stopFilter, + // Retrieve Result + async (msgReaction: MessageReaction, reactor: User) => { + return msgReaction.emoji.name; + }, + expireFunction, + COLLECT_OPTIONS + ); + + await MessageUtils.delete(memberAnniversaryChannelMessage); + + if (memberAnniversaryChannelOption === undefined) return; + + switch (memberAnniversaryChannelOption) { + case Config.emotes.create: { + // Create channel with desired attributes + memberAnniversaryChannel = ( + await guild.channels.create( + Lang.getRef('defaults.memberAnniversaryChannelName', LangCode.EN_US), + { + type: 'text', + topic: Lang.getRef( + 'defaults.memberAnniversaryChannelTopic', + LangCode.EN_US + ), + permissionOverwrites: [ + { + id: guild.id, + deny: ['SEND_MESSAGES'], + allow: ['VIEW_CHANNEL'], + }, + { + id: guild.me.roles.cache.filter(role => role.managed).first(), + allow: [ + 'VIEW_CHANNEL', + 'SEND_MESSAGES', + 'EMBED_LINKS', + 'ADD_REACTIONS', + 'READ_MESSAGE_HISTORY', + ], + }, + ], + } + ) + )?.id; + break; + } + case Config.emotes.select: { + let selectMessage = await MessageUtils.send( + channel, + Lang.getEmbed('serverPrompts.inputChannel', LangCode.EN_US) + ); + + memberAnniversaryChannel = await CollectorUtils.collectByMessage( + msg.channel, + // Collect Filter + (nextMsg: Message) => nextMsg.author.id === msg.author.id, + stopFilter, + // Retrieve Result + async (nextMsg: Message) => { + // Find mentioned channel + let channelInput: TextChannel = nextMsg.mentions.channels.first(); + + if (!channelInput) { + channelInput = guild.channels.cache + .filter(channel => channel instanceof TextChannel) + .map(channel => channel as TextChannel) + .find(channel => + channel.name + .toLowerCase() + .includes(nextMsg.content.toLowerCase()) + ); + } + + if (!channelInput) { + MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidChannel', LangCode.EN_US) + ); + return; + } + + // Bot needs to be able to message in the desired channel + if (!PermissionUtils.canSend(channelInput)) { + MessageUtils.send( + channel, + Lang.getEmbed('validation.noAccessToChannel', LangCode.EN_US, { + CHANNEL: channelInput.toString(), + }) + ); + return; + } + return channelInput?.id; + }, + expireFunction, + COLLECT_OPTIONS + ); + + MessageUtils.delete(selectMessage); + + if (memberAnniversaryChannel === undefined) { + return; + } + break; + } + case Config.emotes.deny: { + memberAnniversaryChannel = '0'; + break; + } + } + + // Sever Anniversary Channel Setup + + let serverAnniversaryChannelEmbed = Lang.getEmbed( + 'serverPrompts.anniversarySetupServerChannel', + LangCode.EN_US, + { + ICON: msg.client.user.avatarURL(), + } + ); + + let serverAnniversaryChannelMessage = await MessageUtils.send( + channel, + serverAnniversaryChannelEmbed + ); + for (let reactOption of reactOptions) { + await MessageUtils.react(serverAnniversaryChannelMessage, reactOption); + } + + let serverAnniversaryChannelOption: string = await CollectorUtils.collectByReaction( + serverAnniversaryChannelMessage, + // Collect Filter + (msgReaction: MessageReaction, reactor: User) => + reactor.id === msg.author.id && reactOptions.includes(msgReaction.emoji.name), + stopFilter, + // Retrieve Result + async (msgReaction: MessageReaction, reactor: User) => { + return msgReaction.emoji.name; + }, + expireFunction, + COLLECT_OPTIONS + ); + + await MessageUtils.delete(serverAnniversaryChannelMessage); + + if (serverAnniversaryChannelOption === undefined) return; + + switch (serverAnniversaryChannelOption) { + case Config.emotes.create: { + // Create channel with desired attributes + serverAnniversaryChannel = ( + await guild.channels.create( + Lang.getRef('defaults.serverAnniversaryChannelName', LangCode.EN_US), + { + type: 'text', + topic: Lang.getRef( + 'defaults.serverAnniversaryChannelTopic', + LangCode.EN_US + ), + permissionOverwrites: [ + { + id: guild.id, + deny: ['SEND_MESSAGES'], + allow: ['VIEW_CHANNEL'], + }, + { + id: guild.me.roles.cache.filter(role => role.managed).first(), + allow: [ + 'VIEW_CHANNEL', + 'SEND_MESSAGES', + 'EMBED_LINKS', + 'ADD_REACTIONS', + 'READ_MESSAGE_HISTORY', + ], + }, + ], + } + ) + )?.id; + break; + } + case Config.emotes.select: { + let selectMessage = await MessageUtils.send( + channel, + Lang.getEmbed('serverPrompts.inputChannel', LangCode.EN_US) + ); + + serverAnniversaryChannel = await CollectorUtils.collectByMessage( + msg.channel, + // Collect Filter + (nextMsg: Message) => nextMsg.author.id === msg.author.id, + stopFilter, + // Retrieve Result + async (nextMsg: Message) => { + // Find mentioned channel + let channelInput: TextChannel = nextMsg.mentions.channels.first(); + + if (!channelInput) { + channelInput = guild.channels.cache + .filter(channel => channel instanceof TextChannel) + .map(channel => channel as TextChannel) + .find(channel => + channel.name + .toLowerCase() + .includes(nextMsg.content.toLowerCase()) + ); + } + + if (!channelInput) { + MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidChannel', LangCode.EN_US) + ); + return; + } + + // Bot needs to be able to message in the desired channel + if (!PermissionUtils.canSend(channelInput)) { + MessageUtils.send( + channel, + Lang.getEmbed('validation.noAccessToChannel', LangCode.EN_US, { + CHANNEL: channelInput.toString(), + }) + ); + return; + } + return channelInput?.id; + }, + expireFunction, + COLLECT_OPTIONS + ); + + MessageUtils.delete(selectMessage); + + if (serverAnniversaryChannel === undefined) { + return; + } + break; + } + case Config.emotes.deny: { + serverAnniversaryChannel = '0'; + break; + } + } + + // Output + + let memberAnniversaryChannelOutput = + memberAnniversaryChannel === '0' + ? `${Lang.getRef('terms.notSet', LangCode.EN_US)}` + : guild.channels.resolve(memberAnniversaryChannel)?.toString() || + `**${Lang.getRef('terms.unknownChannel', LangCode.EN_US)}**`; + let serverAnniversaryChannelOutput = + serverAnniversaryChannel === '0' + ? `${Lang.getRef('terms.notSet', LangCode.EN_US)}` + : guild.channels.resolve(serverAnniversaryChannel)?.toString() || + `**${Lang.getRef('terms.unknownChannel', LangCode.EN_US)}**`; + + await MessageUtils.send( + channel, + Lang.getEmbed('results.anniversarySetup', LangCode.EN_US, { + MEMBER_CHANNEL: memberAnniversaryChannelOutput, + SERVER_CHANNEL: serverAnniversaryChannelOutput, + ICON: msg.client.user.avatarURL(), + }) + ); + + await this.guildRepo.guildSetupAnniversary( + guild.id, + memberAnniversaryChannel, + serverAnniversaryChannel + ); + } +} diff --git a/src/commands/setup/setup-message.ts b/src/commands/setup/setup-message.ts deleted file mode 100644 index 3ae0b5e9..00000000 --- a/src/commands/setup/setup-message.ts +++ /dev/null @@ -1,249 +0,0 @@ -import { ActionUtils, MessageUtils } from '../../utils'; -import { - CollectOptions, - CollectorUtils, - ExpireFunction, - MessageFilter, -} from 'discord.js-collector-utils'; -import { Message, MessageEmbed, MessageReaction, Role, TextChannel, User } from 'discord.js'; - -import { GuildRepo } from '../../services/database/repos'; - -let Config = require('../../../config/config.json'); - -const COLLECT_OPTIONS: CollectOptions = { - time: Config.experience.promptExpireTime * 1000, - reset: true, -}; - -export class SetupMessage { - constructor(private guildRepo: GuildRepo) {} - - public async execute(args: string[], msg: Message, channel: TextChannel) { - let guild = channel.guild; - let botUser = guild.client.user; - let stopFilter: MessageFilter = (nextMsg: Message) => - nextMsg.author.id === msg.author.id && - [Config.prefix, ...Config.stopCommands].includes( - nextMsg.content.split(/\s+/)[0].toLowerCase() - ); - let expireFunction: ExpireFunction = async () => { - await MessageUtils.send( - channel, - new MessageEmbed() - .setTitle('Message Setup - Expired') - .setDescription('Type `bday setup message` to rerun the setup.') - .setColor(Config.colors.error) - ); - }; - - let messageTime: number; - let mention: string; - let useEmbed: number; - - let messageTimeEmbed = new MessageEmbed() - .setAuthor(`${guild.name}`, guild.iconURL()) - .setTitle('Message Setup - Birthday Message Time') - .setDescription( - `For help, view the message setup guide [here](${Config.links.docs}/server-setup/message-setup)!` + - `\n\nPlease give the hour for your Birthday Messages [(?)](${Config.links.docs}/faq#what-is-the-birthday-message-time)` + - '\n\nAccepted Values: `0-23`\nDefault Value: `0`' + - '\n\n**Example Usage**: `13` (1PM)' - ) - .setFooter(`This message expires in 2 minutes!`, botUser.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); - - let timeMessage = await MessageUtils.send(channel, messageTimeEmbed); - - messageTime = await CollectorUtils.collectByMessage( - msg.channel, - // Collect Filter - (nextMsg: Message) => nextMsg.author.id === msg.author.id, - stopFilter, - // Retrieve Result - async (nextMsg: Message) => { - if (!messageTime && messageTime !== 0) { - // Try and get the time - let time: number; - try { - time = parseInt(nextMsg.content.split(/\s+/)[0]); - } catch (error) { - let embed = new MessageEmbed() - .setTitle('Message Setup - Message Time') - .setDescription('Invalid time!') - .setFooter(`Please check above and try again!`, botUser.avatarURL()) - .setTimestamp() - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - if (time !== 0 && (time < 0 || time > 23 || !time)) { - let embed = new MessageEmbed() - .setTitle('Message Setup - Message Time') - .setDescription('Invalid time!') - .setFooter(`Please check above and try again!`, botUser.avatarURL()) - .setTimestamp() - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - return time; - } - }, - expireFunction, - COLLECT_OPTIONS - ); - - MessageUtils.delete(timeMessage); - - if (messageTime === undefined) { - return; - } - - let messageMentionEmbed = new MessageEmbed() - .setAuthor(`${guild.name}`, guild.iconURL()) - .setTitle('Message Setup - Birthday Message Mention') - .setDescription( - `Now you can set your birthday message mention! [(?)](${Config.links.docs}/faq#what-is-the-birthday-message-mention)` + - '\n\nAcceptable inputs: `everyone`, `here`, `@role/role-name`, or `none`' + - '\n\nDefault Value: `none`' - ) - .setFooter(`This message expires in 2 minutes!`, botUser.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); - - let mentionMessage = await MessageUtils.send(channel, messageMentionEmbed); - - mention = await CollectorUtils.collectByMessage( - msg.channel, - // Collect Filter - (nextMsg: Message) => nextMsg.author.id === msg.author.id, - stopFilter, - // Retrieve Result - async (nextMsg: Message) => { - // Find mentioned role - let roleInput: Role = nextMsg.mentions.roles.first(); - - if (!roleInput) { - roleInput = guild.roles.cache.find(role => - role.name.toLowerCase().includes(nextMsg?.content.toLowerCase()) - ); - } - - if (!roleInput || roleInput.guild.id !== guild.id) { - // If there is no roles then check for other accepted values - let roleOptions = ['everyone', 'here', '@here', 'none']; - if (!roleOptions.includes(nextMsg?.content.toLowerCase())) { - let embed = new MessageEmbed() - .setTitle('Message Setup - Birthday Message Mention') - .setDescription('Could not find the group or role!') - .setFooter(`Please check above and try again!`, botUser.avatarURL()) - .setTimestamp() - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } else { - if (nextMsg?.content.toLowerCase() === '@here') { - // Support for the @here input - mention = `here`; - } else { - mention = nextMsg?.content.toLowerCase(); // Else it is either here, everyone, or none - } - } - } else { - mention = roleInput?.id; // If roleInput does exists then get the role Id - } - return mention; - }, - expireFunction, - COLLECT_OPTIONS - ); - - MessageUtils.delete(mentionMessage); - - if (mention === undefined) { - return; - } - - let embedMessage = new MessageEmbed() - .setAuthor(`${guild.name}`, guild.iconURL()) - .setTitle('Message Setup - Embed Birthday Message') - .setDescription( - `Now you can choose if the Birthday Message should be embedded or not! [(?)](${Config.links.docs}/faq#what-is-an-embed)` + - `\n\nDisable this if you use a image/gif in your Custom Birthday Message(s). [(?)](${Config.links.docs}/faq#what-is-a-custom-birthday-message)` + - `\n\nEnabled: ${Config.emotes.confirm}` + - `\nDisabled: ${Config.emotes.deny}` - ) - .setFooter(`This message expires in 2 minutes!`, botUser.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); - - let reactOptions = [Config.emotes.confirm, Config.emotes.deny]; - - let optionMessage = await MessageUtils.send(channel, embedMessage); - for (let reactOption of reactOptions) { - await MessageUtils.react(optionMessage, reactOption); - } - - let messageOption: string = await CollectorUtils.collectByReaction( - optionMessage, - // Collect Filter - (msgReaction: MessageReaction, reactor: User) => - reactor.id === msg.author.id && reactOptions.includes(msgReaction.emoji.name), - stopFilter, - // Retrieve Result - async (msgReaction: MessageReaction, reactor: User) => { - return msgReaction.emoji.name; - }, - expireFunction, - COLLECT_OPTIONS - ); - - MessageUtils.delete(optionMessage); - - if (messageOption === undefined) return; - - useEmbed = messageOption === Config.emotes.confirm ? 1 : 0; - let timeOutput: string; - if (messageTime === 0) timeOutput = '12:00 AM'; - else if (messageTime === 12) timeOutput = '12:00 PM'; - else if (messageTime < 12) timeOutput = messageTime + ':00 AM'; - else timeOutput = messageTime - 12 + ':00 PM'; - - let mentionOutput: string; - - // Find mentioned role - let roleInput: Role = guild.roles.resolve(mention); - - if (!roleInput || roleInput.guild.id !== guild.id) { - if (mention.toLowerCase() === 'everyone' || mention.toLowerCase() === 'here') { - mentionOutput = '@' + mention; - } else if (mention.toLowerCase() === 'none') { - mentionOutput = '`None`'; - } - } else { - mentionOutput = roleInput.toString(); - } - - let embed = new MessageEmbed() - .setAuthor(`${guild.name}`, guild.iconURL()) - .setTitle('Message Setup - Completed') - .setDescription( - 'You have successfully completed the server message setup!' + - `\n\n**Birthday Message Time**: \`${timeOutput}\`` + - `\n**Mention Setting**: ${mentionOutput}` + - `\n**Use Embed**: \`${useEmbed === 1 ? 'True' : 'False'}\`` - ) - .setFooter(`Message Setup Complete!`, botUser.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); - - if (mention === 'none') mention = '0'; - - await this.guildRepo.guildSetupMessage(guild.id, messageTime, mention, useEmbed); - - await MessageUtils.send(channel, embed); - } -} diff --git a/src/commands/setup/setup-required.ts b/src/commands/setup/setup-required.ts index a4059749..a075a8a0 100644 --- a/src/commands/setup/setup-required.ts +++ b/src/commands/setup/setup-required.ts @@ -1,13 +1,15 @@ -import { ActionUtils, MessageUtils, PermissionUtils } from '../../utils'; import { CollectOptions, CollectorUtils, ExpireFunction, MessageFilter, } from 'discord.js-collector-utils'; -import { Message, MessageEmbed, MessageReaction, Role, TextChannel, User } from 'discord.js'; +import { Message, MessageReaction, Role, TextChannel, User } from 'discord.js'; +import { MessageUtils, PermissionUtils } from '../../utils'; import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; let Config = require('../../../config/config.json'); @@ -19,7 +21,7 @@ const COLLECT_OPTIONS: CollectOptions = { export class SetupRequired { constructor(private guildRepo: GuildRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel) { + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { let guild = channel.guild; let botUser = guild.client.user; let stopFilter: MessageFilter = (nextMsg: Message) => @@ -28,34 +30,19 @@ export class SetupRequired { nextMsg.content.split(/\s+/)[0].toLowerCase() ); let expireFunction: ExpireFunction = async () => { - await MessageUtils.send( - channel, - new MessageEmbed() - .setTitle('Required Setup - Expired') - .setDescription('Type `bday setup` to rerun the setup.') - .setColor(Config.colors.error) - ); + await MessageUtils.reply(msg, Lang.getEmbed('results.promptExpired', LangCode.EN_US)); }; let birthdayChannel: string; let birthdayRole: string; - let channelEmbed = new MessageEmbed() - .setAuthor(`${guild.name}`, guild.iconURL()) - .setTitle('Server Setup - Birthday Channel') - .setDescription( - `For help, view the required setup guide [here](${Config.links.docs}/server-setup/required-setup)!` + - `\n\nTo begin you must set up the birthday channel [(?)](${Config.links.docs}/faq#why-does-birthday-bot-need-my-timezone)` + - '\n\nPlease select an option' - ) - .addField( - `Create New Channel ${Config.emotes.create}\nSelect Pre-Existing Channel ${Config.emotes.select}\nNo Birthday Channel ${Config.emotes.deny}`, - '\u200b' - ) - .setFooter(`This message expires in 2 minutes!`, botUser.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); - + let channelEmbed = Lang.getEmbed( + 'serverPrompts.requiredSetupBirthdayChannel', + LangCode.EN_US, + { + ICON: msg.client.user.avatarURL(), + } + ).setAuthor(`${guild.name}`, guild.iconURL()); let reactOptions = [Config.emotes.create, Config.emotes.select, Config.emotes.deny]; let channelMessage = await MessageUtils.send(channel, channelEmbed); @@ -85,29 +72,38 @@ export class SetupRequired { case Config.emotes.create: { // Create channel with desired attributes birthdayChannel = ( - await guild.channels.create(`${Config.emotes.birthday} birthdays`, { - type: 'text', - topic: 'Birthday Announcements!', - permissionOverwrites: [ - { - id: guild.id, - deny: ['SEND_MESSAGES'], - allow: ['VIEW_CHANNEL'], - }, - { - id: guild.me.roles.cache.filter(role => role.managed).first(), - allow: ['VIEW_CHANNEL', 'SEND_MESSAGES'], - }, - ], - }) + await guild.channels.create( + Lang.getRef('defaults.birthdayChannelName', LangCode.EN_US), + { + type: 'text', + topic: Lang.getRef('defaults.birthdayChannelTopic', LangCode.EN_US), + permissionOverwrites: [ + { + id: guild.id, + deny: ['SEND_MESSAGES'], + allow: ['VIEW_CHANNEL'], + }, + { + id: guild.me.roles.cache.filter(role => role.managed).first(), + allow: [ + 'VIEW_CHANNEL', + 'SEND_MESSAGES', + 'EMBED_LINKS', + 'ADD_REACTIONS', + 'READ_MESSAGE_HISTORY', + ], + }, + ], + } + ) )?.id; break; } case Config.emotes.select: { - let embed = new MessageEmbed() - .setDescription(`Please mention a channel or input a channel's name.`) - .setColor(Config.colors.default); - let selectMessage = await MessageUtils.send(channel, embed); + let selectMessage = await MessageUtils.send( + channel, + Lang.getEmbed('serverPrompts.inputChannel', LangCode.EN_US) + ); birthdayChannel = await CollectorUtils.collectByMessage( msg.channel, @@ -131,23 +127,21 @@ export class SetupRequired { } if (!channelInput) { - let embed = new MessageEmbed() - .setDescription('Invalid channel!') - .setFooter('Please try again.') - .setColor(Config.colors.error); - - MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidChannel', LangCode.EN_US) + ); return; } // Bot needs to be able to message in the desired channel if (!PermissionUtils.canSend(channelInput)) { - let embed = new MessageEmbed() - .setDescription( - `I don't have permission to send messages in ${channelInput.toString()}!` - ) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noAccessToChannel', LangCode.EN_US, { + CHANNEL: channelInput.toString(), + }) + ); return; } return channelInput?.id; @@ -169,21 +163,9 @@ export class SetupRequired { } } - let roleEmbed = new MessageEmbed() - .setAuthor(`${guild.name}`, guild.iconURL()) - .setTitle('Server Setup - Birthday Role') - .setDescription( - `Now, set up the birthday role [(?)](${Config.links.docs}/faq#what-is-the-birthday-role)` + - `\nNote: The Birthday Role is actively removed from those whose birthday it isn't. [(?)](${Config.links.docs}/faq#what-does-it-mean-by-the-birthday-role-is-actively-removed)` + - '\n\nPlease select an option' - ) - .addField( - `Create New Role ${Config.emotes.create}\nSelect Pre-Existing Role ${Config.emotes.select}\nNo Birthday Role ${Config.emotes.deny}`, - '\u200b' - ) - .setFooter(`This message expires in 2 minutes!`, botUser.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); + let roleEmbed = Lang.getEmbed('serverPrompts.requiredSetupBirthdayRole', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }).setAuthor(`${guild.name}`, guild.iconURL()); let roleMessage = await MessageUtils.send(channel, roleEmbed); for (let reactOption of reactOptions) { @@ -224,10 +206,10 @@ export class SetupRequired { break; } case Config.emotes.select: { - let embed = new MessageEmbed() - .setDescription(`Please mention a role or input a role's name.`) - .setColor(Config.colors.default); - let selectMessage = await MessageUtils.send(channel, embed); + let selectMessage = await MessageUtils.send( + channel, + Lang.getEmbed('serverPrompts.inputRole', LangCode.EN_US) + ); birthdayRole = await CollectorUtils.collectByMessage( msg.channel, @@ -253,11 +235,10 @@ export class SetupRequired { roleInput.id === guild.id || nextMsg?.content.toLowerCase() === 'everyone' ) { - let embed = new MessageEmbed() - .setDescription(`Invalid role!`) - .setFooter('Please try again.') - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); + MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidRole', LangCode.EN_US) + ); return; } @@ -266,52 +247,48 @@ export class SetupRequired { roleInput.position > guild.members.resolve(botUser).roles.highest.position ) { - let embed = new MessageEmbed() - .setDescription(`Birthday Role must be bellow the Bot's role!`) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); + await MessageUtils.send( + msg.channel as TextChannel, + Lang.getEmbed('validation.roleHierarchyError', LangCode.EN_US, { + BOT: msg.client.user.toString(), + ICON: msg.client.user.avatarURL(), + }) + ); return; } // Check if the role is managed if (roleInput.managed) { - let embed = new MessageEmbed() - .setDescription( - `Birthday Role cannot be managed by an external service!` - ) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); + MessageUtils.send( + channel, + Lang.getEmbed('validation.birthdayRoleManaged', LangCode.EN_US) + ); return; } let membersWithRole = roleInput.members.size; if (membersWithRole > 0 && membersWithRole < 100) { - let embed = new MessageEmbed() - .setTitle('Warning') - .setDescription( - `We have detected that __**${membersWithRole}**__ user${ - membersWithRole > 1 ? 's' : '' - } already have that role!\nThe Birthday Role should ONLY be the role that users GET on their birthday!` - ) - .setFooter( - `The Bot removes the Birthday Role from anyone whose birthday it isn't!`, - msg.client.user.avatarURL() + await MessageUtils.send( + channel, + Lang.getEmbed( + 'validation.birthdayRoleUsedWarning', + LangCode.EN_US, + { + AMOUNT: membersWithRole.toString(), + S_Value: membersWithRole > 1 ? 's' : '', + ICON: msg.client.user.avatarURL(), + } ) - .setColor(Config.colors.warning); - MessageUtils.send(channel, embed); + ); } else if (membersWithRole > 100) { - let embed = new MessageEmbed() - .setTitle('Error') - .setDescription( - `We have detected that __**${membersWithRole}**__ users already have that role!\nThe Birthday Role should ONLY be the role that users GET on their birthday!` - ) - .setFooter( - `The Bot removes the Birthday Role from anyone whose birthday it isn't!`, - msg.client.user.avatarURL() - ) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.birthdayRoleUsedError', LangCode.EN_US, { + AMOUNT: membersWithRole.toString(), + ICON: msg.client.user.avatarURL(), + }) + ); return; } @@ -336,26 +313,23 @@ export class SetupRequired { let channelOutput = birthdayChannel === '0' - ? 'Not Set' - : guild.channels.resolve(birthdayChannel)?.toString() || '**Unknown**'; + ? `${Lang.getRef('terms.notSet', LangCode.EN_US)}` + : guild.channels.resolve(birthdayChannel)?.toString() || + `**${Lang.getRef('terms.unknownChannel', LangCode.EN_US)}**`; let roleOutput = birthdayRole === '0' - ? 'Not Set' - : guild.roles.resolve(birthdayRole)?.toString() || '**Unknown**'; - - let embed = new MessageEmbed() - .setAuthor(`${guild.name}`, guild.iconURL()) - .setTitle('Server Setup - Completed') - .setDescription( - 'You have successfully completed the required server setup!' + - `\n\n**Birthday Channel**: ${channelOutput}` + - `\n**Birthday Role**: ${roleOutput}` - ) - .setFooter(`All server commands unlocked!`, botUser.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); - - await MessageUtils.send(channel, embed); + ? `${Lang.getRef('terms.notSet', LangCode.EN_US)}` + : guild.roles.resolve(birthdayRole)?.toString() || + `**${Lang.getRef('terms.unknownRole', LangCode.EN_US)}**`; + + await MessageUtils.send( + channel, + Lang.getEmbed('results.requiredSetup', LangCode.EN_US, { + CHANNEL: channelOutput, + ROLE: roleOutput, + ICON: msg.client.user.avatarURL(), + }) + ); await this.guildRepo.addOrUpdateGuild(guild.id, birthdayChannel, birthdayRole); } diff --git a/src/commands/setup/setup-trusted.ts b/src/commands/setup/setup-trusted.ts index 35fb3df4..c896bf1f 100644 --- a/src/commands/setup/setup-trusted.ts +++ b/src/commands/setup/setup-trusted.ts @@ -1,13 +1,15 @@ -import { ActionUtils, MessageUtils } from '../../utils'; import { CollectOptions, CollectorUtils, ExpireFunction, MessageFilter, } from 'discord.js-collector-utils'; -import { Message, MessageEmbed, MessageReaction, Role, TextChannel, User } from 'discord.js'; +import { Message, MessageReaction, TextChannel, User } from 'discord.js'; import { GuildRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MessageUtils } from '../../utils'; let Config = require('../../../config/config.json'); @@ -19,160 +21,44 @@ const COLLECT_OPTIONS: CollectOptions = { export class SetupTrusted { constructor(private guildRepo: GuildRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel) { + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { let guild = channel.guild; - let botUser = guild.client.user; + // let botUser = guild.client.user; let stopFilter: MessageFilter = (nextMsg: Message) => nextMsg.author.id === msg.author.id && [Config.prefix, ...Config.stopCommands].includes( nextMsg.content.split(/\s+/)[0].toLowerCase() ); let expireFunction: ExpireFunction = async () => { - await MessageUtils.send( - channel, - new MessageEmbed() - .setTitle('Trusted Setup - Expired') - .setDescription('Type `bday setup trusted` to rerun the setup.') - .setColor(Config.colors.error) - ); + await MessageUtils.reply(msg, Lang.getEmbed('results.promptExpired', LangCode.EN_US)); }; - let trustedRole: string; let preventMessage: number; let preventRole: number; + let requireAllTrustedRoles: number = 1; - // Create/Select/No Trusted Role - - let roleEmbed = new MessageEmbed() - .setAuthor(`${guild.name}`, guild.iconURL()) - .setTitle('Trusted Setup - Trusted Role') - .setDescription( - `For help, view the trusted setup guide [here](${Config.links.docs}/server-setup/trusted-setup)!` + - `\n\nTo begin you must select the Trusted Role [(?)](${Config.links.docs}/faq#do-i-need-to-set-up-the-trusted-role)` + - '\n\nPlease select an option' - ) - .addField( - `Create New Role ${Config.emotes.create}\nSelect Pre-Existing Role ${Config.emotes.select}\nNo Trusted Role ${Config.emotes.deny}`, - '\u200b' - ) - .setFooter(`This message expires in 2 minutes!`, botUser.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); - - let reactOptions = [Config.emotes.create, Config.emotes.select, Config.emotes.deny]; - - let roleMessage = await MessageUtils.send(channel, roleEmbed); - for (let reactOption of reactOptions) { - await MessageUtils.react(roleMessage, reactOption); - } - - let roleOptions: string = await CollectorUtils.collectByReaction( - roleMessage, - // Collect Filter - (msgReaction: MessageReaction, reactor: User) => - reactor.id === msg.author.id && reactOptions.includes(msgReaction.emoji.name), - stopFilter, - // Retrieve Result - async (msgReaction: MessageReaction, reactor: User) => { - return msgReaction.emoji.name; - }, - expireFunction, - COLLECT_OPTIONS - ); - - MessageUtils.delete(roleMessage); - - if (roleOptions === undefined) return; - - switch (roleOptions) { - case Config.emotes.create: { - // Create role with desired attributes - trustedRole = ( - await guild.roles.create({ - data: { - name: 'BirthdayTrusted', - }, - }) - )?.id; - break; - } - case Config.emotes.select: { - let embed = new MessageEmbed() - .setDescription(`Please mention a role or input a role's name.`) - .setColor(Config.colors.default); - let selectMessage = await MessageUtils.send(channel, embed); - - trustedRole = await CollectorUtils.collectByMessage( - msg.channel, - // Collect Filter - (nextMsg: Message) => nextMsg.author.id === msg.author.id, - // Stop Filter - stopFilter, - // Retrieve Result - async (nextMsg: Message) => { - // Find mentioned role - let roleInput: Role = nextMsg.mentions.roles.first(); - - // Search guild for role - if (!roleInput) { - roleInput = guild.roles.cache.find(role => - role.name.toLowerCase().includes(nextMsg?.content.toLowerCase()) - ); - } - - // If it couldn't find the role, the role was in another guild, or the role the everyone role - if ( - !roleInput || - roleInput.id === guild.id || - nextMsg?.content.toLowerCase() === 'everyone' - ) { - let embed = new MessageEmbed() - .setDescription(`Invalid role!`) - .setFooter('Please try again.') - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); - return; - } - return roleInput?.id; - }, - expireFunction, - COLLECT_OPTIONS - ); - - MessageUtils.delete(selectMessage); - - if (trustedRole === undefined) { - return; - } - break; - } - case Config.emotes.deny: { - trustedRole = '0'; - break; + let trustedPreventsRoleEmbed = Lang.getEmbed( + 'serverPrompts.trustedSetupPreventsRole', + LangCode.EN_US, + { + ICON: msg.client.user.avatarURL(), } - } - - let preventMessageEmbed = new MessageEmbed() - .setAuthor(`${guild.name}`, guild.iconURL()) - .setTitle('Trusted Setup - Trusted Prevents Message') - .setDescription( - `Should the trusted role prevent the birthday message? [(?)](${Config.links.docs}/faq#what-is-the-trusted-prevents-message-role)` + - `\n\nTrue: ${Config.emotes.confirm}` + - `\nFalse: ${Config.emotes.deny}` - ) - .setFooter(`This message expires in 2 minutes!`, botUser.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); + ); let trueFalseOptions = [Config.emotes.confirm, Config.emotes.deny]; - let settingMessage = await MessageUtils.send(channel, preventMessageEmbed); // Send confirmation and emotes + let trustedPreventsRoleMessage = await MessageUtils.send(channel, trustedPreventsRoleEmbed); // Send confirmation and emotes for (let option of trueFalseOptions) { - await MessageUtils.react(settingMessage, option); + await MessageUtils.react(trustedPreventsRoleMessage, option); } - let messageOption: string = await CollectorUtils.collectByReaction( - settingMessage, + let trustedPreventsRoleOptions: string = await CollectorUtils.collectByReaction( + trustedPreventsRoleMessage, // Collect Filter (msgReaction: MessageReaction, reactor: User) => reactor.id === msg.author.id && trueFalseOptions.includes(msgReaction.emoji.name), @@ -185,31 +71,28 @@ export class SetupTrusted { COLLECT_OPTIONS ); - MessageUtils.delete(settingMessage); + await MessageUtils.delete(trustedPreventsRoleMessage); - if (roleOptions === undefined) return; + if (trustedPreventsRoleOptions === undefined) return; - preventMessage = messageOption === Config.emotes.confirm ? 1 : 0; - - let preventRoleEmbed = new MessageEmbed() - .setAuthor(`${guild.name}`, guild.iconURL()) - .setTitle('Trusted Setup - Trusted Prevents Role') - .setDescription( - `Should the trusted role prevent the birthday role? [(?)](${Config.links.docs}/faq#what-is-the-trusted-prevents-message-role)` + - `\n\nTrue: ${Config.emotes.confirm}` + - `\nFalse: ${Config.emotes.deny}` - ) - .setFooter(`This message expires in 2 minutes!`, botUser.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); + let trustedPreventsMessageEmbed = Lang.getEmbed( + 'serverPrompts.trustedSetupPreventsMessage', + LangCode.EN_US, + { + ICON: msg.client.user.avatarURL(), + } + ); - let settingRole = await MessageUtils.send(channel, preventRoleEmbed); // Send confirmation and emotes + let trustedPreventsMessageMessage = await MessageUtils.send( + channel, + trustedPreventsMessageEmbed + ); // Send confirmation and emotes for (let option of trueFalseOptions) { - await settingRole.react(option); + await trustedPreventsMessageMessage.react(option); } - let roleSettingOptions: string = await CollectorUtils.collectByReaction( - settingRole, + let trustedPreventsMessageOptions: string = await CollectorUtils.collectByReaction( + trustedPreventsMessageMessage, // Collect Filter (msgReaction: MessageReaction, reactor: User) => reactor.id === msg.author.id && trueFalseOptions.includes(msgReaction.emoji.name), @@ -222,32 +105,73 @@ export class SetupTrusted { COLLECT_OPTIONS ); - MessageUtils.delete(settingRole); + await MessageUtils.delete(trustedPreventsMessageMessage); - if (roleOptions === undefined) return; + if (trustedPreventsMessageOptions === undefined) return; - preventRole = roleSettingOptions === Config.emotes.confirm ? 1 : 0; + if (hasPremium) { + let requireAllTrustedRolesEmbed = Lang.getEmbed( + 'serverPrompts.trustedSetupRequireAll', + LangCode.EN_US, + { + ICON: msg.client.user.avatarURL(), + } + ); - let roleOutput = - trustedRole === '0' - ? 'Not Set' - : guild.roles.resolve(trustedRole)?.toString() || '**Unknown**'; + let requireAllTrustedRolesMessage = await MessageUtils.send( + channel, + requireAllTrustedRolesEmbed + ); // Send confirmation and emotes + for (let option of trueFalseOptions) { + await requireAllTrustedRolesMessage.react(option); + } + + let requireAllTrustedRolesOptions: string = await CollectorUtils.collectByReaction( + requireAllTrustedRolesMessage, + // Collect Filter + (msgReaction: MessageReaction, reactor: User) => + reactor.id === msg.author.id && + trueFalseOptions.includes(msgReaction.emoji.name), + stopFilter, + // Retrieve Result + async (msgReaction: MessageReaction, reactor: User) => { + return msgReaction.emoji.name; + }, + expireFunction, + COLLECT_OPTIONS + ); + + await MessageUtils.delete(requireAllTrustedRolesMessage); - let embed = new MessageEmbed() - .setAuthor(`${guild.name}`, guild.iconURL()) - .setTitle('Trusted Setup - Completed') - .setDescription( - 'You have successfully completed the trusted server setup!' + - `\n\n**Trusted Role**: ${roleOutput}` + - `\n**Trusted Prevents Role**: \`${preventRole === 1 ? 'True' : 'False'}\`` + - `\n**Trusted Prevents Message**: \`${preventMessage === 1 ? 'True' : 'False'}\`` - ) - .setFooter(`Trusted Setup Complete!`, botUser.avatarURL()) - .setColor(Config.colors.default) - .setTimestamp(); + if (requireAllTrustedRolesOptions === undefined) return; + + requireAllTrustedRoles = + requireAllTrustedRolesOptions === Config.emotes.confirm ? 1 : 0; + } + + preventRole = trustedPreventsMessageOptions === Config.emotes.confirm ? 1 : 0; + preventMessage = trustedPreventsRoleOptions === Config.emotes.confirm ? 1 : 0; + + let embed = hasPremium + ? Lang.getEmbed('results.trustedSetupPremium', LangCode.EN_US, { + PREVENTS_ROLE: preventRole === 1 ? 'True' : 'False', + PREVENTS_MESSAGE: preventMessage === 1 ? 'True' : 'False', + REQUIRE_ALL_ROLES: requireAllTrustedRoles === 1 ? 'True' : 'False', + ICON: msg.client.user.avatarURL(), + }) + : Lang.getEmbed('results.trustedSetup', LangCode.EN_US, { + PREVENTS_ROLE: preventRole === 1 ? 'True' : 'False', + PREVENTS_MESSAGE: preventMessage === 1 ? 'True' : 'False', + ICON: msg.client.user.avatarURL(), + }); await MessageUtils.send(channel, embed); - await this.guildRepo.guildSetupTrusted(guild.id, trustedRole, preventMessage, preventRole); + await this.guildRepo.guildSetupTrusted( + guild.id, + requireAllTrustedRoles, + preventMessage, + preventRole + ); } } diff --git a/src/commands/stats-command.ts b/src/commands/stats-command.ts index 3d2a9a9f..73f29b2b 100644 --- a/src/commands/stats-command.ts +++ b/src/commands/stats-command.ts @@ -1,12 +1,12 @@ +import { DMChannel, Message, TextChannel } from 'discord.js'; import { MessageUtils, ShardUtils } from '../utils'; -import djs, { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; import { UserRepo } from '../services/database/repos'; import moment from 'moment'; -let Config = require('../../config/config.json'); - export class StatsCommand implements Command { public name: string = 'stats'; public aliases = ['stat', 'statistics', 'info', 'information', 'data']; @@ -20,7 +20,11 @@ export class StatsCommand implements Command { constructor(private userRepo: UserRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { let today = moment().format('MM-DD'); let month = moment().format('MM'); let totalBirthdays = await this.userRepo.getUserCount(); @@ -42,18 +46,17 @@ export class StatsCommand implements Command { let shardId = msg.guild?.shardID || 0; - let embed = new MessageEmbed() - .setColor(Config.colors.default) - .setThumbnail(msg.client.user.displayAvatarURL({ dynamic: true })) - .setAuthor(msg.author.tag, msg.author.displayAvatarURL({ dynamic: true })) - .addField('Total Birthdays', totalBirthdays.toLocaleString(), true) - .addField('Total Servers', serverCount.toLocaleString(), true) - .addField('Shard ID', `${shardId + 1}/${msg.client.shard.count}`, true) - .addField('Birthdays Today', birthdaysToday.toLocaleString(), true) - .addField('Birthdays This Month', birthdaysThisMonth.toLocaleString(), true) - .addField('Node.js', process.version, true) - .addField('discord.js', `v${djs.version}`, true); - - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('info.stats', LangCode.EN_US, { + TOTAL_BIRTHDAYS: totalBirthdays.toLocaleString(), + TOTAL_SERVERS: serverCount.toLocaleString(), + SHARD_ID: `${shardId + 1}/${msg.client.shard.count}`, + BIRTHDAYS_TODAY: birthdaysToday.toLocaleString(), + BIRTHDAYS_THIS_MONTH: birthdaysThisMonth.toLocaleString(), + }) + .setThumbnail(msg.client.user.displayAvatarURL({ dynamic: true })) + .setAuthor(msg.author.tag, msg.author.displayAvatarURL({ dynamic: true })) + ); } } diff --git a/src/commands/subscribe-command.ts b/src/commands/subscribe-command.ts index 0bbb4561..6ee38a1d 100644 --- a/src/commands/subscribe-command.ts +++ b/src/commands/subscribe-command.ts @@ -1,7 +1,8 @@ -import { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; -import { Logger, SubscriptionService } from '../services'; +import { DMChannel, Message, TextChannel } from 'discord.js'; +import { Lang, Logger, SubscriptionService } from '../services'; import { Command } from './command'; +import { LangCode } from '../models/enums'; import { MessageUtils } from '../utils'; import { PlanName } from '../models/subscription-models'; @@ -21,26 +22,24 @@ export class SubscribeCommand implements Command { constructor(private subscriptionService: SubscriptionService) {} - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { if (!Config.payments.enabled) { - let embed = new MessageEmbed() - .setTitle('Birthday Bot Premium') - .setDescription( - `Premium subscriptions are currently disabled. Enjoy premium features for free! Woohoo!` - ) - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('premiumPrompts.premiumDisabled', LangCode.EN_US) + ); return; } if (!Config.payments.allowNewTransactions) { - let embed = new MessageEmbed() - .setTitle('Birthday Bot Premium') - .setDescription( - `New subscriptions are not being accepted at this time. Please try again later.\n\n[Join Support Server](${Config.links.support})` - ) - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('premiumPrompts.refuseNewTransactions', LangCode.EN_US) + ); return; } @@ -49,47 +48,32 @@ export class SubscribeCommand implements Command { msg.guild.id ); if (!subLink) { - let embed = new MessageEmbed() - .setAuthor(msg.guild.name, msg.guild.iconURL()) - .setTitle('Birthday Bot Premium') - .setDescription( - `This server already has an active premium subscription or one that is currently processing.\n\nYou cannot purchase premium for this server at this time. If you've recently checked out, you may need to wait for processing. If the subscription was recently cancelled you may need to wait until the paid time runs out before re-subscribing. As always, feel free to contact support at the link below with any questions.\n\n[Join Support Server](${Config.links.support})` - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('premiumPrompts.premiumAlreadyActive', LangCode.EN_US) + ); return; } - let messagesLimitFree = Config.validation.message.maxCount.free; - let messagesLimitPremium = Config.validation.message.maxCount.paid; - - let privateEmbed = new MessageEmbed() - .setAuthor(msg.guild.name, msg.guild.iconURL()) - .setTitle('Birthday Bot Premium') - .setDescription( - `Complete your servers subscription by checking out with PayPal at the link below.` - ) - .addField('Purchase Premium', `[Checkout with PayPal Here!](${subLink.link})`) - .addField( - 'Premium Features', - `- No voting needed for commands\n- Up to **${messagesLimitPremium.toLocaleString()}** custom birthday messages *(vs **${messagesLimitFree.toLocaleString()}** free)*\n- Access to user-specific custom birthday messages\n- Customize the color of the birthday message embed\n- Premium support\nFeatures apply **server-wide** (this server only).` - ) - .addField('Price', '$2.99 USD / Month') - .addField( - 'Purchase Details', - `Your servers subscription will activate within 5 minutes of checking out with PayPal. You may cancel at any time on your [PayPal Automatic Payments](${Config.links.autopay}) page. Any paid time after cancelling will still count as premium service. As always, feel free to contact support at the link below with any questions.\n\n[Join Support Server](${Config.links.support})` - ) - .setColor(Config.colors.default); - await MessageUtils.send(msg.author, privateEmbed); + await MessageUtils.send( + msg.author, + Lang.getEmbed('premiumPrompts.subscriptionPM', LangCode.EN_US, { + SUB_LINK: subLink.link, + BIRTHDAY_MESSAGE_MAX_FREE: Config.validation.message.maxCount.birthday.free.toLocaleString(), + BIRTHDAY_MESSAGE_MAX_PAID: Config.validation.message.maxCount.birthday.paid.free.toLocaleString(), + MEMBER_ANNIVERSARY_MESSAGE_MAX_FREE: Config.validation.message.maxCount.memberAnniversary.paid.free.toLocaleString(), + MEMBER_ANNIVERSARY_MESSAGE_MAX_PAID: Config.validation.message.maxCount.memberAnniversary.paid.free.toLocaleString(), + SERVER_ANNIVERSARY_MESSAGE_MAX_FREE: Config.validation.message.maxCount.serverAnniversary.paid.free.toLocaleString(), + SERVER_ANNIVERSARY_MESSAGE_MAX_PAID: Config.validation.message.maxCount.serverAnniversary.paid.free.toLocaleString(), + MAX_ANNIVERSARY_ROLES: Config.validation.trustedRoles.maxCount.paid.free.toLocaleString(), + MAX_TRUSTED_ROLES: Config.validation.maxCount.paid.free.toLocaleString(), + }) + ); - let embed = new MessageEmbed() - .setAuthor(msg.guild.name, msg.guild.iconURL()) - .setTitle('Birthday Bot Premium') - .setDescription( - 'I have private messaged you with a PayPal subscription link!\n\nIf you did not receive a message, please make sure you have direct messages enabled for this server (under this servers Privacy Settings) and run this command again.' - ) - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('premiumPrompts.subscriptionDMPrompt', LangCode.EN_US) + ); Logger.info( Logs.info.unsubRanSubCmd diff --git a/src/commands/support-command.ts b/src/commands/support-command.ts index f9d7b9b4..b3612cd3 100644 --- a/src/commands/support-command.ts +++ b/src/commands/support-command.ts @@ -1,10 +1,10 @@ -import { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; +import { DMChannel, Message, TextChannel } from 'discord.js'; import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; import { MessageUtils } from '../utils'; -let Config = require('../../config/config.json'); - export class SupportCommand implements Command { public name: string = 'support'; public aliases = ['supportserver', 'server']; @@ -16,11 +16,11 @@ export class SupportCommand implements Command { public requirePremium = false; public getPremium = false; - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { - let embed = new MessageEmbed() - .setDescription(`For support join our discord server [here](${Config.links.support})!`) - .setColor(Config.colors.default); - - await MessageUtils.send(channel, embed); + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + await MessageUtils.send(msg.channel, Lang.getEmbed('info.support', LangCode.EN_US)); } } diff --git a/src/commands/test-command.ts b/src/commands/test-command.ts index 63ecfc6a..a9bfd3aa 100644 --- a/src/commands/test-command.ts +++ b/src/commands/test-command.ts @@ -1,16 +1,30 @@ -import { BlacklistRepo, GuildRepo } from '../services/database/repos'; -import { GuildUtils, MessageUtils } from '../utils'; -import { Message, MessageEmbed, TextChannel, User } from 'discord.js'; +import { + ActionUtils, + CelebrationUtils, + FormatUtils, + GuildUtils, + MessageUtils, + ParseUtils, + PermissionUtils, +} from '../utils'; +import { + BlacklistRepo, + CustomMessageRepo, + GuildRepo, + MemberAnniversaryRoleRepo, + TrustedRoleRepo, + UserRepo, +} from '../services/database/repos'; +import { CustomMessage, UserData } from '../models/database'; +import { Message, MessageEmbed, Role, TextChannel, User } from 'discord.js'; -import { BirthdayService } from '../services'; import { Command } from './command'; -import { UserData } from '../models/database'; -import moment from 'moment'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { MemberAnniversaryRole } from '../models/database/member-anniversary-role-models'; let Config = require('../../config/config.json'); -const MOCK_TIME_ZONE: string = 'America/New_York'; - export class TestCommand implements Command { public name: string = 'test'; public aliases = ['tst']; @@ -20,85 +34,516 @@ export class TestCommand implements Command { public ownerOnly = false; public voteOnly = false; public requirePremium = false; - public getPremium = false; + public getPremium = true; constructor( - private birthdayService: BirthdayService, private guildRepo: GuildRepo, - private blacklistRepo: BlacklistRepo + private userRepo: UserRepo, + private customMessageRepo: CustomMessageRepo, + private trustedRoleRepo: TrustedRoleRepo, + private blacklistRepo: BlacklistRepo, + private memberAnniversaryRoleRepo: MemberAnniversaryRoleRepo ) {} - public async execute(args: string[], msg: Message, channel: TextChannel) { + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { + // bday test [user] [year] let guild = msg.guild; - let guildData = await this.guildRepo.getGuild(msg.guild.id); - // Mock message time to current hour - guildData.MessageTime = moment().tz(MOCK_TIME_ZONE).hour(); + if (args.length < 3) { + MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidCelebrationType', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + let type = FormatUtils.extractCelebrationType(args[2].toLowerCase()); + + if (!type) { + MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidCelebrationType', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } let target: User; + let year = 0; + let userData: UserData; - if (args.length === 3) { + if (args.length >= 4) { // Get who they are mentioning target = msg.mentions.members.first()?.user || - GuildUtils.findMember(msg.guild, args[2])?.user; + GuildUtils.findMember(msg.guild, args[3])?.user || + (args.length >= 5 && GuildUtils.findMember(msg.guild, args[4])?.user); // Did we find a user? if (!target) { - let embed = new MessageEmbed() - .setDescription('Could not find that user!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; + try { + year = ParseUtils.parseInt(args[3]); + } catch (error) { + // no year + } + } else { + if (args.length >= 5) { + try { + year = ParseUtils.parseInt(args[4]); + } catch (error) { + // no year + } + } } - } else { - // They didn't mention anyone - target = msg.client.user; + userData = await this.userRepo.getUser(target.id); + } + + // Default target to bot for the test + if (!target) target = msg.client.user; + + let guildData = await this.guildRepo.getGuild(msg.guild.id); + + let messageChannel: TextChannel; + + try { + messageChannel = guild.channels.resolve( + type === 'birthday' + ? guildData.BirthdayChannelDiscordId + : type === 'memberanniversary' + ? guildData.MemberAnniversaryChannelDiscordId + : guildData.ServerAnniversaryChannelDiscordId + ) as TextChannel; + } catch (error) { + // No birthday channel } - // Get the blacklist data for this guild - let blacklistData = await this.blacklistRepo.getBlacklist(guild.id); - - if (blacklistData.blacklist.map(data => data.UserDiscordId).includes(target.id)) { - let testingEmbed = new MessageEmbed() - .setThumbnail(guild.iconURL()) - .setTitle('Birthday Event Test - [BETA]') - .setDescription( - 'Below are the checks to ensure your settings are correct for the birthday event.\n\nIf the checks are passed and either the birthday message and/or birthday role were not given ' + - `when they should have then ${guild.client.user.toString()} most likely did not have the correct permissions. [(?)](${ - Config.links.docs - }/faq)\n\nFor more help: [Join Support Server](${Config.links.support})` + let customMessages: CustomMessage[]; + let mentionString = CelebrationUtils.getMentionString(guildData, guild, type); + + if (type === 'birthday') { + // run the birthday test + + // If a check is true, it "passes" (we are trying to pass all checks) + // example: blackListCheck false means the user was IN the blacklist + let roleCheck = false; + let messageCheck = PermissionUtils.canSend(messageChannel); + let trustedCheckMessage = false; + let trustedCheckRole = false; + let trustedPreventsMessage = guildData.TrustedPreventsMessage; + let trustedPreventsRole = guildData.TrustedPreventsRole; + let birthdayCheck = target.bot ? true : userData.Birthday; + let blacklistCheck = false; + + let message = Lang.getRef('defaults.birthdayMessage', LangCode.EN_US); + let color = Config.colors.default; + let useEmbed = true; + + // Get the blacklist data for this guild + let blacklistData = await this.blacklistRepo.getBlacklist(guild.id); + + blacklistCheck = !blacklistData.blacklist + .map(data => data.UserDiscordId) + .includes(target.id); + + // Get the birthday role for this guild + let birthdayRole: Role; + try { + birthdayRole = guild.roles.resolve(guildData.BirthdayRoleDiscordId) as Role; + } catch (error) { + // No Birthday Role + } + + // See if the bot can give the roles + let guildMember = guild.members.resolve(target); + roleCheck = CelebrationUtils.canGiveAllRoles(guild, [birthdayRole], guildMember); + + // Get the trusted roles for this guild using our celebration utils + let trustedRoles = await this.trustedRoleRepo.getTrustedRoles(guild.id); + let trustedRoleList: Role[] = await CelebrationUtils.getTrustedRoleList( + guild, + trustedRoles.trustedRoles + ); + + // Get our trusted checks for each using celebration utils + trustedCheckRole = CelebrationUtils.passesTrustedCheck( + guildData.RequireAllTrustedRoles, + trustedRoleList, + guildMember, + trustedPreventsRole, + hasPremium + ); + trustedCheckMessage = CelebrationUtils.passesTrustedCheck( + guildData.RequireAllTrustedRoles, + trustedRoleList, + guildMember, + trustedPreventsMessage, + hasPremium + ); + + // Check for user specific messages + if (target.bot) { + customMessages = ( + await this.customMessageRepo.getCustomUserMessages(guild.id, 'birthday') + ).customMessages.filter(message => message.UserDiscordId === target.id); + } + + // if we never looked for them or there were none to match this user then lets get regular custom birthday messages + if (!customMessages || customMessages.length === 0) + customMessages = ( + await this.customMessageRepo.getCustomMessages(guild.id, 'birthday') + ).customMessages; + + if (blacklistCheck && birthdayCheck) { + if (roleCheck && trustedCheckRole) { + await ActionUtils.giveRole(guildMember, birthdayRole); + } + + if (messageCheck && trustedCheckRole) { + let userList = CelebrationUtils.getUserListString(guildData, [guildMember]); + + if (customMessages.length > 0) { + // Get our custom message + let customMessage = CelebrationUtils.randomMessage( + customMessages, + hasPremium + ); + // Find the color of the embed + color = CelebrationUtils.getMessageColor(customMessage, hasPremium); + + useEmbed = customMessage.Embed ? true : false; + + message = customMessage.Message; + } + + // Replace the placeholders + message = CelebrationUtils.replacePlaceHolders( + message, + guild, + type, + userList, + null + ); + + // Send our message(s) + if (mentionString && mentionString !== '') + await MessageUtils.send(messageChannel, mentionString); + + let embed = new MessageEmbed().setDescription(message).setColor(color); + await MessageUtils.send(messageChannel, useEmbed ? embed : message); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Give Test Result Message + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + let testingEmbed = Lang.getEmbed('results.birthdayTest', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + .setColor(Config.colors.default) + .addField( + Lang.getRef('terms.birthdayChannel', LangCode.EN_US), + messageCheck + ? `${Config.emotes.confirm} ${Lang.getRef( + 'terms.correctlySet', + LangCode.EN_US + )}` + : `${Config.emotes.deny} ${Lang.getRef( + 'terms.notSetOrIncorrect', + LangCode.EN_US + )}`, + true ) - .setFooter( - 'This is the info from your latest birthday event test.', - guild.client.user.avatarURL() + .addField( + Lang.getRef('terms.birthdayRole', LangCode.EN_US), + roleCheck + ? `${Config.emotes.confirm} ${Lang.getRef( + 'terms.correctlySet', + LangCode.EN_US + )}` + : `${Config.emotes.deny} ${Lang.getRef( + 'terms.notSetOrIncorrect', + LangCode.EN_US + )}`, + true + ); + + if (blacklistData.blacklist.length > 0) { + testingEmbed.addField( + Lang.getRef('terms.memberInBlacklist', LangCode.EN_US), + trustedCheckMessage + ? `${Config.emotes.confirm} ${Lang.getRef( + 'terms.notInBlacklist', + LangCode.EN_US + )}` + : `${Config.emotes.deny} ${Lang.getRef( + 'terms.inBlacklist', + LangCode.EN_US + )}`, + true + ); + } + + if (trustedRoles.trustedRoles.length > 0) { + testingEmbed.addField( + Lang.getRef('terms.trustedPreventMsg', LangCode.EN_US), + trustedCheckMessage + ? `${Config.emotes.confirm} ${Lang.getRef( + 'terms.didntPreventMsg', + LangCode.EN_US + )}` + : `${Config.emotes.deny} ${Lang.getRef( + 'terms.didPreventMsg', + LangCode.EN_US + )}`, + true + ); + testingEmbed.addField( + Lang.getRef('terms.trustedPreventRole', LangCode.EN_US), + trustedCheckRole + ? `${Config.emotes.confirm} ${Lang.getRef( + 'terms.didntPreventRole', + LangCode.EN_US + )}` + : `${Config.emotes.deny} ${Lang.getRef( + 'terms.didPreventRole', + LangCode.EN_US + )}`, + true + ); + } + await MessageUtils.send(channel, testingEmbed); + return; + } else if (type === 'memberanniversary') { + // run the member anniversary test + + // If a check is true, it "passes" (we are trying to pass all checks) + // example: blackListCheck false means the user was IN the blacklist + let messageCheck = PermissionUtils.canSend(messageChannel); + let memberAnniversaryRolesCheck = false; + let memberAnniversaryRoles: MemberAnniversaryRole[]; + let anniversaryResolvedRoles: Role[]; + + let message = Lang.getRef('defaults.memberAnniversaryMessage', LangCode.EN_US); + let color = Config.colors.default; + let useEmbed = true; + let guildMember = guild.members.resolve(target); + + let timezoneCheck = guildData?.DefaultTimezone !== '0'; + + // Only premium guilds get anniversary roles + if (hasPremium) { + memberAnniversaryRoles = ( + await this.memberAnniversaryRoleRepo.getMemberAnniversaryRoles(guild.id) + ).memberAnniversaryRoles; + // Get our list of anniversary roles + anniversaryResolvedRoles = await CelebrationUtils.getMemberAnniversaryRoleList( + guild, + memberAnniversaryRoles + ); + + // Get the data of the roles we could resolve (we need the data so we can check years later!) + memberAnniversaryRoles = memberAnniversaryRoles.filter(data => + anniversaryResolvedRoles + .map(r => r.id) + .includes(data.MemberAnniversaryRoleDiscordId) + ); + + // See if the bot can give the roles + memberAnniversaryRolesCheck = CelebrationUtils.canGiveAllRoles( + guild, + anniversaryResolvedRoles, + guildMember + ); + } + + // Check for user specific messages + if (target.bot) { + customMessages = ( + await this.customMessageRepo.getCustomUserMessages( + guild.id, + 'memberanniversary' + ) + ).customMessages.filter(message => message.UserDiscordId === target.id); + } + + // if we never looked for them or there were none to match this user then lets get regular custom birthday messages + if (!customMessages || customMessages.length === 0) + customMessages = ( + await this.customMessageRepo.getCustomMessages(guild.id, 'memberanniversary') + ).customMessages; + + if (anniversaryResolvedRoles) { + for (let role of anniversaryResolvedRoles) { + let roleData = memberAnniversaryRoles.find( + data => data.MemberAnniversaryRoleDiscordId === role.id + ); + + if (roleData.Year === year) { + await ActionUtils.giveRole(guildMember, role); + } + } + } + if (messageCheck) { + let userList = CelebrationUtils.getUserListString(guildData, [guildMember]); + + if (customMessages.length > 0) { + // Get our custom message + let customMessage = CelebrationUtils.randomMessage(customMessages, hasPremium); + // Find the color of the embed + color = CelebrationUtils.getMessageColor(customMessage, hasPremium); + useEmbed = customMessage.Embed ? true : false; + + message = customMessage.Message; + } + + // Replace the placeholders + message = CelebrationUtils.replacePlaceHolders( + message, + guild, + type, + userList, + year === 0 ? 1 : year + ); + + // Send our message(s) + if (mentionString && mentionString !== '') + await MessageUtils.send(messageChannel, mentionString); + + let embed = new MessageEmbed().setDescription(message).setColor(color); + await MessageUtils.send(messageChannel, useEmbed ? embed : message); + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Give Test Result Message + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + let testingEmbed = Lang.getEmbed('results.memberAnniversaryTest', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + .addField( + Lang.getRef('terms.defaultTimezone', LangCode.EN_US), + timezoneCheck + ? `${Config.emotes.confirm} ${Lang.getRef( + 'terms.correctlySet', + LangCode.EN_US + )}` + : `${Config.emotes.deny} ${Lang.getRef('terms.notSet', LangCode.EN_US)}`, + true ) - .setTimestamp() - .setColor(Config.colors.default) .addField( - 'Birthday Blacklist', - `${Config.emotes.deny} Member is in the blacklist.`, + Lang.getRef('terms.memberAnniversaryChannel', LangCode.EN_US), + messageCheck + ? `${Config.emotes.confirm} ${Lang.getRef( + 'terms.correctlySet', + LangCode.EN_US + )}` + : `${Config.emotes.deny} ${Lang.getRef( + 'terms.notSetOrIncorrect', + LangCode.EN_US + )}`, + true + ); + + if (hasPremium && memberAnniversaryRoles.length > 0) { + testingEmbed.addField( + Lang.getRef('terms.memberAnniversaryRoles', LangCode.EN_US), + memberAnniversaryRolesCheck + ? `${Config.emotes.confirm} ${Lang.getRef( + 'terms.canBeGiven', + LangCode.EN_US + )}` + : `${Config.emotes.deny} ${Lang.getRef( + 'terms.cantBeGivenPermIssue', + LangCode.EN_US + )}`, true ); + } await MessageUtils.send(channel, testingEmbed); return; - } + } else { + // run the server anniversary test - // Mock user data for bot - let userData: UserData = { - Birthday: moment().toDate().toString(), - ChangesLeft: 5, - TimeZone: MOCK_TIME_ZONE, - UserDiscordId: target.id, - }; - - this.birthdayService.celebrateBirthdays( - guild, - guildData, - [userData], - guild.members.cache, - true, - channel - ); + // If a check is true, it "passes" (we are trying to pass all checks) + // example: blackListCheck false means the user was IN the blacklist + let messageCheck = PermissionUtils.canSend(messageChannel); + + let message = Lang.getRef('defaults.serverAnniversaryMessage', LangCode.EN_US); + let color = Config.colors.default; + let useEmbed = true; + + let timezoneCheck = guildData?.DefaultTimezone !== '0'; + + customMessages = ( + await this.customMessageRepo.getCustomMessages(guild.id, 'serveranniversary') + ).customMessages; + + if (messageCheck) { + if (customMessages.length > 0) { + // Get our custom message + let customMessage = CelebrationUtils.randomMessage(customMessages, hasPremium); + // Find the color of the embed + color = CelebrationUtils.getMessageColor(customMessage, hasPremium); + useEmbed = customMessage.Embed ? true : false; + + message = customMessage.Message; + } + + // Replace the placeholders + message = CelebrationUtils.replacePlaceHolders( + message, + guild, + type, + target.toString(), + year === 0 ? 1 : year + ); + + // Send our message(s) + if (mentionString && mentionString !== '') + await MessageUtils.send(messageChannel, mentionString); + + let embed = new MessageEmbed().setDescription(message).setColor(color); + await MessageUtils.send(messageChannel, useEmbed ? embed : message); + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Give Test Result Message + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + let testingEmbed = Lang.getEmbed('results.serverAnniversaryTest', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + .addField( + Lang.getRef('terms.defaultTimezone', LangCode.EN_US), + timezoneCheck + ? `${Config.emotes.confirm} ${Lang.getRef( + 'terms.correctlySet', + LangCode.EN_US + )}` + : `${Config.emotes.deny} ${Lang.getRef('terms.notSet', LangCode.EN_US)}`, + true + ) + .addField( + Lang.getRef('terms.serverAnniversaryChannel', LangCode.EN_US), + timezoneCheck + ? `${Config.emotes.confirm} ${Lang.getRef( + 'terms.correctlySet', + LangCode.EN_US + )}` + : `${Config.emotes.deny} ${Lang.getRef( + 'terms.notSetOrIncorrect', + LangCode.EN_US + )}`, + true + ); + await MessageUtils.send(channel, testingEmbed); + return; + } } } diff --git a/src/commands/trusted-command.ts b/src/commands/trusted-command.ts deleted file mode 100644 index 1aaff5cd..00000000 --- a/src/commands/trusted-command.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { FormatUtils, MessageUtils } from '../utils'; -import { Message, MessageEmbed, TextChannel } from 'discord.js'; - -import { Command } from './command'; -import { GuildRepo } from '../services/database/repos'; - -let Config = require('../../config/config.json'); - -export class TrustedCommand implements Command { - public name: string = 'trusted'; - public aliases = []; - public requireSetup = true; - public guildOnly = true; - public adminOnly = true; - public ownerOnly = false; - public voteOnly = false; - public requirePremium = false; - public getPremium = false; - - constructor(private guildRepo: GuildRepo) {} - - public async execute(args: string[], msg: Message, channel: TextChannel) { - if (args.length === 2) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription( - 'Please specify what to create!\nAccepted Values: `preventMessage ` or `preventRole `,' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - if (args[2].toLowerCase() === 'preventmsg' || args[2].toLowerCase() === 'preventmessage') { - // Do Stuff - if (args.length < 4) { - let embed = new MessageEmbed() - .setDescription('Please provide a value! (True/False)') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - let preventMessage = FormatUtils.findBoolean(args[3]); - - if (preventMessage === undefined || preventMessage === null) { - let embed = new MessageEmbed() - .setTitle('Invalid Value!') - .setDescription('Accepted Values: `True/False`') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - await this.guildRepo.updateTrustedPreventsMessage(msg.guild.id, preventMessage ? 1 : 0); - - let embed = new MessageEmbed() - .setDescription( - preventMessage - ? 'Trusted Role is now required for the birthday message!' - : 'Trusted Role is now not required for the birthday message!' - ) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } else if (args[2].toLowerCase() === 'preventrole') { - // Do Stuff - if (args.length < 4) { - let embed = new MessageEmbed() - .setDescription('Please provide a value! (True/False)') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - let preventRole = FormatUtils.findBoolean(args[3]); - - if (preventRole === undefined || preventRole === null) { - let embed = new MessageEmbed() - .setTitle('Invalid Value!') - .setDescription('Accepted Values: `True/False`') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - await this.guildRepo.updateTrustedPreventsRole(msg.guild.id, preventRole ? 1 : 0); - - let embed = new MessageEmbed() - .setDescription( - preventRole - ? 'Trusted Role is now required for the birthday role!' - : 'Trusted Role is now not required for the birthday role!' - ) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } - } -} diff --git a/src/commands/trusted-role-command.ts b/src/commands/trusted-role-command.ts new file mode 100644 index 00000000..9baeabca --- /dev/null +++ b/src/commands/trusted-role-command.ts @@ -0,0 +1,64 @@ +import { FormatUtils, MessageUtils } from '../utils'; +import { Message, TextChannel } from 'discord.js'; +import { + TrustedRoleAddSubCommand, + TrustedRoleClearSubCommand, + TrustedRoleListSubCommand, + TrustedRoleRemoveSubCommand, +} from './trusted'; + +import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; + +export class TrustedRoleCommand implements Command { + public name: string = 'trustedrole'; + public aliases = ['tr', 'trusted']; + public requireSetup = true; + public guildOnly = true; + public adminOnly = true; + public ownerOnly = false; + public voteOnly = false; + public requirePremium = false; + public getPremium = true; + + constructor( + private trustedRoleAddSubCommand: TrustedRoleAddSubCommand, + private trustedRoleRemoveSubCommand: TrustedRoleRemoveSubCommand, + private trustedRoleClearSubCommand: TrustedRoleClearSubCommand, + private trustedRoleListSubCommand: TrustedRoleListSubCommand + ) {} + + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { + if (args.length === 2) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noTrustedRoleArgs', LangCode.EN_US) + ); + return; + } + + let action = FormatUtils.extractMiscActionType(args[2].toLowerCase())?.toLowerCase() ?? ''; + + if (action === 'add') { + this.trustedRoleAddSubCommand.execute(args, msg, channel, hasPremium); + } else if (action === 'remove') { + this.trustedRoleRemoveSubCommand.execute(args, msg, channel); + } else if (action === 'clear') { + this.trustedRoleClearSubCommand.execute(args, msg, channel); + } else if (action === 'list') { + this.trustedRoleListSubCommand.execute(args, msg, channel, hasPremium); + } else { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noTrustedRoleArgs', LangCode.EN_US) + ); + return; + } + } +} diff --git a/src/commands/trusted/index.ts b/src/commands/trusted/index.ts new file mode 100644 index 00000000..986b3611 --- /dev/null +++ b/src/commands/trusted/index.ts @@ -0,0 +1,4 @@ +export { TrustedRoleAddSubCommand } from './trusted-role-add-sub-command'; +export { TrustedRoleRemoveSubCommand } from './trusted-role-remove-sub-command'; +export { TrustedRoleClearSubCommand } from './trusted-role-clear-sub-command'; +export { TrustedRoleListSubCommand } from './trusted-role-list-sub-command'; diff --git a/src/commands/trusted/trusted-role-add-sub-command.ts b/src/commands/trusted/trusted-role-add-sub-command.ts new file mode 100644 index 00000000..871fa040 --- /dev/null +++ b/src/commands/trusted/trusted-role-add-sub-command.ts @@ -0,0 +1,101 @@ +import { Message, Role, TextChannel } from 'discord.js'; + +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MessageUtils } from '../../utils'; +import { TrustedRoleRepo } from '../../services/database/repos'; + +let Config = require('../../../config/config.json'); + +const errorEmbed = Lang.getEmbed('validation.noTrustedRoleSpecified', LangCode.EN_US); + +export class TrustedRoleAddSubCommand { + constructor(private trustedRoleRepo: TrustedRoleRepo) { } + + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { + if (args.length === 3) { + await MessageUtils.send(channel, errorEmbed); + return; + } + // See if a role was specified + let trustedRole: Role = msg.mentions.roles.first(); + + if (!trustedRole) { + trustedRole = msg.guild.roles.cache.find( + role => + role.name.toLowerCase().includes(args[3].toLowerCase()) || + role.id === args[3].toLowerCase() + ); + } + + if ( + !trustedRole || + trustedRole.id === msg.guild.id || + args[3].toLowerCase() === 'everyone' + ) { + MessageUtils.send(channel, Lang.getEmbed('validation.invalidRole', LangCode.EN_US)); + return; + } + + if (trustedRole.managed) { + MessageUtils.send( + channel, + Lang.getEmbed('validation.trustedRoleManaged', LangCode.EN_US) + ); + return; + } + + let trustedRoles = await this.trustedRoleRepo.getTrustedRoles(msg.guild.id); + + if ( + trustedRoles && + trustedRoles.trustedRoles.length >= Config.validation.trustedRoles.maxCount.free && + !hasPremium + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.maxFreeTrustedRoles', LangCode.EN_US, { + FREE_MAX: Config.validation.trustedRoles.maxCount.free.toString(), + PAID_MAX: Config.validation.trustedRoles.maxCount.paid.toString(), + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } else if ( + trustedRoles && + trustedRoles.trustedRoles.length >= Config.validation.trustedRoles.maxCount.paid + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.maxPaidTrustedRoles', LangCode.EN_US, { + PAID_MAX: Config.validation.trustedRoles.maxCount.paid.toString(), + }) + ); + return; + } + + if (trustedRoles.trustedRoles.find(role => role.TrustedRoleDiscordId === trustedRole.id)) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.duplicateTrustedRole', LangCode.EN_US, { + ROLE: trustedRole.toString(), + }) + ); + return; + } + + await this.trustedRoleRepo.addTrustedRole(msg.guild.id, trustedRole?.id); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.addedTrustedRole', LangCode.EN_US, { + ROLE: trustedRole.toString(), + }) + ); + } +} diff --git a/src/commands/trusted/trusted-role-clear-sub-command.ts b/src/commands/trusted/trusted-role-clear-sub-command.ts new file mode 100644 index 00000000..fecbe734 --- /dev/null +++ b/src/commands/trusted/trusted-role-clear-sub-command.ts @@ -0,0 +1,89 @@ +import { + CollectOptions, + CollectorUtils, + ExpireFunction, + MessageFilter, +} from 'discord.js-collector-utils'; +import { Message, MessageReaction, TextChannel, User } from 'discord.js'; + +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { MessageUtils } from '../../utils'; +import { TrustedRoleRepo } from '../../services/database/repos'; + +let Config = require('../../../config/config.json'); + +const COLLECT_OPTIONS: CollectOptions = { + time: Config.experience.promptExpireTime * 1000, + reset: true, +}; + +export class TrustedRoleClearSubCommand { + constructor(private trustedRoleRepo: TrustedRoleRepo) {} + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + let stopFilter: MessageFilter = (nextMsg: Message) => + nextMsg.author.id === msg.author.id && + [Config.prefix, ...Config.stopCommands].includes( + nextMsg.content.split(/\s+/)[0].toLowerCase() + ); + let expireFunction: ExpireFunction = async () => { + await MessageUtils.reply(msg, Lang.getEmbed('results.promptExpired', LangCode.EN_US)); + }; + + let trustedRoles = await this.trustedRoleRepo.getTrustedRoles(msg.guild.id); + + if (trustedRoles.trustedRoles.length === 0) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noTrustedRoles', LangCode.EN_US) + ); + return; + } + + let trueFalseOptions = [Config.emotes.confirm, Config.emotes.deny]; + + let confirmationMessage = await MessageUtils.send( + channel, + Lang.getEmbed('serverPrompts.trustedRoleClearConfirmation', LangCode.EN_US, { + TOTAL: trustedRoles.trustedRoles.length.toString(), + ICON: msg.client.user.avatarURL(), + }) + ); // Send confirmation and emotes + for (let option of trueFalseOptions) { + await MessageUtils.react(confirmationMessage, option); + } + + let confirmation: string = await CollectorUtils.collectByReaction( + confirmationMessage, + // Collect Filter + (msgReaction: MessageReaction, reactor: User) => + reactor.id === msg.author.id && trueFalseOptions.includes(msgReaction.emoji.name), + stopFilter, + // Retrieve Result + async (msgReaction: MessageReaction, reactor: User) => { + return msgReaction.emoji.name; + }, + expireFunction, + COLLECT_OPTIONS + ); + + MessageUtils.delete(confirmationMessage); + + if (confirmation === undefined) return; + + if (confirmation === Config.emotes.confirm) { + // Confirm + await this.trustedRoleRepo.clearTrustedRoles(msg.guild.id); + await MessageUtils.send( + channel, + Lang.getEmbed('results.clearedTrustedRole', LangCode.EN_US) + ); + } else { + await MessageUtils.send( + channel, + Lang.getEmbed('results.actionCanceled', LangCode.EN_US) + ); + } + } +} diff --git a/src/commands/message/message-user-list-sub-command.ts b/src/commands/trusted/trusted-role-list-sub-command.ts similarity index 50% rename from src/commands/message/message-user-list-sub-command.ts rename to src/commands/trusted/trusted-role-list-sub-command.ts index a62ef212..573c3a5f 100644 --- a/src/commands/message/message-user-list-sub-command.ts +++ b/src/commands/trusted/trusted-role-list-sub-command.ts @@ -1,39 +1,41 @@ import { FormatUtils, MessageUtils, ParseUtils } from '../../utils'; import { Message, TextChannel } from 'discord.js'; -import { CustomMessageRepo } from '../../services/database/repos'; +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { TrustedRoleRepo } from '../../services/database/repos'; let Config = require('../../../config/config.json'); -export class MessageUserListSubCommand { - constructor(private customMessageRepo: CustomMessageRepo) {} +export class TrustedRoleListSubCommand { + constructor(private trustedRoleRepo: TrustedRoleRepo) {} - public async execute(args: string[], msg: Message, channel: TextChannel, hasPremium: boolean) { + public async execute( + args: string[], + msg: Message, + channel: TextChannel, + hasPremium: boolean + ): Promise { let page = 1; if (args[3]) { - try { - page = ParseUtils.parseInt(args[4]); - } catch (error) { - // Not A Number - } + page = ParseUtils.parseInt(args[4]); if (!page || page <= 0 || page > 100000) page = 1; } let pageSize = Config.experience.birthdayMessageListSize; - let customMessageResults = await this.customMessageRepo.getCustomMessageUserList( + let trustedRoleResults = await this.trustedRoleRepo.getTrustedRoleList( msg.guild.id, pageSize, page ); - if (page > customMessageResults.stats.TotalPages) - page = customMessageResults.stats.TotalPages; + if (page > trustedRoleResults.stats.TotalPages) page = trustedRoleResults.stats.TotalPages; - let embed = await FormatUtils.getCustomUserMessageListEmbed( + let embed = await FormatUtils.getTrustedRoleList( msg.guild, - customMessageResults, + trustedRoleResults, page, pageSize, hasPremium @@ -41,7 +43,7 @@ export class MessageUserListSubCommand { let message = await MessageUtils.send(channel, embed); - if (embed.description === '**No Custom Birthday Messages!**') return; + if (embed.description === Lang.getRef('list.noTrustedRoles', LangCode.EN_US)) return; await MessageUtils.react(message, Config.emotes.previousPage); await MessageUtils.react(message, Config.emotes.jumpToPage); diff --git a/src/commands/trusted/trusted-role-remove-sub-command.ts b/src/commands/trusted/trusted-role-remove-sub-command.ts new file mode 100644 index 00000000..19f1dd0d --- /dev/null +++ b/src/commands/trusted/trusted-role-remove-sub-command.ts @@ -0,0 +1,90 @@ +import { Message, Role, TextChannel } from 'discord.js'; +import { MessageUtils, ParseUtils } from '../../utils'; + +import { Lang } from '../../services'; +import { LangCode } from '../../models/enums'; +import { TrustedRole } from '../../models/database'; +import { TrustedRoleRepo } from '../../services/database/repos'; + +const errorEmbed = Lang.getEmbed('validation.trustedRoleNoRoleOrPosition', LangCode.EN_US); + +export class TrustedRoleRemoveSubCommand { + constructor(private trustedRoleRepo: TrustedRoleRepo) {} + + public async execute(args: string[], msg: Message, channel: TextChannel): Promise { + // See if a role was specified + let trustedRole: Role = msg.mentions.roles.first(); + let position: number; + + if (args.length <= 3) { + await MessageUtils.send(channel, errorEmbed); + return; + } + + if (!trustedRole) { + trustedRole = msg.guild.roles.cache.find( + role => + role.name.toLowerCase().includes(args[3].toLowerCase()) || + role.id === args[3].toLowerCase() + ); + } + + if ( + trustedRole && + (trustedRole.id === msg.guild.id || args[3].toLowerCase() === 'everyone') + ) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidRole', LangCode.EN_US) + ); + return; + } + + let trustedRoles = await this.trustedRoleRepo.getTrustedRoles(msg.guild.id); + + if (trustedRole) { + let role = trustedRoles.trustedRoles.filter( + r => r.TrustedRoleDiscordId === trustedRole.id + ); + + if (role.length > 0) position = role[0].Position; + } + + if (!position) { + position = ParseUtils.parseInt(args[3]); + } + + if (!position) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidTrustedRole', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + let role = trustedRoles.trustedRoles.find(r => r.Position === position); + + if (!role) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidTrustedRole', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); + return; + } + + await this.trustedRoleRepo.removeTrustedRole(msg.guild.id, position); + + let r = msg.guild.roles.resolve(role.TrustedRoleDiscordId); + + await MessageUtils.send( + channel, + Lang.getEmbed('results.removedTrustedRole', LangCode.EN_US, { + ROLE: r ? r.toString() : '**Deleted Role**', + }) + ); + } +} diff --git a/src/commands/update-command.ts b/src/commands/update-command.ts index b8cd353d..687dbd48 100644 --- a/src/commands/update-command.ts +++ b/src/commands/update-command.ts @@ -1,259 +1,32 @@ -import { Message, MessageEmbed, Role, TextChannel } from 'discord.js'; -import { MessageUtils, PermissionUtils } from '../utils'; +import { DMChannel, Message, TextChannel } from 'discord.js'; import { Command } from './command'; -import { GuildRepo } from '../services/database/repos'; - -let Config = require('../../config/config.json'); +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { MessageUtils } from '../utils'; export class UpdateCommand implements Command { public name: string = 'update'; - public aliases = ['overwrite']; - public requireSetup = true; - public guildOnly = true; - public adminOnly = true; + public aliases = ['poggers']; + public requireSetup = false; + public guildOnly = false; + public adminOnly = false; public ownerOnly = false; public voteOnly = false; public requirePremium = false; public getPremium = false; - constructor(private guildRepo: GuildRepo) {} - - public async execute(args: string[], msg: Message, channel: TextChannel) { - if (args.length === 2) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription( - 'Please specify what to create!\nAccepted Values: `channel`, `role`, `trustedRole`, `birthdayMasterRole' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } else if (args.length === 3) { - let embed = new MessageEmbed() - .setTitle('Invalid Usage!') - .setDescription('Please specify what to update it with!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - if (args[2].toLowerCase() === 'channel') { - if (!msg.guild.me.hasPermission('MANAGE_CHANNELS')) { - let embed = new MessageEmbed() - .setTitle('Not Enough Permissions!') - .setDescription('The bot must have permission to manage channel!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - // Find channel with desired attributes - let birthdayChannel: TextChannel = msg.mentions.channels.first(); - - // If could not find in mention check, try to find by name - if (!birthdayChannel) { - birthdayChannel = msg.guild.channels.cache - .filter(channel => channel instanceof TextChannel) - .map(channel => channel as TextChannel) - .find(channel => channel.name.toLowerCase().includes(args[3].toLowerCase())); - } - - // Could it find the channel in either check? - if (!birthdayChannel) { - let embed = new MessageEmbed() - .setDescription('Invalid channel!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - // Bot needs to be able to message in the desired channel - if (!PermissionUtils.canSend(birthdayChannel)) { - let embed = new MessageEmbed() - .setDescription( - `I don't have permission to send messages in ${birthdayChannel.toString()}!` - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - await this.guildRepo.updateBirthdayChannel(msg.guild.id, birthdayChannel?.id); - - let embed = new MessageEmbed() - .setDescription( - `Successfully set the birthday channel to ${birthdayChannel.toString()}!` - ) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } else if (args[2].toLowerCase() === 'role') { - if (!msg.guild.me.hasPermission('MANAGE_ROLES')) { - let embed = new MessageEmbed() - .setTitle('Not Enough Permissions!') - .setDescription('The bot must have permission to manage roles!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - // Find role with desired attributes - let birthdayRole: Role = msg.mentions.roles.first(); - - if (!birthdayRole) { - birthdayRole = msg.guild.roles.cache.find(role => - role.name.toLowerCase().includes(args[3].toLowerCase()) - ); - } - - if ( - !birthdayRole || - birthdayRole.id === msg.guild.id || - args[3].toLowerCase() === 'everyone' - ) { - let embed = new MessageEmbed() - .setDescription(`Invalid Role!`) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); - return; - } - - if ( - birthdayRole.position > - msg.guild.members.resolve(msg.client.user).roles.highest.position - ) { - let embed = new MessageEmbed() - .setDescription(`Birthday Role must be bellow the Bot's role!`) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); - return; - } - - if (birthdayRole.managed) { - let embed = new MessageEmbed() - .setDescription(`Birthday Role cannot be managed by an external service!`) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); - return; - } - - let membersWithRole = birthdayRole.members.size; - - if (membersWithRole > 0 && membersWithRole < 100) { - let embed = new MessageEmbed() - .setTitle('Warning') - .setDescription( - `We have detected that __**${membersWithRole}**__ user${ - membersWithRole > 1 ? 's' : '' - } already have that role!\nThe Birthday Role should ONLY be the role that users GET on their birthday!` - ) - .setFooter( - `The Bot removes the Birthday Role from anyone whose birthday it isn't!`, - msg.client.user.avatarURL() - ) - .setColor(Config.colors.warning); - MessageUtils.send(channel, embed); - } else if (membersWithRole > 100) { - let embed = new MessageEmbed() - .setTitle('Error') - .setDescription( - `We have detected that __**${membersWithRole}**__ users already have that role!\nThe Birthday Role should ONLY be the role that users GET on their birthday!` - ) - .setFooter( - `The Bot removes the Birthday Role from anyone whose birthday it isn't!`, - msg.client.user.avatarURL() - ) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); - return; - } - - await this.guildRepo.updateBirthdayRole(msg.guild.id, birthdayRole?.id); - - let embed = new MessageEmbed() - .setDescription(`Successfully set the birthday role to ${birthdayRole.toString()}!`) - .setFooter(`This role is actively removed from those whose birthday it isn't.`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } else if (args[2].toLowerCase() === 'trustedrole') { - // Set role with desired attributes - let trustedRole: Role = msg.mentions.roles.first(); - - if (!trustedRole) { - trustedRole = msg.guild.roles.cache.find(role => - role.name.toLowerCase().includes(args[3].toLowerCase()) - ); - } - - if ( - !trustedRole || - trustedRole.id === msg.guild.id || - args[3].toLowerCase() === 'everyone' - ) { - let embed = new MessageEmbed() - .setDescription(`Invalid Role!`) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); - return; - } - - if (trustedRole.managed) { - let embed = new MessageEmbed() - .setDescription(`Trusted Role cannot be managed by an external service!`) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); - return; - } - - await this.guildRepo.updateTrustedRole(msg.guild.id, trustedRole?.id); - - let embed = new MessageEmbed() - .setDescription(`Successfully set the trusted role to ${trustedRole.toString()}!`) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } else if ( - args[2].toLowerCase() === 'birthdaymaster' || - args[2].toLowerCase() === 'birthdaymasterrole' - ) { - // Set role with desired attributes - let birthdayMasterRole: Role = msg.mentions.roles.first(); - - if (!birthdayMasterRole) { - birthdayMasterRole = msg.guild.roles.cache.find(role => - role.name.toLowerCase().includes(args[3].toLowerCase()) - ); - } - - if ( - !birthdayMasterRole || - birthdayMasterRole.id === msg.guild.id || - args[3].toLowerCase() === 'everyone' - ) { - let embed = new MessageEmbed() - .setDescription(`Invalid Role!`) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); - return; - } - - if (birthdayMasterRole.managed) { - let embed = new MessageEmbed() - .setDescription( - `Birthday Master Role cannot be managed by an external service!` - ) - .setColor(Config.colors.error); - MessageUtils.send(channel, embed); - return; - } - - await this.guildRepo.updateBirthdayMasterRole(msg.guild.id, birthdayMasterRole?.id); - - let embed = new MessageEmbed() - .setDescription( - `Successfully set the birthday master role to ${birthdayMasterRole.toString()}!` - ) - .setColor(Config.colors.success); - await MessageUtils.send(channel, embed); - } + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + await MessageUtils.send( + channel, + Lang.getEmbed('info.update', LangCode.EN_US).setAuthor( + 'Birthday Bot', + msg.client.user.avatarURL() + ) + ); } } diff --git a/src/commands/view-command.ts b/src/commands/view-command.ts index 4fed5588..1539cf9a 100644 --- a/src/commands/view-command.ts +++ b/src/commands/view-command.ts @@ -1,12 +1,12 @@ -import { DMChannel, Message, MessageEmbed, TextChannel, User } from 'discord.js'; -import { GuildUtils, MessageUtils } from '../utils'; +import { DMChannel, GuildMember, Message, TextChannel } from 'discord.js'; +import { FormatUtils, GuildUtils, MessageUtils } from '../utils'; import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; import { UserRepo } from '../services/database/repos'; import moment from 'moment'; -let Config = require('../../config/config.json'); - export class ViewCommand implements Command { public name: string = 'view'; public aliases = ['see']; @@ -18,60 +18,111 @@ export class ViewCommand implements Command { public requirePremium = false; public getPremium = false; - constructor(private userRepo: UserRepo) {} + constructor(private userRepo: UserRepo) { } + + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + let type: string; + let foundType = false; + let foundTarget = false; + + if (args.length > 2) { + type = FormatUtils.extractCelebrationType(args[2].toLowerCase())?.toLowerCase() ?? ''; + if (type === 'birthday' || type === 'memberanniversary') foundType = true; + } + + type = !type ? 'birthday' : type; + + let checkArg = foundType ? 4 : 3; - public async execute(args: string[], msg: Message, channel: TextChannel | DMChannel) { - let target: User; + let target: GuildMember; - if (args.length === 3) { - // Check if the user is trying to set another person's birthday + if (args.length === checkArg) { + // Check if the user is trying to view another person's birthday if (channel instanceof DMChannel) { - let embed = new MessageEmbed() - .setDescription(`You cannot request another user's information in a DM!`) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.viewUserInDm', LangCode.EN_US) + ); return; } // Get who they are mentioning target = - msg.mentions.members.first()?.user || - GuildUtils.findMember(msg.guild, args[2])?.user; + msg.mentions.members.first() || + GuildUtils.findMember(msg.guild, args[checkArg - 1]); // Did we find a user? - if (!target) { - let embed = new MessageEmbed() - .setDescription('Could not find that user!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } + if (target) foundTarget = true; } else { // They didn't mention anyone - target = msg.author; + target = msg.member; } - let userData = await this.userRepo.getUser(target.id); - - if (!userData || !userData.Birthday || !userData.TimeZone) { - let embed = new MessageEmbed().setColor(Config.colors.error); - if (target !== msg.author) { - embed.setDescription('That user has not set their birthday!'); - } else { - embed.setDescription('You have not set your birthday!'); - } - await MessageUtils.send(channel, embed); + if (!foundTarget && !foundType && args.length > 2) { + await MessageUtils.send( + channel, + Lang.getEmbed('validation.invalidViewArgs', LangCode.EN_US) + ); return; } - let embed = new MessageEmbed() - .setDescription( - `${target.toString()}'s birthday is on **${moment(userData.Birthday).format( - 'MMMM Do' - )}, ${userData.TimeZone}**!` - ) - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); - return; + if (type === 'birthday') { + let userData = await this.userRepo.getUser(target.id); + + if (!userData || !userData.Birthday || !userData.TimeZone) { + target === msg.member + ? await MessageUtils.send( + channel, + Lang.getEmbed('validation.birthdayNotSet', LangCode.EN_US) + ) + : await MessageUtils.send( + channel, + Lang.getEmbed('validation.userBirthdayNotSet', LangCode.EN_US, { + USER: target.toString(), + }) + ); + return; + } + + msg.member === target + ? await MessageUtils.send( + channel, + Lang.getEmbed('results.viewBirthday', LangCode.EN_US, { + BIRTHDAY: moment(userData.Birthday).format('MMMM Do'), + TIMEZONE: userData.TimeZone, + CHANGES_LEFT: userData.ChangesLeft.toString(), + ICON: msg.client.user.avatarURL(), + }) + ) + : await MessageUtils.send( + channel, + Lang.getEmbed('results.viewUserBirthday', LangCode.EN_US, { + USER: target.toString(), + BIRTHDAY: moment(userData.Birthday).format('MMMM Do'), + TIMEZONE: userData.TimeZone, + }) + ); + } else if (type === 'memberanniversary') { + let memberAnniversary = moment(target.joinedAt).format('MMMM Do'); + + msg.member === target + ? await MessageUtils.send( + channel, + Lang.getEmbed('results.viewMemberAnniversary', LangCode.EN_US, { + DATE: memberAnniversary, + }) + ) + : await MessageUtils.send( + channel, + Lang.getEmbed('results.viewUserMemberAnniversary', LangCode.EN_US, { + USER: target.toString(), + DATE: memberAnniversary, + }) + ); + } } } diff --git a/src/commands/vote-command.ts b/src/commands/vote-command.ts new file mode 100644 index 00000000..89f8c9c0 --- /dev/null +++ b/src/commands/vote-command.ts @@ -0,0 +1,29 @@ +import { DMChannel, Message, TextChannel } from 'discord.js'; + +import { Command } from './command'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { MessageUtils } from '../utils'; + +export class VoteCommand implements Command { + public name: string = 'vote'; + public aliases = ['v']; + public requireSetup = false; + public guildOnly = false; + public adminOnly = false; + public ownerOnly = false; + public voteOnly = false; + public requirePremium = false; + public getPremium = false; + + public async execute( + args: string[], + msg: Message, + channel: TextChannel | DMChannel + ): Promise { + await MessageUtils.send( + channel, + Lang.getEmbed('info.vote', LangCode.EN_US, { BOT: msg.client.user.toString() }) + ); + } +} diff --git a/src/events/guild-join-handler.ts b/src/events/guild-join-handler.ts index c3305205..a39c55de 100644 --- a/src/events/guild-join-handler.ts +++ b/src/events/guild-join-handler.ts @@ -1,12 +1,11 @@ -import { Guild, MessageEmbed, Permissions } from 'discord.js'; +import { Guild, Permissions } from 'discord.js'; import { EventHandler } from './event-handler'; -import { Logger } from '../services'; +import { Logger, Lang } from '../services'; import { MessageUtils } from '../utils'; +import { LangCode } from '../models/enums'; let Logs = require('../../lang/logs.json'); -let Config = require('../../config/config.json'); - export class GuildJoinHandler implements EventHandler { public async process(guild: Guild): Promise { Logger.info( @@ -14,23 +13,6 @@ export class GuildJoinHandler implements EventHandler { .replace('{GUILD_NAME}', guild.name) .replace('{GUILD_ID}', guild.id) ); - - let prefix = Config.prefix; - let embed = new MessageEmbed() - .setAuthor(guild.name, guild.iconURL()) - .setTitle('Thank you for using Birthday Bot!') - .setDescription( - `To support the bot and unlock special features use \`${prefix} premium\` in your server.` + - `\n\nTo view the commands of this bot use \`${prefix} help\`.` + - `\nTo setup the bot run \`${prefix} setup\`.` + - `\nTo set your birthday use \`${prefix} set\`.` + - `\n\nView the [Documentation](${Config.links.docs}) or the [FAQ](${Config.links.docs}/faq.).` + - `\nFor more support join our discord server [here](${Config.links.support})!` - ) - .setFooter('Join our support server for help!', guild.iconURL()) - .setTimestamp() - .setColor(Config.colors.default); - // Get someone to message let user = guild.owner; if (!user) { @@ -42,6 +24,8 @@ export class GuildJoinHandler implements EventHandler { if (!user) return; let userChannel = await user.createDM(); - await MessageUtils.send(userChannel, embed); + await MessageUtils.send(userChannel, Lang.getEmbed('info.guildJoin', LangCode.EN_US, { + ICON: guild.client.user.avatarURL(), + })); } } diff --git a/src/events/guild-leave-handler.ts b/src/events/guild-leave-handler.ts index 0eeccdb8..c8cdae6f 100644 --- a/src/events/guild-leave-handler.ts +++ b/src/events/guild-leave-handler.ts @@ -1,7 +1,6 @@ +import { EventHandler } from './event-handler'; import { Guild } from 'discord.js'; - import { Logger } from '../services'; -import { EventHandler } from './event-handler'; let Logs = require('../../lang/logs.json'); diff --git a/src/events/message-handler.ts b/src/events/message-handler.ts index 16d0990c..d36a1248 100644 --- a/src/events/message-handler.ts +++ b/src/events/message-handler.ts @@ -1,9 +1,10 @@ -import { DMChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; +import { DMChannel, Message, TextChannel } from 'discord.js'; import { GuildRepo, UserRepo } from '../services/database/repos'; -import { Logger, SubscriptionService } from '../services'; +import { Lang, Logger, SubscriptionService } from '../services'; import { MessageUtils, PermissionUtils } from '../utils'; import { Command } from '../commands'; +import { LangCode } from '../models/enums'; import { PlanName } from '../models/subscription-models'; import { RateLimiter } from 'discord.js-rate-limiter'; import moment from 'moment'; @@ -23,16 +24,20 @@ export class MessageHandler { private subscriptionService: SubscriptionService, private guildRepo: GuildRepo, private userRepo: UserRepo - ) {} + ) { } public async process(msg: Message): Promise { - if (msg.partial) return; - - // Ignore bots & System messages - if (msg.author.bot || msg.system) return; + // Don't respond to partial messages, system messages, or bots + if (msg.partial || msg.system || msg.author.bot) return; let channel = msg.channel; + // await MessageUtils.send( + // channel, + // `${msg.author.username}'s joinedAt is: \`${msg.member.joinedAt}\` and their joinedTimestamp is: \`${msg.member.joinedTimestamp}\`` + // ); + + // Only handle messages from text or DM channels if (!(channel instanceof TextChannel || channel instanceof DMChannel)) return; if (channel instanceof TextChannel) { @@ -41,13 +46,10 @@ export class MessageHandler { return; } if (!PermissionUtils.canReact(channel)) { - let embed = new MessageEmbed() - .setTitle('Missing Permissions!') - .setDescription( - 'I need permission to **Add Reactions** & **Read Message History**!' - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.needReactAndMessageHistoryPerms', LangCode.EN_US) + ); return; } } @@ -84,8 +86,7 @@ export class MessageHandler { } // Try to find the command the user wants - let userCommand = args[1]; - let command = this.getCommand(userCommand); + let command = this.findCommand(args[1]); // If no command found, run the help command if (!command) { @@ -103,11 +104,10 @@ export class MessageHandler { msg.member.roles.cache.has(Config.support.role); if (!sentByStaff) { - let embed = new MessageEmbed() - .setDescription('This command can only be used by Birthday Bot staff!') - .setColor(Config.colors.error); - - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.onlyStaff', LangCode.EN_US) + ); return; } } @@ -116,10 +116,10 @@ export class MessageHandler { // Check if the command is a server only command if (command.guildOnly && channel instanceof DMChannel) { - let embed = new MessageEmbed() - .setDescription('This command can only be used in a discord server!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.guildOnlyCommand', LangCode.EN_US) + ); return; } @@ -129,25 +129,19 @@ export class MessageHandler { // Get premium status if needed let retrievePremium = Config.payments.enabled && (checkVote || command.requirePremium || command.getPremium); - let hasPremium = retrievePremium - ? await this.subscriptionService.hasService(PlanName.premium1, msg.guild.id) - : false; + let hasPremium = + !Config.payments.enabled || + (retrievePremium + ? await this.subscriptionService.hasService(PlanName.premium1, msg.guild.id) + : false); if (checkPremium && !hasPremium) { - let embed = new MessageEmbed() - .setTitle('Premium Required!') - .setDescription('This command requires this server to have premium!') - .addField( - `Premium Commands`, - 'Subscribe to **Birthday bot Premium** for access to our premium features.\nSee `bday premium` for more information.' - ) - .setFooter( - 'Premium helps us support and maintain the bot!', - msg.client.user.avatarURL() - ) - .setTimestamp() - .setColor(Config.colors.default); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('premiumRequired.command', LangCode.EN_US, { + ICON: msg.client.user.avatarURL(), + }) + ); return; } @@ -155,22 +149,18 @@ export class MessageHandler { // Get the user's last vote and check if the command requires a vote let userVote = await this.userRepo.getUserVote(msg.author.id); let voteTime = moment(userVote?.VoteTime); - let voteTimeAgo = userVote ? voteTime.fromNow() : 'Never'; + let voteTimeAgo = userVote + ? voteTime.fromNow() + : Lang.getRef('terms.never', LangCode.EN_US); if (!userVote || voteTime.clone().add(Config.voting.hours, 'hours') < moment()) { - let embed = new MessageEmbed() - .setAuthor(msg.author.tag, msg.author.avatarURL()) - .setThumbnail('https://i.imgur.com/wak8g4V.png') - .setTitle('Vote Required!') - .setDescription('This command requires you to have voted in the past 24 hours!') - .addField('Last Vote', `${voteTimeAgo}`, true) - .addField('Vote Here', `[Top.gg](${Config.links.vote})`, true) - .setFooter( - `Don't want to vote? Try Birthday Bot Premium!`, - msg.client.user.avatarURL() - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.voteRequired', LangCode.EN_US, { + LAST_VOTE: voteTimeAgo, + ICON: msg.client.user.avatarURL(), + }) + ); return; } } @@ -180,23 +170,19 @@ export class MessageHandler { if (channel instanceof TextChannel) { let guildData = await this.guildRepo.getGuild(msg.guild.id); if (command.requireSetup && !guildData) { - let embed = new MessageEmbed() - .setTitle('Server Setup Required!') - .setDescription( - `Please run server setup with \`bday setup\` before using that command!` - ) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.setupRequired', LangCode.EN_US) + ); return; } // Check if user has permission if (!PermissionUtils.hasPermission(msg.member, guildData, command)) { - let embed = new MessageEmbed() - .setTitle('Permission Required!') - .setDescription(`You don't have permission to run that command!`) - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); + await MessageUtils.send( + channel, + Lang.getEmbed('validation.noPermission', LangCode.EN_US) + ); return; } } @@ -208,11 +194,7 @@ export class MessageHandler { try { await MessageUtils.send( channel, - new MessageEmbed() - .setDescription(`Something went wrong!`) - .addField('Error code', msg.id) - .addField('Contact support', Config.links.support) - .setColor(Config.colors.error) + Lang.getEmbed('info.error', LangCode.EN_US, { ERROR_CODE: msg.id }) ); } catch { // Ignore @@ -245,16 +227,11 @@ export class MessageHandler { } } - private getCommand(userCommand: string): Command { - userCommand = userCommand.toLowerCase(); - for (let cmd of this.commands) { - if (cmd.name === userCommand.toLowerCase()) { - return cmd; - } - - if (cmd.aliases.includes(userCommand)) { - return cmd; - } - } + private findCommand(input: string): Command { + input = input.toLowerCase(); + return ( + this.commands.find(command => command.name === input) ?? + this.commands.find(command => command.aliases.includes(input)) + ); } } diff --git a/src/events/reaction-add-handler.ts b/src/events/reaction-add-handler.ts index a2aaa337..b1f02f36 100644 --- a/src/events/reaction-add-handler.ts +++ b/src/events/reaction-add-handler.ts @@ -1,18 +1,28 @@ -import { ActionUtils, FormatUtils, MessageUtils, PermissionUtils } from '../utils'; -import { BlacklistRepo, CustomMessageRepo, UserRepo } from '../services/database/repos'; +import { BlacklistRepo, CustomMessageRepo, GuildRepo, UserRepo } from '../services/database/repos'; import { CollectOptions, CollectorUtils, ExpireFunction, MessageFilter, } from 'discord.js-collector-utils'; -import { Collection, Message, MessageEmbed, MessageReaction, TextChannel, User } from 'discord.js'; -import { Logger, SubscriptionService } from '../services'; - +import { + Collection, + DiscordAPIError, + Message, + MessageReaction, + TextChannel, + User, +} from 'discord.js'; +import { FormatUtils, ListUtils, MessageUtils, ParseUtils, PermissionUtils } from '../utils'; +import { Lang, Logger, SubscriptionService } from '../services'; + +import { CustomMessages } from '../models/database'; import { EventHandler } from '.'; -import { ListUtils } from '../utils/list-utils'; +import { LangCode } from '../models/enums'; import { PlanName } from '../models/subscription-models'; import { RateLimiter } from 'discord.js-rate-limiter'; +import { TrustedRoleRepo } from '../services/database/repos'; +import moment from 'moment'; let Logs = require('../../lang/logs.json'); let Config = require('../../config/config.json'); @@ -23,27 +33,43 @@ const COLLECT_OPTIONS: CollectOptions = { }; export class ReactionAddHandler implements EventHandler { + // Bday list limiter private rateLimiter = new RateLimiter( Config.rateLimiting.list.amount, Config.rateLimiting.list.interval * 1000 ); + // Bday list limiter + private memberAnniversaryListLimiter = new RateLimiter( + Config.rateLimiting.list.amount, + Config.rateLimiting.list.interval * 1000 + ); + // Custom message list private messageLimiter = new RateLimiter( - Config.rateLimiting.commands.amount, - Config.rateLimiting.commands.interval * 1000 + Config.rateLimiting.list.amount, + Config.rateLimiting.list.interval * 1000 ); + // bday blacklist list private blacklistLimiter = new RateLimiter( - Config.rateLimiting.commands.amount, - Config.rateLimiting.commands.interval * 1000 + Config.rateLimiting.list.amount, + Config.rateLimiting.list.interval * 1000 ); + // user message list private userMessagesLimiter = new RateLimiter( - Config.rateLimiting.commands.amount, - Config.rateLimiting.commands.interval * 1000 + Config.rateLimiting.list.amount, + Config.rateLimiting.list.interval * 1000 + ); + // Trusted Role limiter + private trustedRoleLimiter = new RateLimiter( + Config.rateLimiting.list.amount, + Config.rateLimiting.list.interval * 1000 ); constructor( private userRepo: UserRepo, + private guildRepo: GuildRepo, private customMessageRepo: CustomMessageRepo, private blacklistRepo: BlacklistRepo, + private trustedRoleRepo: TrustedRoleRepo, private subscriptionService: SubscriptionService ) {} @@ -58,6 +84,7 @@ export class ReactionAddHandler implements EventHandler { !PermissionUtils.canHandleReaction(channel) || !PermissionUtils.canReact(channel) ) { + // Give a response as to why it can't respond? With a rate limit on the guild return; } @@ -67,6 +94,8 @@ export class ReactionAddHandler implements EventHandler { Config.emotes.nextPage, Config.emotes.previousPage, Config.emotes.jumpToPage, + Config.emotes.fastReverse, + Config.emotes.fastForward, ].includes(msgReaction.emoji.name) ) { return; @@ -78,8 +107,18 @@ export class ReactionAddHandler implements EventHandler { try { msg = await msgReaction.message?.fetch(); } catch (error) { - Logger.error(Logs.error.messagePartial, error); - return; + // 10003: "Unknown Channel" + // 10008: "Unknown Message" (message was deleted) + // 50001: "Missing Access" + if ( + error instanceof DiscordAPIError && + [10003, 10008, 50001].includes(error.code) + ) { + return; + } else { + Logger.error(Logs.error.messagePartial, error); + return; + } } } else { msg = msgReaction.message; @@ -106,6 +145,8 @@ export class ReactionAddHandler implements EventHandler { let checkNextPage: boolean = msgReaction.emoji.name === Config.emotes.nextPage; let checkPreviousPage: boolean = msgReaction.emoji.name === Config.emotes.previousPage; let checkJumpToPage: boolean = msgReaction.emoji.name === Config.emotes.jumpToPage; + let checkFastRewind: boolean = msgReaction.emoji.name === Config.emotes.fastReverse; + let checkFastForward: boolean = msgReaction.emoji.name === Config.emotes.fastForward; let stopFilter: MessageFilter = (nextMsg: Message) => nextMsg.author.id === msg.author.id && @@ -119,146 +160,209 @@ export class ReactionAddHandler implements EventHandler { if (!titleArgs) return; - if (checkNextPage || checkPreviousPage) { - if (titleArgs[1] === 'Messages') { - let oldPage: number; - let page = 1; - let pageSize = Config.experience.birthdayMessageListSize; - - if (titleArgs[4]) { - try { - oldPage = FormatUtils.extractPageNumber(titleArgs.join(' ')); - if (checkNextPage) page = oldPage + 1; - else page = oldPage - 1; - } catch (error) { - // Not A Number - } - if (!page) page = 1; - } + if (checkNextPage || checkPreviousPage || checkFastForward || checkFastRewind) { + let oldPage: number; + let page = 1; + let pageSize: number; + let hasPremium: boolean; + let user = false; - let customMessageResults = await this.customMessageRepo.getCustomMessageList( - msg.guild.id, - pageSize, - page - ); + try { + oldPage = FormatUtils.extractPageNumber(titleArgs.join(' ')); + if (!oldPage) return; + if (checkNextPage) page = oldPage + 1; + else if (checkFastForward) page = oldPage + Config.experience.fastForwardAmount; + else if (checkFastRewind) page = oldPage - Config.experience.fastRewindAmount; + else page = oldPage - 1; + } catch (error) { + // Not A Number + } + + if (!page) page = 1; + + if ( + oldPage === 1 && + checkPreviousPage // if the old page was page 1 and they are trying to decrease + ) { + await MessageUtils.removeReaction(msgReaction, reactor); + return; + } + + if (oldPage <= Config.experience.fastRewindAmount && checkFastRewind) page = 1; + + if (msg.embeds[0]?.title?.includes(Lang.getRef('terms.messages', LangCode.EN_US))) { + pageSize = Config.experience.birthdayMessageListSize; + let customMessageResults: CustomMessages; + hasPremium = + !Config.payments.enabled || + (await this.subscriptionService.hasService(PlanName.premium1, msg.guild.id)); + + let type = 'birthday'; + + // This breaks with lang + // TODO: ....fix it (find better way to differentiate between message types in lists) + if (msg.embeds[0]?.title?.includes('Member')) type = 'memberanniversary'; + else if (msg.embeds[0]?.title?.includes('Server')) type = 'serveranniversary'; + + if (msg.embeds[0]?.title?.includes(Lang.getRef('terms.user', LangCode.EN_US))) { + user = true; + customMessageResults = await this.customMessageRepo.getCustomMessageUserList( + msg.guild.id, + pageSize, + page, + type + ); + } else { + customMessageResults = await this.customMessageRepo.getCustomMessageList( + msg.guild.id, + pageSize, + page, + type + ); + } if ( - (oldPage === 1 && checkPreviousPage) || // if the old page was page 1 and they are trying to decrease - (oldPage === customMessageResults.stats.TotalPages && checkNextPage) // if the old page was the max page and they are trying to increase - ) { + oldPage >= + customMessageResults.stats.TotalPages - + Config.experience.fastRewindAmount && + checkFastForward + ) + page = customMessageResults.stats.TotalPages; + + if (oldPage === customMessageResults.stats.TotalPages && checkNextPage) { await MessageUtils.removeReaction(msgReaction, reactor); return; } - - let hasPremium = Config.payments.enabled - ? await this.subscriptionService.hasService(PlanName.premium1, msg.guild.id) - : false; - await ListUtils.updateMessageList( customMessageResults, msg.guild, msg, page, pageSize, - hasPremium + hasPremium, + type, + user ); + } else if ( + msg.embeds[0]?.title?.includes( + Lang.getRef('terms.birthday', LangCode.EN_US) + + ' ' + + Lang.getRef('terms.list', LangCode.EN_US) + ) + ) { + pageSize = Config.experience.birthdayListSize; + let users = msg.guild.members.cache.filter(member => !member.user.bot).keyArray(); - await MessageUtils.removeReaction(msgReaction, reactor); - } else if (titleArgs[0] === 'User') { - let oldPage: number; - let page = 1; - let pageSize = Config.experience.birthdayMessageListSize; - - if (titleArgs[5]) { - try { - oldPage = FormatUtils.extractPageNumber(titleArgs.join(' ')); - if (checkNextPage) page = oldPage + 1; - else page = oldPage - 1; - } catch (error) { - // Not A Number - } - if (!page) page = 1; - } - - let customMessageResults = await this.customMessageRepo.getCustomMessageUserList( - msg.guild.id, + let userDataResults = await this.userRepo.getBirthdayListFull( + users, pageSize, page ); if ( - (oldPage === 1 && checkPreviousPage) || // if the old page was page 1 and they are trying to decrease - (oldPage === customMessageResults.stats.TotalPages && checkNextPage) // if the old page was the max page and they are trying to increase - ) { + oldPage >= + userDataResults.stats.TotalPages - Config.experience.fastRewindAmount && + checkFastForward + ) + page = userDataResults.stats.TotalPages; + + if (oldPage === userDataResults.stats.TotalPages && checkNextPage) { await MessageUtils.removeReaction(msgReaction, reactor); return; } - let hasPremium = Config.payments.enabled - ? await this.subscriptionService.hasService(PlanName.premium1, msg.guild.id) - : false; + let guildData = await this.guildRepo.getGuild(msg.guild.id); - await ListUtils.updateMessageUserList( - customMessageResults, + await ListUtils.updateBdayList( + userDataResults, msg.guild, + guildData, msg, page, - pageSize, - hasPremium + pageSize + ); + } else if ( + msg.embeds[0]?.title?.includes( + Lang.getRef('terms.memberAnniversary', LangCode.EN_US) + + ' ' + + Lang.getRef('terms.list', LangCode.EN_US) + ) + ) { + pageSize = ParseUtils.parseInt(Config.experience.memberAnniversaryListSize); + + // Member Anniversary List + let memberList = msg.guild.members.cache + .filter(member => !member.user.bot) + .map(member => member); + + let totalMembers = memberList.length; + + memberList = memberList.sort( + (first, second) => + 0 - + (moment(first.joinedAt).format('MM-DD') > + moment(second.joinedAt).format('MM-DD') + ? -1 + : 1) ); - await MessageUtils.removeReaction(msgReaction, reactor); - } else if (titleArgs[1] === 'List') { - let oldPage: number; - let page = 1; - let pageSize = Config.experience.birthdayListSize; + let totalPages = Math.ceil(memberList.length / pageSize); - let users = msg.guild.members.cache.filter(member => !member.user.bot).keyArray(); + let startMember = (page - 1) * pageSize; - if (titleArgs[4]) { - try { - oldPage = FormatUtils.extractPageNumber(titleArgs.join(' ')); - if (checkNextPage) page = oldPage + 1; - else page = oldPage - 1; - } catch (error) { - // Not A Number - } - if (!page) page = 1; - } + if (!startMember) startMember = 0; - let userDataResults = await this.userRepo.getBirthdayListFull( - users, + memberList = memberList.slice(startMember, startMember + pageSize); + + let guildData = await this.guildRepo.getGuild(msg.guild.id); + + await ListUtils.updateMemberAnniversaryList( + memberList, + msg.guild, + guildData, + msg, + page, + pageSize, + totalPages, + totalMembers + ); + } else if ( + msg.embeds[0]?.title?.includes(Lang.getRef('terms.trustedRoles', LangCode.EN_US)) + ) { + pageSize = Config.experience.trustedRoleListSize; + let trustedRoleResults = await this.trustedRoleRepo.getTrustedRoleList( + msg.guild.id, pageSize, page ); + hasPremium = + !Config.payments.enabled || + (await this.subscriptionService.hasService(PlanName.premium1, msg.guild.id)); if ( - (oldPage === 1 && checkPreviousPage) || // if the old page was page 1 and they are trying to decrease - (oldPage === userDataResults.stats.TotalPages && checkNextPage) // if the old page was the max page and they are trying to increase - ) { + oldPage >= + trustedRoleResults.stats.TotalPages - Config.experience.fastRewindAmount && + checkFastForward + ) + page = trustedRoleResults.stats.TotalPages; + + if (oldPage === trustedRoleResults.stats.TotalPages && checkNextPage) { await MessageUtils.removeReaction(msgReaction, reactor); return; } - await ListUtils.updateBdayList(userDataResults, msg.guild, msg, page, pageSize); - - await MessageUtils.removeReaction(msgReaction, reactor); - } else if (titleArgs[1] === 'Blacklist') { - let oldPage: number; - let page = 1; - let pageSize = Config.experience.blacklistSize; - - if (titleArgs[4]) { - try { - oldPage = FormatUtils.extractPageNumber(titleArgs.join(' ')); - if (checkNextPage) page = oldPage + 1; - else page = oldPage - 1; - } catch (error) { - // Not A Number - } - if (!page) page = 1; - } - + await ListUtils.updateTrustedRoleList( + trustedRoleResults, + msg.guild, + msg, + page, + pageSize, + hasPremium + ); + } else if ( + msg.embeds[0]?.title?.includes(Lang.getRef('terms.blacklist', LangCode.EN_US)) + ) { + pageSize = Config.experience.blacklistSize; let blacklistResults = await this.blacklistRepo.getBlacklistList( msg.guild.id, pageSize, @@ -266,9 +370,13 @@ export class ReactionAddHandler implements EventHandler { ); if ( - (oldPage === 1 && checkPreviousPage) || // if the old page was page 1 and they are trying to decrease - (oldPage === blacklistResults.stats.TotalPages && checkNextPage) // if the old page was the max page and they are trying to increase - ) { + oldPage >= + blacklistResults.stats.TotalPages - Config.experience.fastRewindAmount && + checkFastForward + ) + page = blacklistResults.stats.TotalPages; + + if (oldPage === blacklistResults.stats.TotalPages && checkNextPage) { await MessageUtils.removeReaction(msgReaction, reactor); return; } @@ -280,271 +388,241 @@ export class ReactionAddHandler implements EventHandler { page, pageSize ); - - await MessageUtils.removeReaction(msgReaction, reactor); } + + await MessageUtils.removeReaction(msgReaction, reactor); } else if (checkJumpToPage) { - if (titleArgs[1] === 'Messages') { - // Check if user is rate limited - let limited = this.messageLimiter.take(reactor.id); + // Jump to page + let user = false; + let pageSize: number; + let hasPremium: boolean; + let oldPage: number; + // Check if user is rate limited + + if (msg.embeds[0]?.title?.includes(Lang.getRef('terms.messages', LangCode.EN_US))) { + user = msg.embeds[0]?.title?.includes(Lang.getRef('terms.user', LangCode.EN_US)); + pageSize = Config.experience.birthdayMessageListSize; + if (user) { + let limited = this.userMessagesLimiter.take(reactor.id); + if (limited) { + return; + } + } else { + let limited = this.messageLimiter.take(reactor.id); + if (limited) { + return; + } + } + } else if ( + msg.embeds[0]?.title?.includes( + Lang.getRef('terms.birthday', LangCode.EN_US) + + ' ' + + Lang.getRef('terms.list', LangCode.EN_US) + ) + ) { + pageSize = Config.experience.birthdayListSize; + let limited = this.rateLimiter.take(reactor.id); if (limited) { return; } - let page: number; - - let messageTimeEmbed = new MessageEmbed() - .setDescription('Please input the page you would like to jump to:') - .setColor(Config.colors.default); - - let prompt = await MessageUtils.send(channel, messageTimeEmbed); - - page = await CollectorUtils.collectByMessage( - msg.channel, - // Collect Filter - (nextMsg: Message) => nextMsg.author.id === reactor.id, - stopFilter, - // Retrieve Result - async (nextMsg: Message) => { - await MessageUtils.delete(nextMsg); - if (!page && page !== 0) { - // Try and get the time - let page: number; - try { - page = parseInt(nextMsg.content.split(/\s+/)[0]); - } catch (error) { - let embed = new MessageEmbed() - .setDescription('Invalid page!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - page = Math.round(page); - - if (!page || page <= 0 || page > 100000) page = 1; - - return page; - } - }, - expireFunction, - COLLECT_OPTIONS - ); + } else if ( + msg.embeds[0]?.title?.includes( + Lang.getRef('terms.memberAnniversary', LangCode.EN_US) + + ' ' + + Lang.getRef('terms.list', LangCode.EN_US) + ) + ) { + pageSize = ParseUtils.parseInt(Config.experience.memberAnniversaryListSize); + let limited = this.memberAnniversaryListLimiter.take(reactor.id); + if (limited) { + return; + } + } else if ( + msg.embeds[0]?.title?.includes(Lang.getRef('terms.trustedRoles', LangCode.EN_US)) + ) { + pageSize = Config.experience.trustedRoleListSize; + let limited = this.trustedRoleLimiter.take(reactor.id); + if (limited) { + return; + } + } else if ( + msg.embeds[0]?.title?.includes(Lang.getRef('terms.blacklist', LangCode.EN_US)) + ) { + pageSize = Config.experience.blacklistSize; + let limited = this.blacklistLimiter.take(reactor.id); + if (limited) { + return; + } + } - MessageUtils.delete(prompt); + let page: number; - if (page === undefined) return; + let prompt = await MessageUtils.send( + channel, + Lang.getEmbed('userPrompts.inputPage', LangCode.EN_US) + ); - let pageSize = Config.experience.birthdayMessageListSize; + page = await CollectorUtils.collectByMessage( + msg.channel, + // Collect Filter + (nextMsg: Message) => nextMsg.author.id === reactor.id, + stopFilter, + // Retrieve Result + async (nextMsg: Message) => { + await MessageUtils.delete(nextMsg); + if (!page && page !== 0) { + // Try and get the time + let page = ParseUtils.parseInt(nextMsg.content.split(/\s+/)[0]); - let customMessageResults = await this.customMessageRepo.getCustomMessageList( - msg.guild.id, - pageSize, - page - ); + page = Math.round(page); - let hasPremium = Config.payments.enabled - ? await this.subscriptionService.hasService(PlanName.premium1, msg.guild.id) - : false; + if (!page || page <= 0 || page > 100000) page = 1; + return page; + } + }, + expireFunction, + COLLECT_OPTIONS + ); + + MessageUtils.delete(prompt); + + if (page === undefined) return; + + try { + oldPage = FormatUtils.extractPageNumber(titleArgs.join(' ')); + if (oldPage === page) return; + } catch (error) { + // Not A Number + } + + if (msg.embeds[0]?.title?.includes(Lang.getRef('terms.messages', LangCode.EN_US))) { + let customMessageResults: CustomMessages; + hasPremium = + !Config.payments.enabled || + (await this.subscriptionService.hasService(PlanName.premium1, msg.guild.id)); + + let type = 'birthday'; + + if (msg.embeds[0]?.title?.includes('Member')) type = 'memberanniversary'; + else if (msg.embeds[0]?.title?.includes('Server')) type = 'serveranniversary'; + if (user) { + customMessageResults = await this.customMessageRepo.getCustomMessageUserList( + msg.guild.id, + pageSize, + page, + type + ); + } else { + customMessageResults = await this.customMessageRepo.getCustomMessageList( + msg.guild.id, + pageSize, + page, + type + ); + } await ListUtils.updateMessageList( customMessageResults, msg.guild, msg, page, pageSize, - hasPremium + hasPremium, + type, + user ); + } else if ( + msg.embeds[0]?.title?.includes( + Lang.getRef('terms.birthday', LangCode.EN_US) + + ' ' + + Lang.getRef('terms.list', LangCode.EN_US) + ) + ) { + let users = msg.guild.members.cache.filter(member => !member.user.bot).keyArray(); - await MessageUtils.removeReaction(msgReaction, reactor); - } else if (titleArgs[0] === 'User') { - // Check if user is rate limited - let limited = this.userMessagesLimiter.take(reactor.id); - if (limited) { - return; - } - let page: number; - - let messageTimeEmbed = new MessageEmbed() - .setDescription('Please input the page you would like to jump to:') - .setColor(Config.colors.default); - - let prompt = await MessageUtils.send(channel, messageTimeEmbed); - - page = await CollectorUtils.collectByMessage( - msg.channel, - // Collect Filter - (nextMsg: Message) => nextMsg.author.id === reactor.id, - stopFilter, - // Retrieve Result - async (nextMsg: Message) => { - await MessageUtils.delete(nextMsg); - if (!page && page !== 0) { - // Try and get the time - let page: number; - try { - page = parseInt(nextMsg.content.split(/\s+/)[0]); - } catch (error) { - let embed = new MessageEmbed() - .setDescription('Invalid page!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - page = Math.round(page); - - if (!page || page <= 0 || page > 100000) page = 1; - - return page; - } - }, - expireFunction, - COLLECT_OPTIONS - ); - - MessageUtils.delete(prompt); - - if (page === undefined) return; - - let pageSize = Config.experience.birthdayMessageListSize; - - let customMessageResults = await this.customMessageRepo.getCustomMessageUserList( - msg.guild.id, + let userDataResults = await this.userRepo.getBirthdayListFull( + users, pageSize, page ); - let hasPremium = Config.payments.enabled - ? await this.subscriptionService.hasService(PlanName.premium1, msg.guild.id) - : false; + let guildData = await this.guildRepo.getGuild(msg.guild.id); - await ListUtils.updateMessageUserList( - customMessageResults, + await ListUtils.updateBdayList( + userDataResults, msg.guild, + guildData, msg, page, - pageSize, - hasPremium + pageSize ); - - await MessageUtils.removeReaction(msgReaction, reactor); - } else if (titleArgs[1] === 'List') { - let page: number; - // Check if user is rate limited - let limited = this.rateLimiter.take(reactor.id); - if (limited) { - return; - } - - let messageTimeEmbed = new MessageEmbed() - .setDescription('Please input the page you would like to jump to:') - .setColor(Config.colors.default); - - let prompt = await MessageUtils.send(channel, messageTimeEmbed); - - page = await CollectorUtils.collectByMessage( - msg.channel, - // Collect Filter - (nextMsg: Message) => nextMsg.author.id === reactor.id, - stopFilter, - // Retrieve Result - async (nextMsg: Message) => { - await MessageUtils.delete(nextMsg); - if (!page && page !== 0) { - // Try and get the time - let page: number; - try { - page = parseInt(nextMsg.content.split(/\s+/)[0]); - } catch (error) { - let embed = new MessageEmbed() - .setDescription('Invalid page!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - page = Math.round(page); - - if (!page || page <= 0 || page > 100000) page = 1; - - return page; - } - }, - expireFunction, - COLLECT_OPTIONS + } else if ( + msg.embeds[0]?.title?.includes( + Lang.getRef('terms.memberAnniversary', LangCode.EN_US) + + ' ' + + Lang.getRef('terms.list', LangCode.EN_US) + ) + ) { + // Member Anniversary List + let memberList = msg.guild.members.cache + .filter(member => !member.user.bot) + .map(member => member); + + let totalMembers = memberList.length; + + memberList = memberList.sort( + (first, second) => + 0 - + (moment(first.joinedAt).format('MM-DD') > + moment(second.joinedAt).format('MM-DD') + ? -1 + : 1) ); - MessageUtils.delete(prompt); + let totalPages = Math.ceil(memberList.length / pageSize); - if (page === undefined) { - return; - } + let startMember = (page - 1) * pageSize; - let pageSize = Config.experience.birthdayListSize; + if (!startMember) startMember = 0; - let users = msg.guild.members.cache.filter(member => !member.user.bot).keyArray(); + memberList = memberList.slice(startMember, startMember + pageSize); - let userDataResults = await this.userRepo.getBirthdayListFull( - users, + let guildData = await this.guildRepo.getGuild(msg.guild.id); + + await ListUtils.updateMemberAnniversaryList( + memberList, + msg.guild, + guildData, + msg, + page, + pageSize, + totalPages, + totalMembers + ); + } else if ( + msg.embeds[0]?.title?.includes(Lang.getRef('terms.trustedRoles', LangCode.EN_US)) + ) { + let trustedRoleResults = await this.trustedRoleRepo.getTrustedRoleList( + msg.guild.id, pageSize, page ); - await ListUtils.updateBdayList(userDataResults, msg.guild, msg, page, pageSize); + hasPremium = + !Config.payments.enabled || + (await this.subscriptionService.hasService(PlanName.premium1, msg.guild.id)); - await MessageUtils.removeReaction(msgReaction, reactor); - } else if (titleArgs[1] === 'Blacklist') { - let page: number; - // Check if user is rate limited - let limited = this.blacklistLimiter.take(reactor.id); - if (limited) { - return; - } - - let messageTimeEmbed = new MessageEmbed() - .setDescription('Please input the page you would like to jump to:') - .setColor(Config.colors.default); - - let prompt = await MessageUtils.send(channel, messageTimeEmbed); - - page = await CollectorUtils.collectByMessage( - msg.channel, - // Collect Filter - (nextMsg: Message) => nextMsg.author.id === reactor.id, - stopFilter, - // Retrieve Result - async (nextMsg: Message) => { - await MessageUtils.delete(nextMsg); - if (!page && page !== 0) { - // Try and get the time - let page: number; - try { - page = parseInt(nextMsg.content.split(/\s+/)[0]); - } catch (error) { - let embed = new MessageEmbed() - .setDescription('Invalid page!') - .setColor(Config.colors.error); - await MessageUtils.send(channel, embed); - return; - } - - page = Math.round(page); - - if (!page || page <= 0 || page > 100000) page = 1; - - return page; - } - }, - expireFunction, - COLLECT_OPTIONS + await ListUtils.updateTrustedRoleList( + trustedRoleResults, + msg.guild, + msg, + page, + pageSize, + hasPremium ); - - MessageUtils.delete(prompt); - - if (page === undefined) return; - - let pageSize = Config.experience.blacklistSize; - + } else if ( + msg.embeds[0]?.title?.includes(Lang.getRef('terms.blacklist', LangCode.EN_US)) + ) { let blacklistResults = await this.blacklistRepo.getBlacklistList( msg.guild.id, pageSize, @@ -558,9 +636,8 @@ export class ReactionAddHandler implements EventHandler { page, pageSize ); - - await MessageUtils.removeReaction(msgReaction, reactor); } + await MessageUtils.removeReaction(msgReaction, reactor); } } } diff --git a/src/extensions/custom-client.ts b/src/extensions/custom-client.ts index 8025c598..61ae0a68 100644 --- a/src/extensions/custom-client.ts +++ b/src/extensions/custom-client.ts @@ -1,11 +1,11 @@ -import { ActivityType, Client, ClientOptions, MessageEmbed, Presence } from 'discord.js'; -import { DiscordService, Logger } from '../services'; +import { ActivityType, Client, ClientOptions, Presence } from 'discord.js'; +import { DiscordService, Logger, Lang } from '../services'; import { MessageUtils, PermissionUtils } from '../utils'; import { PlanName, SubscriptionStatusName } from '../models/subscription-models'; import { GuildRepo } from '../services/database/repos'; +import { LangCode } from '../models/enums'; -let Config = require('../../config/config.json'); let Logs = require('../../lang/logs.json'); export class CustomClient extends Client { @@ -50,76 +50,50 @@ export class CustomClient extends Client { PlanName.premium3 || PlanName.premium6 || PlanName.premium12: { - switch (status) { - case SubscriptionStatusName.ACTIVE: { - let embed = new MessageEmbed() - .setAuthor(guild.name, guild.iconURL()) - .setTitle('Birthday Bot Premium') - .setDescription( - `${Config.emotes.party} ${guild.name}'s premium has been successfully activated! Enjoy the new features! ${Config.emotes.party}` - ) - .addField( - 'bday premium', - `View details about your subscription.\n\n[Join Support Server](${Config.links.support})` - ) - .setFooter( - 'Thanks for supporting Birthday Bot!', - guild.client.user.avatarURL() - ) - .setTimestamp() - .setColor(Config.colors.default); - MessageUtils.send(channel, embed); - break; + switch (status) { + case SubscriptionStatusName.ACTIVE: { + MessageUtils.send( + channel, + Lang.getEmbed('premiumPrompts.subscriptionAdded', LangCode.EN_US, { + SERVER_NAME: guild.name, + ICON: guild.client.user.avatarURL(), + }) + ); + break; + } + case SubscriptionStatusName.CANCELLED: { + MessageUtils.send( + channel, + Lang.getEmbed('premiumPrompts.subscriptionCanceled', LangCode.EN_US, { + SERVER_NAME: guild.name, + ICON: guild.client.user.avatarURL(), + }) + ); + break; + } + case SubscriptionStatusName.EXPIRED: { + MessageUtils.send( + channel, + Lang.getEmbed('premiumPrompts.subscriptionExpired', LangCode.EN_US, { + SERVER_NAME: guild.name, + ICON: guild.client.user.avatarURL(), + }) + ); + break; + } + default: { + break; + } } - case SubscriptionStatusName.CANCELLED: { - let embed = new MessageEmbed() - .setAuthor(guild.name, guild.iconURL()) - .setTitle('Birthday Bot Premium') - .setDescription(`${guild.name}'s premium has been canceled! :(`) - .addField( - 'bday premium', - `View details about your subscription.\n\n[Join Support Server](${Config.links.support})` - ) - .setFooter( - 'Consider letting us know how to improve premium!', - guild.client.user.avatarURL() - ) - .setTimestamp() - .setColor(Config.colors.default); - MessageUtils.send(channel, embed); - break; - } - case SubscriptionStatusName.EXPIRED: { - let embed = new MessageEmbed() - .setAuthor(guild.name, guild.iconURL()) - .setTitle('Birthday Bot Premium') - .setDescription(`${guild.name}'s premium has expired! :(`) - .addField( - 'bday premium', - `Resubscribe to premium!\n\n[Join Support Server](${Config.links.support})` - ) - .setFooter( - 'Consider letting us know how to improve premium!', - guild.client.user.avatarURL() - ) - .setTimestamp() - .setColor(Config.colors.default); - MessageUtils.send(channel, embed); - break; - } - default: { - break; - } - } - Logger.info( - Logs.info.guildSubStatus - .replace('{GUILD_NAME}', guild.name) - .replace('{GUILD_ID}', guild.id) - .replace('{PLAN_NAME}', plan) - .replace('{SUBSCRIPTION_STATUS}', status) - ); - } + Logger.info( + Logs.info.guildSubStatus + .replace('{GUILD_NAME}', guild.name) + .replace('{GUILD_ID}', guild.id) + .replace('{PLAN_NAME}', plan) + .replace('{SUBSCRIPTION_STATUS}', status) + ); + } } } } diff --git a/src/jobs/celebration-job.ts b/src/jobs/celebration-job.ts new file mode 100644 index 00000000..c5890e94 --- /dev/null +++ b/src/jobs/celebration-job.ts @@ -0,0 +1,250 @@ +import { CelebrationUtils, TimeUtils } from '../utils'; +import { Client, Collection, Guild, GuildMember } from 'discord.js'; +import { CombinedRepo, UserRepo } from '../services/database/repos'; +import { Logger, MessageService, RoleService, SubscriptionService } from '../services'; + +import { Job } from './job'; +import { UserData } from '../models/database'; +import moment from 'moment'; +import schedule from 'node-schedule'; + +let Config = require('../../config/config.json'); +let Logs = require('../../lang/logs.json'); + +export class CelebrationJob implements Job { + public name = 'Celebration'; + public schedule: string = Config.jobs.postCelebrationJob.schedule; + public log: boolean = Config.jobs.postCelebrationJob.log; + + constructor( + private client: Client, + private userRepo: UserRepo, + private combinedRepo: CombinedRepo, + private messageService: MessageService, + private roleService: RoleService, + private subscriptionService: SubscriptionService + ) {} + + public async run(): Promise { + let now = moment(); + let today = moment().format('MM-DD'); + let tomorrow = moment().add(1, 'day').format('MM-DD'); + let yesterday = moment().subtract(1, 'day').format('MM-DD'); + + // Get a user data list of all POSSIBLE birthday events, this includes birthday role, message AND role take. + // Do to timezones and custom message time this can range by a day, thus we get 3 days worth of birthdays for each check + let birthdayUserData: UserData[] = [ + ...(await this.userRepo.getUsersWithBirthday(today)), + ...(await this.userRepo.getUsersWithBirthday(tomorrow)), + ...(await this.userRepo.getUsersWithBirthday(yesterday)), + ]; + + if ( + !TimeUtils.isLeap(now.year()) && + (today === '03-01' || tomorrow === '03-01' || yesterday === '03-01') + ) { + // Add leap year birthdays to list + birthdayUserData.push(...(await this.userRepo.getUsersWithBirthday('02-29'))); + } + // Collection of guilds + let guildCache = this.client.guilds.cache; + + // String of guild ids who have an active subscription to birthday bot premium + // TODO: Update APS to allow us the get all active subscribers so we can initialize this array + let premiumGuildIds: string[] = Config.payments.enabled + ? (await this.subscriptionService.getAllSubscription('premium-1')) + .filter(g => g.service) + .map(g => g.subscriber) + : guildCache.map(g => g.id); + + // Get list of guilds the client is connected to + let discordIds = guildCache.map(guild => guild.id); + + // Get the data from the database + let guildCelebrationDatas = CelebrationUtils.convertCelebrationData( + await this.combinedRepo.GetRawCelebrationData(discordIds) + ); + + // List of members with a birthday today + let birthdayMessageGuildMembers: GuildMember[] = []; + + // This will be the array of GuildMembers who have an anniversary this hour + let memberAnniversaryMessageGuildMembers: GuildMember[] = []; + + // This will be the array of guilds with server anniversaries today + let guildsWithAnniversaryMessage: Guild[] = []; + + // Message service will take in a list of birthdayGuildMembers objects, a list of guild members with an anniversary today, and a list of guilds with an anniversary today + // To get these we will: + // --Birthday GuildMembers: + // ----Get users with a birthday today, tomorrow, and yesterday from the database + // ----Loop through the guilds, fetch each guild's members, and figure out if we are using the server or user timezone + // ----Take the user list and filter for using the member list to get a guildMember list + // ----Filter that guildMember list to those with birthdays today and who are not in the blacklist + // --Member Anniversary GuildMembers: + // ----Using the unfiltered guildMember List we simply filter it for those who have their member anniversary today, tomorrow, or yesterday + // ----Then filter it again to check for those who are this hour (based on the timezone settings) + // ------This is more tricky, you don't have to have your birthday set for this. + // --Server Anniversary Guilds: + // ----Simply check if this guild has a server anniversary today, tomorrow, or yesterday + // ----Then check for the hour based on the timezone of the server + + // This will be the array of GuildMembers who are able to get the birthday role + let addBirthdayRoleGuildMembers: GuildMember[] = []; + + // This will be the array of GuildMembers who are able to have the birthday role removed + let removeBirthdayRoleGuildMembers: GuildMember[] = []; + + // This will be the array of GuildMembers who are able to get anniversary roles + let anniversaryRoleGuildMembers: GuildMember[] = []; + + for (let guild of guildCache.array()) { + let hasPremium = premiumGuildIds.includes(guild.id); + let guildMembers: Collection = guild.members.cache; + let beforeCacheSize = guild.members.cache.size; + + if (Math.abs(guild.memberCount - beforeCacheSize) > 1) { + try { + guildMembers = await guild.members.fetch(); + } catch (error) { + guildMembers = guild.members.cache; + // TODO: Update Logs + Logger.error( + Logs.error.celebrationJobForGuild + .replace('{GUILD_ID}', guild.id) + .replace('{GUILD_NAME}', guild.name) + .replace('{MEMBER_COUNT}', guild.memberCount.toLocaleString()) + .replace( + '{MEMBER_CACHE_BEFORE_COUNT}', + beforeCacheSize.toLocaleString() + ) + .replace( + '{MEMBER_CACHE_AFTER_COUNT}', + guild.members.cache.size.toLocaleString() + ), + error + ); + } + } + + // Get the guildData for this guild + let guildData = guildCelebrationDatas.find( + data => data.guildData.GuildDiscordId === guild.id + )?.guildData; + + // Get the blacklist for this guild + let blacklist = guildCelebrationDatas.find( + data => data.guildData.GuildDiscordId === guild.id + )?.blacklistedMembers; + + // We now have our list of guildMembers + + // Get a list of members with a birthday today (by using either the user or server timezone) + let membersWithBirthdayToday = guildMembers + .filter( + member => + CelebrationUtils.isBirthdayToday( + birthdayUserData.find(data => data.UserDiscordId === member.id), + guildData + ) && !blacklist.map(data => data.UserDiscordId).includes(member.id) + ) + .array(); + + // Only put those who need the birthday message (based on the timezone and hour) into birthdayMessageGuildMembers + birthdayMessageGuildMembers = birthdayMessageGuildMembers.concat( + membersWithBirthdayToday.filter(member => + CelebrationUtils.needsBirthdayMessage( + birthdayUserData.find(data => data.UserDiscordId === member.id), + guildData + ) + ) + ); + + // We now have the full, filtered, list of birthdayMessageGuildMembers + + // Only put those who need the birthday role (based on the timezone and hour) into addBirthdayRoleGuildMembers + addBirthdayRoleGuildMembers = addBirthdayRoleGuildMembers.concat( + membersWithBirthdayToday.filter(member => + CelebrationUtils.needsBirthdayRoleAdded( + birthdayUserData.find(data => data.UserDiscordId === member.id), + guildData + ) + ) + ); + + // We now have the full, filtered, list of addBirthdayRoleGuildMembers + + // Only put those who need the birthday role removed (based on the timezone and hour) into removeBirthdayRoleGuildMembers + removeBirthdayRoleGuildMembers = removeBirthdayRoleGuildMembers.concat( + membersWithBirthdayToday.filter(member => + CelebrationUtils.needsBirthdayRoleRemoved( + birthdayUserData.find(data => data.UserDiscordId === member.id), + guildData + ) + ) + ); + + // We now have the full, filtered, list of removeBirthdayRoleGuildMembers + + // Next lets get the list of guild members who are eligible for the birthday role + + // Next lets find the list of members with anniversaries today + let anniversaryMembers = guildMembers + .filter(member => CelebrationUtils.isMemberAnniversaryMessage(member, guildData)) + .array(); + + // Only add these members to the anniversaryRolesGuildMembers array if the server has premium + if (hasPremium) { + anniversaryRoleGuildMembers = + anniversaryRoleGuildMembers.concat(anniversaryMembers); + } + + // All servers get member anniversary messages so add them regardless of if they have premium + memberAnniversaryMessageGuildMembers = + memberAnniversaryMessageGuildMembers.concat(anniversaryMembers); + + // We now have the full, filtered, list of memberAnniversaryMessageGuildMembers + + // Next lets find the list of guildsWithAnniversaryMessage + if (CelebrationUtils.isServerAnniversaryMessage(guild, guildData)) + guildsWithAnniversaryMessage.push(guild); + + // We now have the full, filtered, list of guildsWithAnniversaryMessage + } + + // We should now have the filtered lists of birthdayMessageGuildMembers, memberAnniversaryMessageGuildMembers, and guildsWithAnniversaryMessage + // as well as the filtered lists of addBirthdayRoleGuildMembers, removeBirthdayRoleGuildMembers, and anniversaryRoleGuildMembers + // This means we should be able to call the MessageService & the RoleService + + this.messageService.run( + this.client, + guildCelebrationDatas, + birthdayMessageGuildMembers, + memberAnniversaryMessageGuildMembers, + guildsWithAnniversaryMessage, + premiumGuildIds + ); + + this.roleService.run( + this.client, + guildCelebrationDatas, + addBirthdayRoleGuildMembers, + removeBirthdayRoleGuildMembers, + anniversaryRoleGuildMembers, + premiumGuildIds + ); + } + + public start(): void { + // TODO: change logs + schedule.scheduleJob(this.schedule, async () => { + try { + Logger.info(Logs.info.birthdayJobStarted); + await this.run(); + Logger.info(Logs.info.completedBirthdayJob); + } catch (error) { + Logger.error(Logs.error.birthdayJob, error); + } + }); + } +} diff --git a/src/jobs/index.ts b/src/jobs/index.ts index 976c11d1..c58a29cb 100644 --- a/src/jobs/index.ts +++ b/src/jobs/index.ts @@ -1,3 +1,3 @@ export { Job } from './job'; -export { PostBirthdaysJob } from './post-birthdays-job'; export { UpdateServerCountJob } from './update-server-count-job'; +export { CelebrationJob } from './celebration-job'; diff --git a/src/jobs/job.ts b/src/jobs/job.ts index 9df8c8c5..54e47c08 100644 --- a/src/jobs/job.ts +++ b/src/jobs/job.ts @@ -3,4 +3,4 @@ export interface Job { log: boolean; schedule: string; run(): Promise; -} \ No newline at end of file +} diff --git a/src/jobs/post-birthdays-job.ts b/src/jobs/post-birthdays-job.ts deleted file mode 100644 index e055254a..00000000 --- a/src/jobs/post-birthdays-job.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { BdayUtils, MathUtils, TimeUtils } from '../utils'; -import { BirthdayService, Logger } from '../services'; -import { BlacklistRepo, GuildRepo, UserRepo } from '../services/database/repos'; -import { Client, Collection, Guild, GuildMember } from 'discord.js'; - -import { Job } from './job'; -import { UserData } from '../models/database'; -import moment from 'moment'; -import schedule from 'node-schedule'; - -let Config = require('../../config/config.json') -let Logs = require('../../lang/logs.json'); - -export class PostBirthdaysJob implements Job { - public name = 'Post Birthday'; - public schedule: string = Config.jobs.postBirthdays.schedule; - public log: boolean = Config.jobs.postBirthdays.log; - public interval: number = Config.jobs.postBirthdays.interval; - - constructor( - private client: Client, - private guildRepo: GuildRepo, - private userRepo: UserRepo, - private blacklistRepo: BlacklistRepo, - private birthdayService: BirthdayService - ) { } - - public async run(): Promise { - let now = moment(); - let today = moment().format('MM-DD'); - let tomorrow = moment().add(1, 'day').format('MM-DD'); - let yesterday = moment().subtract(1, 'day').format('MM-DD'); - - // Get a user data list of all POSSIBLE birthday events, this includes birthday role, message AND role take. - // Do to timezones and custom message time this can range by a day, thus we get 3 days worth of birthdays for each check - let userDatas: UserData[] = [ - ...(await this.userRepo.getUsersWithBirthday(today)), - ...(await this.userRepo.getUsersWithBirthday(tomorrow)), - ...(await this.userRepo.getUsersWithBirthday(yesterday)), - ]; - - if ( - !MathUtils.isLeap(now.year()) && - (today === '02-28' || tomorrow === '02-28' || yesterday === '02-28') - ) { - // Add leap year birthdays to list - userDatas.push(...(await this.userRepo.getUsersWithBirthday('02-29'))); - } - - // Remove people whose birthday isn't today (isBirthday() considers timezones) - userDatas = userDatas.filter(userData => BdayUtils.isBirthday(userData)); - - // Get list of guilds the client is connected to - let discordIds = this.client.guilds.cache.map(guild => guild.id); - - // Get guild data from the database - let guildDatas = await this.guildRepo.getGuilds(discordIds); - Logger.info( - Logs.info.birthdayJobGuildCount.replace( - '{GUILD_COUNT}', - guildDatas.length.toLocaleString() - ) - ); - - let promises = []; - - for (let guildData of guildDatas) { - // Resolve Guild - let guild: Guild; - try { - guild = this.client.guilds.resolve(guildData.GuildDiscordId); - if (!guild) continue; - } catch (error) { - Logger.error( - Logs.error.resolveGuild - .replace('{GUILD_ID}', guildData.GuildDiscordId) - .replace('{GUILD_NAME}', guild.name), - error - ); - continue; - } - - try { - let members: Collection = guild.members.cache; - let beforeCacheSize = guild.members.cache.size; - - if (Math.abs(guild.memberCount - beforeCacheSize) > 1) { - try { - members = await guild.members.fetch(); - } catch (error) { - members = guild.members.cache; - Logger.error( - Logs.error.birthdayService - .replace('{GUILD_ID}', guildData.GuildDiscordId) - .replace('{GUILD_NAME}', guild.name) - .replace('{MEMBER_COUNT}', guild.memberCount.toLocaleString()) - .replace( - '{MEMBER_CACHE_BEFORE_COUNT}', - beforeCacheSize.toLocaleString() - ) - .replace( - '{MEMBER_CACHE_AFTER_COUNT}', - guild.members.cache.size.toLocaleString() - ), - error - ); - } - } - - // Get a list of memberIds - let memberIds = members.map(member => member.id); - - // Get the blacklist data for this guild - let blacklistData = await this.blacklistRepo.getBlacklist(guild.id); - - // Remove members who are not apart of this guild and who are in the birthday blacklist - let memberUserDatas = userDatas.filter( - userData => - memberIds.includes(userData.UserDiscordId) && - !blacklistData.blacklist - .map(data => data.UserDiscordId) - .includes(userData.UserDiscordId) - ); - - promises.push( - this.birthdayService - .celebrateBirthdays(guild, guildData, memberUserDatas, members) - .catch(error => { - // send userRoleList and messageList - Logger.error( - Logs.error.celebrateBirthday - .replace('{GUILD_NAME}', guild.name) - .replace('{GUILD_ID}', guild.id), - error - ); - }) - ); - } catch (error) { - Logger.error( - Logs.error.birthdayService - .replace('{GUILD_ID}', guildData.GuildDiscordId) - .replace('{GUILD_NAME}', guild.name) - .replace('{MEMBER_COUNT}', guild.memberCount.toLocaleString()) - .replace('{MEMBER_CACHE_COUNT}', guild.members.cache.size.toLocaleString()), - error - ); - continue; - } - - await TimeUtils.sleep(this.interval); - } - - // Wait for all birthday celebrations to finish - await Promise.allSettled(promises); - } - - public start(): void { - schedule.scheduleJob(this.schedule, async () => { - try { - Logger.info(Logs.info.birthdayJobStarted); - await this.run(); - Logger.info(Logs.info.completedBirthdayJob); - } catch (error) { - Logger.error(Logs.error.birthdayJob, error); - } - }); - } -} diff --git a/src/jobs/update-server-count-job.ts b/src/jobs/update-server-count-job.ts index 941bab7f..b49ca9e3 100644 --- a/src/jobs/update-server-count-job.ts +++ b/src/jobs/update-server-count-job.ts @@ -1,10 +1,10 @@ -import { ShardingManager } from 'discord.js'; -import schedule from 'node-schedule'; +import { HttpService, Logger } from '../services'; import { BotSite } from '../models/config-models'; -import { HttpService, Logger } from '../services'; -import { ShardUtils } from '../utils'; import { Job } from './job'; +import { ShardUtils } from '../utils'; +import { ShardingManager } from 'discord.js'; +import schedule from 'node-schedule'; let Config = require('../../config/config.json'); let BotSites: BotSite[] = require('../../config/bot-sites.json'); @@ -22,7 +22,6 @@ export class UpdateServerCountJob implements Job { public async run(): Promise { let serverCount = await ShardUtils.serverCount(this.shardManager); - await this.shardManager.broadcastEval(` (async () => { return await this.setPresence('STREAMING', 'bdays to ${serverCount.toLocaleString()} servers', '${ diff --git a/src/manager.ts b/src/manager.ts index ee0ae4dc..7f79d5d5 100644 --- a/src/manager.ts +++ b/src/manager.ts @@ -1,13 +1,13 @@ import { Shard, ShardingManager } from 'discord.js'; import { JobService, Logger } from './services'; -import { Job } from './jobs'; -let Logs = require('../lang/logs.json'); let Config = require('../config/config.json'); +let Debug = require('../config/debug.json'); +let Logs = require('../lang/logs.json'); export class Manager { - constructor(private shardManager: ShardingManager, private jobsService: JobService) { } + constructor(private shardManager: ShardingManager, private jobsService: JobService) {} public async start(): Promise { this.registerListeners(); @@ -26,11 +26,16 @@ export class Manager { Config.sharding.spawnDelay * 1000, Config.sharding.spawnTimeout * 1000 ); + Logger.info(Logs.info.allShardsSpawned); } catch (error) { Logger.error(Logs.error.spawnShard, error); return; } + if (Debug.dummyMode.enabled) { + return; + } + this.jobsService.start(); } diff --git a/src/models/database/blacklisted-models.ts b/src/models/database/blacklisted-models.ts index 68e384ef..327bb982 100644 --- a/src/models/database/blacklisted-models.ts +++ b/src/models/database/blacklisted-models.ts @@ -11,6 +11,7 @@ export class Blacklisted { } export class Blacklist { + GuildId: number; UserDiscordId: string; Position: number; } diff --git a/src/models/database/custom-messages-models.ts b/src/models/database/custom-messages-models.ts index aa03da7c..b93a0164 100644 --- a/src/models/database/custom-messages-models.ts +++ b/src/models/database/custom-messages-models.ts @@ -11,7 +11,11 @@ export class CustomMessages { } export class CustomMessage { + GuildId: number; Message: string; UserDiscordId: string; + Type: string; Position: number; + Color: string; + Embed: number; } diff --git a/src/models/database/guild-celebration-models.ts b/src/models/database/guild-celebration-models.ts new file mode 100644 index 00000000..694ad87e --- /dev/null +++ b/src/models/database/guild-celebration-models.ts @@ -0,0 +1,13 @@ +import { CustomMessage, TrustedRole } from '.'; + +import { Blacklist } from './blacklisted-models'; +import { GuildData } from './guild-models'; +import { MemberAnniversaryRole } from './member-anniversary-role-models'; + +export class GuildCelebrationData { + guildData: GuildData; + customMessages: CustomMessage[]; + blacklistedMembers: Blacklist[]; + trustedRoles: TrustedRole[]; + anniversaryRoles: MemberAnniversaryRole[]; +} diff --git a/src/models/database/guild-models.ts b/src/models/database/guild-models.ts index aa7c0179..c26c6f96 100644 --- a/src/models/database/guild-models.ts +++ b/src/models/database/guild-models.ts @@ -1,13 +1,21 @@ export interface GuildData { + GuildId: number; GuildDiscordId: string; BirthdayChannelDiscordId: string; BirthdayRoleDiscordId: string; - TrustedRoleDiscordId: string; BirthdayMasterRoleDiscordId: string; - MentionSetting: string; - MessageTime: number; + BirthdayMentionSetting: string; + MemberAnniversaryChannelDiscordId: string; + ServerAnniversaryChannelDiscordId: string; + MemberAnniversaryMentionSetting: string; + ServerAnniversaryMentionSetting: string; + BirthdayMessageTime: number; + MemberAnniversaryMessageTime: number; + ServerAnniversaryMessageTime: number; TrustedPreventsRole: number; TrustedPreventsMessage: number; - UseEmbed: number; - MessageEmbedColor: string; + NameFormat: string; + DefaultTimezone: string; + UseTimezone: string; + RequireAllTrustedRoles: number; } diff --git a/src/models/database/index.ts b/src/models/database/index.ts index d9cf7992..da0a7754 100644 --- a/src/models/database/index.ts +++ b/src/models/database/index.ts @@ -6,3 +6,7 @@ export { UserDataResults } from './user-data-results-models'; export { UserData } from './user-models'; export { Vote } from './vote-models'; export { Blacklisted } from './blacklisted-models'; +export { TrustedRole } from './trusted-role-models'; +export { MemberAnniversaryRole } from './member-anniversary-role-models'; +export { RawGuildCelebrationData } from './raw-guild-celebration-models'; +export { GuildCelebrationData } from './guild-celebration-models'; diff --git a/src/models/database/member-anniversary-role-models.ts b/src/models/database/member-anniversary-role-models.ts new file mode 100644 index 00000000..20683522 --- /dev/null +++ b/src/models/database/member-anniversary-role-models.ts @@ -0,0 +1,18 @@ +import { StatsData } from './stats-models'; + +export class MemberAnniversaryRoles { + memberAnniversaryRoles: MemberAnniversaryRole[]; + stats: StatsData; + + constructor(memberAnniversaryRoleRows: MemberAnniversaryRole[], statsRow: StatsData) { + this.memberAnniversaryRoles = memberAnniversaryRoleRows; + this.stats = statsRow; + } +} + +export class MemberAnniversaryRole { + GuildId: number; + MemberAnniversaryRoleDiscordId: string; + Year: number; + Position: number; +} diff --git a/src/models/database/raw-guild-celebration-models.ts b/src/models/database/raw-guild-celebration-models.ts new file mode 100644 index 00000000..369be907 --- /dev/null +++ b/src/models/database/raw-guild-celebration-models.ts @@ -0,0 +1,15 @@ +import { CustomMessage, TrustedRole } from '.'; + +import { Blacklist } from './blacklisted-models'; +import { GuildData } from './guild-models'; +import { MemberAnniversaryRole } from './member-anniversary-role-models'; + +export class RawGuildCelebrationData { + constructor( + public guildDatas: GuildData[], + public customMessages: CustomMessage[], + public blacklistedMembers: Blacklist[], + public trustedRoles: TrustedRole[], + public anniversaryRoles: MemberAnniversaryRole[] + ) {} +} diff --git a/src/models/database/trusted-role-models.ts b/src/models/database/trusted-role-models.ts new file mode 100644 index 00000000..b6757a60 --- /dev/null +++ b/src/models/database/trusted-role-models.ts @@ -0,0 +1,17 @@ +import { StatsData } from './stats-models'; + +export class TrustedRoles { + trustedRoles: TrustedRole[]; + stats: StatsData; + + constructor(trustedRoleRows: TrustedRole[], statsRow: StatsData) { + this.trustedRoles = trustedRoleRows; + this.stats = statsRow; + } +} + +export class TrustedRole { + GuildId: number; + TrustedRoleDiscordId: string; + Position: number; +} diff --git a/src/models/enums/index.ts b/src/models/enums/index.ts new file mode 100644 index 00000000..de386462 --- /dev/null +++ b/src/models/enums/index.ts @@ -0,0 +1 @@ +export { Language, LangCode } from './language'; diff --git a/src/models/enums/language.ts b/src/models/enums/language.ts new file mode 100644 index 00000000..dc10ff10 --- /dev/null +++ b/src/models/enums/language.ts @@ -0,0 +1,28 @@ +import { Lang } from '../../services'; + +export enum LangCode { + EN_US = 'en-US', +} + +export class Language { + public static keyword(langCode: LangCode): RegExp { + return Lang.getRegex('meta.language', langCode); + } + + public static regex(langCode: LangCode): RegExp { + return Lang.getRegex('meta.language', langCode); + } + + public static displayName(langCode: LangCode): string { + return Lang.getRef('meta.languageDisplay', langCode); + } + + public static find(input: string): LangCode { + for (let key of Object.keys(LangCode)) { + let langCode: LangCode = LangCode[key]; + if (this.regex(langCode).test(input)) { + return langCode; + } + } + } +} diff --git a/src/models/index.ts b/src/models/index.ts new file mode 100644 index 00000000..bee75b02 --- /dev/null +++ b/src/models/index.ts @@ -0,0 +1,7 @@ +export { BotSite } from './config-models'; +export { + SubscriptionLink, + SubscriptionStatus, + PlanName, + SubscriptionStatusName, +} from './subscription-models'; diff --git a/src/services/birthday-service.ts b/src/services/birthday-service.ts deleted file mode 100644 index 27507e39..00000000 --- a/src/services/birthday-service.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { ActionUtils, BdayUtils, FormatUtils, MessageUtils, PermissionUtils } from '../utils'; -import { Collection, Guild, GuildMember, MessageEmbed, Role, TextChannel } from 'discord.js'; -import { GuildData, UserData } from '../models/database'; - -import { ColorUtils } from '../utils/color-utils'; -import { CustomMessageRepo } from './database/repos'; -import { PlanName } from '../models/subscription-models'; -import { SubscriptionService } from './subscription-service'; - -let Config = require('../../config/config.json'); - -export class BirthdayService { - constructor( - private customMessageRepo: CustomMessageRepo, - private subscriptionService: SubscriptionService - ) {} - - public async celebrateBirthdays( - guild: Guild, - guildData: GuildData, - userDatas: UserData[], - members: Collection, - isTest: boolean = false, - testChannel: TextChannel = null - ): Promise { - let birthdayChannel: TextChannel; - let birthdayRole: Role; - let trustedRole: Role; - - let trustedCheckRole = false; - let trustedCheckMessage = false; - - let testingEmbed = new MessageEmbed() - .setThumbnail(guild.iconURL()) - .setTitle('Birthday Event Test - [BETA]') - .setFooter( - 'This is the info from your latest birthday event test.', - guild.client.user.avatarURL() - ) - .setTimestamp() - .setColor(Config.colors.default); - - try { - birthdayChannel = guild.channels.resolve( - guildData.BirthdayChannelDiscordId - ) as TextChannel; - } catch (error) { - // No Birthday Channel - } - try { - birthdayRole = guild.roles.resolve(guildData.BirthdayRoleDiscordId); - } catch (error) { - // No Birthday Channel - } - try { - trustedRole = guild.roles.resolve(guildData.TrustedRoleDiscordId); - } catch (error) { - // No Birthday Channel - } - - if (!guild.me.hasPermission('MANAGE_ROLES')) birthdayRole = null; // If I can't edit roles the birthday Role is essentially null since I can't give the role - if (birthdayChannel && !PermissionUtils.canSend(birthdayChannel)) birthdayChannel = null; // If I can't message in the birthday channel it is essentially null since I can't send the birthday message - - if (!birthdayRole && !birthdayChannel) { - // Skip guild - if (!isTest) return; - } - - let birthdayMessageUsers: GuildMember[] = []; - - let preventMessage = guildData.TrustedPreventsMessage; - let preventRole = guildData.TrustedPreventsRole; - - for (let user of userDatas) { - let member: GuildMember; - try { - member = guild.members.resolve(user.UserDiscordId); - } catch (error) { - // Can't find member? - continue; - } - - if (!member) continue; - - if ( - trustedRole && - preventMessage && - preventRole && - !member.roles.cache.has(trustedRole.id) - ) { - // For test cases - trustedCheckMessage = true; - trustedCheckRole = true; - continue; - } - - // Birthday role is actively given, no time check needed! - if (birthdayRole) { - if (!(trustedRole && preventRole && !member.roles.cache.has(trustedRole.id))) { - ActionUtils.giveRole(member, birthdayRole); - } else { - // For test cases - trustedCheckRole = true; - } - } - - if ( - (isTest || BdayUtils.isTimeForBirthdayMessage(guildData.MessageTime, user)) && - birthdayChannel - ) { - if (!(trustedRole && preventMessage && !member.roles.cache.has(trustedRole.id))) { - birthdayMessageUsers.push(member); - } else { - // For test cases - trustedCheckMessage = true; - } - } - } - - // get a string array of the userData keys - let userDataKeys = userDatas.map(userData => userData.UserDiscordId); - - // Filter OUT anyone whose in userData (whose birthday is today) (This list will then have the birthday role removed since it isn't their birthday) - members = members.filter(member => !userDataKeys.includes(member.id)); - - // Birthday role is actively taken, no time check needed! - if (birthdayRole) { - members - .filter(member => member.roles.cache.has(birthdayRole.id)) - .forEach(member => { - ActionUtils.removeRole(member, birthdayRole); - }); - } - - // Birthday Message - if (birthdayMessageUsers.length > 0) { - let hasPremium = Config.payments.enabled - ? await this.subscriptionService.hasService(PlanName.premium1, guild.id) - : false; - let globalMessages = await this.customMessageRepo.getCustomMessages(guild.id); - - // Get a list of custom user-specific messages - let userMessages = await this.customMessageRepo.getCustomUserMessages(guild.id); - - // Define variable - let usersWithSpecificMessage: GuildMember[]; - - // IF THEY HAVE PREMIUM - if (hasPremium) { - // Guild Member list of people with a user-specific custom birthday message - usersWithSpecificMessage = birthdayMessageUsers.filter(member => - userMessages.customMessages - .map(message => message.UserDiscordId) - .includes(member.id) - ); - - // Remove all users who have a user-specific custom birthday message - birthdayMessageUsers = birthdayMessageUsers.filter( - member => !usersWithSpecificMessage.includes(member) - ); - } - - // Find mentioned role - let mentionSetting: string; - let roleInput: Role = guild.roles.resolve(guildData.MentionSetting); - - if (!roleInput || roleInput.guild.id !== guild.id) { - if ( - guildData.MentionSetting.toLowerCase() === 'everyone' || - guildData.MentionSetting.toLowerCase() === 'here' - ) { - mentionSetting = '@' + guildData.MentionSetting; - } - } else { - mentionSetting = roleInput.toString(); - } - - // Send the mention setting - if (mentionSetting) MessageUtils.send(birthdayChannel, mentionSetting); - - let color = guildData.MessageEmbedColor === '0' ? Config.colors.default : null; - - color = - !color && hasPremium - ? '#' + ColorUtils.findHex(guildData.MessageEmbedColor) ?? Config.colors.default - : Config.colors.default; - - // Create and send the default or the global custom birthday message that was chosen for those without a user-specific custom birthday message - if (birthdayMessageUsers.length > 0) { - let userList = FormatUtils.joinWithAnd( - birthdayMessageUsers.map(user => user.toString()) - ); - let message = BdayUtils.randomMessage(globalMessages, hasPremium) - .split('@Users') - .join(userList) - .split('') - .join(userList); - - let embed = new MessageEmbed().setDescription(message).setColor(color); - await MessageUtils.send(birthdayChannel, guildData.UseEmbed ? embed : message); - } - - if (hasPremium) { - // Now, loop through the members with a user-specific custom birthday message - for (let member of usersWithSpecificMessage) { - // Get their birthday message - let message = userMessages.customMessages - .find(message => message.UserDiscordId === member.user.id) - .Message.split('@Users') - .join(member.toString()) - .split('') - .join(member.toString()); - - // Create it and send it - let embed = new MessageEmbed().setDescription(message).setColor(color); - await MessageUtils.send(birthdayChannel, guildData.UseEmbed ? embed : message); - } - } - } - - if (isTest) { - let member = guild.members.resolve(userDatas[0].UserDiscordId); - if (trustedRole) { - testingEmbed.addField( - 'User Has Trusted Role', - `${ - member.roles.cache.has(trustedRole.id) - ? `${Config.emotes.confirm} Yes.` - : `${Config.emotes.deny} No.` - }`, - true - ); - } - testingEmbed.addField( - 'Birthday Role', - `${ - birthdayRole - ? `${Config.emotes.confirm} Correctly set.` - : `${Config.emotes.deny} Not set or is a deleted role.` - }`, - true - ); - if (trustedRole) { - testingEmbed.addField( - 'Trusted Prevents Role', - `${ - !trustedCheckRole - ? `${Config.emotes.confirm} Passed.` - : `${Config.emotes.deny} Trusted role/settings prevented the birthday role.` - }`, - true - ); - } - testingEmbed.addField( - 'Birthday Channel', - `${ - birthdayChannel - ? `${Config.emotes.confirm} Correctly set.` - : `${Config.emotes.deny} Not set or is a deleted channel.` - }`, - true - ); - if (trustedRole) { - testingEmbed.addField( - 'Trusted Prevents Message', - `${ - !trustedCheckMessage - ? `${Config.emotes.confirm} Passed.` - : `${Config.emotes.deny} Trusted role/settings prevented the birthday message.` - }`, - true - ); - } - testingEmbed.addField( - 'Birthday Blacklist', - `${Config.emotes.confirm} Member is not blacklisted.`, - true - ); - - testingEmbed.setDescription( - 'Below are the checks to ensure your settings are correct for the birthday event.\n\nIf the checks are passed and either the birthday message and/or birthday role were not given ' + - `when they should have then ${guild.client.user.toString()} most likely did not have the correct permissions. [(?)](${ - Config.links.docs - }/faq)\n\nFor more help: [Join Support Server](${Config.links.support})` - ); - - if (testChannel) { - await MessageUtils.send(testChannel, testingEmbed); - } - } - } -} diff --git a/src/services/database/data-access.ts b/src/services/database/data-access.ts index 2e93c271..758dc33a 100644 --- a/src/services/database/data-access.ts +++ b/src/services/database/data-access.ts @@ -1,6 +1,6 @@ import mysql, { ConnectionConfig, Pool } from 'mysql'; -import { SQLUtils } from '../../utils'; +import { SqlUtils } from '../../utils'; export class DataAccess { private pool: Pool; @@ -10,7 +10,7 @@ export class DataAccess { } public async executeProcedure(name: string, params: any[]): Promise { - let sql = SQLUtils.createProcedureSql(name, params); + let sql = SqlUtils.createProcedureSql(name, params); return new Promise((resolve, reject) => { // let startTime = Date.now(); this.pool.query(sql, (error, results) => { diff --git a/src/services/database/procedure.ts b/src/services/database/procedure.ts index cbb02849..d60b6257 100644 --- a/src/services/database/procedure.ts +++ b/src/services/database/procedure.ts @@ -4,29 +4,49 @@ export enum Procedure { Blacklist_Clear = 'Blacklist_Clear', Blacklist_Get = 'Blacklist_Get', Blacklist_GetList = 'Blacklist_GetList', - CustomMessages_Add = 'CustomMessages_Add', - CustomMessages_Remove = 'CustomMessages_Remove', - CustomMessages_RemoveUser = 'CustomMessages_RemoveUser', - CustomMessages_Clear = 'CustomMessages_Clear', - CustomMessages_Get = 'CustomMessages_Get', - CustomMessages_GetUser = 'CustomMessages_GetUser', - CustomMessages_GetList = 'CustomMessages_GetList', - CustomMessages_GetUserList = 'CustomMessages_GetUserList', + Message_Add = 'Message_Add', + Message_Remove = 'Message_Remove', + Message_RemoveUser = 'Message_RemoveUser', + Message_Clear = 'Message_Clear', + Message_ClearUser = 'Message_ClearUser', + Message_Get = 'Message_Get', + Message_GetUser = 'Message_GetUser', + Message_GetList = 'Message_GetList', + Message_GetUserList = 'Message_GetUserList', Guild_Get = 'Guild_Get', Guild_GetAll = 'Guild_GetAll', Guild_UpdateBirthdayChannel = 'Guild_UpdateBirthdayChannel', + Guild_UpdateMemberAnniversaryChannel = 'Guild_UpdateMemberAnniversaryChannel', + Guild_UpdateServerAnniversaryChannel = 'Guild_UpdateServerAnniversaryChannel', Guild_UpdateBirthdayRole = 'Guild_UpdateBirthdayRole', - Guild_UpdateTrustedRole = 'Guild_UpdateTrustedRole', Guild_UpdateBirthdayMasterRole = 'Guild_UpdateBirthdayMasterRole', - Guild_UpdateMessageTime = 'Guild_UpdateMessageTime', - Guild_UpdateMentionSetting = 'Guild_UpdateMentionSetting', + Guild_UpdateBirthdayMessageTime = 'Guild_UpdateBirthdayMessageTime', + Guild_UpdateMemberAnniversaryTime = 'Guild_UpdateMemberAnniversaryTime', + Guild_UpdateServerAnniversaryTime = 'Guild_UpdateServerAnniversaryTime', + Guild_UpdateBirthdayMentionSetting = 'Guild_UpdateBirthdayMentionSetting', + Guild_UpdateMemberAnniversaryMentionSetting = 'Guild_UpdateMemberAnniversaryMentionSetting', + Guild_UpdateServerAnniversaryMentionSetting = 'Guild_UpdateServerAnniversaryMentionSetting', + Guild_UpdateNameFormat = 'Guild_UpdateNameFormat', + Guild_UpdateDefaultTimezone = 'Guild_UpdateDefaultTimezone', + Guild_UpdateUseTimezone = 'Guild_UpdateUseTimezone', Guild_UpdateUseEmbed = 'Guild_UpdateUseEmbed', Guild_UpdateMessageEmbedColor = 'Guild_UpdateMessageEmbedColor', Guild_UpdateTrustedPreventsMessage = 'Guild_UpdateTrustedPreventsMessage', Guild_UpdateTrustedPreventsRole = 'Guild_UpdateTrustedPreventsRole', - Guild_SetupMessage = 'Guild_SetupMessage', + Guild_UpdateRequireAllTrustedRoles = 'Guild_UpdateRequireAllTrustedRoles', + Guild_SetupAnniversary = 'Guild_SetupAnniversary', Guild_SetupTrusted = 'Guild_SetupTrusted', Guild_AddOrUpdate = 'Guild_AddOrUpdate', + TrustedRole_Add = 'TrustedRole_Add', + TrustedRole_Clear = 'TrustedRole_Clear', + TrustedRole_Get = 'TrustedRole_Get', + TrustedRole_GetList = 'TrustedRole_GetList', + TrustedRole_Remove = 'TrustedRole_Remove', + MemberAnniversaryRole_Add = 'MemberAnniversaryRole_Add', + MemberAnniversaryRole_Clear = 'MemberAnniversaryRole_Clear', + MemberAnniversaryRole_Get = 'MemberAnniversaryRole_Get', + MemberAnniversaryRole_GetList = 'MemberAnniversaryRole_GetList', + MemberAnniversaryRole_Remove = 'MemberAnniversaryRole_Remove', User_AddOrUpdate = 'User_AddOrUpdate', User_Get = 'User_Get', User_GetAll = 'User_GetAll', @@ -38,4 +58,5 @@ export enum Procedure { User_GetTotalCount = 'User_GetTotalCount', User_GetBirthdaysTodayCount = 'User_GetBirthdaysTodayCount', User_GetBirthdaysThisMonthCount = 'User_GetBirthdaysThisMonthCount', + Combined_GetRawCelebrationData = 'Combined_GetRawCelebrationData', } diff --git a/src/services/database/repos/blacklist-repo.ts b/src/services/database/repos/blacklist-repo.ts index 526a9436..896a8e98 100644 --- a/src/services/database/repos/blacklist-repo.ts +++ b/src/services/database/repos/blacklist-repo.ts @@ -2,7 +2,7 @@ import { Blacklisted } from '../../../models/database'; import { DataAccess } from '../data-access'; import { Procedure } from '../procedure'; -import { SQLUtils } from '../../../utils'; +import { SqlUtils } from '../../../utils'; export class BlacklistRepo { constructor(private dataAccess: DataAccess) {} @@ -22,7 +22,7 @@ export class BlacklistRepo { public async getBlacklist(discordId: string): Promise { let results = await this.dataAccess.executeProcedure(Procedure.Blacklist_Get, [discordId]); - let blacklist = SQLUtils.getTable(results, 0); + let blacklist = SqlUtils.getTable(results, 0); return new Blacklisted(blacklist, null); } @@ -37,8 +37,8 @@ export class BlacklistRepo { page, ]); - let blacklistData = SQLUtils.getTable(results, 0); - let stats = SQLUtils.getRow(results, 1, 0); + let blacklistData = SqlUtils.getTable(results, 0); + let stats = SqlUtils.getRow(results, 1, 0); return new Blacklisted(blacklistData, stats); } } diff --git a/src/services/database/repos/combined-repo.ts b/src/services/database/repos/combined-repo.ts new file mode 100644 index 00000000..6843bca7 --- /dev/null +++ b/src/services/database/repos/combined-repo.ts @@ -0,0 +1,27 @@ +import { DataAccess } from '../data-access'; +import { Procedure } from '../procedure'; +import { RawGuildCelebrationData } from '../../../models/database'; +import { SqlUtils } from '../../../utils'; + +export class CombinedRepo { + constructor(private dataAccess: DataAccess) {} + + public async GetRawCelebrationData(discordIds: string[]): Promise { + let results = await this.dataAccess.executeProcedure( + Procedure.Combined_GetRawCelebrationData, + [discordIds.join(',')] + ); + let guildData = SqlUtils.getTable(results, 0); + let customMessages = SqlUtils.getTable(results, 1); + let blacklistedMembers = SqlUtils.getTable(results, 2); + let trustedRoles = SqlUtils.getTable(results, 3); + let anniversaryRoles = SqlUtils.getTable(results, 4); + return new RawGuildCelebrationData( + guildData, + customMessages, + blacklistedMembers, + trustedRoles, + anniversaryRoles + ); + } +} diff --git a/src/services/database/repos/custom-message-repo.ts b/src/services/database/repos/custom-message-repo.ts index 0b5752c0..df8de879 100644 --- a/src/services/database/repos/custom-message-repo.ts +++ b/src/services/database/repos/custom-message-repo.ts @@ -1,7 +1,7 @@ import { CustomMessages } from '../../../models/database'; import { DataAccess } from '../data-access'; import { Procedure } from '../procedure'; -import { SQLUtils } from '../../../utils'; +import { SqlUtils } from '../../../utils'; export class CustomMessageRepo { constructor(private dataAccess: DataAccess) {} @@ -9,77 +9,102 @@ export class CustomMessageRepo { public async addCustomMessage( discordId: string, message: string, - userId: string + userId: string, + type: string, + color: string, + embed: number ): Promise { - await this.dataAccess.executeProcedure(Procedure.CustomMessages_Add, [ + await this.dataAccess.executeProcedure(Procedure.Message_Add, [ discordId, message, userId, + type, + color, + embed, ]); } - public async removeCustomMessage(discordId: string, value: number): Promise { - await this.dataAccess.executeProcedure(Procedure.CustomMessages_Remove, [discordId, value]); + public async removeCustomMessage( + discordId: string, + value: number, + type: string + ): Promise { + await this.dataAccess.executeProcedure(Procedure.Message_Remove, [discordId, value, type]); } - public async removeCustomMessageUser(discordId: string, value: number): Promise { - await this.dataAccess.executeProcedure(Procedure.CustomMessages_RemoveUser, [ + public async removeCustomMessageUser( + discordId: string, + value: number, + type: string + ): Promise { + await this.dataAccess.executeProcedure(Procedure.Message_RemoveUser, [ discordId, value, + type, ]); } - public async clearCustomMessages(discordId: string): Promise { - await this.dataAccess.executeProcedure(Procedure.CustomMessages_Clear, [discordId]); + public async clearCustomMessages(discordId: string, type: string): Promise { + await this.dataAccess.executeProcedure(Procedure.Message_Clear, [discordId, type]); + } + + public async clearCustomUserMessages(discordId: string, type: string): Promise { + await this.dataAccess.executeProcedure(Procedure.Message_ClearUser, [discordId, type]); } - public async getCustomMessages(discordId: string): Promise { - let results = await this.dataAccess.executeProcedure(Procedure.CustomMessages_Get, [ + public async getCustomMessages(discordId: string, type: string): Promise { + let results = await this.dataAccess.executeProcedure(Procedure.Message_Get, [ discordId, + type, ]); - let customMessages = SQLUtils.getTable(results, 0); + let customMessages = SqlUtils.getTable(results, 0); return new CustomMessages(customMessages, null); } - public async getCustomUserMessages(discordId: string): Promise { - let results = await this.dataAccess.executeProcedure(Procedure.CustomMessages_GetUser, [ + public async getCustomUserMessages(discordId: string, type: string): Promise { + let results = await this.dataAccess.executeProcedure(Procedure.Message_GetUser, [ discordId, + type, ]); - let customMessages = SQLUtils.getTable(results, 0); + let customMessages = SqlUtils.getTable(results, 0); return new CustomMessages(customMessages, null); } public async getCustomMessageList( guildId: string, pageSize: number, - page: number + page: number, + type: string ): Promise { - let results = await this.dataAccess.executeProcedure(Procedure.CustomMessages_GetList, [ + let results = await this.dataAccess.executeProcedure(Procedure.Message_GetList, [ guildId, pageSize, page, + type, ]); - let customMessageData = SQLUtils.getTable(results, 0); - let stats = SQLUtils.getRow(results, 1, 0); + let customMessageData = SqlUtils.getTable(results, 0); + let stats = SqlUtils.getRow(results, 1, 0); return new CustomMessages(customMessageData, stats); } public async getCustomMessageUserList( guildId: string, pageSize: number, - page: number + page: number, + type: string ): Promise { - let results = await this.dataAccess.executeProcedure(Procedure.CustomMessages_GetUserList, [ + let results = await this.dataAccess.executeProcedure(Procedure.Message_GetUserList, [ guildId, pageSize, page, + type, ]); - let customMessageData = SQLUtils.getTable(results, 0); - let stats = SQLUtils.getRow(results, 1, 0); + let customMessageData = SqlUtils.getTable(results, 0); + let stats = SqlUtils.getRow(results, 1, 0); return new CustomMessages(customMessageData, stats); } } diff --git a/src/services/database/repos/guild-repo.ts b/src/services/database/repos/guild-repo.ts index 9e925387..0643295f 100644 --- a/src/services/database/repos/guild-repo.ts +++ b/src/services/database/repos/guild-repo.ts @@ -1,21 +1,21 @@ import { DataAccess } from '../data-access'; import { GuildData } from '../../../models/database'; import { Procedure } from '../procedure'; -import { SQLUtils } from '../../../utils'; +import { SqlUtils } from '../../../utils'; export class GuildRepo { constructor(private dataAccess: DataAccess) {} public async getGuild(discordId: string): Promise { let results = await this.dataAccess.executeProcedure(Procedure.Guild_Get, [discordId]); - return SQLUtils.getRow(results, 0, 0); + return SqlUtils.getRow(results, 0, 0); } public async getGuilds(discordIds: string[]): Promise { let results = await this.dataAccess.executeProcedure(Procedure.Guild_GetAll, [ discordIds.join(','), ]); - return SQLUtils.getTable(results, 0); + return SqlUtils.getTable(results, 0); } public async addOrUpdateGuild( @@ -30,27 +30,37 @@ export class GuildRepo { ]); } - public async updateBirthdayChannel( + public async updateBirthdayChannel(discordId: string, channelId: string): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateBirthdayChannel, [ + discordId, + channelId, + ]); + } + + public async updateMemberAnniversaryChannel( discordId: string, - birthdayChannelId: string + channelId: string ): Promise { - await this.dataAccess.executeProcedure(Procedure.Guild_UpdateBirthdayChannel, [ + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateMemberAnniversaryChannel, [ discordId, - birthdayChannelId, + channelId, ]); } - public async updateBirthdayRole(discordId: string, birthdayRoleId: string): Promise { - await this.dataAccess.executeProcedure(Procedure.Guild_UpdateBirthdayRole, [ + public async updateServerAnniversaryChannel( + discordId: string, + channelId: string + ): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateServerAnniversaryChannel, [ discordId, - birthdayRoleId, + channelId, ]); } - public async updateTrustedRole(discordId: string, trustedRoleId: string): Promise { - await this.dataAccess.executeProcedure(Procedure.Guild_UpdateTrustedRole, [ + public async updateBirthdayRole(discordId: string, birthdayRoleId: string): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateBirthdayRole, [ discordId, - trustedRoleId, + birthdayRoleId, ]); } @@ -71,20 +81,81 @@ export class GuildRepo { ]); } - public async updateMessageTime(discordId: string, messageTime: number): Promise { - await this.dataAccess.executeProcedure(Procedure.Guild_UpdateMessageTime, [ + public async updateBirthdayMessageTime(discordId: string, messageTime: number): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateBirthdayMessageTime, [ discordId, messageTime, ]); } - public async updateMentionSetting(discordId: string, mention: string): Promise { - await this.dataAccess.executeProcedure(Procedure.Guild_UpdateMentionSetting, [ + public async updateMemberAnniversaryMessageTime( + discordId: string, + messageTime: number + ): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateMemberAnniversaryTime, [ + discordId, + messageTime, + ]); + } + + public async updateServerAnniversaryMessageTime( + discordId: string, + messageTime: number + ): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateServerAnniversaryTime, [ + discordId, + messageTime, + ]); + } + + public async updateBirthdayMentionSetting(discordId: string, mention: string): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateBirthdayMentionSetting, [ discordId, mention, ]); } + public async updateMemberAnniversaryMentionSetting( + discordId: string, + mention: string + ): Promise { + await this.dataAccess.executeProcedure( + Procedure.Guild_UpdateMemberAnniversaryMentionSetting, + [discordId, mention] + ); + } + + public async updateServerAnniversaryMentionSetting( + discordId: string, + mention: string + ): Promise { + await this.dataAccess.executeProcedure( + Procedure.Guild_UpdateServerAnniversaryMentionSetting, + [discordId, mention] + ); + } + + public async updateNameFormat(discordId: string, format: string): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateNameFormat, [ + discordId, + format, + ]); + } + + public async updateDefaultTimezone(discordId: string, timezone: string): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateDefaultTimezone, [ + discordId, + timezone, + ]); + } + + public async updateUseTimezone(discordId: string, value: string): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateUseTimezone, [ + discordId, + value, + ]); + } + public async updateUseEmbed(discordId: string, value: number): Promise { await this.dataAccess.executeProcedure(Procedure.Guild_UpdateUseEmbed, [discordId, value]); } @@ -103,31 +174,36 @@ export class GuildRepo { ]); } - public async guildSetupMessage( - discordId: string, - messageTime: number, - mentionSetting: string, - useEmbed: number - ): Promise { - await this.dataAccess.executeProcedure(Procedure.Guild_SetupMessage, [ + public async updateRequireAllTrustedRoles(discordId: string, value: number): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_UpdateRequireAllTrustedRoles, [ discordId, - messageTime, - mentionSetting, - useEmbed, + value, ]); } public async guildSetupTrusted( discordId: string, - trustedRole: string, + requireAllTrustedRoles: number, preventRole: number, preventMessage: number ): Promise { await this.dataAccess.executeProcedure(Procedure.Guild_SetupTrusted, [ discordId, - trustedRole, + requireAllTrustedRoles, preventRole, preventMessage, ]); } + + public async guildSetupAnniversary( + discordId: string, + memberAnniversaryChannelId: string, + serverAnniversaryChannelId: string + ): Promise { + await this.dataAccess.executeProcedure(Procedure.Guild_SetupAnniversary, [ + discordId, + memberAnniversaryChannelId, + serverAnniversaryChannelId, + ]); + } } diff --git a/src/services/database/repos/index.ts b/src/services/database/repos/index.ts index 96996745..61b77fb4 100644 --- a/src/services/database/repos/index.ts +++ b/src/services/database/repos/index.ts @@ -1,4 +1,7 @@ +export { BlacklistRepo } from './blacklist-repo'; +export { CustomMessageRepo } from './custom-message-repo'; export { GuildRepo } from './guild-repo'; +export { MemberAnniversaryRoleRepo } from './member-anniversary-role-repo'; +export { TrustedRoleRepo } from './trusted-role-repo'; export { UserRepo } from './user-repo'; -export { CustomMessageRepo } from './custom-message-repo'; -export { BlacklistRepo } from './blacklist-repo'; +export { CombinedRepo } from './combined-repo'; diff --git a/src/services/database/repos/member-anniversary-role-repo.ts b/src/services/database/repos/member-anniversary-role-repo.ts new file mode 100644 index 00000000..0fc5c07f --- /dev/null +++ b/src/services/database/repos/member-anniversary-role-repo.ts @@ -0,0 +1,55 @@ +import { DataAccess } from '../data-access'; +import { MemberAnniversaryRoles } from '../../../models/database/member-anniversary-role-models'; +import { Procedure } from '../procedure'; +import { SqlUtils } from '../../../utils'; + +export class MemberAnniversaryRoleRepo { + constructor(private dataAccess: DataAccess) {} + + public async addMemberAnniversaryRole( + discordId: string, + roleId: string, + year: number + ): Promise { + await this.dataAccess.executeProcedure(Procedure.MemberAnniversaryRole_Add, [ + discordId, + roleId, + year, + ]); + } + + public async removeMemberAnniversaryRole(discordId: string, year: number): Promise { + await this.dataAccess.executeProcedure(Procedure.MemberAnniversaryRole_Remove, [ + discordId, + year, + ]); + } + + public async clearMemberAnniversaryRoles(discordId: string): Promise { + await this.dataAccess.executeProcedure(Procedure.MemberAnniversaryRole_Clear, [discordId]); + } + + public async getMemberAnniversaryRoles(discordId: string): Promise { + let results = await this.dataAccess.executeProcedure(Procedure.MemberAnniversaryRole_Get, [ + discordId, + ]); + + let memberAnniversaryRoles = SqlUtils.getTable(results, 0); + return new MemberAnniversaryRoles(memberAnniversaryRoles, null); + } + + public async getMemberAnniversaryRoleList( + guildId: string, + pageSize: number, + page: number + ): Promise { + let results = await this.dataAccess.executeProcedure( + Procedure.MemberAnniversaryRole_GetList, + [guildId, pageSize, page] + ); + + let memberAnniversaryRolesData = SqlUtils.getTable(results, 0); + let stats = SqlUtils.getRow(results, 1, 0); + return new MemberAnniversaryRoles(memberAnniversaryRolesData, stats); + } +} diff --git a/src/services/database/repos/trusted-role-repo.ts b/src/services/database/repos/trusted-role-repo.ts new file mode 100644 index 00000000..bebf2e41 --- /dev/null +++ b/src/services/database/repos/trusted-role-repo.ts @@ -0,0 +1,45 @@ +import { DataAccess } from '../data-access'; +import { Procedure } from '../procedure'; +import { SqlUtils } from '../../../utils'; +import { TrustedRoles } from '../../../models/database/trusted-role-models'; + +export class TrustedRoleRepo { + constructor(private dataAccess: DataAccess) {} + + public async addTrustedRole(discordId: string, roleId: string): Promise { + await this.dataAccess.executeProcedure(Procedure.TrustedRole_Add, [discordId, roleId]); + } + + public async removeTrustedRole(discordId: string, position: number): Promise { + await this.dataAccess.executeProcedure(Procedure.TrustedRole_Remove, [discordId, position]); + } + + public async clearTrustedRoles(discordId: string): Promise { + await this.dataAccess.executeProcedure(Procedure.TrustedRole_Clear, [discordId]); + } + + public async getTrustedRoles(discordId: string): Promise { + let results = await this.dataAccess.executeProcedure(Procedure.TrustedRole_Get, [ + discordId, + ]); + + let trustedRoles = SqlUtils.getTable(results, 0); + return new TrustedRoles(trustedRoles, null); + } + + public async getTrustedRoleList( + guildId: string, + pageSize: number, + page: number + ): Promise { + let results = await this.dataAccess.executeProcedure(Procedure.TrustedRole_GetList, [ + guildId, + pageSize, + page, + ]); + + let trustedRoleData = SqlUtils.getTable(results, 0); + let stats = SqlUtils.getRow(results, 1, 0); + return new TrustedRoles(trustedRoleData, stats); + } +} diff --git a/src/services/database/repos/user-repo.ts b/src/services/database/repos/user-repo.ts index df15fcc6..f26a2906 100644 --- a/src/services/database/repos/user-repo.ts +++ b/src/services/database/repos/user-repo.ts @@ -2,28 +2,28 @@ import { UserData, UserDataResults, Vote } from '../../../models/database'; import { DataAccess } from '../data-access'; import { Procedure } from '../procedure'; -import { SQLUtils } from '../../../utils'; +import { SqlUtils } from '../../../utils'; export class UserRepo { constructor(private dataAccess: DataAccess) {} public async getUser(discordId: string): Promise { let results = await this.dataAccess.executeProcedure(Procedure.User_Get, [discordId]); - return SQLUtils.getRow(results, 0, 0); + return SqlUtils.getRow(results, 0, 0); } public async getUserVote(discordId: string): Promise { let results = await this.dataAccess.executeProcedure(Procedure.User_GetLastVote, [ discordId, ]); - return SQLUtils.getRow(results, 0, 0); + return SqlUtils.getRow(results, 0, 0); } public async getAllUsers(discordIds: string[]): Promise { let results = await this.dataAccess.executeProcedure(Procedure.User_GetAll, [ discordIds.join(','), ]); - return SQLUtils.getTable(results, 0); + return SqlUtils.getTable(results, 0); } public async addOrUpdateUser( @@ -51,8 +51,8 @@ export class UserRepo { date, ]); - let userData = SQLUtils.getTable(results, 0); - let stats = SQLUtils.getRow(results, 1, 0); + let userData = SqlUtils.getTable(results, 0); + let stats = SqlUtils.getRow(results, 1, 0); return new UserDataResults(userData, stats); } @@ -67,8 +67,8 @@ export class UserRepo { page, ]); - let userData = SQLUtils.getTable(results, 0); - let stats = SQLUtils.getRow(results, 1, 0); + let userData = SqlUtils.getTable(results, 0); + let stats = SqlUtils.getRow(results, 1, 0); return new UserDataResults(userData, stats); } @@ -77,7 +77,7 @@ export class UserRepo { birthday, ]); - return SQLUtils.getTable(results, 0); + return SqlUtils.getTable(results, 0); } public async addUserVote(botSiteName: string, discordId: string): Promise { @@ -86,7 +86,7 @@ export class UserRepo { public async getUserCount(): Promise { let results = await this.dataAccess.executeProcedure(Procedure.User_GetTotalCount, []); - return SQLUtils.getRow(results, 0, 0).Total; + return SqlUtils.getRow(results, 0, 0).Total; } public async getUserBirthdaysTodayCount(birthday: string): Promise { @@ -94,7 +94,7 @@ export class UserRepo { Procedure.User_GetBirthdaysTodayCount, [birthday] ); - return SQLUtils.getRow(results, 0, 0).Total; + return SqlUtils.getRow(results, 0, 0).Total; } public async getUserBirthdaysThisMonthCount(birthday: string): Promise { @@ -102,6 +102,6 @@ export class UserRepo { Procedure.User_GetBirthdaysThisMonthCount, [birthday] ); - return SQLUtils.getRow(results, 0, 0).Total; + return SqlUtils.getRow(results, 0, 0).Total; } } diff --git a/src/services/index.ts b/src/services/index.ts index 9fcfe0be..545b1927 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,7 +1,9 @@ -export { BirthdayService } from './birthday-service'; export { HttpService } from './http-service'; export { Logger } from './logger'; export { MasterApiService } from './master-api-services'; export { SubscriptionService } from './subscription-service'; +export { Lang } from './lang'; export { JobService } from './job-service'; export { DiscordService } from './discord-service'; +export { RoleService } from './role-service'; +export { MessageService } from './message-service'; diff --git a/src/services/job-service.ts b/src/services/job-service.ts index a9647e79..dc5cb4ac 100644 --- a/src/services/job-service.ts +++ b/src/services/job-service.ts @@ -1,11 +1,11 @@ import schedule from 'node-schedule'; import { Logger } from '.'; -import { Job } from "../jobs"; +import { Job } from '../jobs'; let Logs = require('../../lang/logs.json'); export class JobService { - constructor(private jobs: Job[]) { } + constructor(private jobs: Job[]) {} public start(): void { for (let job of this.jobs) { @@ -31,4 +31,4 @@ export class JobService { ); } } -} \ No newline at end of file +} diff --git a/src/services/lang.ts b/src/services/lang.ts new file mode 100644 index 00000000..2fc6e323 --- /dev/null +++ b/src/services/lang.ts @@ -0,0 +1,38 @@ +import { LangCode } from '../models/enums'; +import { MessageEmbed } from 'discord.js'; +import { MultilingualService } from 'discord.js-multilingual-utils'; +import path from 'path'; + +export class Lang { + private static multilingualService: MultilingualService = new MultilingualService( + path.resolve(__dirname, '../../lang') + ); + + public static getNotImplementedEmbed(): MessageEmbed { + return new MessageEmbed().setDescription(`This lang embed hasn't been implemented.`); + } + + public static getNotImplementedRef(): string { + return `This lang embed hasn't been implemented.`; + } + + public static getEmbed( + embedName: string, + langCode: LangCode, + variables?: { [name: string]: string } + ): MessageEmbed { + return this.multilingualService.getEmbed(embedName, langCode, variables); + } + + public static getRegex(regexName: string, langCode: LangCode): RegExp { + return this.multilingualService.getRegex(regexName, langCode); + } + + public static getRef( + refName: string, + langCode: LangCode, + variables?: { [name: string]: string } + ): string { + return this.multilingualService.getRef(refName, langCode, variables); + } +} diff --git a/src/services/message-service.ts b/src/services/message-service.ts new file mode 100644 index 00000000..8095100d --- /dev/null +++ b/src/services/message-service.ts @@ -0,0 +1,509 @@ +import { CelebrationUtils, MessageUtils } from '../utils'; +import { Client, Guild, GuildMember, MessageEmbed, Role, TextChannel } from 'discord.js'; + +import { GuildCelebrationData } from '../models/database'; +import { Lang } from './lang'; +import { LangCode } from '../models/enums'; +import { Logger } from '.'; + +let Config = require('../../config/config.json'); +let Logs = require('../../lang/logs.json'); +export class MessageService { + // TODO: add to config + public interval: number = 0.5; + + public async run( + client: Client, + guildCelebrationDatas: GuildCelebrationData[], + birthdayMessageGuildMembers: GuildMember[], + memberAnniversaryMessageGuildMembers: GuildMember[], + guildsWithAnniversaryMessage: Guild[], + guildsWithPremium: string[] + ): Promise { + // Only get the guilds which actually might need a message sent + let filteredGuilds = guildCelebrationDatas.filter( + data => + birthdayMessageGuildMembers + .map(member => member.guild.id) + .includes(data.guildData.GuildDiscordId) || + memberAnniversaryMessageGuildMembers + .map(member => member.guild.id) + .includes(data.guildData.GuildDiscordId) || + guildsWithAnniversaryMessage + .map(guild => guild.id) + .includes(data.guildData.GuildDiscordId) + ); + + // Lets loop through the guilds that are left + for (let filteredGuild of filteredGuilds) { + // Lets see if we already have the guild + let guild: Guild = guildsWithAnniversaryMessage.find( + data => data.id === filteredGuild.guildData.GuildDiscordId + ); + + // If we don't we need to fetch it + if (!guild) { + try { + guild = await client.guilds.fetch(filteredGuild.guildData.GuildDiscordId); + } catch (error) { + continue; + } + } + + try { + // We need to filter the lists to only the GuildMembers in this guild and find the data for this guild + let birthdaysInThisGuild: GuildMember[] = birthdayMessageGuildMembers.filter( + member => member.guild.id === guild.id + ); + let anniversariesInThisGuild: GuildMember[] = + memberAnniversaryMessageGuildMembers.filter( + member => member.guild.id === guild.id + ); + let thisGuildCelebrationData: GuildCelebrationData = guildCelebrationDatas.find( + data => data.guildData.GuildDiscordId === guild.id + ); + + let hasPremium = guildsWithPremium.includes(guild.id); + let birthdayChannel: TextChannel; + let trustedRoles: Role[]; + let memberAnniversaryChannel: TextChannel; + let serverAnniversaryChannel: TextChannel; + + try { + birthdayChannel = guild.channels.resolve( + filteredGuild.guildData.BirthdayChannelDiscordId + ) as TextChannel; + } catch (error) { + // No birthday channel + } + + try { + memberAnniversaryChannel = guild.channels.resolve( + filteredGuild.guildData.MemberAnniversaryChannelDiscordId + ) as TextChannel; + } catch (error) { + // No Member Anniversary Channel channel + } + + try { + serverAnniversaryChannel = guild.channels.resolve( + filteredGuild.guildData.ServerAnniversaryChannelDiscordId + ) as TextChannel; + } catch (error) { + // No Server Anniversary channel + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // The birthday channel must exists and we need to have members who need the message + if (birthdayChannel && birthdaysInThisGuild.length > 0) { + // Get our list of trusted roles + trustedRoles = await CelebrationUtils.getTrustedRoleList( + guild, + filteredGuild.trustedRoles + ); + + // Remove the GuildMembers who don't pass the trusted check + birthdaysInThisGuild = birthdaysInThisGuild.filter(member => + CelebrationUtils.passesTrustedCheck( + filteredGuild.guildData.RequireAllTrustedRoles, + trustedRoles, + member, + filteredGuild.guildData.TrustedPreventsMessage, + hasPremium + ) + ); + + // Get all birthday messages + let birthdayMessages = filteredGuild.customMessages.filter( + message => message.Type === 'birthday' && message.UserDiscordId === '0' + ); + + // Lets deal with the user specific messages if they have premium + if (hasPremium) { + let color = Config.colors.default; + // All messages with a user id (and where that user id exists in our birthday guild member list) is a user specific message + let birthdayUserSpecificMessages = filteredGuild.customMessages.filter( + message => + message.Type === 'birthday' && + message.UserDiscordId !== '0' && + birthdaysInThisGuild + .map(member => member.id) + .includes(message.UserDiscordId) + ); + + // List of members with a user specific message + let birthdayMembersUserSpecific: GuildMember[] = + birthdaysInThisGuild.filter(member => + birthdayUserSpecificMessages + .map(message => message.UserDiscordId) + .includes(member.id) + ); + + // Now update the original list by removing guildMembers in the user specific list + birthdaysInThisGuild = birthdaysInThisGuild.filter( + birthday => !birthdayMembersUserSpecific.includes(birthday) + ); + + // Send our user specific messages for this guild + for (let birthdayMember of birthdayMembersUserSpecific) { + let message: string; + // Get our custom message + let customMessage = birthdayUserSpecificMessages.find( + message => message.UserDiscordId + ); + // Get the mention string + let mentionString = + filteredGuild.guildData.BirthdayMentionSetting !== 'none' + ? CelebrationUtils.getMentionString( + filteredGuild.guildData, + guild, + 'birthday' + ) + : ''; + + // Compile our user list to put in the message + let userList = CelebrationUtils.getUserListString( + filteredGuild.guildData, + [birthdayMember] + ); + + // Replace the placeholders + message = CelebrationUtils.replacePlaceHolders( + customMessage.Message, + guild, + customMessage.Type, + userList, + null + ); + + // Find the color of the embed + color = CelebrationUtils.getMessageColor(customMessage, hasPremium); + + // Send our message(s) + if (mentionString && mentionString !== '') + await MessageUtils.send(birthdayChannel, mentionString); + + let embed = new MessageEmbed().setDescription(message).setColor(color); + await MessageUtils.sendWithDelay( + birthdayChannel, + customMessage.Embed ? embed : message, + Config.delays.messages + ); + } + } + + // Get our generic birthday messages + let message = Lang.getRef('defaults.birthdayMessage', LangCode.EN_US); + let color = Config.colors.default; + let useEmbed = true; + + // Get the mention string + let mentionString = + filteredGuild.guildData.BirthdayMentionSetting !== 'none' + ? CelebrationUtils.getMentionString( + filteredGuild.guildData, + guild, + 'birthday' + ) + : ''; + + // Compile our user list to put in the message + let userList = CelebrationUtils.getUserListString( + filteredGuild.guildData, + birthdaysInThisGuild + ); + + // Add the compiled user list + if (birthdayMessages.length > 0) { + // Get our custom message + let customMessage = CelebrationUtils.randomMessage( + birthdayMessages, + hasPremium + ); + + // Find the color of the embed + color = CelebrationUtils.getMessageColor(customMessage, hasPremium); + useEmbed = customMessage.Embed ? true : false; + + message = customMessage.Message; + } + + // Replace the placeholders + message = CelebrationUtils.replacePlaceHolders( + message, + guild, + 'birthday', + userList, + null + ); + + // Send our message(s) + if (mentionString && mentionString !== '') + await MessageUtils.send(birthdayChannel, mentionString); + + let embed = new MessageEmbed().setDescription(message).setColor(color); + await MessageUtils.sendWithDelay( + birthdayChannel, + useEmbed ? embed : message, + Config.delays.messages + ); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // The member anniversary channel must exists and we need to have members who need the message + if (memberAnniversaryChannel && anniversariesInThisGuild.length > 0) { + // Get our generic member anniversary message + let message = Lang.getRef('defaults.memberAnniversaryMessage', LangCode.EN_US); + let color = Config.colors.default; + let useEmbed = true; + + // Get all member anniversary messages + let memberAnniversaryMessages = filteredGuild.customMessages.filter( + message => + message.Type === 'memberanniversary' && message.UserDiscordId === '0' + ); + + // Get an array of year values (Use set to remove duplicates) + let differentYears = [ + ...new Set( + anniversariesInThisGuild.map(data => + CelebrationUtils.getMemberYears(data, filteredGuild.guildData) + ) + ), + ]; + // Get the mention string + let mentionString = + filteredGuild.guildData.MemberAnniversaryMentionSetting !== 'none' + ? CelebrationUtils.getMentionString( + filteredGuild.guildData, + guild, + 'memberanniversary' + ) + : ''; + + let embedMessagesToSend: MessageEmbed[] = []; + let regularMessagesToSend: string[] = []; + + for (let year of differentYears) { + // Compile our user list to put in the message + let userList = CelebrationUtils.getUserListString( + filteredGuild.guildData, + anniversariesInThisGuild.filter( + member => + CelebrationUtils.getMemberYears( + member, + filteredGuild.guildData + ) === year + ) + ); + + // Add the compiled user list + if (memberAnniversaryMessages.length > 0) { + // Get our custom message + let customMessage = CelebrationUtils.randomMessage( + memberAnniversaryMessages, + hasPremium + ); + + // TEMP UNTIL THE YEAR PROBLEM IS ADDRESSED + // Find the color of the embed + color = CelebrationUtils.getMessageColor(customMessage, hasPremium); + + useEmbed = customMessage.Embed ? true : false; + + message = customMessage.Message; + } + + // Replace the placeholders + message = CelebrationUtils.replacePlaceHolders( + message, + guild, + 'memberanniversary', + userList, + year + ); + + let embed = new MessageEmbed().setDescription(message).setColor(color); + if (useEmbed) { + embedMessagesToSend.push(embed); + } else { + regularMessagesToSend.push(message); + } + } + + if ( + mentionString && + mentionString !== '' && + (embedMessagesToSend.length > 0 || regularMessagesToSend.length > 0) + ) + await MessageUtils.send(memberAnniversaryChannel, mentionString); + + // Compile our list of regular messages to send based on the 4096 character limit + let regularMessages: string[] = []; + let counter = 0; + if (regularMessagesToSend.length > 0) { + for (let message of regularMessagesToSend) { + if (regularMessages[counter].concat('\n\n' + message).length > 4096) { + counter++; + regularMessages.push(message); + } else { + regularMessages[counter] = regularMessages[counter].concat( + '\n\n' + message + ); + } + } + } + + // Compile our list of embed messages to send based on the 4096 character limit + + // First we need to get a list of colors by mapping and removing duplicates + // If they don't have premium use the default color otherwise use the colors of the custom messages + let colors: number[] = hasPremium + ? [...new Set(embedMessagesToSend.map(embed => embed.color))] + : [Config.colors.default]; + + // Now we loop through the colors and create a list of messages to send + let embedMessages: MessageEmbed[] = []; + counter = 0; + for (let color of colors) { + // Only get the messages that have the specified color if they have premium + let embedDescriptions: string[] = embedMessagesToSend + .filter(embed => !hasPremium || embed.color === color) + .map(embed => embed.description); + for (let message of embedDescriptions) { + if (embedMessages.length === 0) { + embedMessages.push( + new MessageEmbed().setColor(color).setDescription(message) + ); + } else if ( + embedMessages[counter].description.concat('\n\n' + message).length > + 4096 + ) { + counter++; + embedMessages.push( + new MessageEmbed().setDescription(message).setColor(color) + ); + } else { + embedMessages[counter].description = embedMessages[ + counter + ].description.concat('\n\n' + message); + } + } + counter++; + } + + if (embedMessages.length > 0) { + // Send our message(s) + + for (let message of embedMessages) + await MessageUtils.sendWithDelay( + memberAnniversaryChannel, + message, + Config.delays.messages + ); + } + + if (regularMessagesToSend.length > 0) { + // Send our message(s) + + for (let message of regularMessagesToSend) + await MessageUtils.sendWithDelay( + memberAnniversaryChannel, + message, + Config.delays.messages + ); + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // The server anniversary channel must exists and this guild needs to have Celebration Data + if ( + guildsWithAnniversaryMessage + .map(g => g.id) + .includes(filteredGuild.guildData.GuildDiscordId) && + serverAnniversaryChannel && + thisGuildCelebrationData + ) { + // Get our generic server anniversary message + let message = Lang.getRef('defaults.serverAnniversaryMessage', LangCode.EN_US); + let color = Config.colors.default; + let useEmbed = true; + + // Get all server anniversary messages + let serverAnniversaryMessages = filteredGuild.customMessages.filter( + message => + message.Type === 'serveranniversary' && message.UserDiscordId === '0' + ); + + // Get the mention string + let mentionString = + filteredGuild.guildData.ServerAnniversaryMentionSetting !== 'none' + ? CelebrationUtils.getMentionString( + filteredGuild.guildData, + guild, + 'serveranniversary' + ) + : ''; + + let serverYears = CelebrationUtils.getServerYears( + guild, + filteredGuild.guildData + ); + // Add the compiled user list + if (serverAnniversaryMessages.length > 0) { + // Get our custom message + let customMessage = CelebrationUtils.randomMessage( + serverAnniversaryMessages, + hasPremium + ); + + // Replace the placeholders + message = CelebrationUtils.replacePlaceHolders( + customMessage.Message, + guild, + customMessage.Type, + null, + serverYears + ); + + // Find the color of the embed + color = CelebrationUtils.getMessageColor(customMessage, hasPremium); + + useEmbed = customMessage.Embed ? true : false; + + message = customMessage.Message; + } + + // Replace the placeholders + message = CelebrationUtils.replacePlaceHolders( + message, + guild, + 'serveranniversary', + null, + serverYears + ); + + // Send our message(s) + if (mentionString && mentionString !== '') + await MessageUtils.send(serverAnniversaryChannel, mentionString); + + let embed = new MessageEmbed().setDescription(message).setColor(color); + await MessageUtils.sendWithDelay( + serverAnniversaryChannel, + useEmbed ? embed : message, + Config.delays.messages + ); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + } catch (error) { + // This guild had an error but we want to keep going + Logger.error( + Logs.error.messageServiceFailedForGuild + .replace('{GUILD_ID}', guild.id) + .replace('{GUILD_NAME}', guild.name), + error + ); + continue; + } + } + } +} diff --git a/src/services/role-service.ts b/src/services/role-service.ts new file mode 100644 index 00000000..f8e6e236 --- /dev/null +++ b/src/services/role-service.ts @@ -0,0 +1,161 @@ +import { ActionUtils, CelebrationUtils, TimeUtils } from '../utils'; +import { Client, Guild, GuildMember, Role } from 'discord.js'; +import { GuildCelebrationData, MemberAnniversaryRole } from '../models/database'; + +import { Logger } from '.'; + +let Config = require('../../config/config.json'); +let Logs = require('../../lang/logs.json'); + +export class RoleService { + // TODO: add to config + public interval: number = 0.5; + + public async run( + client: Client, + guildCelebrationDatas: GuildCelebrationData[], + addBirthdayRoleGuildMembers: GuildMember[], + removeBirthdayRoleGuildMembers: GuildMember[], + anniversaryRoleGuildMembers: GuildMember[], + guildsWithPremium: string[] + ): Promise { + // Only get the guilds which actually might need a role given + let filteredGuilds = guildCelebrationDatas.filter( + data => + addBirthdayRoleGuildMembers + .map(member => member.guild.id) + .includes(data.guildData.GuildDiscordId) || + removeBirthdayRoleGuildMembers + .map(member => member.guild.id) + .includes(data.guildData.GuildDiscordId) || + anniversaryRoleGuildMembers + .map(member => member.guild.id) + .includes(data.guildData.GuildDiscordId) + ); + + // Lets loop through the guilds + for (let filteredGuild of filteredGuilds) { + let guild: Guild; + try { + guild = await client.guilds.fetch(filteredGuild.guildData.GuildDiscordId); + } catch (error) { + continue; + } + try { + // We need to filter the GuildMember lists given by the parameters to only those in this guild + let addBirthdayGuildMembers = addBirthdayRoleGuildMembers.filter( + member => member.guild.id === guild.id + ); + let removeBirthdayGuildMembers = removeBirthdayRoleGuildMembers.filter( + member => member.guild.id === guild.id + ); + let addAnniversaryGuildMembers = anniversaryRoleGuildMembers.filter( + member => member.guild.id === guild.id + ); + + let hasPremium = guildsWithPremium.includes(guild.id); + let birthdayRole: Role; + let trustedRoles: Role[]; + let anniversaryRoles: Role[]; + + try { + birthdayRole = await guild.roles.fetch( + filteredGuild.guildData.BirthdayRoleDiscordId + ); + } catch (error) { + // No birthday role + } + + let anniversaryRoleData: MemberAnniversaryRole[]; + + // Only premium guilds get anniversary roles + if (hasPremium) { + // Get our list of anniversary roles + anniversaryRoles = await CelebrationUtils.getMemberAnniversaryRoleList( + guild, + filteredGuild.anniversaryRoles + ); + + // Get the data of the roles we could resolve (we need the data so we can check years later!) + anniversaryRoleData = filteredGuild.anniversaryRoles.filter(data => + anniversaryRoles + .map(r => r.id) + .includes(data.MemberAnniversaryRoleDiscordId) + ); + } + + // The birthday role must exist in order to add/remove it and we need at least one member who need the role + if (birthdayRole && addBirthdayGuildMembers.length > 0) { + // Get our list of trusted roles + trustedRoles = await CelebrationUtils.getTrustedRoleList( + guild, + filteredGuild.trustedRoles + ); + + for (let addBirthdayMember of addBirthdayGuildMembers) { + if ( + CelebrationUtils.passesTrustedCheck( + filteredGuild.guildData.RequireAllTrustedRoles, + trustedRoles, + addBirthdayMember, + filteredGuild.guildData.TrustedPreventsRole, + hasPremium + ) + ) { + // Don't send an api request if they already have the role + if (!addBirthdayMember.roles.cache.has(birthdayRole.id)) { + await ActionUtils.giveRole(addBirthdayMember, birthdayRole, Config.delays.roles); + } + } + } + + // Take birthday role regardless of trusted role? + for (let removeBirthdayMember of removeBirthdayGuildMembers) { + // Don't send an api request if they don't have the role + if (removeBirthdayMember.roles.cache.has(birthdayRole.id)) { + await ActionUtils.removeRole(removeBirthdayMember, birthdayRole, Config.delays.roles); + } + } + } + + // There has to be anniversary roles in order to give them (extra premium check is prob redundant) + if ( + addAnniversaryGuildMembers.length > 0 && + anniversaryRoles.length > 0 && + hasPremium + ) { + for (let addAnniversaryRoleMember of addAnniversaryGuildMembers) { + let memberYears = CelebrationUtils.getMemberYears( + addAnniversaryRoleMember, + filteredGuild.guildData + ); + for (let role of anniversaryRoles) { + let roleData = anniversaryRoleData.find( + data => data.MemberAnniversaryRoleDiscordId === role.id + ); + + if (roleData.Year === memberYears) { + // Don't send an api request if they already have the role + if (!addAnniversaryRoleMember.roles.cache.has(role.id)) { + await ActionUtils.giveRole(addAnniversaryRoleMember, role, Config.delays.roles); + } + } + } + } + } + + // Wait between guilds + await TimeUtils.sleep(this.interval); + } catch (error) { + // This guild had an error but we want to keep going + Logger.error( + Logs.error.messageServiceFailedForGuild + .replace('{GUILD_ID}', guild.id) + .replace('{GUILD_NAME}', guild.name), + error + ); + continue; + } + } + } +} diff --git a/src/services/subscription-service.ts b/src/services/subscription-service.ts index bd9c3bf0..6ed09b9f 100644 --- a/src/services/subscription-service.ts +++ b/src/services/subscription-service.ts @@ -40,6 +40,19 @@ export class SubscriptionService { return await res.json(); } + public async getAllSubscription(planName: string): Promise { + let res = await this.httpService.get( + `${Config.payments.url}/plans/${planName}/subscriptions/`, + Config.payments.token + ); + + if (res.status === 404) return; + + if (!res.ok) return; + + return await res.json(); + } + public async hasService(planName: string, subscriberId: string): Promise { let subscription = await this.getSubscription(planName, subscriberId); return !subscription ? false : subscription.service; diff --git a/src/start.ts b/src/start.ts index 0503107f..7c94b617 100644 --- a/src/start.ts +++ b/src/start.ts @@ -1,4 +1,3 @@ -import { BirthdayService, HttpService, JobService, Logger, SubscriptionService } from './services'; import { BlacklistAddSubCommand, BlacklistClearSubCommand, @@ -7,52 +6,92 @@ import { } from './commands/blacklist'; import { BlacklistCommand, - ClearCommand, - CreateCommand, + ConfigCommand, + DevCommand, DocumentationCommand, DonateCommand, - FAQCommand, + FaqCommand, HelpCommand, + InfoCommand, InviteCommand, ListCommand, MapCommand, + MemberAnniversaryRoleCommand, MessageCommand, NextCommand, + PremiumCommand, PurgeCommand, SetAttemptsCommand, SetCommand, SettingsCommand, SetupCommand, + StatsCommand, + SubscribeCommand, SupportCommand, TestCommand, - TrustedCommand, + TrustedRoleCommand, UpdateCommand, ViewCommand, + VoteCommand, } from './commands'; -import { BlacklistRepo, CustomMessageRepo, GuildRepo, UserRepo } from './services/database/repos'; +import { + BlacklistRepo, + CombinedRepo, + CustomMessageRepo, + GuildRepo, + MemberAnniversaryRoleRepo, + TrustedRoleRepo, + UserRepo, +} from './services/database/repos'; import { ClientOptions, DiscordAPIError } from 'discord.js'; +import { + ConfigBirthdayMasterRoleSubCommand, + ConfigChannelSubCommand, + ConfigNameFormatSubCommand, + ConfigRequireAllTrustedRolesSubCommand, + ConfigRoleSubCommand, + ConfigTimezoneSubCommand, + ConfigTrustedPreventsMsgSubCommand, + ConfigTrustedPreventsRoleSubCommand, + ConfigUseTimezoneSubCommand, +} from './commands/config'; import { GuildJoinHandler, GuildLeaveHandler, MessageHandler, ReactionAddHandler } from './events'; +import { + HttpService, + JobService, + Logger, + MessageService, + RoleService, + SubscriptionService, +} from './services'; +import { + MemberAnniversaryRoleAddSubCommand, + MemberAnniversaryRoleClaimSubCommand, + MemberAnniversaryRoleClearSubCommand, + MemberAnniversaryRoleListSubCommand, + MemberAnniversaryRoleRemoveSubCommand, +} from './commands/memberAnniversaryRole'; import { MessageAddSubCommand, MessageClearSubCommand, - MessageColorSubCommand, - MessageEmbedSubCommand, MessageListSubCommand, MessageMentionSubCommand, MessageRemoveSubCommand, MessageTestSubCommand, MessageTimeSubCommand, - MessageUserListSubCommand, } from './commands/message'; -import { SetupMessage, SetupRequired, SetupTrusted } from './commands/setup'; +import { SetupAnniversary, SetupRequired, SetupTrusted } from './commands/setup'; +import { + TrustedRoleAddSubCommand, + TrustedRoleClearSubCommand, + TrustedRoleListSubCommand, + TrustedRoleRemoveSubCommand, +} from './commands/trusted'; import { Bot } from './bot'; +import { CelebrationJob } from './jobs'; import { CustomClient } from './extensions/custom-client'; import { DataAccess } from './services/database/data-access'; -import { PostBirthdaysJob } from './jobs'; -import { PremiumCommand } from './commands/premium-commands'; -import { StatsCommand } from './commands/stats-command'; -import { SubscribeCommand } from './commands/subscribe-command'; let Config = require('../config/config.json'); @@ -66,6 +105,9 @@ async function start(): Promise { let userRepo = new UserRepo(dataAccess); let customMessageRepo = new CustomMessageRepo(dataAccess); let blacklistRepo = new BlacklistRepo(dataAccess); + let trustedRoleRepo = new TrustedRoleRepo(dataAccess); + let memberAnniversaryRoleRepo = new MemberAnniversaryRoleRepo(dataAccess); + let combinedRepo = new CombinedRepo(dataAccess); let clientOptions: ClientOptions = { ws: { intents: Config.client.intents }, @@ -81,40 +123,46 @@ async function start(): Promise { // Services let subscriptionService = new SubscriptionService(httpService); - let birthdayService = new BirthdayService(customMessageRepo, subscriptionService); + let messageService = new MessageService(); + let roleService = new RoleService(); // Commands - let setCommand = new SetCommand(guildRepo, userRepo); - let statsCommand = new StatsCommand(userRepo); - - let createCommand = new CreateCommand(guildRepo); - let updateCommand = new UpdateCommand(guildRepo); - let clearCommand = new ClearCommand(guildRepo); - let settingsCommand = new SettingsCommand(guildRepo); - - let listCommand = new ListCommand(userRepo); - let purgeCommand = new PurgeCommand(userRepo); - let inviteCommand = new InviteCommand(); - let supportCommand = new SupportCommand(); - let mapCommand = new MapCommand(); - let viewCommand = new ViewCommand(userRepo); - let nextCommand = new NextCommand(userRepo); - let trustedCommand = new TrustedCommand(guildRepo); - let setAttemptsCommand = new SetAttemptsCommand(userRepo); - let testCommand = new TestCommand(birthdayService, guildRepo, blacklistRepo); - let faqCommand = new FAQCommand(); + let devCommand = new DevCommand(); let documentationCommand = new DocumentationCommand(); let donateCommand = new DonateCommand(); + let faqCommand = new FaqCommand(); + let infoCommand = new InfoCommand(); + let inviteCommand = new InviteCommand(); + let listCommand = new ListCommand(userRepo, guildRepo); + let mapCommand = new MapCommand(); + let nextCommand = new NextCommand(userRepo, guildRepo); let premiumCommand = new PremiumCommand(subscriptionService); + let purgeCommand = new PurgeCommand(userRepo); + let setAttemptsCommand = new SetAttemptsCommand(userRepo); + let setCommand = new SetCommand(guildRepo, userRepo); + let settingsCommand = new SettingsCommand(guildRepo, trustedRoleRepo); + let statsCommand = new StatsCommand(userRepo); let subscribeCommand = new SubscribeCommand(subscriptionService); + let supportCommand = new SupportCommand(); + let testCommand = new TestCommand( + guildRepo, + userRepo, + customMessageRepo, + trustedRoleRepo, + blacklistRepo, + memberAnniversaryRoleRepo + ); + let viewCommand = new ViewCommand(userRepo); + let voteCommand = new VoteCommand(); + let updateCommand = new UpdateCommand(); // Setup Sub Commands let setupRequired = new SetupRequired(guildRepo); - let setupMessage = new SetupMessage(guildRepo); let setupTrusted = new SetupTrusted(guildRepo); + let setupAnniversary = new SetupAnniversary(guildRepo); // Setup Command - let setupCommand = new SetupCommand(guildRepo, setupRequired, setupMessage, setupTrusted); + let setupCommand = new SetupCommand(guildRepo, setupRequired, setupTrusted, setupAnniversary); // Message Sub Commands let messageListSubCommand = new MessageListSubCommand(customMessageRepo); @@ -123,10 +171,7 @@ async function start(): Promise { let messageRemoveSubCommand = new MessageRemoveSubCommand(customMessageRepo); let messageTimSubCommand = new MessageTimeSubCommand(guildRepo); let messageMentionSubCommand = new MessageMentionSubCommand(guildRepo); - let messageEmbedSubCommand = new MessageEmbedSubCommand(guildRepo); let messageTestSubCommand = new MessageTestSubCommand(guildRepo, customMessageRepo); - let messageColorSubCommand = new MessageColorSubCommand(guildRepo); - let messageUserListSubCommand = new MessageUserListSubCommand(customMessageRepo); // Message Command let messageCommand = new MessageCommand( @@ -136,10 +181,33 @@ async function start(): Promise { messageRemoveSubCommand, messageTimSubCommand, messageMentionSubCommand, - messageEmbedSubCommand, - messageTestSubCommand, - messageColorSubCommand, - messageUserListSubCommand + messageTestSubCommand + ); + + // Config Sub Commands + let configChannelSubCommand = new ConfigChannelSubCommand(guildRepo); + let configRoleSubCommand = new ConfigRoleSubCommand(guildRepo); + let configBirthdayMasterRoleSubCommand = new ConfigBirthdayMasterRoleSubCommand(guildRepo); + let configNameFormatSubCommand = new ConfigNameFormatSubCommand(guildRepo); + let configTrustedPreventMsgSubCommand = new ConfigTrustedPreventsMsgSubCommand(guildRepo); + let configTrustedPreventRoleSubCommand = new ConfigTrustedPreventsRoleSubCommand(guildRepo); + let configTimezoneSubCommand = new ConfigTimezoneSubCommand(guildRepo); + let configUseTimezoneSubCommand = new ConfigUseTimezoneSubCommand(guildRepo); + let configRequireAllTrustedRolesSubCommand = new ConfigRequireAllTrustedRolesSubCommand( + guildRepo + ); + + // Config Command + let configCommand = new ConfigCommand( + configBirthdayMasterRoleSubCommand, + configChannelSubCommand, + configRoleSubCommand, + configNameFormatSubCommand, + configTrustedPreventMsgSubCommand, + configTrustedPreventRoleSubCommand, + configTimezoneSubCommand, + configUseTimezoneSubCommand, + configRequireAllTrustedRolesSubCommand ); // Blacklist Sub Commands @@ -156,34 +224,78 @@ async function start(): Promise { blacklistListSubCommand ); + // Trusted Role Sub Commands + let trustedRoleAddSubCommand = new TrustedRoleAddSubCommand(trustedRoleRepo); + let trustedRoleRemoveSubCommand = new TrustedRoleRemoveSubCommand(trustedRoleRepo); + let trustedRoleClearSubCommand = new TrustedRoleClearSubCommand(trustedRoleRepo); + let trustedRoleListSubCommand = new TrustedRoleListSubCommand(trustedRoleRepo); + + // Trusted Role Command + let trustedRoleCommand = new TrustedRoleCommand( + trustedRoleAddSubCommand, + trustedRoleRemoveSubCommand, + trustedRoleClearSubCommand, + trustedRoleListSubCommand + ); + + // Member Anniversary Role Sub Commands + let memberAnniversaryRoleAddSubCommand = new MemberAnniversaryRoleAddSubCommand( + memberAnniversaryRoleRepo + ); + let memberAnniversaryRoleRemoveSubCommand = new MemberAnniversaryRoleRemoveSubCommand( + memberAnniversaryRoleRepo + ); + let memberAnniversaryRoleClearSubCommand = new MemberAnniversaryRoleClearSubCommand( + memberAnniversaryRoleRepo + ); + let memberAnniversaryRoleListSubCommand = new MemberAnniversaryRoleListSubCommand( + memberAnniversaryRoleRepo + ); + let memberAnniversaryRoleClaimSubCommand = new MemberAnniversaryRoleClaimSubCommand( + memberAnniversaryRoleRepo + ); + + // Member Anniversary Role Command + let memberAnniversaryRoleCommand = new MemberAnniversaryRoleCommand( + guildRepo, + memberAnniversaryRoleAddSubCommand, + memberAnniversaryRoleRemoveSubCommand, + memberAnniversaryRoleClearSubCommand, + memberAnniversaryRoleListSubCommand, + memberAnniversaryRoleClaimSubCommand + ); + // Events handlers let messageHandler = new MessageHandler( helpCommand, [ - setCommand, - setupCommand, - createCommand, - updateCommand, - clearCommand, - messageCommand, - listCommand, - purgeCommand, + blacklistCommand, + configCommand, + devCommand, + documentationCommand, + donateCommand, + faqCommand, + infoCommand, inviteCommand, - supportCommand, + listCommand, mapCommand, - viewCommand, + memberAnniversaryRoleCommand, + messageCommand, nextCommand, - trustedCommand, + premiumCommand, + purgeCommand, setAttemptsCommand, + setCommand, settingsCommand, - testCommand, + setupCommand, statsCommand, - faqCommand, - documentationCommand, - donateCommand, - blacklistCommand, - premiumCommand, subscribeCommand, + supportCommand, + trustedRoleCommand, + viewCommand, + voteCommand, + testCommand, + updateCommand, ], subscriptionService, guildRepo, @@ -191,22 +303,25 @@ async function start(): Promise { ); let reactionAddHandler = new ReactionAddHandler( userRepo, + guildRepo, customMessageRepo, blacklistRepo, + trustedRoleRepo, subscriptionService ); let guildJoinHandler = new GuildJoinHandler(); let guildLeaveHandler = new GuildLeaveHandler(); let jobService = new JobService([ - new PostBirthdaysJob( + new CelebrationJob( client, - guildRepo, userRepo, - blacklistRepo, - birthdayService - ) - ]) + combinedRepo, + messageService, + roleService, + subscriptionService + ), + ]); let bot = new Bot( Config.client.token, @@ -222,9 +337,11 @@ async function start(): Promise { } process.on('unhandledRejection', (reason, promise) => { - if (reason instanceof DiscordAPIError) { - if (reason.code === 10003) return; + // 10003: "Unknown channel" + if (reason instanceof DiscordAPIError && reason.code === 10003) { + return; } + Logger.error('Unhandled promise rejection.', reason); }); diff --git a/src/utils/action-utils.ts b/src/utils/action-utils.ts index aedcd022..2fd1df17 100644 --- a/src/utils/action-utils.ts +++ b/src/utils/action-utils.ts @@ -1,15 +1,20 @@ -import { DiscordAPIError, GuildMember, Message, Role } from 'discord.js'; +import { DiscordAPIError, GuildMember, Role } from 'discord.js'; +import { TimeUtils } from './time-utils'; -export abstract class ActionUtils { - public static async giveRole(member: GuildMember, role: Role): Promise { +let Config = require('../../config/config.json'); + +export class ActionUtils { + // TODO: Have giveRole and removeRole take in an interval to sleep for to prevent rate limits + public static async giveRole(member: GuildMember, role: Role, delay?: number): Promise { + delay = Config.delays.enabled ? delay : 0; try { await member.roles.add(role); + await TimeUtils.sleep(delay ?? 0); } catch (error) { - // Error code 50001: "Missing Access", Error code: 10011: "Unknown Role" (Role was deleted), Error code: 50013: "Missing Permission" - if ( - error instanceof DiscordAPIError && - (error.code === 50001 || error.code === 10011 || error.code === 50013) - ) { + // 10011: "Unknown Role" (Role was deleted) + // 50001: "Missing Access" + // 50013: "Missing Permission" + if (error instanceof DiscordAPIError && [10011, 50001, 50013].includes(error.code)) { return; } else { throw error; @@ -17,15 +22,16 @@ export abstract class ActionUtils { } } - public static async removeRole(member: GuildMember, role: Role): Promise { + public static async removeRole(member: GuildMember, role: Role, delay?: number): Promise { + delay = Config.delays.enabled ? delay : 0; try { await member.roles.remove(role); + await TimeUtils.sleep(delay ?? 0); } catch (error) { - // Error code 50001: "Missing Access", Error code: 10011: "Unknown Role" (Role was deleted), Error code: 50013: "Missing Permission" - if ( - error instanceof DiscordAPIError && - (error.code === 50001 || error.code === 10011 || error.code === 50013) - ) { + // 10011: "Unknown Role" (Role was deleted) + // 50001: "Missing Access" + // 50013: "Missing Permission" + if (error instanceof DiscordAPIError && [10011, 50001, 50013].includes(error.code)) { return; } else { throw error; diff --git a/src/utils/anniversary-utils.ts b/src/utils/anniversary-utils.ts new file mode 100644 index 00000000..b73b99a4 --- /dev/null +++ b/src/utils/anniversary-utils.ts @@ -0,0 +1,65 @@ +import { Guild, GuildMember } from 'discord.js'; +import { GuildData, UserData } from '../models/database'; + +import { TimeUtils } from '.'; +import moment from 'moment'; + +let Debug = require('../../config/debug.json'); + +export class AnniversaryUtils { + public static isMemberAnniversary( + member: GuildMember, + guildData: GuildData, + userData?: UserData + ): boolean { + if (Debug.alwaysMemberAnniversary) { + return true; + } + + if ((!userData && !guildData) || (!userData.TimeZone && !guildData.DefaultTimezone)) + return false; + + let timeZone: string; + + if (guildData?.UseTimezone) { + if (guildData.UseTimezone === 'server') { + timeZone = + guildData.DefaultTimezone === '0' + ? userData.TimeZone + : guildData.DefaultTimezone; + } else { + timeZone = + userData.TimeZone === '0' ? guildData.DefaultTimezone : userData.TimeZone; + } + } else { + timeZone = userData.TimeZone === '0' ? guildData.DefaultTimezone : userData.TimeZone; + } + + let currentDate = moment().tz(timeZone); + let joinDate = moment(member.joinedAt).tz(userData.TimeZone); + + let currentDateFormatted = currentDate.format('MM-DD'); + let joinDateFormatted = joinDate.format('MM-DD'); + if (joinDateFormatted === '02-29' && !TimeUtils.isLeap(currentDate.year())) + joinDateFormatted = '02-28'; + return currentDateFormatted === joinDateFormatted; + } + + public static isServerAnniversary(guild: Guild, guildData: GuildData): boolean { + if (Debug.alwaysServerAnniversary) { + return true; + } + + if (!guildData?.DefaultTimezone) return false; + + let currentDate = moment().tz(guildData.DefaultTimezone); + let creationDate = moment(guild.createdAt).tz(guildData.DefaultTimezone); + + let currentDateFormatted = currentDate.format('MM-DD'); + let creationDateFormatted = creationDate.format('MM-DD'); + + if (creationDateFormatted === '02-29' && !TimeUtils.isLeap(currentDate.year())) + creationDateFormatted = '02-28'; + return currentDateFormatted === creationDateFormatted; + } +} diff --git a/src/utils/array-utils.ts b/src/utils/array-utils.ts index 789ebf03..2f6d1156 100644 --- a/src/utils/array-utils.ts +++ b/src/utils/array-utils.ts @@ -1,4 +1,4 @@ -export abstract class ArrayUtils { +export class ArrayUtils { public static chooseRandom(items: any[]): any { return items[Math.floor(Math.random() * items.length)]; } diff --git a/src/utils/bday-utils.ts b/src/utils/bday-utils.ts deleted file mode 100644 index 850410c2..00000000 --- a/src/utils/bday-utils.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { CustomMessages, SplitUsers, UserData } from '../models/database'; - -import { ArrayUtils } from './array-utils'; -import { MathUtils } from './math-utils'; -import { Moment } from 'moment-timezone'; -import moment from 'moment'; - -let Debug = require('../../config/debug.json'); - -let Config = require('../../config/config.json'); - -export class BdayUtils { - public static getNextUsers(userDatas: UserData[], timeZone: string): UserData[] { - let userTime = timeZone ? moment.tz(timeZone) : moment.tz(); - - let { before: usersBefore, after: usersAfter } = this.splitUserDatasByTime( - userDatas, - userTime - ); - - if (usersAfter.length > 0) { - let nextBirthday = usersAfter[0].Birthday; // First birthday after current date - return usersAfter.filter(userData => userData.Birthday === nextBirthday); // TODO: Check by only month and day - } - - if (usersBefore.length > 0) { - let nextBirthday = usersBefore[0].Birthday; // First birthday starting at beginning of year - return usersBefore.filter(userData => userData.Birthday === nextBirthday); // TODO: Check by only month and day - } - } - - public static splitUserDatasByTime(userDatas: UserData[], splitTime: Moment): SplitUsers { - // TODO: Split into before and after, and sort by dates - return { - before: userDatas - .filter(user => moment(user.Birthday).format('MM-DD') < splitTime.format('MM-DD')) - .sort(this.compareUserDatas), - after: userDatas - .filter(user => moment(user.Birthday).format('MM-DD') > splitTime.format('MM-DD')) - .sort(this.compareUserDatas), - }; - } - - public static compareUserDatas(a: UserData, b: UserData): number { - let aBday = moment(a.Birthday).format('MM-DD'); - let bBday = moment(b.Birthday).format('MM-DD'); - - if (aBday > bBday) { - return 1; - } - - if (aBday < bBday) { - return -1; - } - - return 0; - } - - public static isTimeForBirthdayMessage(messageHour: number, userData: UserData): boolean { - if (Debug.alwaysSendBirthdayMessage) { - return true; - } - - let currentDate = moment().tz(userData.TimeZone); - let currentHour = currentDate.hour(); - return currentHour === messageHour; - } - - public static isTimeForBirthdayRole(userData: UserData): boolean { - if (Debug.alwaysGiveBirthdayRole) { - return true; - } - - let currentDate = moment().tz(userData.TimeZone); - - let currentHour = currentDate.hour(); - return currentHour === 0; - } - - public static isBirthday(userData: UserData): boolean { - if (Debug.alwaysGiveBirthdayRole) { - return true; - } - - let currentDate = moment().tz(userData.TimeZone); - let birthday = moment(userData.Birthday); - - let currentDateFormatted = currentDate.format('MM-DD'); - let birthdayFormatted = birthday.format('MM-DD'); - - if (birthdayFormatted === '02-29' && !MathUtils.isLeap(moment().year())) - birthdayFormatted = '02-28'; - return currentDateFormatted === birthdayFormatted; - } - - public static randomMessage(messages: CustomMessages, hasPremium: boolean): string { - if (messages.customMessages.length > 0) { - if (hasPremium) { - // Choose a random one - return ArrayUtils.chooseRandom(messages.customMessages).Message; - } else { - // Only choose from the first 10 - return ArrayUtils.chooseRandom( - messages.customMessages.slice(0, Config.validation.message.maxCount.free) - ).Message; - } - } else { - // Return the default birthday message - return 'Happy Birthday !'; - } - } -} diff --git a/src/utils/celebration-utils.ts b/src/utils/celebration-utils.ts new file mode 100644 index 00000000..d83b0fcf --- /dev/null +++ b/src/utils/celebration-utils.ts @@ -0,0 +1,459 @@ +import { ArrayUtils, ColorUtils, FormatUtils, TimeUtils } from '.'; +import { + CustomMessage, + GuildCelebrationData, + GuildData, + RawGuildCelebrationData, + SplitUsers, + TrustedRole, + UserData, +} from '../models/database'; +import { Guild, GuildMember, Role } from 'discord.js'; + +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { MemberAnniversaryRole } from '../models/database/member-anniversary-role-models'; +import { Moment } from 'moment-timezone'; +import moment from 'moment'; + +let Debug = require('../../config/debug.json'); + +let Config = require('../../config/config.json'); + +export class CelebrationUtils { + public static getMessageColor(message: CustomMessage, hasPremium: boolean): any { + let color = message.Color === '0' || !hasPremium ? Config.colors.default : null; + + color = !color + ? '#' + ColorUtils.findHex(message?.Color) ?? Config.colors.default + : Config.colors.default; + + return color; + } + + public static getNextUsers(userDatas: UserData[], timeZone: string): UserData[] { + let userTime = timeZone && timeZone !== '0' ? moment.tz(timeZone) : moment.tz(); + + let { before: usersBefore, after: usersAfter } = this.splitUserDatasByTime( + userDatas, + userTime + ); + + if (usersAfter.length > 0) { + let nextBirthday = usersAfter[0].Birthday; // First birthday after current date + return usersAfter.filter(userData => userData.Birthday === nextBirthday); // TODO: Check by only month and day + } + + if (usersBefore.length > 0) { + let nextBirthday = usersBefore[0].Birthday; // First birthday starting at beginning of year + return usersBefore.filter(userData => userData.Birthday === nextBirthday); // TODO: Check by only month and day + } + } + + public static splitUserDatasByTime(userDatas: UserData[], splitTime: Moment): SplitUsers { + // TODO: Split into before and after, and sort by dates + return { + before: userDatas + .filter(user => moment(user.Birthday).format('MM-DD') < splitTime.format('MM-DD')) + .sort(this.compareUserDatas), + after: userDatas + .filter(user => moment(user.Birthday).format('MM-DD') > splitTime.format('MM-DD')) + .sort(this.compareUserDatas), + }; + } + + public static compareUserDatas(a: UserData, b: UserData): number { + let aBday = moment(a.Birthday).format('MM-DD'); + let bBday = moment(b.Birthday).format('MM-DD'); + + if (aBday > bBday) { + return 1; + } + + if (aBday < bBday) { + return -1; + } + + return 0; + } + + public static isBirthdayToday(userData: UserData, guildData: GuildData): boolean { + if (!userData || !guildData) return false; + + // If the server doesn't have a default timezone, use the user's timezone + // Else, since we have a server timezone, if the UseTimezone setting in the server does not prioritize the server, use the user's timezone + // Else, use the server's default timezone + let currentDate = moment().tz( + guildData.DefaultTimezone === '0' + ? userData.TimeZone + : guildData.UseTimezone !== 'server' + ? userData.TimeZone + : guildData.DefaultTimezone + ); + let birthday = moment(userData.Birthday); + + let currentDateFormatted = currentDate.format('MM-DD'); + let birthdayFormatted = birthday.format('MM-DD'); + + if (birthdayFormatted === '02-29' && !TimeUtils.isLeap(moment().year())) + birthdayFormatted = '03-01'; + + // The date is correct, now check the time + return currentDateFormatted !== birthdayFormatted; + } + + public static needsBirthdayRoleAdded(userData: UserData, guildData: GuildData): boolean { + if (Debug.alwaysGiveBirthdayRole) { + return true; + } + + // If the server doesn't have a default timezone, use the user's timezone + // Else, since we have a server timezone, if the UseTimezone setting in the server does not prioritize the server, use the user's timezone + // Else, use the server's default timezone + let currentDate = moment().tz( + guildData.DefaultTimezone === '0' + ? userData.TimeZone + : guildData.UseTimezone !== 'server' + ? userData.TimeZone + : guildData.DefaultTimezone + ); + let currentHour = currentDate.hour(); + return currentHour === 0; + } + + public static needsBirthdayRoleRemoved(userData: UserData, guildData: GuildData): boolean { + if (Debug.alwaysGiveBirthdayRole) { + return true; + } + + // If the server doesn't have a default timezone, use the user's timezone + // Else, since we have a server timezone, if the UseTimezone setting in the server does not prioritize the server, use the user's timezone + // Else, use the server's default timezone + let currentDate = moment() + .tz( + guildData.DefaultTimezone === '0' + ? userData.TimeZone + : guildData.UseTimezone !== 'server' + ? userData.TimeZone + : guildData.DefaultTimezone + ) + .subtract(1, 'days'); + let currentHour = currentDate.hour(); + return currentHour === 0; + } + + public static needsBirthdayMessage(userData: UserData, guildData: GuildData): boolean { + if (Debug.alwaysSendBirthdayMessage) { + return true; + } + + // If the server doesn't have a default timezone, use the user's timezone + // Else, since we have a server timezone, if the UseTimezone setting in the server does not prioritize the server, use the user's timezone + // Else, use the server's default timezone + let currentDate = moment().tz( + guildData.DefaultTimezone === '0' + ? userData.TimeZone + : guildData.UseTimezone !== 'server' + ? userData.TimeZone + : guildData.DefaultTimezone + ); + let currentHour = currentDate.hour(); + return currentHour === guildData.BirthdayMessageTime; + } + + public static isMemberAnniversaryMessage( + guildMember: GuildMember, + guildData: GuildData + ): boolean { + // TODO: add debug mode for member anniversary + // if (Debug.alwaysGiveBirthdayRole) { + // return true; + // } + + if (!guildMember || !guildData || guildData.DefaultTimezone === '0') return false; + let currentDate = moment().tz(guildData.DefaultTimezone); + let memberAnniversary = moment(guildMember.joinedAt); + + let currentDateFormatted = currentDate.format('MM-DD'); + let anniversaryFormatted = memberAnniversary.format('MM-DD'); + + if (anniversaryFormatted === '02-29' && !TimeUtils.isLeap(moment().year())) + anniversaryFormatted = '03-01'; + + if (currentDate.year() - memberAnniversary.year() === 0) return false; + + // The date is correct, now check the time + return currentDateFormatted !== anniversaryFormatted + ? false + : currentDate.hour() !== guildData.MemberAnniversaryMessageTime + ? false + : true; + } + + public static isServerAnniversaryMessage(guild: Guild, guildData: GuildData): boolean { + // TODO: add debug mode for server anniversary + // if (Debug.alwaysGiveBirthdayRole) { + // return true; + // } + + if (!guild || !guildData || guildData.DefaultTimezone === '0') return false; + let currentDate = moment().tz(guildData.DefaultTimezone); + let serverAnniversary = moment(guild.createdAt); + + let currentDateFormatted = currentDate.format('MM-DD'); + let anniversaryFormatted = serverAnniversary.format('MM-DD'); + + if (anniversaryFormatted === '02-29' && !TimeUtils.isLeap(moment().year())) + anniversaryFormatted = '03-01'; + + // The date is correct, now check the time + return currentDateFormatted !== anniversaryFormatted + ? false + : currentDate.hour() !== guildData.ServerAnniversaryMessageTime + ? false + : true; + } + + public static getMemberYears(guildMember: GuildMember, guildData: GuildData): number { + if (!guildMember || !guildData || !guildData.DefaultTimezone) return 0; + let currentYear = moment().tz(guildData.DefaultTimezone).year(); + let memberAnniversaryYear = moment(guildMember.joinedAt).year(); + return currentYear - memberAnniversaryYear; + } + + public static getServerYears(guild: Guild, guildData: GuildData): number { + if (!guild || !guildData || !guildData.DefaultTimezone) return 0; + let currentYear = moment().tz(guildData.DefaultTimezone).year(); + let memberAnniversaryYear = moment(guild.createdAt).year(); + return currentYear - memberAnniversaryYear; + } + + // Change input to just take an array of CustomMessage + public static randomMessage(messages: CustomMessage[], hasPremium: boolean): CustomMessage { + if (messages.length > 0) { + if (hasPremium) { + // Choose a random one + return ArrayUtils.chooseRandom(messages); + } else { + // Only choose from the first 10 + return ArrayUtils.chooseRandom( + messages.slice(0, Config.validation.message.maxCount.birthday.free) + ); + } + } else { + // Return null + return null; + } + } + + public static getMentionString(guildData: GuildData, guild: Guild, type: string): string { + // Find mentioned role + let mentionSetting = ( + type === 'birthday' + ? guildData.BirthdayMentionSetting + : type === 'memberanniversary' + ? guildData.MemberAnniversaryMentionSetting + : guildData.ServerAnniversaryMentionSetting + ).toLowerCase(); + + if (mentionSetting === '0') return null; + + if (mentionSetting === 'here') { + return '@here'; + } + + if (mentionSetting === guild.id || mentionSetting === 'everyone') { + return '@everyone'; + } + + return `<@&${mentionSetting}>`; + } + + public static replacePlaceHolders( + message: string, + guild: Guild, + type: string, + userList: string, + year: number + ): string { + if (message) { + message = message.split('').join(guild.name).split('@Server').join(guild.name); + + if (type !== 'serveranniversary') + message = message.split('').join(userList).split('@Users').join(userList); + if (type !== 'birthday') + message = message + .split('') + .join(year.toString()) + .split('@Year') + .join(year.toString()); + } + + return message; + } + + public static replaceLangPlaceHolders( + message: string, + guild: Guild, + type: string, + userString: string + ): string { + if (message) { + let serverPlaceholder = Lang.getRef('placeHolders.server', LangCode.EN_US); + message = message + .split('') + .join(serverPlaceholder) + .split('@Server') + .join(serverPlaceholder); + + if (type !== 'serveranniversary') { + let userPlaceholder = Lang.getRef('placeHolders.users', LangCode.EN_US); + message = message + .split('') + .join(userString ?? userPlaceholder) + .split('@Users') + .join(userString ?? userPlaceholder); + } + if (type !== 'birthday') { + let yearPlaceholder = Lang.getRef('placeHolders.year', LangCode.EN_US); + message = message + .split('') + .join(yearPlaceholder) + .split('@Year') + .join(yearPlaceholder); + } + } + + return message; + } + + public static getUserListString(guildData: GuildData, guildMember: GuildMember[]): string { + // Find mentioned role + let userList: string; + // Format the user list based off the servers name format + if (guildData?.NameFormat === 'username') + userList = FormatUtils.joinWithAnd( + guildMember.map(member => `**${member.user.username}**`) + ); + else if (guildData?.NameFormat === 'nickname') + userList = FormatUtils.joinWithAnd( + guildMember.map(member => `**${member.displayName}**`) + ); + else if (guildData?.NameFormat === 'tag') + userList = FormatUtils.joinWithAnd( + guildMember.map( + member => `**${member.user.username}#${member.user.discriminator}**` + ) + ); + else userList = FormatUtils.joinWithAnd(guildMember.map(member => member.toString())); + return userList; + } + + public static passesTrustedCheck( + requireAllTrustedRoles: number, + trustedRoles: Role[], + birthdayMember: GuildMember, + trustedSetting: number, + hasPremium: boolean + ): boolean { + // If it passed the trusted role(s) check + // Default this to true if there are no trusted roles + // If there are trusted roles and trusted DOESN'T prevent Role/Message (the trusted setting passed in) then set it to true + let passTrustedCheck = + !trustedRoles || trustedRoles.length === 0 ? true : trustedSetting ? false : true; + + // if passTrustedCheck is already true we don't have to check for trusted role(s) + if (!passTrustedCheck) { + trustedRoles = hasPremium ? trustedRoles : [trustedRoles[0]]; + if (requireAllTrustedRoles) { + let hasAllTrusted = true; + for (let role of trustedRoles) { + if (!birthdayMember.roles.cache.has(role.id)) { + hasAllTrusted = false; + break; + } + } + passTrustedCheck = hasAllTrusted; + } else { + for (let role of trustedRoles) { + if (birthdayMember.roles.cache.has(role.id)) { + passTrustedCheck = true; + break; + } + } + } + } + return passTrustedCheck; + } + + public static async getTrustedRoleList(guild: Guild, roles: TrustedRole[]): Promise { + let trustedRoles: Role[] = []; + for (let role of roles) { + try { + let tRole: Role = await guild.roles.fetch(role.TrustedRoleDiscordId); + if (tRole) trustedRoles.push(tRole); + } catch (error) { + // Trusted role is invalid + } + } + return trustedRoles; + } + + public static async getMemberAnniversaryRoleList( + guild: Guild, + roles: MemberAnniversaryRole[] + ): Promise { + let memberAnniversaryRoles: Role[] = []; + for (let role of roles) { + try { + let tRole: Role = await guild.roles.fetch(role.MemberAnniversaryRoleDiscordId); + if (tRole) memberAnniversaryRoles.push(tRole); + } catch (error) { + // Member Anniversary role is invalid + } + } + return memberAnniversaryRoles; + } + + public static canGiveAllRoles(guild: Guild, roles: Role[], guildMember: GuildMember): boolean { + if (!roles) return true; + let check = true; + for (let role of roles) { + // See if the bot can give the roles + let highestBotRole = guild.members.resolve(guild.client.user).roles.highest.position; + // If a user isn't given and we test against the bot the last boolean will always be false + check = + role && + role.position < highestBotRole && + (guildMember.user.bot || guildMember.roles.highest.position < highestBotRole); + if (!check) return false; + } + return true; + } + + public static convertCelebrationData( + rawGuildCelebrationData: RawGuildCelebrationData + ): GuildCelebrationData[] { + let dataSet: GuildCelebrationData[] = []; + + for (let rawData of rawGuildCelebrationData.guildDatas) { + let celebrationData = new GuildCelebrationData(); + celebrationData.guildData = rawData; + celebrationData.customMessages = rawGuildCelebrationData.customMessages.filter( + guild => (guild.GuildId = rawData.GuildId) + ); + celebrationData.blacklistedMembers = rawGuildCelebrationData.blacklistedMembers.filter( + guild => (guild.GuildId = rawData.GuildId) + ); + celebrationData.trustedRoles = rawGuildCelebrationData.trustedRoles.filter( + guild => (guild.GuildId = rawData.GuildId) + ); + celebrationData.anniversaryRoles = rawGuildCelebrationData.anniversaryRoles.filter( + guild => (guild.GuildId = rawData.GuildId) + ); + dataSet.push(celebrationData); + } + + return dataSet; + } +} diff --git a/src/utils/client-utils.ts b/src/utils/client-utils.ts new file mode 100644 index 00000000..76e6a49c --- /dev/null +++ b/src/utils/client-utils.ts @@ -0,0 +1,69 @@ +import { Client, DiscordAPIError, Guild, GuildMember, TextChannel, User } from 'discord.js'; +import { PermissionUtils, RegexUtils } from '.'; + +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; + +const FETCH_MEMBER_LIMIT = 20; + +export class ClientUtils { + public static async getUser(client: Client, discordId: string): Promise { + discordId = RegexUtils.discordId(discordId); + if (!discordId) { + return; + } + + try { + return await client.users.fetch(discordId); + } catch (error) { + // 10013: "Unknown User" + if (error instanceof DiscordAPIError && [10013].includes(error.code)) { + return; + } else { + throw error; + } + } + } + + public static async findMember(guild: Guild, input: string): Promise { + try { + let discordId = RegexUtils.discordId(input); + if (discordId) { + return await guild.members.fetch(discordId); + } + + let tag = RegexUtils.tag(input); + if (tag) { + return ( + await guild.members.fetch({ query: tag.username, limit: FETCH_MEMBER_LIMIT }) + ).find(member => member.user.discriminator === tag.discriminator); + } + + return (await guild.members.fetch({ query: input, limit: 1 })).first(); + } catch (error) { + // 10007: "Unknown Member" + // 10013: "Unknown User" + if (error instanceof DiscordAPIError && [10007, 10013].includes(error.code)) { + return; + } else { + throw error; + } + } + } + + public static findNotifyChannel(guild: Guild, langCode: LangCode): TextChannel { + // Prefer the system channel + let systemChannel = guild.systemChannel; + if (systemChannel && PermissionUtils.canSend(systemChannel)) { + return systemChannel; + } + + // Otherwise look for a bot channel + return guild.channels.cache.find( + channel => + channel instanceof TextChannel && + PermissionUtils.canSend(channel) && + Lang.getRegex('channels.bot', langCode).test(channel.name) + ) as TextChannel; + } +} diff --git a/src/utils/color-utils.ts b/src/utils/color-utils.ts index 0ac3fe47..e9672491 100644 --- a/src/utils/color-utils.ts +++ b/src/utils/color-utils.ts @@ -2,7 +2,7 @@ let colors: { name: string; hex: string }[] = require('color-name-list'); const COLOR_HEX_REGEX = /#?[0-9A-F]{6}/i; -export abstract class ColorUtils { +export class ColorUtils { public static isHex(input: string): boolean { return COLOR_HEX_REGEX.test(input); } diff --git a/src/utils/format-utils.ts b/src/utils/format-utils.ts index bc33dc84..fa0b88e6 100644 --- a/src/utils/format-utils.ts +++ b/src/utils/format-utils.ts @@ -1,9 +1,14 @@ import * as Chrono from 'chrono-node'; -import { Blacklisted, CustomMessages, UserDataResults } from '../models/database'; -import { Guild, Message, MessageEmbed, User, Util } from 'discord.js'; - -import { GuildUtils } from '.'; +import { Blacklisted, CustomMessages, GuildData, UserDataResults } from '../models/database'; +import { Guild, GuildMember, Message, MessageEmbed, Role, User } from 'discord.js'; +import { GuildUtils, ParseUtils } from '.'; + +import { CelebrationUtils } from './celebration-utils'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { MemberAnniversaryRoles } from '../models/database/member-anniversary-role-models'; +import { TrustedRoles } from '../models/database/trusted-role-models'; import moment from 'moment-timezone'; let Config = require('../../config/config.json'); @@ -13,34 +18,14 @@ let zoneNames = moment.tz .names() .filter(name => Config.validation.regions.some((region: any) => name.startsWith(`${region}/`))); -export abstract class FormatUtils { - public static getRoleName(guild: Guild, roleDiscordId: string): string { - return roleDiscordId - ? guild.roles.resolve(roleDiscordId)?.toString() || '**Unknown**' - : '**None**'; - } - - public static getMemberDisplayName(memberDiscordId: string, guild: Guild): string { - let displayName = guild.members.resolve(memberDiscordId)?.displayName; - return displayName ? Util.escapeMarkdown(displayName) : 'Unknown Member'; - } - - public static getMemberMention(memberDiscordId: string, guild: Guild): string { - return guild.members.resolve(memberDiscordId)?.toString() || 'Unknown Member'; - } - - public static getPercent(decimal: number): string { - return Math.floor(decimal * 100) + '%'; - } - - public static isHour(input: number): boolean { - return Number.isInteger(input) && input >= 0 && input <= 23; - } - +export class FormatUtils { + // TODO: fix so there isn't a comma where there are only two users public static joinWithAnd(values: string[]): string { - return [values.slice(0, -1).join(', '), values.slice(-1)[0]].join( - values.length < 2 ? '' : ', and ' - ); + return values.length === 2 + ? values[0] + ` ${Lang.getRef('terms.and', LangCode.EN_US)} ` + values[1] + : [values.slice(0, -1).join(', '), values.slice(-1)[0]].join( + values.length < 2 ? '' : `, ${Lang.getRef('terms.and', LangCode.EN_US)} ` + ); } public static checkAbbreviation(input: string): boolean { @@ -52,15 +37,28 @@ export abstract class FormatUtils { return zoneNames.find(zone => zone.toLowerCase().includes(zoneSearch)); } + // TODO: take another look at this public static getBirthday(input: string): string { // Try and get a date from the 3rd args if ( - input === '02/29' || - input === '2/29' || - input.toLowerCase() === 'february 29' || - input.toLowerCase() === 'feb 29' || - input.toLowerCase() === 'february 29th' || - input.toLowerCase() === 'feb 29th' + input.includes('02/29') || + input.includes('2/29') || + input + .toLowerCase() + .includes(Lang.getRef('months.feb', LangCode.EN_US).toLowerCase() + ' 29') || + input + .toLowerCase() + .includes( + Lang.getRef('months.feb', LangCode.EN_US).toLowerCase().slice(0, 2) + ' 29' + ) || + input + .toLowerCase() + .includes(Lang.getRef('months.feb', LangCode.EN_US).toLowerCase() + ' 29th') || + input + .toLowerCase() + .includes( + Lang.getRef('months.feb', LangCode.EN_US).toLowerCase().slice(0, 2) + ' 29th' + ) ) input = '2000-02-29'; let results = Chrono.parseDate(input); // Try an parse a date @@ -86,34 +84,35 @@ export abstract class FormatUtils { public static getMonth(month: number): string { switch (month) { case 1: - return 'January'; + return Lang.getRef('months.jan', LangCode.EN_US); case 2: - return 'February'; + return Lang.getRef('months.feb', LangCode.EN_US); case 3: - return 'March'; + return Lang.getRef('months.mar', LangCode.EN_US); case 4: - return 'April'; + return Lang.getRef('months.apr', LangCode.EN_US); case 5: - return 'May'; + return Lang.getRef('months.may', LangCode.EN_US); case 6: - return 'June'; + return Lang.getRef('months.jun', LangCode.EN_US); case 7: - return 'July'; + return Lang.getRef('months.jul', LangCode.EN_US); case 8: - return 'August'; + return Lang.getRef('months.aug', LangCode.EN_US); case 9: - return 'September'; + return Lang.getRef('months.sep', LangCode.EN_US); case 10: - return 'October'; + return Lang.getRef('months.oct', LangCode.EN_US); case 11: - return 'November'; + return Lang.getRef('months.nov', LangCode.EN_US); case 12: - return 'December'; + return Lang.getRef('months.dec', LangCode.EN_US); default: - return 'Invalid month'; + return Lang.getRef('months.invalid', LangCode.EN_US); } } + // TODO: add usage of arrays in lang system public static findBoolean(input: string): boolean { switch (input.toLowerCase()) { case 'enabled': @@ -146,30 +145,46 @@ export abstract class FormatUtils { customMessageResults: CustomMessages, page: number, pageSize: number, - hasPremium: boolean + hasPremium: boolean, + type: string ): Promise { - let embed = new MessageEmbed() - .setTitle(`Birthday Messages | Page ${page}/${customMessageResults.stats.TotalPages}`) - .setThumbnail(guild.iconURL()) - .setColor(Config.colors.default) - .setFooter( - `Total Messages: ${customMessageResults.stats.TotalItems} • ${Config.experience.birthdayMessageListSize} per page`, - guild.iconURL() - ) - .setTimestamp(); + let embed: MessageEmbed; let i = (page - 1) * pageSize + 1; if (customMessageResults.customMessages.length === 0) { - let embed = new MessageEmbed() - .setDescription('**No Custom Birthday Messages!**') - .setColor(Config.colors.default); + embed = new MessageEmbed() + .setColor(Config.colors.default) + .setDescription( + Lang.getRef( + type === 'birthday' + ? 'list.noCustomBirthdayMessages' + : type === 'memberanniversary' + ? 'list.noCustomMemberAnniversaryMessages' + : 'list.noCustomServerAnniversaryMessages', + LangCode.EN_US + ) + ); return embed; } - let description = `*A random birthday message is chosen for each birthday. If there are none, the default will be used. [(?)](${Config.links.docs}/faq#what-is-a-custom-birthday-message)*\n\n`; + let description = ''; + + let maxMessagesFree: number = + type === 'memberanniversary' + ? Config.validation.message.maxCount.memberAnniversary.free + : type === 'serveranniversary' + ? Config.validation.message.maxCount.serverAnniversary.free + : Config.validation.message.maxCount.birthday.free; + let maxMessagesPaid: number = + type === 'memberanniversary' + ? Config.validation.message.maxCount.memberAnniversary.paid + : type === 'serveranniversary' + ? Config.validation.message.maxCount.serverAnniversary.paid + : Config.validation.message.maxCount.birthday.paid; for (let customMessage of customMessageResults.customMessages) { - if (hasPremium || customMessage.Position <= 10) { + // dynamically check which ones to cross out due to the server not having premium anymore + if (hasPremium || customMessage.Position <= maxMessagesFree) { description += `**${i.toLocaleString()}.** ${customMessage.Message}\n\n`; } else { description += `**${i.toLocaleString()}.** ~~${customMessage.Message}~~\n\n`; @@ -177,15 +192,43 @@ export abstract class FormatUtils { i++; } - if (!hasPremium && customMessageResults.stats.TotalItems > 10) - embed.addField( - 'Message Limit', - `The free version of Birthday Bot can only have up to **${Config.validation.message.maxCount.free}** custom birthday messages. Unlock up to **${Config.validation.message.maxCount.paid}** with \`bday premium\`!\n\n` - ); - - embed.setDescription(description); + let listEmbed = 'list.'; + + if (!hasPremium && customMessageResults.stats.TotalItems > maxMessagesFree) { + listEmbed += + type === 'memberanniversary' + ? 'memberAnniversaryMessageLocked' + : type === 'serveranniversary' + ? 'serverAnniversaryMessageLocked' + : 'birthdayMessageLocked'; + embed = Lang.getEmbed(listEmbed, LangCode.EN_US, { + PAGE: page.toString(), + LIST_DATA: description, + TOTAL_PAGES: customMessageResults.stats.TotalPages.toString(), + TOTAL_MESSAGES: customMessageResults.stats.TotalItems.toString(), + PER_PAGE: Config.experience.birthdayMessageListSize.toString(), + MAX_FREE: maxMessagesFree.toString(), + MAX_PAID: maxMessagesPaid.toString(), + ICON: guild.client.user.avatarURL(), + }); + } else { + listEmbed += + type === 'memberanniversary' + ? 'memberAnniversaryMessageUnLocked' + : type === 'serveranniversary' + ? 'serverAnniversaryMessageUnLocked' + : 'birthdayMessageUnLocked'; + embed = Lang.getEmbed(listEmbed, LangCode.EN_US, { + PAGE: page.toString(), + LIST_DATA: description, + TOTAL_PAGES: customMessageResults.stats.TotalPages.toString(), + TOTAL_MESSAGES: customMessageResults.stats.TotalItems.toString(), + PER_PAGE: Config.experience.birthdayMessageListSize.toString(), + ICON: guild.client.user.avatarURL(), + }); + } - return embed; + return embed.setThumbnail(guild.iconURL()); } public static async getCustomUserMessageListEmbed( @@ -193,75 +236,158 @@ export abstract class FormatUtils { customMessageResults: CustomMessages, page: number, pageSize: number, - hasPremium: boolean + hasPremium: boolean, + type: string ): Promise { - let embed = new MessageEmbed() - .setTitle( - `User Birthday Messages | Page ${page}/${customMessageResults.stats.TotalPages}` - ) - .setThumbnail(guild.iconURL()) - .setColor(Config.colors.default) - .setFooter( - `Total Messages: ${customMessageResults.stats.TotalItems} • ${Config.experience.birthdayMessageListSize} per page`, - guild.iconURL() - ) - .setTimestamp(); + let embed: MessageEmbed; if (customMessageResults.customMessages.length === 0) { - let embed = new MessageEmbed() - .setDescription('**No User-Specific Birthday Messages!**') + embed = new MessageEmbed() + .setDescription( + Lang.getRef( + type === 'birthday' + ? 'list.noCustomUserSpecificBirthdayMessages' + : 'list.noCustomUserSpecificMemberAnnivesaryMessages', + LangCode.EN_US + ) + ) .setColor(Config.colors.default); return embed; } - let description = `*A user-specific birthday message is the birthday message sent to the designated user on their birthday. [(?)](${Config.links.docs}/faq#what-is-a-user-specific-birthday-message)*\n\n`; + let description = ''; for (let customMessage of customMessageResults.customMessages) { let member = guild.members.resolve(customMessage.UserDiscordId); if (hasPremium) { - description += `${member ? `**${member.displayName}**: ` : '**Unknown Member** '} ${ - customMessage.Message - }\n\n`; + description += `${ + member + ? `**${member.displayName}**: ` + : `**${Lang.getRef('terms.unknownMember', LangCode.EN_US)}** ` + } ${customMessage.Message.replace('', member.toString())}\n\n`; } else { description += `${ - member ? `**${member.displayName}**: ` : '**Unknown Member** ' - } ~~${customMessage.Message}~~\n\n`; + member + ? `**${member.displayName}**: ` + : `**${Lang.getRef('terms.unknownMember', LangCode.EN_US)}** ` + } ~~${customMessage.Message.replace('', member.toString())}~~\n\n`; } } - if (!hasPremium) - embed.addField( - 'Locked Feature', - `User-specific messages are a premium only feature. Unlock them with \`bday premium\`!\n\n` - ); - - embed.setDescription(description); + let listEmbed = 'list.'; + + if (!hasPremium) { + listEmbed += + type === 'memberanniversary' + ? 'userSpecificMemberAnniversaryMessageLocked' + : 'userSpecificBirthdayMessageLocked'; + embed = Lang.getEmbed(listEmbed, LangCode.EN_US, { + PAGE: page.toString(), + LIST_DATA: description, + TOTAL_PAGES: customMessageResults.stats.TotalPages.toString(), + TOTAL_MESSAGES: customMessageResults.stats.TotalItems.toString(), + PER_PAGE: Config.experience.birthdayMessageListSize.toString(), + ICON: guild.client.user.avatarURL(), + }); + } else { + listEmbed += + type === 'memberanniversary' + ? 'userSpecificMemberAnniversaryMessageUnLocked' + : 'userSpecificBirthdayMessageUnLocked'; + embed = Lang.getEmbed(listEmbed, LangCode.EN_US, { + PAGE: page.toString(), + LIST_DATA: description, + TOTAL_PAGES: customMessageResults.stats.TotalPages.toString(), + TOTAL_MESSAGES: customMessageResults.stats.TotalItems.toString(), + PER_PAGE: Config.experience.birthdayMessageListSize.toString(), + ICON: guild.client.user.avatarURL(), + }); + } return embed; } + public static async getTrustedRoleList( + guild: Guild, + trustedRoleResults: TrustedRoles, + page: number, + pageSize: number, + hasPremium: boolean + ): Promise { + let embed: MessageEmbed; + + let i = (page - 1) * pageSize + 1; + + if (trustedRoleResults.trustedRoles.length === 0) { + let embed = new MessageEmbed() + .setDescription(Lang.getRef('list.noTrustedRoles', LangCode.EN_US)) + .setColor(Config.colors.default); + return embed; + } + let description = ''; + + for (let trustedRole of trustedRoleResults.trustedRoles) { + // dynamically check which ones to cross out due to the server not having premium anymore + let role = guild.roles.resolve(trustedRole.TrustedRoleDiscordId); + if ( + hasPremium || + trustedRole.Position <= Config.validation.trustedRoles.maxCount.free + ) { + description += `**${i.toLocaleString()}.** ${ + role ? `${role.toString()} ` : `**** ` + }\n\n`; + } else { + description += `**${i.toLocaleString()}.** ${ + role + ? `~~${role.toString()}~~ ` + : `**${Lang.getRef('terms.deletedRole', LangCode.EN_US)}** ` + }\n\n`; + } + i++; + } + + if ( + !hasPremium && + trustedRoleResults.stats.TotalItems > Config.validation.trustedRoles.maxCount.free + ) { + embed = Lang.getEmbed('list.trustedRolePaid', LangCode.EN_US, { + PAGE: page.toString(), + LIST_DATA: description, + TOTAL_PAGES: trustedRoleResults.stats.TotalPages.toString(), + TOTAL_ROLES: trustedRoleResults.stats.TotalItems.toString(), + PER_PAGE: Config.experience.trustedRoleListSize.toString(), + MAX_FREE: Config.validation.trustedRoles.maxCount.free.toString(), + MAX_PAID: Config.validation.trustedRoles.maxCount.paid.toString(), + ICON: guild.client.user.avatarURL(), + }); + } else { + embed = Lang.getEmbed('list.trustedRoleFree', LangCode.EN_US, { + PAGE: page.toString(), + LIST_DATA: description, + TOTAL_PAGES: trustedRoleResults.stats.TotalPages.toString(), + TOTAL_ROLES: trustedRoleResults.stats.TotalItems.toString(), + PER_PAGE: Config.experience.trustedRoleListSize.toString(), + ICON: guild.client.user.avatarURL(), + }); + } + + return embed.setThumbnail(guild.iconURL()); + } + public static async getBirthdayListFullEmbed( guild: Guild, userDataResults: UserDataResults, + guildData: GuildData, page: number, pageSize: number ): Promise { - let embed = new MessageEmbed() - .setTitle(`Birthday List | Page ${page}/${userDataResults.stats.TotalPages}`) - .setThumbnail(guild.iconURL()) - .setColor(Config.colors.default) - .setFooter( - `Total Birthdays: ${userDataResults.stats.TotalItems} • ${Config.experience.birthdayListSize} per page`, - guild.iconURL() - ) - .setTimestamp(); - + let embed: MessageEmbed; if (userDataResults.userData.length === 0) { let embed = new MessageEmbed() - .setDescription('**No Birthdays in this server!**') + .setDescription(Lang.getRef('list.noBirthdays', LangCode.EN_US)) .setColor(Config.colors.default); return embed; } - let description = `*Birthdays are celebrated on the day (and __time zone__) of the birthday user. To set your birthday use \`bday set\`!*\n\n`; + let description = ''; let birthdays = [ ...new Set( userDataResults.userData.map(data => moment(data.Birthday).format('MMMM Do')) @@ -273,19 +399,69 @@ export abstract class FormatUtils { let users = userDataResults.userData.filter( data => moment(data.Birthday).format('MMMM Do') === birthday ); // Get all users with this birthday to create the sub list - let userNames: string[] = []; - for (let user of users) { - userNames.push( - `${guild.members.resolve(user.UserDiscordId)?.displayName}` || '**Unknown**' - ); - } - let userList = this.joinWithAnd(userNames); // Get the sub list of usernames for this date - description += `**${birthday}**: ${userList}\n`; // Append the description + + let members = guild.members.cache + .filter(m => users.map(u => u.UserDiscordId).includes(m.id)) + .map(member => member); + + let userList = CelebrationUtils.getUserListString(guildData, members); // Get the sub list of usernames for this date + description += `__**${birthday}**__: ${userList}\n`; // Append the description } - embed.setDescription(description); + embed = Lang.getEmbed('list.birthday', LangCode.EN_US, { + PAGE: page.toString(), + LIST_DATA: description, + TOTAL_PAGES: userDataResults.stats.TotalPages.toString(), + TOTAL_BIRTHDAYS: userDataResults.stats.TotalItems.toString(), + PER_PAGE: pageSize.toString(), + ICON: guild.client.user.avatarURL(), + }); - return embed; + return embed.setThumbnail(guild.iconURL()); + } + + public static async getMemberAnniversaryListFullEmbed( + guild: Guild, + guildMembers: GuildMember[], + guildData: GuildData, + page: number, + pageSize: number, + totalPages: number, + totalMembers: number + ): Promise { + let embed: MessageEmbed; + if (guildMembers.length === 0) { + // Not implemented + let embed = new MessageEmbed() + .setDescription(Lang.getRef('list.noMemberAnniversaries', LangCode.EN_US)) + .setColor(Config.colors.default); + return embed; + } + let description = ''; + let anniversaries = [ + ...new Set(guildMembers.map(m => moment(m.joinedAt).format('MMMM Do'))), + ]; // remove duplicates + + // Go through the list of birthdays + for (let anniversary of anniversaries) { + let members = guildMembers.filter( + m => moment(m.joinedAt).format('MMMM Do') === anniversary + ); // Get all users with this birthday to create the sub list + let userList = CelebrationUtils.getUserListString(guildData, members); // Get the sub list of usernames for this date + description += `__**${anniversary}**__: ${userList}\n`; // Append the description + } + + // Update config variables and add member anniversary list message + embed = Lang.getEmbed('list.memberAnniversary', LangCode.EN_US, { + PAGE: page.toString(), + LIST_DATA: description, + TOTAL_PAGES: totalPages.toString(), + TOTAL_ANNIVERSARIES: totalMembers.toString(), + PER_PAGE: pageSize.toString(), + ICON: guild.client.user.avatarURL(), + }); + + return embed.setThumbnail(guild.iconURL()); } public static async getBlacklistFullEmbed( @@ -294,38 +470,279 @@ export abstract class FormatUtils { page: number, pageSize: number ): Promise { - let embed = new MessageEmbed() - .setTitle(`Birthday Blacklist List | Page ${page}/${blacklistResults.stats.TotalPages}`) - .setThumbnail(guild.iconURL()) - .setColor(Config.colors.default) - .setFooter( - `Total Blacklisted Users: ${blacklistResults.stats.TotalItems} • ${Config.experience.blacklistSize} per page`, - guild.iconURL() - ) - .setTimestamp(); + let embed: MessageEmbed; if (blacklistResults.blacklist.length === 0) { let embed = new MessageEmbed() - .setDescription('**The blacklist is empty!**') + .setDescription(Lang.getRef('list.emptyBlacklist', LangCode.EN_US)) .setColor(Config.colors.default); return embed; } - let description = `*Users on this list will not have their birthdays celebrated no matter what. Edit this list with \`bday blacklist \`!*\n\n`; + let description = ''; let users = blacklistResults.blacklist.map(data => data.UserDiscordId); for (let user of users) { description += `**${ - guild.members.resolve(user)?.displayName || 'Unknown' + guild.members.resolve(user)?.displayName || + `**${Lang.getRef('terms.unknownMember', LangCode.EN_US)}**` }**: (ID: ${user})\n`; // Append the description } - embed.setDescription(description); + embed = Lang.getEmbed('list.blacklist', LangCode.EN_US, { + PAGE: page.toString(), + LIST_DATA: description, + TOTAL_PAGES: blacklistResults.stats.TotalPages.toString(), + TOTAL_BLACKLIST: blacklistResults.stats.TotalItems.toString(), + PER_PAGE: Config.experience.blacklistSize.toString(), + ICON: guild.client.user.avatarURL(), + }); - return embed; + return embed.setThumbnail(guild.iconURL()); + } + + public static async getMemberAnniversaryRoleList( + guild: Guild, + memberAnniversaryRoleResults: MemberAnniversaryRoles, + page: number, + pageSize: number, + hasPremium: boolean + ): Promise { + let embed: MessageEmbed; + + if (memberAnniversaryRoleResults.memberAnniversaryRoles.length === 0) { + let embed = new MessageEmbed() + .setDescription(Lang.getRef('list.noMemberAnniversaryRoles', LangCode.EN_US)) + .setColor(Config.colors.default); + return embed; + } + let description = ''; + + for (let memberAnniversaryRole of memberAnniversaryRoleResults.memberAnniversaryRoles) { + // dynamically check which ones to cross out due to the server not having premium anymore + let role = guild.roles.resolve(memberAnniversaryRole.MemberAnniversaryRoleDiscordId); + if ( + hasPremium || + memberAnniversaryRole.Position <= + Config.validation.memberAnniversaryRoles.maxCount.free + ) { + description += `**Year ${memberAnniversaryRole.Year}:** ${ + role ? `${role.toString()} ` : `**** ` + }\n\n`; + } else { + description += `**Year ${memberAnniversaryRole.Year}:** ${ + role + ? `~~${role.toString()}~~ ` + : `**${Lang.getRef('terms.deletedRole', LangCode.EN_US)}** ` + }\n\n`; + } + } + + if ( + !hasPremium && + memberAnniversaryRoleResults.stats.TotalItems > + Config.validation.memberAnniversaryRoles.maxCount.free + ) { + embed = Lang.getEmbed('list.memberAnniversaryRolePaid', LangCode.EN_US, { + PAGE: page.toString(), + LIST_DATA: description, + TOTAL_PAGES: memberAnniversaryRoleResults.stats.TotalPages.toString(), + TOTAL_ROLES: memberAnniversaryRoleResults.stats.TotalItems.toString(), + PER_PAGE: Config.experience.memberAnniversaryRoleListSize.toString(), + MAX_PAID: Config.validation.memberAnniversaryRoles.maxCount.paid.toString(), + ICON: guild.client.user.avatarURL(), + }); + } else { + embed = Lang.getEmbed('list.memberAnniversaryRoleFree', LangCode.EN_US, { + PAGE: page.toString(), + LIST_DATA: description, + TOTAL_PAGES: memberAnniversaryRoleResults.stats.TotalPages.toString(), + TOTAL_ROLES: memberAnniversaryRoleResults.stats.TotalItems.toString(), + PER_PAGE: Config.experience.memberAnniversaryRoleListSize.toString(), + ICON: guild.client.user.avatarURL(), + }); + } + + return embed.setThumbnail(guild.iconURL()); } public static extractPageNumber(input: string): number { let match = PAGE_REGEX.exec(input); - return match ? parseInt(match[1]) : null; + return match ? ParseUtils.parseInt(match[1]) : null; + } + + // THIS IS WRONG + // ALTERNATIVES ARE SUPPOSED TO BE ARRAYS BUT LANG SYSTEM DOESN'T SUPPORT IT + public static extractCelebrationType(type: string): string { + switch (type) { + case Lang.getRef('types.birthday', LangCode.EN_US).toLowerCase() || + Lang.getRef('types.alternatives.birthday', LangCode.EN_US).toLowerCase(): + return 'birthday'; + case Lang.getRef('types.memberAnniversary', LangCode.EN_US).toLowerCase() || + Lang.getRef('types.alternatives.memberAnniversary', LangCode.EN_US).toLowerCase(): + return 'memberanniversary'; + + case Lang.getRef('types.serverAnniversary', LangCode.EN_US).toLowerCase() || + Lang.getRef('types.alternatives.serverAnniversary', LangCode.EN_US).toLowerCase(): + return 'serveranniversary'; + + case Lang.getRef('types.userSpecificBirthday', LangCode.EN_US).toLowerCase() || + Lang.getRef( + 'types.alternatives.userSpecificBirthday', + LangCode.EN_US + ).toLowerCase(): + return 'userspecificbirthday'; + + case Lang.getRef('types.userSpecificMemberAnniversary', LangCode.EN_US).toLowerCase() || + Lang.getRef( + 'types.alternatives.userSpecificMemberAnniversary', + LangCode.EN_US + ).toLowerCase(): + return 'userspecificmemberanniversary'; + default: + return null; + } + } + public static extractConfigType(type: string): string { + switch (type) { + case Lang.getRef('types.channel', LangCode.EN_US).toLowerCase(): + return 'channel'; + case Lang.getRef('types.birthdayRole', LangCode.EN_US).toLowerCase(): + return 'role'; + case Lang.getRef('types.birthdayMasterRole', LangCode.EN_US).toLowerCase(): + return 'birthdayMasterRole'; + case Lang.getRef('types.nameFormat', LangCode.EN_US).toLowerCase(): + return 'nameFormat'; + case Lang.getRef('types.timezone', LangCode.EN_US).toLowerCase(): + return 'timezone'; + case Lang.getRef('types.useTimezone', LangCode.EN_US).toLowerCase(): + return 'useTimezone'; + case Lang.getRef('types.trustedPreventsRole', LangCode.EN_US).toLowerCase(): + return 'trustedPreventsRole'; + case Lang.getRef('types.trustedPreventsMessage', LangCode.EN_US).toLowerCase(): + return 'trustedPreventsMessage'; + case Lang.getRef('types.requireAllTrustedRoles', LangCode.EN_US).toLowerCase(): + return 'requireAllTrustedRoles'; + default: + return null; + } + } + + public static extractNameFormatType(type: string): string { + switch (type) { + case Lang.getRef('types.mention', LangCode.EN_US).toLowerCase(): + return 'mention'; + case Lang.getRef('types.nickname', LangCode.EN_US).toLowerCase(): + return 'nickname'; + case Lang.getRef('types.username', LangCode.EN_US).toLowerCase(): + return 'username'; + case Lang.getRef('types.tag', LangCode.EN_US).toLowerCase(): + return 'tag'; + case Lang.getRef('types.default', LangCode.EN_US).toLowerCase(): + return 'default'; + default: + return null; + } + } + + public static extractMiscActionType(type: string): string { + switch (type) { + case Lang.getRef('types.add', LangCode.EN_US).toLowerCase(): + return 'add'; + case Lang.getRef('types.remove', LangCode.EN_US).toLowerCase(): + return 'remove'; + case Lang.getRef('types.clear', LangCode.EN_US).toLowerCase(): + return 'clear'; + case Lang.getRef('types.list', LangCode.EN_US).toLowerCase(): + return 'list'; + case Lang.getRef('types.mention', LangCode.EN_US).toLowerCase(): + return 'mention'; + case Lang.getRef('types.time', LangCode.EN_US).toLowerCase(): + return 'time'; + case Lang.getRef('types.useEmbed', LangCode.EN_US).toLowerCase(): + return 'useEmbed'; + case Lang.getRef('types.help', LangCode.EN_US).toLowerCase(): + return 'help'; + case Lang.getRef('types.setup', LangCode.EN_US).toLowerCase(): + return 'setup'; + case Lang.getRef('types.anniversary', LangCode.EN_US).toLowerCase(): + return 'anniversary'; + case Lang.getRef('types.message', LangCode.EN_US).toLowerCase(): + return 'message'; + case Lang.getRef('types.blacklist', LangCode.EN_US).toLowerCase(): + return 'blacklist'; + case Lang.getRef('types.advanced', LangCode.EN_US).toLowerCase(): + return 'advanced'; + case Lang.getRef('types.premium', LangCode.EN_US).toLowerCase(): + return 'premium'; + case Lang.getRef('types.test', LangCode.EN_US).toLowerCase(): + return 'test'; + case Lang.getRef('types.create', LangCode.EN_US).toLowerCase(): + return 'create'; + case Lang.getRef('types.user', LangCode.EN_US).toLowerCase(): + return 'user'; + case Lang.getRef('types.server', LangCode.EN_US).toLowerCase(): + return 'server'; + case Lang.getRef('types.trusted', LangCode.EN_US).toLowerCase(): + return 'trusted'; + case Lang.getRef('types.claim', LangCode.EN_US).toLowerCase(): + return 'claim'; + default: + return null; + } + } + + public static getCelebrationDisplayType(type: string, plural: boolean): string { + switch (type) { + case 'birthday': + return Lang.getRef('terms.birthdayMessage' + (plural ? 's' : ''), LangCode.EN_US); + case 'memberanniversary': + return Lang.getRef( + 'terms.memberAnniversaryMessage' + (plural ? 's' : ''), + LangCode.EN_US + ); + case 'serveranniversary': + return Lang.getRef( + 'terms.serverAnniversaryMessage' + (plural ? 's' : ''), + LangCode.EN_US + ); + case 'userspecificbirthday': + return Lang.getRef( + 'terms.userBirthdayMessage' + (plural ? 's' : ''), + LangCode.EN_US + ); + case 'userspecificmemberanniversary': + return Lang.getRef( + 'terms.userMemberAnniversaryMessage' + (plural ? 's' : ''), + LangCode.EN_US + ); + default: + return null; + } + } + + public static getMentionSetting(mentionSetting: string, guild: Guild): string { + // Find mentioned role + let roleInput: Role = guild.roles.resolve(mentionSetting); + + if (!roleInput || roleInput.guild.id !== guild.id) { + if ( + mentionSetting.toLowerCase() === 'everyone' || + mentionSetting.toLowerCase() === 'here' + ) { + return '@' + mentionSetting; + } + } else { + return roleInput.toString(); + } + return 'none'; + } + + public static getMessageTime(time: number): string { + let am = Lang.getRef('terms.amTime', LangCode.EN_US); + let pm = Lang.getRef('terms.pmTime', LangCode.EN_US); + if (time === 0) return '12:00 ' + am; + else if (time === 12) return '12:00 ' + pm; + else if (time < 12) return time + ':00 ' + am; + else return time - 12 + ':00 ' + pm; } } diff --git a/src/utils/guild-utils.ts b/src/utils/guild-utils.ts index ea6c1e0c..3dd45563 100644 --- a/src/utils/guild-utils.ts +++ b/src/utils/guild-utils.ts @@ -1,6 +1,9 @@ -import { Guild, GuildMember } from 'discord.js'; +import { Guild, GuildMember, Util } from 'discord.js'; -export abstract class GuildUtils { +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; + +export class GuildUtils { public static findMember(guild: Guild, input: string): GuildMember { let search = input.toLowerCase(); return guild.members.cache.find( @@ -13,7 +16,17 @@ export abstract class GuildUtils { public static getRoleName(roleDiscordId: string, guild: Guild): string { return roleDiscordId - ? guild.roles.resolve(roleDiscordId)?.toString() || '**Unknown**' - : '**None**'; + ? guild.roles.resolve(roleDiscordId)?.toString() || + `**${Lang.getRef('terms.unknownRole', LangCode.EN_US)}**` + : `**${Lang.getRef('terms.none', LangCode.EN_US)}**`; + } + + public static getMemberDisplayName(memberDiscordId: string, guild: Guild): string { + let displayName = guild.members.resolve(memberDiscordId)?.displayName; + return displayName ? Util.escapeMarkdown(displayName) : 'Unknown Member'; + } + + public static getMemberMention(memberDiscordId: string, guild: Guild): string { + return guild.members.resolve(memberDiscordId)?.toString() || 'Unknown Member'; } } diff --git a/src/utils/index.ts b/src/utils/index.ts index c49788d0..1ee21564 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,12 +1,18 @@ +export { ActionUtils } from './action-utils'; +export { ArrayUtils } from './array-utils'; +export { CelebrationUtils } from './celebration-utils'; +export { ClientUtils } from './client-utils'; +export { ColorUtils } from './color-utils'; export { FormatUtils } from './format-utils'; +export { GuildUtils } from './guild-utils'; +export { ListUtils } from './list-utils'; +export { MathUtils } from './math-utils'; export { MessageUtils } from './message-utils'; export { ParseUtils } from './parse-utils'; -export { SQLUtils } from './sql-utils'; -export { ShardUtils } from './shard-utils'; -export { MathUtils } from './math-utils'; +export { PartialUtils } from './partial-utils'; export { PermissionUtils } from './permission-utils'; -export { ActionUtils } from './action-utils'; -export { ArrayUtils } from './array-utils'; -export { BdayUtils } from './bday-utils'; -export { GuildUtils } from './guild-utils'; +export { RegexUtils } from './regex-utils'; +export { ShardUtils } from './shard-utils'; +export { SqlUtils } from './sql-utils'; +export { StringUtils } from './string-utils'; export { TimeUtils } from './time-utils'; diff --git a/src/utils/list-utils.ts b/src/utils/list-utils.ts index 49ba49d5..da8e2232 100644 --- a/src/utils/list-utils.ts +++ b/src/utils/list-utils.ts @@ -1,13 +1,15 @@ -import { Blacklisted, CustomMessages, UserDataResults } from '../models/database'; -import { Guild, Message } from 'discord.js'; +import { Blacklisted, CustomMessages, GuildData, UserDataResults } from '../models/database'; +import { FormatUtils, MessageUtils } from '.'; +import { Guild, GuildMember, Message, MessageEmbed } from 'discord.js'; -import { FormatUtils } from '.'; -import { MessageUtils } from './message-utils'; +import { MemberAnniversaryRoles } from '../models/database/member-anniversary-role-models'; +import { TrustedRoles } from '../models/database/trusted-role-models'; -export abstract class ListUtils { +export class ListUtils { public static async updateBdayList( userDataResults: UserDataResults, guild: Guild, + guildData: GuildData, message: Message, page: number, pageSize: number @@ -17,59 +19,104 @@ export abstract class ListUtils { let embed = await FormatUtils.getBirthdayListFullEmbed( guild, userDataResults, + guildData, page, pageSize ); message = await MessageUtils.edit(message, embed); - if (embed.description === '**No Birthdays in this server!**') { + if (!embed.title) { await message.reactions.removeAll(); return; } } - - public static async updateMessageList( - customMessageResults: CustomMessages, + public static async updateMemberAnniversaryList( + guildMembers: GuildMember[], guild: Guild, + guildData: GuildData, message: Message, page: number, pageSize: number, - hasPremium: boolean + totalPages: number, + totalMembers: number ): Promise { - if (page > customMessageResults.stats.TotalPages) - page = customMessageResults.stats.TotalPages; + if (page > totalPages) page = totalPages; - let embed = await FormatUtils.getCustomMessageListEmbed( + let embed = await FormatUtils.getMemberAnniversaryListFullEmbed( guild, - customMessageResults, + guildMembers, + guildData, page, pageSize, - hasPremium + totalPages, + totalMembers ); message = await MessageUtils.edit(message, embed); - if (embed.description === '**No Custom Birthday Messages!**') { + if (!embed.title) { await message.reactions.removeAll(); return; } } - public static async updateMessageUserList( + public static async updateMessageList( customMessageResults: CustomMessages, guild: Guild, message: Message, page: number, pageSize: number, - hasPremium: boolean + hasPremium: boolean, + type: string, + user: boolean ): Promise { if (page > customMessageResults.stats.TotalPages) page = customMessageResults.stats.TotalPages; - let embed = await FormatUtils.getCustomUserMessageListEmbed( + let embed: MessageEmbed; + + if (user) { + embed = await FormatUtils.getCustomUserMessageListEmbed( + guild, + customMessageResults, + page, + pageSize, + hasPremium, + type + ); + } else { + embed = await FormatUtils.getCustomMessageListEmbed( + guild, + customMessageResults, + page, + pageSize, + hasPremium, + type + ); + } + + message = await MessageUtils.edit(message, embed); + + if (!embed.title) { + await message.reactions.removeAll(); + return; + } + } + + public static async updateTrustedRoleList( + trustedRoleResults: TrustedRoles, + guild: Guild, + message: Message, + page: number, + pageSize: number, + hasPremium: boolean + ): Promise { + if (page > trustedRoleResults.stats.TotalPages) page = trustedRoleResults.stats.TotalPages; + + let embed = await FormatUtils.getTrustedRoleList( guild, - customMessageResults, + trustedRoleResults, page, pageSize, hasPremium @@ -77,7 +124,7 @@ export abstract class ListUtils { message = await MessageUtils.edit(message, embed); - if (embed.description === '**No User-Specific Birthday Messages!**') { + if (!embed.title) { await message.reactions.removeAll(); return; } @@ -101,7 +148,34 @@ export abstract class ListUtils { message = await MessageUtils.edit(message, embed); - if (embed.description === '**The blacklist is empty!**') { + if (!embed.title) { + await message.reactions.removeAll(); + return; + } + } + + public static async updateMemberAnniversaryRoleList( + memberAnniversaryRoleResults: MemberAnniversaryRoles, + guild: Guild, + message: Message, + page: number, + pageSize: number, + hasPremium: boolean + ): Promise { + if (page > memberAnniversaryRoleResults.stats.TotalPages) + page = memberAnniversaryRoleResults.stats.TotalPages; + + let embed = await FormatUtils.getMemberAnniversaryRoleList( + guild, + memberAnniversaryRoleResults, + page, + pageSize, + hasPremium + ); + + message = await MessageUtils.edit(message, embed); + + if (!embed.title) { await message.reactions.removeAll(); return; } diff --git a/src/utils/math-utils.ts b/src/utils/math-utils.ts index 9c58885f..3c7231a3 100644 --- a/src/utils/math-utils.ts +++ b/src/utils/math-utils.ts @@ -1,15 +1,12 @@ -export abstract class MathUtils { +export class MathUtils { public static sum(numbers: number[]): number { return numbers.reduce((a, b) => a + b, 0); } + public static clamp(input: number, min: number, max: number): number { return Math.min(Math.max(input, min), max); } - public static isLeap(year: number): boolean { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - } - public static range(start: number, size: number): number[] { return [...Array(size).keys()].map(i => i + start); } @@ -17,4 +14,8 @@ export abstract class MathUtils { public static ceilToMultiple(input: number, multiple: number): number { return Math.ceil(input / multiple) * multiple; } + + public static getPercent(decimal: number): string { + return Math.floor(decimal * 100) + '%'; + } } diff --git a/src/utils/message-utils.ts b/src/utils/message-utils.ts index 686afd2f..68c83ee3 100644 --- a/src/utils/message-utils.ts +++ b/src/utils/message-utils.ts @@ -3,27 +3,36 @@ import { DiscordAPIError, EmojiResolvable, Message, - MessageEmbed, MessageReaction, + NewsChannel, StringResolvable, TextChannel, User, } from 'discord.js'; +import { Lang } from '../services'; +import { LangCode } from '../models/enums'; +import { TimeUtils } from '.'; + let Config = require('../../config/config.json'); -export abstract class MessageUtils { +export class MessageUtils { public static async send( - target: User | DMChannel | TextChannel, + target: User | DMChannel | TextChannel | NewsChannel, content: StringResolvable ): Promise { try { return await target.send(content); } catch (error) { - // Error code 50007: "Cannot send messages to this user" (User blocked bot or DM disabled), Error code 10013: "Unknown User", Error code 50001: "Missing Access" + // 10003: "Unknown channel" + // 10004: "Unknown guild" + // 10013: "Unknown user" + // 50001: "Missing access" + // 50007: "Cannot send messages to this user" (User blocked bot or DM disabled) + // 50013: "Missing Permissions" if ( error instanceof DiscordAPIError && - (error.code === 50007 || error.code === 10013 || error.code === 50001) + [10003, 10004, 10013, 50001, 50007, 50013].includes(error.code) ) { return; } else { @@ -31,36 +40,52 @@ export abstract class MessageUtils { } } } - public static async edit(target: Message, content: StringResolvable): Promise { + public static async sendWithDelay( + target: User | DMChannel | TextChannel | NewsChannel, + content: StringResolvable, + delay?: number + ): Promise { + delay = Config.delays.enabled ? delay : 0; try { - return await target.edit(content); + await target.send(content); + await TimeUtils.sleep(delay ?? 0); + return; } catch (error) { - // Error code 10008: "Unknown Message" (User blocked bot or DM disabled), Error code 10013: "Unknown User" + // 10003: "Unknown channel" + // 10004: "Unknown guild" + // 10013: "Unknown user" + // 50001: "Missing access" + // 50007: "Cannot send messages to this user" (User blocked bot or DM disabled) + // 50013: "Missing Permissions" if ( error instanceof DiscordAPIError && - (error.code === 10008 || error.code === 10013) + [10003, 10004, 10013, 50001, 50007, 50013].includes(error.code) ) { return; - } else if (error.code === 50001) { - // Error code 50001: "Missing Access" - let embed = new MessageEmbed() - .setColor(Config.colors.error) - .setDescription('I do not have permission to edit that message!'); - this.send(target.channel as TextChannel, embed); } else { throw error; } } } - public static async react(msg: Message, emoji: EmojiResolvable): Promise { + public static async reply( + msg: Message, + content: StringResolvable, + delay?: number + ): Promise { + delay = Config.delays.enabled ? delay : 0; try { - return await msg.react(emoji); + return await msg.reply(content); } catch (error) { - // Error code 90001: "Reaction blocked" (User blocked bot) Error code: 10008: "Unknown Message" (Message was deleted) + // 10003: "Unknown channel" + // 10004: "Unknown guild" + // 10013: "Unknown user" + // 50001: "Missing access" + // 50007: "Cannot send messages to this user" (User blocked bot or DM disabled) + // 50013: "Missing Permissions" if ( error instanceof DiscordAPIError && - (error.code === 90001 || error.code === 10008) + [10003, 10004, 10013, 50001, 50007, 50013].includes(error.code) ) { return; } else { @@ -69,33 +94,77 @@ export abstract class MessageUtils { } } + public static async edit(msg: Message, content: StringResolvable): Promise { + try { + return await msg.edit(content); + } catch (error) { + if (error instanceof DiscordAPIError) { + // 10008: "Unknown Message" (Message was deleted) + // 10013: "Unknown User" + // 50007: "Cannot send messages to this user" (User blocked bot or DM disabled) + // 50013: "Missing Permissions" + if ([10008, 10013, 50007, 50013].includes(error.code)) { + return; + } + + // 50001: "Missing Access" + if ([50001].includes(error.code)) { + await this.send( + msg.channel, + Lang.getEmbed('validation.noPermToEdit', LangCode.EN_US) + ); + return; + } + } + + throw error; + } + } + + public static async react(msg: Message, emoji: EmojiResolvable): Promise { + try { + return await msg.react(emoji); + } catch (error) { + // 10008: "Unknown Message" (Message was deleted) + // 90001: "Reaction Blocked" (User blocked bot) + if (error instanceof DiscordAPIError && [10008, 90001].includes(error.code)) { + return; + } else { + throw error; + } + } + } + public static async removeReaction( msgReaction: MessageReaction, reactor: User ): Promise { try { - return msgReaction.users.remove(reactor); + return await msgReaction.users.remove(reactor); } catch (error) { - // Error code 50001: "Missing Access", Error code: 10008: "Unknown Message" (Message was deleted), Error code: 50013: "Missing Permission" - if ( - error instanceof DiscordAPIError && - (error.code === 50001 || error.code === 10008 || error.code === 50013) - ) { + // 10008: "Unknown Message" (Message was deleted) + // 50001: "Missing Access" + // 50013: "Missing Permission" + if (error instanceof DiscordAPIError && [10008, 50001, 50013].includes(error.code)) { return; } else { throw error; } } } - - public static async delete(message: Message): Promise { + public static async delete(msg: Message): Promise { try { - if (message.deletable) await message.delete(); + if (msg.deletable) { + return await msg.delete(); + } } catch (error) { - // Error code 50001: "Missing Access", Error code: 10008: "Unknown Message" (Message was deleted), Error code: 50013: "Missing Permission" + // 10008: "Unknown Message" (Message was deleted) + // 50001: "Missing Access" + // 50007: "Cannot send messages to this user" (User blocked bot or DM disabled) + // 50013: "Missing Permission" if ( error instanceof DiscordAPIError && - (error.code === 50001 || error.code === 10008 || error.code === 50013) + [10008, 50001, 50007, 50013].includes(error.code) ) { return; } else { diff --git a/src/utils/parse-utils.ts b/src/utils/parse-utils.ts index a3b877b1..d57957b4 100644 --- a/src/utils/parse-utils.ts +++ b/src/utils/parse-utils.ts @@ -1,5 +1,11 @@ -export abstract class ParseUtils { +export class ParseUtils { public static parseInt(input: string): number { - return parseInt(input.replace(/,/g, '')); + let int: number; + try { + int = parseInt(input.replace(/,/g, '')); + } catch { + return; + } + return int; } } diff --git a/src/utils/partial-utils.ts b/src/utils/partial-utils.ts new file mode 100644 index 00000000..cc0cc164 --- /dev/null +++ b/src/utils/partial-utils.ts @@ -0,0 +1,45 @@ +import { DiscordAPIError, Message, MessageReaction } from 'discord.js'; + +// 10003: "Unknown Channel" (Channel was deleted) +// 10008: "Unknown Message" (Message was deleted) +// 50001: "Missing Access" +const IGNORED_ERROR_CODES = [10003, 10008, 50001]; + +export class PartialUtils { + public static async fillMessage(msg: Message): Promise { + if (msg.partial) { + try { + msg = await msg.fetch(); + } catch (error) { + if (error instanceof DiscordAPIError && IGNORED_ERROR_CODES.includes(error.code)) { + return; + } else { + throw error; + } + } + } + + return msg; + } + + public static async fillReaction(msgReaction: MessageReaction): Promise { + if (msgReaction.partial) { + try { + msgReaction = await msgReaction.fetch(); + } catch (error) { + if (error instanceof DiscordAPIError && IGNORED_ERROR_CODES.includes(error.code)) { + return; + } else { + throw error; + } + } + } + + msgReaction.message = await this.fillMessage(msgReaction.message); + if (!msgReaction.message) { + return; + } + + return msgReaction; + } +} diff --git a/src/utils/permission-utils.ts b/src/utils/permission-utils.ts index e84e0813..578e0b9b 100644 --- a/src/utils/permission-utils.ts +++ b/src/utils/permission-utils.ts @@ -7,45 +7,62 @@ export class PermissionUtils { public static canSend(channel: TextChannel | DMChannel | NewsChannel): boolean { if (channel instanceof DMChannel) return true; - let channelPerms = channel?.permissionsFor(channel.client.user); + let channelPerms = channel.permissionsFor(channel.client.user); if (!channelPerms) { // This can happen if the guild disconnected while a collector is running return false; } + // VIEW_CHANNEL - Needed to view the channel // SEND_MESSAGES - Needed to send messages // EMBED_LINKS - Needed to send embedded links - // ADD_REACTIONS - Needed to add reactions + // ADD_REACTIONS - Needed to add new reactions to messages + // READ_MESSAGE_HISTORY - Needed to add new reactions to messages + // https://discordjs.guide/popular-topics/permissions-extended.html#implicit-permissions return channelPerms.has([ Permissions.FLAGS.VIEW_CHANNEL, Permissions.FLAGS.SEND_MESSAGES, Permissions.FLAGS.EMBED_LINKS, Permissions.FLAGS.ADD_REACTIONS, + Permissions.FLAGS.READ_MESSAGE_HISTORY, ]); } - public static canReact(channel: TextChannel | DMChannel | NewsChannel): boolean { - if (channel instanceof DMChannel) return true; - let channelPerms = channel?.permissionsFor(channel.client.user); + public static canReact( + channel: DMChannel | TextChannel | NewsChannel, + removeOthers: boolean = false + ): boolean { + // Bot always has permission in direct message + if (channel instanceof DMChannel) { + return true; + } + + let channelPerms = channel.permissionsFor(channel.client.user); if (!channelPerms) { // This can happen if the guild disconnected while a collector is running return false; } // VIEW_CHANNEL - Needed to view the channel - // READ_MESSAGE_HISTORY - Needed to react to old messages - // ADD_REACTIONS - Needed to add reactions + // ADD_REACTIONS - Needed to add new reactions to messages + // READ_MESSAGE_HISTORY - Needed to add new reactions to messages + // https://discordjs.guide/popular-topics/permissions-extended.html#implicit-permissions + // MANAGE_MESSAGES - Needed to remove others reactions return channelPerms.has([ Permissions.FLAGS.VIEW_CHANNEL, Permissions.FLAGS.READ_MESSAGE_HISTORY, Permissions.FLAGS.ADD_REACTIONS, + ...(removeOthers ? [Permissions.FLAGS.MANAGE_MESSAGES] : []), ]); } public static canHandleReaction(channel: TextChannel | DMChannel | NewsChannel): boolean { - if (channel instanceof DMChannel) return true; + // Bot always has permission in direct message + if (channel instanceof DMChannel) { + return true; + } - let channelPerms = channel?.permissionsFor(channel.client.user); + let channelPerms = channel.permissionsFor(channel.client.user); if (!channelPerms) { // This can happen if the guild disconnected while a collector is running return false; @@ -65,7 +82,7 @@ export class PermissionUtils { command?: Command ): boolean { if (!command || command.adminOnly) { - if (member.hasPermission(Permissions.FLAGS.ADMINISTRATOR)) return true; + if (member.hasPermission(Permissions.FLAGS.MANAGE_GUILD)) return true; if (guildData) { // Check if member has a required role @@ -81,4 +98,20 @@ export class PermissionUtils { } return true; } + + public static hasSubCommandPermission(member: GuildMember, guildData: GuildData): boolean { + if (member.hasPermission(Permissions.FLAGS.MANAGE_GUILD)) return true; + + if (guildData) { + // Check if member has a required role + let memberRoles = member.roles.cache.map(role => role.id); + if ( + guildData.BirthdayMasterRoleDiscordId && + memberRoles.includes(guildData.BirthdayMasterRoleDiscordId) + ) { + return true; + } + } + return false; + } } diff --git a/src/utils/regex-utils.ts b/src/utils/regex-utils.ts new file mode 100644 index 00000000..818618ad --- /dev/null +++ b/src/utils/regex-utils.ts @@ -0,0 +1,27 @@ +export class RegexUtils { + public static regex(input: string): RegExp { + let match = input.match(/^\/(.*)\/([^\/]*)$/); + if (!match) { + return; + } + + return new RegExp(match[1], match[2]); + } + + public static discordId(input: string): string { + return input.match(/\b\d{17,20}\b/)?.[0]; + } + + public static tag(input: string): { username: string; tag: string; discriminator: string } { + let match = input.match(/\b(.+)#([\d]{4})\b/); + if (!match) { + return; + } + + return { + tag: match[0], + username: match[1], + discriminator: match[2], + }; + } +} diff --git a/src/utils/shard-utils.ts b/src/utils/shard-utils.ts index 489fc522..f537c395 100644 --- a/src/utils/shard-utils.ts +++ b/src/utils/shard-utils.ts @@ -1,4 +1,5 @@ import { ShardClientUtil, ShardingManager, Util } from 'discord.js'; + import { MathUtils } from '.'; const MAX_SERVERS_PER_SHARD = 2500; diff --git a/src/utils/sql-utils.ts b/src/utils/sql-utils.ts index 6c65d095..a47fd3f3 100644 --- a/src/utils/sql-utils.ts +++ b/src/utils/sql-utils.ts @@ -1,6 +1,6 @@ import mysql from 'mysql'; -export abstract class SQLUtils { +export class SqlUtils { public static createProcedureSql(name: string, params: any[]): string { let sql = `Call ${name}(${new Array(params.length).fill('?').join(',')});`; params = params.map(this.typeCast); diff --git a/src/utils/string-utils.ts b/src/utils/string-utils.ts new file mode 100644 index 00000000..c6e67d40 --- /dev/null +++ b/src/utils/string-utils.ts @@ -0,0 +1,20 @@ +import removeMarkdown from 'remove-markdown'; + +export class StringUtils { + public static truncate(input: string, length: number, addEllipsis: boolean = false): string { + if (input.length <= length) { + return input; + } + + let output = input.substr(0, addEllipsis ? length - 3 : length); + if (addEllipsis) { + output += '...'; + } + + return output; + } + + public static stripMarkdown(input: string): string { + return removeMarkdown(input); + } +} diff --git a/src/utils/time-utils.ts b/src/utils/time-utils.ts index 1fe45e60..6186cd5d 100644 --- a/src/utils/time-utils.ts +++ b/src/utils/time-utils.ts @@ -5,7 +5,7 @@ import { promisify } from 'util'; let setTimeoutAsync = promisify(setTimeout); -export abstract class TimeUtils { +export class TimeUtils { public static async sleep(ms: number): Promise { return await setTimeoutAsync(ms); } @@ -21,4 +21,12 @@ export abstract class TimeUtils { public static getMomentInZone(zone: string): Moment { return tz(zone); } + + public static isLeap(year: number): boolean { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + public static isHour(input: number): boolean { + return Number.isInteger(input) && input >= 0 && input <= 23; + } }