diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..293be5b1 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +NODE_ENV=development # development, production + +DISCORD_BOT_TOKEN="" +DEV_DISCORD_BOT_TOKEN="" + +# Paste the entire service_account.json files here +FIREBASE_SERVICE_ACCOUNT= +DEV_FIREBASE_SERVICE_ACCOUNT= diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 2f3e00a2..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,33 +0,0 @@ -module.exports = { - 'env': { - 'browser': true, - 'commonjs': true, - 'es2021': true - }, - // 'parser' : '@babel/eslint-parser', - 'extends': 'eslint:recommended', - 'parserOptions': { - 'ecmaVersion': 12, - // 'requireConfigFile' : false, - }, - 'rules': { - 'indent': [ - 'error', - 4, - { 'SwitchCase' : 1 }, - ], - 'linebreak-style': [ - 'error', - 'windows' - ], - 'quotes': [ - 'error', - 'single' - ], - 'semi': [ - 'error', - 'always' - ], - 'no-unused-vars': 'off' - } -}; diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 21cab2f3..02045ebd 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,8 +3,7 @@ name: Bug report about: Create a report to help us improve title: "[BUG]" labels: bug -assignees: '' - +assignees: "" --- **Describe the bug** @@ -12,6 +11,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior on Discord: + 1. Run the command '...' 2. Respond with '....' diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index c2b58c05..6ede15ab 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,10 +1,9 @@ --- name: Feature request about: Suggest an idea for this project -title: '' +title: "" labels: new feature -assignees: '' - +assignees: "" --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8b0e6dd2..e5bd3b66 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,7 @@ # TL;DR # Description + New Feature/Bug Fix ... ## Why diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4f152cf0..8ea57a0f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,12 +13,12 @@ name: "CodeQL" on: push: - branches: [ develop ] + branches: [develop] pull_request: # The branches below must be a subset of the branches above - branches: [ develop ] + branches: [develop] schedule: - - cron: '38 20 * * 5' + - cron: "38 20 * * 5" jobs: analyze: @@ -32,40 +32,40 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript' ] + language: ["javascript"] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - - name: Checkout repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index f1371600..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,53 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: CI - -# Controls when the action will run. -on: - # Triggers the workflow on push or pull request events but only for the develop branch - push: - branches: [ develop ] - pull_request: - branches: [ develop ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - # This workflow contains a single job called "build" - deploy: - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout - uses: actions/checkout@v2 - - - uses: c-hive/gha-npm-cache@v1 - - - name: Install JS dependencies - run: npm install - - - name: JSDoc Build - uses: andstor/jsdoc-action@v1.2.0 - with: - source_dir: ./ - recurse: true - # Output folder for the generated documentation - output_dir: ./docs - # The path to a JSDoc configuration file - config_file: ./jsdoc-conf.json - # The JSDoc template to install - # template: better-docs - - - - - - - - - diff --git a/.gitignore b/.gitignore index 45121b40..3502ef7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,144 @@ -.env +# Created by https://www.toptal.com/developers/gitignore/api/node +# Edit at https://www.toptal.com/developers/gitignore?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories node_modules/ -logs/ -.idea/ -registrations.csv \ No newline at end of file +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +# End of https://www.toptal.com/developers/gitignore/api/node diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000..c304e948 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,11 @@ +export default { + tabWidth: 2, + useTabs: false, + plugins: ["@trivago/prettier-plugin-sort-imports"], + importOrder: ["", "^@?\\w", "^[./]"], + importOrderSeparation: true, + importOrderSortSpecifiers: true, + importOrderGroupNamespaceSpecifiers: true, + importOrderCaseInsensitive: true, + importOrderParserPlugins: ["decorators", "typescript"], +}; diff --git a/.sapphirerc.yml b/.sapphirerc.yml new file mode 100644 index 00000000..6eeae889 --- /dev/null +++ b/.sapphirerc.yml @@ -0,0 +1,12 @@ +projectLanguage: ts +locations: + base: src + arguments: arguments + commands: commands + listeners: listeners + preconditions: preconditions + interaction-handlers: interaction-handlers + routes: "" +customFileTemplates: + enabled: true + location: templates diff --git a/.vscode/launch.json b/.vscode/launch.json index 7815b72d..65f0c91b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,17 +1,15 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "pwa-node", - "request": "launch", - "name": "Launch Program", - "skipFiles": [ - "/**" - ], - "program": "${workspaceFolder}\\app.js" - } - ] -} \ No newline at end of file + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-node", + "request": "launch", + "name": "Launch Program", + "skipFiles": ["/**"], + "program": "${workspaceFolder}\\app.js" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index bb329cd3..71631821 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,22 +1,22 @@ { - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit" - }, - "eslint.validate": ["javascript"], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "eslint.validate": ["javascript"], - "cSpell.words": [ - "'s", - "Bot", - "Bot\\'s", - "COVID", - "Hackathons", - "boothing", - "deletable", - "firestore", - "hacakthons", - "hackathon", - "nwhacks", - "unhide", - "waitlist" - ], -} \ No newline at end of file + "cSpell.words": [ + "'s", + "Bot", + "Bot\\'s", + "COVID", + "Hackathons", + "boothing", + "deletable", + "firestore", + "hacakthons", + "hackathon", + "nwhacks", + "unhide", + "waitlist" + ] +} diff --git a/Dockerfile b/Dockerfile index f999ef80..e1482054 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,12 +6,12 @@ WORKDIR /app COPY package*.json ./ -RUN yarn install +RUN npm ci COPY . . ENV NAME=factotum -ENV NODE_ENV=PROD +ENV NODE_ENV=production -CMD ["node", "app.js"] \ No newline at end of file +CMD ["npm", "start"] \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index a51859ec..00000000 --- a/LICENSE +++ /dev/null @@ -1,697 +0,0 @@ -“Commons Clause” License Condition v1.0 - -The Software is provided to you by the Licensor under the License, as defined below, -subject to the following condition. - -Without limiting other conditions in the License, the grant of rights under the -License will not include, and the License does not grant to you, the right to -Sell the Software. - -For purposes of the foregoing, “Sell” means practicing any or all of the rights -granted to you under the License to provide to third parties, for a fee or other -consideration (including without limitation fees for hosting or consulting/ support -services related to the Software), a product or service whose value derives, entirely -or substantially, from the functionality of the Software. Any license notice or attribution -required by the License must also include this Commons Clause License Condition notice. - -Software: nwPlus_discord_bot "Hackabot" - -License: GNU GENERAL PUBLIC LICENSE V3 - -Licensor: nwPlus - - - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/README.md b/README.md index 64d32daf..a8fe91c9 100644 --- a/README.md +++ b/README.md @@ -1,119 +1,59 @@ -# Factotum The do-it-all Discord Bot +# Factotum 🦫 -Previously known as nwPlus Discord Bot, Factotum started as a Discord bot to support small and medium hackathons run on the Discord platform for free. Now it has become a fully customizable, feature rich bot to support any and all types of events over discord. +The official nwPlus Discord bot for hackathon management. Handles mentor tickets, verification, trivia, and reporting. -## Set up the bot -At the moment, the bot is still in development, but IT CAN BE USED. Please email me at juapgarc@gmail.com or reach out to me on discord JPGarcia99#8803 to talk about using the bot in its current state! If you would like to test the bot that is also a possibility, just reach out! +## Setup -``` +**Requirements:** Node.js 18+, Discord bot token, Firebase service account + +```bash +git clone https://github.com/nwplus/Factotum.git +cd Factotum npm install -npm run dev ``` -## How does Factotum support hackathon teams and events? -Factotum brings a lot of features that would traditionally happen at in-person hacakthons all over the word to the Discord platform. - -All features listed below are unique and requires an admin to start them. All features are highly customizable, from the emojis used to the text sent. - -Most features rely on emoji reactions, custom emojis can be used as well! - -### Mentor Ticket System (WIP) -The mentor ticket system or more generally the ticketing system of Factotum is an advanced ticketing system for a set of users to inquire by the use of tickets, and another set of users can respond to such tickets both via text or voice. - -The ticket system supports general tickets as well as sub-topic tickets (more detailed tickets intended for only a sub-section of the support team). - -The system also has a opt-in/opt-out system for the support team to select what sub-topic they want to be a part of, and admins can always add sub-topics as they see the need for them. - -When opening a ticket: -- Users can add other users (team members) to be part of the ticket -- Can select a general ticket or a sub-topic ticket -- Will need to write a short description of their problem -- Will receive a DM with the ticket information as soon as it is submitted - -When a ticket is submitted: -- Support staff can see the ticket information (description, people involved, sub-topic) -- Can start the help process with a click of a button (a category with a voice and text channel is created) -- Other support staff can always join a ticket, even if someone else is already helping -- Support staff can leave the ticket at any time with the click of a button -- As soon as everyone leaves the ticket, the ticket category and channels are destroyed - -Things to come: -- Automatic ticket trash collector, Discord has a 500 channel limit, it is imperative old tickets get destroyed or the server could reach the limit very fast - -### Team Formation -Team formation is always a huge part of any hackathon, traditionally, staff hold team formation mini-events for hackers to try and form teams. However, with COVID-19 and the transition to discord, the bot brings two unique alternatives. - -#### Team Formation Catalog -The team formation catalog system works by posting information of teams looking for members, or members looking for a team on a specific channel, and the letting users DM those teams or members they are interested on working with. - -1. Team captain or user looking for a team start the process with a click of a button (emoji) -2. Bot DMs instructions and the user sends information via DM -3. Bot posts team or member information on the channel catalog (channel is view only!) -4. Users can read the available members or teams and DM the user who "posted" the information -5. As soon as a person finds a team or a member to join their team, they can eliminate their "post" with a click of a button +**Environment variables (.env):** -As an added incentive for people to reach out to others, when someone joins the system either as a team looking for members, or as a member looking for a team, they will get notified every time a new counterpart is posted. For example, a user looking for a team will be notified of every new team looking for a member. This stops when they remove their information post. +Copy `.env.example` to `.env` and fill out the fields. +Fields prefixed with `DEV_` will be used when running in development mode and ones without prefix will be used when running in production mode. -#### Team Formation Roulette (WIP) -Team formation roulette is a more direct approach to team formation. Users join the queue as solos or teams of up to three (currently hard capped to create groups of 4, will change later). As people join the bot tries to create groups of 4 as efficiently as possible. +**Discord Bot Setup:** -As this feature is a WIP, more information will be added once its production is complete. +- Enable Server Members Intent and Message Content Intent +- Invite with Administrator permissions -### Verify and Attend -Keeping your Discord server is very important, specially if your event is closed to only those hackers that were accepted. The bot gives you two useful commands to keep your server safe. +**Run:** -Members call ``!verify theiremail@gmail.com`` or ``!attend theiremail@gmail.com`` and if the email is found on a firestore db, then they "gain access" by receiving a higher permission role. Emails are always kept private, they are immediately removed from the channel. - -We are working on changing how this works to use a local or free db, and make it easy for admins to add emails, possibly from Discord. - -### Role selector -Roles are an integral part of a well functioning Discord server. Sometimes it is best to let users select the roles they want to have, the role selector lets you do that with ease. - -Admins can start a role selector on any text channel. Admins can then add new roles to the role selector for users to use. As an admin you can select what emojis to use, what roles to give, and what text to put on the role selector message. - -### Report -Keeping your server safe is always hard, specially if your server is very big. With the ``!report`` command, any users can report bad behavior anonymously via DM. The reports get sent to a Admin only text channel. - -### Threads -Many hackathons are transitioning from Slack to Discord. The one thing we love about Slack is the thread functionality. Factotum brings this functionality to Discord! Users can ask questions with the ``!ask`` command and other users can the respond to the question by clicking an emoji. - -### Clear Chat -Sometimes as an Admin you want to delete an entire text channel, well Factotum has a ``!clear-chat`` command that will delete 100 messages from the text channel. You can let the bot know if you want to keep any pinned messages. - -### Channel Creation (WIP) -Channel creation gives users the ability to create private voice or text channels for them to use with their team or group of friends. With a click of a button, the bot starts asking the user questions about what type of channel they want, the name of the channel, and who has access to this channel. +```bash +npm run dev # Development +npm start # Production +``` -At the moment we do not recommend the use of this command with big events. Discord has a 50 channel limit per category so the channel creation category can get filled up very fast! +**Configure:** Use `/init-bot` command to set roles and channels -### E-Room Directory -Sometimes you need to use other systems to connect users, for example zoom or microsoft teams. If this is the case, E-Room Directory gives you the ability to add links for such rooms and you or a specified role can open and close such rooms. The different states have different message colors and when opened, a role can be notified. +## Development -This is commonly used for boothing when it happens over zoom rooms. When sponsor staff are on the room, they open the room and hackers get notified of the change. +Use the Sapphire CLI to add new commands with the provided template: -### Activities and Workshops (WIP) -The bot has an extensive activity and workshop feature. More information will be added after an extensive review of the features. +```bash +npx sapphire generate slashcommand MyCommand +``` -## Technology Used -- Discord.js -- Node.js -- JavaScript -- Firebase Firestore +This will create a new file `src/commands/MyCommand.ts` with a barebones template for a new command. -## History -This bot started as a personal project to be used at nwHacks 2021. However, after seeing all the features this bot could bring to other events, we decided to also use it for HackCamp 2020 and other nwPlus events. +### Docker -After receiving a lot of support from hackers and mentors, @Maggie and I decided to make the code open source and work hard to make this bot accessible to small and medium hacakthons. +Use the provided Dockerfile and compose file: -## Development -The bot is currently in development, we are constantly adding new features and improving current ones. For the next week or two we will stop work on any new features and concentrate on fixing all bugs! +``` +docker compose up +docker compose down +``` -## Open Source -### Need new features or found a bug? -Let us know by filing a ticket! We are always working on improving this bot to make it the best it can be! +--- -### Contribute +## Schema -## Creators -[JP Garcia](https://github.com/JPGarCar) +![Database Schema](./SCHEMA.png) -[Maggie Wang](https://github.com/mwang2000) +Built by [nwPlus](https://github.com/nwplus) • [Issues](https://github.com/nwplus/Factotum/issues) diff --git a/SCHEMA.png b/SCHEMA.png new file mode 100644 index 00000000..8f9f48fe Binary files /dev/null and b/SCHEMA.png differ diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index ea4172cf..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,9 +0,0 @@ -# Security Policy - -## Supported Versions - -All versions are supported, however, versions are additive, if you want fixes, you will need to use the latest version. - -## Reporting a Vulnerability - -Please send me an email at jp@nwplus.io, thank you! diff --git a/app.js b/app.js deleted file mode 100644 index 59719028..00000000 --- a/app.js +++ /dev/null @@ -1,552 +0,0 @@ -require('dotenv-flow').config(); -const firebaseUtil = require('./db/firebase/firebaseUtil'); -// const Commando = require('discord.js-commando'); -const Discord = require('discord.js'); -const winston = require('winston'); -const fs = require('fs'); -const discordServices = require('./discord-services'); -const BotGuildModel = require('./classes/Bot/bot-guild'); -const Verification = require('./classes/Bot/Features/Verification/verification'); -const { StringPrompt } = require('advanced-discord.js-prompts'); -const Sentry = require('@sentry/node'); -const Tracing = require('@sentry/tracing'); -const { LogLevel, SapphireClient } = require('@sapphire/framework'); -const Pronouns = require('./commands/a_utility/pronouns'); -const RoleSelector = require('./commands/a_utility/role-selector'); -const StartReport = require('./commands/hacker_utility/start-report'); -const OrganizerCheckIn = require('./commands/a_utility/organizer-check-in'); - -/** - * The Main App module houses the bot events, process events, and initializes - * the bot. It also handles new members and greets them. - * @module MainApp - */ - - -/** - * Returns the config settings depending on the command line args. - * Read command line args to know if prod, dev, or test and what server - * First arg is one of prod, dev or test - * the second is the test server, but the first one must be test - * @returns {Map} config settings - */ -function getConfig() { - if (process.env.NODE_ENV === 'DEV') { - // Default dev - return JSON.parse(process.env.DEV); - } else if (process.env.NODE_ENV === 'PROD') { - // Production - return JSON.parse(process.env.PROD); - } else if (process.env.NODE_ENV === 'TEST') { - // Test - const testConfig = JSON.parse(process.env.TEST); - let server = process.env.SERVER; - if (server === '1') { - return testConfig['ONE']; - } else if (server === '2') { - return testConfig['TWO']; - } else if (server === '3') { - return testConfig['THREE']; - } else if (server === '4') { - return testConfig['FOUR']; - } - } - - // exit if no configs are loaded! - console.log('No configs were found for given args.'); - process.exit(0); -} - -const config = getConfig(); - -const isLogToConsole = config['consoleLog']; - -if (config['sentryLog']) { - Sentry.init({ - dsn: 'https://19b2c93c05234d1683cb6f5938f8cf1b@o955295.ingest.sentry.io/6062151', - - // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for performance monitoring. - // We recommend adjusting this value in production - tracesSampleRate: 1.0, - }); -} - -const bot = new SapphireClient({ - defaultPrefix: '!', - caseInsensitiveCommands: true, - logger: { - level: LogLevel.Debug - }, - shards: 'auto', - intents: [ - 'GUILDS', - 'GUILD_MEMBERS', - 'GUILD_BANS', - 'GUILD_EMOJIS_AND_STICKERS', - 'GUILD_VOICE_STATES', - 'GUILD_MESSAGES', - 'GUILD_MESSAGE_REACTIONS', - 'DIRECT_MESSAGES', - 'DIRECT_MESSAGE_REACTIONS' - ], -}); - -const customLoggerLevels = { - levels: { - error: 0, - warning: 1, - command: 2, - event: 3, - userStats: 4, - verbose: 5, - debug: 6, - silly: 7, - }, - colors: { - error: 'red', - warning: 'yellow', - command: 'blue', - event: 'green', - userStats: 'magenta', - verbose: 'cyan', - debug: 'white', - silly: 'black', - } -}; - -// the main logger to use for general errors -const mainLogger = createALogger('main', 'main', true, isLogToConsole); -winston.addColors(customLoggerLevels.colors); - - -/** - * Register all the commands except for help and unknown since we have our own. - */ -// bot.registry -// .registerDefaultTypes() -// .registerGroup('a_boothing', 'boothing group for admins') -// .registerGroup('a_activity', 'activity group for admins') -// .registerGroup('a_start_commands', 'advanced admin commands') -// .registerGroup('a_utility', 'utility commands for admins') -// .registerGroup('hacker_utility', 'utility commands for users') -// .registerGroup('verification', 'verification commands') -// .registerGroup('attendance', 'attendance commands') -// .registerGroup('stamps', 'stamp related commands') -// .registerGroup('utility', 'utility commands') -// .registerGroup('essentials', 'essential commands for any guild', true) -// .registerDefaultGroups() -// .registerDefaultCommands({ -// unknownCommand: false, -// help: false, -// }) -// .registerCommandsIn(__dirname + '/commands'); - -/** - * Runs when the bot finishes the set up and is ready to work. - */ -bot.once('ready', async () => { - mainLogger.warning('The bot ' + bot.user.username + ' has started and is ready to hack!'); - - bot.user.setActivity('nwplus.github.io/Factotum'); - - // initialize firebase - const adminSDK = JSON.parse(process.env.NWPLUSADMINSDK); - - firebaseUtil.initializeFirebaseAdmin('Factotum', adminSDK, process.env.FIREBASE_URL); - mainLogger.warning('Connected to nwFirebase successfully!', { event: 'Ready Event' }); - firebaseUtil.connect('Factotum'); - - // make sure all guilds have a botGuild, this is in case the bot goes offline and its added - // to a guild. If botGuild is found, make sure only the correct commands are enabled. - const guildsArr = Array.from(bot.guilds.cache); - for (const [_, guild] of guildsArr) { - // create the logger for the guild - createALogger(guild.id, guild.name, false, isLogToConsole); - - let botGuild = await firebaseUtil.getInitBotInfo(guild.id); - if (!botGuild) { - await newGuild(guild); - mainLogger.verbose(`Created a new botGuild for the guild ${guild.id} - ${guild.name} on bot ready.`, { event: 'Ready Event' }); - } else { - // set all non guarded commands to not enabled for the guild - // bot.registry.groups.forEach((group, key, map) => { - // if (!group.guarded) guild.setGroupEnabled(group, false); - // }); - - // await botGuild.setCommandStatus(bot); - - mainLogger.verbose(`Found a botGuild for ${guild.id} - ${guild.name} on bot ready.`, { event: 'Ready Event' }); - - if (botGuild.isSetUpComplete) { - mainLogger.verbose('Trying to restore existing pronoun command message'); - /** @type {Pronouns} */ - const pronounsCommand = bot.stores.get('commands').get('pronouns'); - const pronounsError = await pronounsCommand.tryRestoreReactionListeners(guild); - if (pronounsError) { - mainLogger.warning(pronounsError); - } else { - mainLogger.verbose('Restored pronoun command message'); - } - - /** @type {StartVerification} */ - const startVerificationCommand = bot.stores.get('commands').get('start-verification'); - const verificationError = await startVerificationCommand.tryRestoreReactionListeners(guild); - if (verificationError) { - mainLogger.warning(verificationError); - } else { - mainLogger.verbose('Restored start verification command message'); - } - - /** @type {StartMentorCave} */ - const mentorCaveCommand = bot.stores.get('commands').get('start-mentor-cave'); - const mentorCaveError = await mentorCaveCommand.tryRestoreReactionListeners(guild); - if (mentorCaveError) { - mainLogger.warning(mentorCaveError); - } else { - mainLogger.verbose('Restored mentor cave command message'); - } - - /** @type {RoleSelector} */ - const roleSelectorCommand = bot.stores.get('commands').get('role-selector'); - const roleSelectorError = await roleSelectorCommand.tryRestoreReactionListeners(guild); - if (mentorCaveError) { - mainLogger.warning(roleSelectorError); - } else { - mainLogger.verbose('Restored role selector command message'); - } - - /** @type {StartReport} */ - const startReportCommand = bot.stores.get('commands').get('start-report'); - const startReportError = await startReportCommand.tryRestoreReactionListeners(guild); - if (startReportError) { - mainLogger.warning(startReportError); - } else { - mainLogger.verbose('Restored start report command message'); - } - - /** @type {OrganizerCheckIn} */ - const organizerCheckInCommand = bot.stores.get('commands').get('organizer-check-in'); - const organizerCheckInError = await organizerCheckInCommand.tryRestoreReactionListeners(guild); - if (organizerCheckInError) { - mainLogger.warning(organizerCheckInError); - } else { - mainLogger.verbose('Restored organizer check-in command message'); - } - } - - guild.commandPrefix = botGuild.prefix; - } - } -}); - -/** - * Runs when the bot is added to a guild. - */ -bot.on('guildCreate', /** @param {Discord.Guild} guild */(guild) => { - mainLogger.warning(`The bot was added to a new guild: ${guild.id} - ${guild.name}.`, { event: 'Guild Create Event' }); - - newGuild(guild); - - // create a logger for this guild - createALogger(guild.id, guild.name); -}); - - -/** - * Will set up a new guild. - * @param {Discord.Guild} guild - * @private - */ -async function newGuild(guild) { - // set all non guarded commands to not enabled for the new guild - // bot.registry.groups.forEach((group, key, map) => { - // if (!group.guarded) guild.setGroupEnabled(group, false); - // }); - // create a botGuild object for this new guild. - await firebaseUtil.createInitBotInfoDoc(guild.id); -} - -/** - * Runs when the bot is removed from a server. - */ -bot.on('guildDelete', async (guild) => { - mainLogger.warning(`The bot was removed from the guild: ${guild.id} - ${guild.name}`); -}); - -/** - * Runs when the bot runs into an error. - */ -bot.on('error', (error) => { - mainLogger.error(`Bot Error: ${error.name} - ${error.message}.`, { event: 'Error', data: error}); -}); - -/** - * Runs when the bot runs into an error when running a command. - */ -bot.on('commandError', (command, error, message) => { - winston.loggers.get(message.channel?.guild?.id || 'main').error(`Command Error: In command ${command.name} got uncaught rejection ${error.name} : ${error.message}`, { event: 'Error', data: error}); -}); - -/** - * Runs when a message is sent in any server the bot is running in. - */ -// bot.on('message', async message => { -// if (message?.guild) { -// let botGuild = await BotGuild.findById(message.guild.id); - -// // Deletes all messages to any channel in the black list with the specified timeout -// // this is to make sure that if the message is for the bot, it is able to get it -// // bot and staff messages are not deleted -// if (botGuild.blackList.has(message.channel.id)) { -// if (!message.author.bot && !discordServices.checkForRole(message.member, botGuild.roleIDs.staffRole)) { -// winston.loggers.get(message.guild.id).verbose(`Deleting message from user ${message.author.id} due to being in the blacklisted channel ${message.channel.name}.`); -// (new Promise(res => setTimeout(res, botGuild.blackList.get(message.channel.id)))).then(() => discordServices.deleteMessage(message)); -// } -// } -// } -// }); - -/** - * Runs when a new member joins a guild the bot is running in. - */ -bot.on('guildMemberAdd', async member => { - let botGuild = await firebaseUtil.getInitBotInfo(member.guild.id); - member.roles.add(botGuild.verification.guestRoleID); - - // if the guild where the user joined is complete then greet and verify. - // also checks to make sure it does not greet bots - // if (botGuild.isSetUpComplete && !member.user.bot) { - // try { - // winston.loggers.get(member.guild.id).userStats('A new user joined the guild and is getting greeted!'); - // await greetNewMember(member, botGuild); - // } catch (error) { - // await fixDMIssue(error, member, botGuild); - // } - // } else { - // winston.loggers.get(member.guild.id).warning('A new user joined the guild but was not greeted because the bot is not set up!'); - // } -}); - -bot.on('commandRun', (command, promise, message, args) => { - winston.loggers.get(message?.guild?.id || 'main').command(`The command ${command.name} with args ${args} is being run from the channel ${message.channel} with id ${message.channel.id} - triggered by the message with id ${message.id} by the user with id ${message.author.id}`); -}); - -/** - * Runs when an unknown command is triggered. - */ -bot.on('unknownCommand', (message) => winston.loggers.get(message?.guild?.id || 'main').command(`An unknown command has been triggered in the channel ${message.channel.name} with id ${message.channel.id}. The message had the content ${message.cleanContent}.`)); - -/** - * Logs in the bot - */ -bot.login(config.token).catch(console.error); - -/** - * Runs when the node process has an uncaught exception. - */ -process.on('uncaughtException', (error) => { - console.log( - 'Uncaught Rejection, reason: ' + error.name + - '\nmessage: ' + error.message + - '\nfile: ' + error.fileName + - '\nline number: ' + error.lineNumber + - '\nstack: ' + error.stack - ); - - if (config['sentryLog']) { - Sentry.captureException(error); - } -}); - -/** - * Runs when the node process has an unhandled rejection. - */ -process.on('unhandledRejection', (error, promise) => { - console.log('Unhandled Rejection at:', promise, - 'Unhandled Rejection, reason: ' + error.name + - '\nmessage: ' + error.message + - '\nfile: ' + error.fileName + - '\nline number: ' + error.lineNumber + - '\nstack: ' + error.stack - ); - - if (config['sentryLog']) { - Sentry.captureException(error); - } -}); - -/** - * Runs when the node process is about to exit and quit. - */ -process.on('exit', () => { - mainLogger.warning('Node is exiting!'); - if (config['sentryLog']) { - Sentry.captureMessage('Node is exiting!'); - } -}); - -/** - * Will create a default logger to use. - * @param {String} loggerName - * @param {String} [loggerLabel=''] - usually a more readable logger name - * @param {Boolean} [handleRejectionsExceptions=false] - will handle rejections and exceptions if true - * @param {Boolean} [LogToConsole=false] - will log all levels to console if true - * @returns {winston.Logger} - */ -function createALogger(loggerName, loggerLabel = '', handelRejectionsExceptions = false, logToConsole = false) { - // custom format - let format = winston.format.printf(info => `${info.timestamp} [${info.label}] ${info.level}${info?.event ? ' <' + info.event + '>' : ''} : ${info.message} ${info?.data ? 'DATA : ' + info.data : '' }`); - - // create main logs directory if not present - if (!fs.existsSync('./logs')) fs.mkdirSync('./logs'); - - // create the directory if not present - if (!fs.existsSync(`./logs/${loggerName}`)) fs.mkdirSync(`./logs/${loggerName}`); - let logger = winston.loggers.add(loggerName, { - levels: customLoggerLevels.levels, - transports: [ - new winston.transports.File({ filename: `./logs/${loggerName}/logs.log`, level: 'silly' }), - new winston.transports.File({ filename: `./logs/${loggerName}/debug.log`, level: 'debug' }), - new winston.transports.File({ filename: `./logs/${loggerName}/verbose.log`, level: 'verbose' }), - new winston.transports.File({ filename: `./logs/${loggerName}/userStats.log`, level: 'userStats' }), - new winston.transports.File({ filename: `./logs/${loggerName}/event.log`, level: 'event' }), - new winston.transports.File({ filename: `./logs/${loggerName}/command.log`, level: 'command' }), - new winston.transports.File({ filename: `./logs/${loggerName}/warning.log`, level: 'warning' }), - new winston.transports.File({ filename: `./logs/${loggerName}/error.log`, level: 'error', handleExceptions: handelRejectionsExceptions, handleRejections: handelRejectionsExceptions, }), - ...(logToConsole ? [new winston.transports.Console({ - level: 'silly', - format: winston.format.combine( - winston.format.colorize({ level: true }), - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - winston.format.splat(), - winston.format.label({ label: loggerLabel}), - format, - ), - handleExceptions: true, - handleRejections: true, - })] : []), - ], - exitOnError: false, - format: winston.format.combine( - winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - winston.format.splat(), - winston.format.label({ label: loggerLabel}), - format, - ) - }); - return logger; -} - -/** - * Greets a member! - * @param {Discord.GuildMember} member - the member to greet - * @param {BotGuildModel} botGuild - * @throws Error if the user has server DMs off - */ -async function greetNewMember(member, botGuild) { - let verifyEmoji = '🍀'; - - var embed = new Discord.MessageEmbed() - .setTitle(`Welcome to the ${member.guild.name} Server!`) - .setDescription('We are very excited to have you here!') - .addField('Have a question?', 'Visit the #welcome-support channel to talk with our staff!') - .addField('Want to learn more about what I can do?', 'Use the !help command anywhere and I will send you a message!') - .setColor(botGuild.colors.embedColor); - - if (botGuild.verification.isEnabled) embed.addField('**VERIFY YOUR EMAIL** to get more access!', 'React to this message with ' + verifyEmoji + ' and follow my instructions!'); - - let msg = await member.send(embed); - - // if verification is on then give guest role and let user verify - if (botGuild.verification.isEnabled) { - discordServices.addRoleToMember(member, botGuild.verification.guestRoleID); - let askedAboutCodex = false; - - msg.react(verifyEmoji); - let verifyCollector = msg.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === verifyEmoji); - - verifyCollector.on('collect', async (reaction, user) => { - try { - var email = await StringPrompt.single({prompt: 'Please send me your email associated to this event!', channel: member.user.dmChannel, userId: member.id, time: 30, cancelable: true}); - } catch (error) { - discordServices.sendEmbedToMember(member, { - title: 'Verification Error', - description: 'Email was not provided, please try again!' - }, true); - return; - } - - try { - await Verification.verify(member, email, member.guild, botGuild); - if (!askedAboutCodex && await firebaseUtil.checkCodexActive(member.guild.id) - && discordServices.checkForRole(member, botGuild.verification.verificationRoles.get('hacker'))) { - try { - discordServices.askBoolQuestion(member,botGuild, 'One more thing!', - 'Would you like to receive free [Codex beta](https://openai.com/blog/openai-codex/) access, courtesy of our sponsor OpenAI (first come first served, while supplies last)?\n\n' + - - 'Open AI is giving out prizes to the best 2 projects using Codex or GPT-3:\n' + - '- 1st place: $120 worth of credits(2 million words in GPT-3 DaVinci)\n' + - '- 2nd place: $60 worth of credits (1 million words in GPT-3 DaVinci)\n\n' + - - 'If you would like a Codex code, please react with a 👍', - 'Thanks for indicating your interest, you have been added to the list! If you are selected to receive an API key, you will get an email.', email); - askedAboutCodex = true; - } catch (error) { - discordServices.sendEmbedToMember(member, { - title: 'Oops, something went wrong', - description: 'Please contact an nwPlus member' - }, false); - } - } - } catch (error) { - discordServices.sendEmbedToMember(member, { - title: 'Verification Error', - description: 'Email provided is not valid! Please try again.' - }, true); - } - }); - - } - // if verification is off, then just give member role - else { - discordServices.addRoleToMember(member, botGuild.roleIDs.memberRole); - } -} - -/** - * Will let the member know how to fix their DM issue. - * @param {Error} error - the error - * @param {Discord.GuildMember} member - the member with the error - * @param {BotGuildModel} botGuild - * @throws Error if the given error is not a DM error - */ -async function fixDMIssue(error, member, botGuild) { - if (error.code === 50007) { - let logger = winston.loggers.get(member.guild.id); - logger.warning(`A new user with id ${member.id} joined the guild but was not able to be greeted, we have asked him to fix the issues!`); - let channelID = botGuild.verification?.welcomeSupportChannelID || botGuild.channelIDs.botSupportChannel; - - member.guild.channels.resolve(channelID).send('<@' + member.id + '> I couldn\'t reach you :(.' + - '\n* Please turn on server DMs, explained in this link: https://support.discord.com/hc/en-us/articles/217916488-Blocking-Privacy-Settings-' + - '\n* Once this is done, please react to this message with 🤖 to let me know!').then(msg => { - msg.react('🤖'); - const collector = msg.createReactionCollector((reaction, user) => user.id === member.id && reaction.emoji.name === '🤖'); - - collector.on('collect', (reaction, user) => { - reaction.users.remove(user.id); - try { - greetNewMember(member, botGuild); - collector.stop(); - msg.delete(); - logger.userStats(`A user with id ${member.id} was able to fix the DM issue and was greeted!`); - } catch (error) { - member.guild.channels.resolve(channelID).send('<@' + member.id + '> Are you sure you made the changes? I couldn\'t reach you again 😕').then(msg => msg.delete({ timeout: 8000 })); - } - }); - }); - } else { - throw error; - } -} diff --git a/classes/Bot/Features/Stamps/stamps-manager.js b/classes/Bot/Features/Stamps/stamps-manager.js deleted file mode 100644 index 8d0a38fe..00000000 --- a/classes/Bot/Features/Stamps/stamps-manager.js +++ /dev/null @@ -1,97 +0,0 @@ -const { Collection, MessageEmbed, GuildMember } = require('discord.js'); -const winston = require('winston'); -const { addRoleToMember, sendEmbedToMember, replaceRoleToMember, sendMessageToMember } = require('../../../../discord-services'); -const Activity = require('../../activities/activity'); - -/** - * @class - * - */ -class StampsManager { - /** - * Will let hackers get a stamp for attending the activity. - * @param {Activity} activity - activity to use - * @param {Number} [time] - time to wait till collector closes, in seconds - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @async - */ - static async distributeStamp(activity, initBotInfo, time = 60) { - - winston.loggers.get(activity.guild.id).event(`Activity named ${activity.name} is distributing stamps.`, {event: 'Activity Manager'}); - - // The users already seen by this stamp distribution. - let seenUsers = new Collection(); - - const promptEmbed = new MessageEmbed() - .setColor(initBotInfo.colors.embedColor) - .setTitle('React within ' + time + ' seconds of the posting of this message to get a stamp for ' + activity.name + '!'); - - let promptMsg = await activity.generalText.send(promptEmbed); - promptMsg.react('👍'); - - // reaction collector, time is needed in milliseconds, we have it in seconds - const collector = promptMsg.createReactionCollector((reaction, user) => !user.bot, { time: (1000 * time) }); - - collector.on('collect', async (reaction, user) => { - // grab the member object of the reacted user - const member = activity.generalText.guild.member(user); - - if (!seenUsers.has(user.id)) { - this.parseRole(member, activity.name, initBotInfo); - seenUsers.set(user.id, user.username); - } - }); - - // edit the message to closed when the collector ends - collector.on('end', collected => { - winston.loggers.get(activity.guild.id).event(`Activity named ${activity.name} stamp distribution has stopped.`, {event: 'Activity Manager'}); - if (!promptMsg.deleted) { - promptMsg.edit(promptEmbed.setTitle('Time\'s up! No more responses are being collected. Thanks for participating in ' + activity.name + '!')); - } - }); - } - - - /** - * Upgrade the stamp role of a member. - * @param {GuildMember} member - the member to add the new role to - * @param {String} activityName - the name of the activity - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @throws Error if the botGuild has stamps disabled - */ - static parseRole(member, activityName, initBotInfo) { - if (!initBotInfo.stamps.isEnabled) { - winston.loggers.get(initBotInfo.id).error(`Stamp system is turned off for guild ${initBotInfo.id} but I was asked to parse a role for member ${member.id} for activity ${activityName}.`, { event: 'Activity Manager' }); - throw Error(`Stamp system is turned of for guild ${initBotInfo.id} but I was asked to parse a role for member ${member.id} for activity ${activityName}.`); - } - - let role = member.roles.cache.find(role => initBotInfo.stamps.stampRoleIDs.has(role.id)); - - if (role === undefined) { - addRoleToMember(member, initBotInfo.stamps.stamp0thRoleId); - sendEmbedToMember(member, 'I did not find an existing stamp role for you so I gave you one for attending ' - + activityName + '. Please contact an admin if there was a problem.', true); - winston.loggers.get(initBotInfo.id).userStats(`Activity named ${activityName} tried to give a stamp to the user with id ${member.id} but he has no stamp, I gave them the first stamp!`, {event: 'Activity Manager'}); - return; - } - - let stampNumber = initBotInfo.stamps.stampRoleIDs.get(role.id); - if (stampNumber === initBotInfo.stamps.stampRoleIDs.size - 1) { - sendMessageToMember(member, 'You already have the maximum allowed number of stamps!', true); - winston.loggers.get(initBotInfo.id).userStats(`Activity named ${activityName} tried to give a stamp to the user with id ${member.id} but he is already in the max stamp ${stampNumber}`, {event: 'Activity Manager'}); - return; - } - let newRoleID; - - initBotInfo.stamps.stampRoleIDs.forEach((num, key, map) => { - if (num === stampNumber + 1) newRoleID = key; - }); - - if (newRoleID != undefined) { - replaceRoleToMember(member, role.id, newRoleID); - sendMessageToMember(member, 'You have received a higher stamp for attending ' + activityName + '!', true); - winston.loggers.get(initBotInfo.id).userStats(`Activity named ${activityName} gave a stamp to the user with id ${member.id} going from stamp number ${stampNumber} to ${stampNumber + 1}`, {event: 'Activity Manager'}); - } - } -} -module.exports = StampsManager; \ No newline at end of file diff --git a/classes/Bot/Features/Team_Formation/team-formation.js b/classes/Bot/Features/Team_Formation/team-formation.js deleted file mode 100644 index 4512f87c..00000000 --- a/classes/Bot/Features/Team_Formation/team-formation.js +++ /dev/null @@ -1,532 +0,0 @@ -const { GuildEmoji, ReactionEmoji, Role, TextChannel, MessageEmbed, Guild, Collection, User, Message, RoleManager } = require('discord.js'); -const { sendEmbedToMember, addRoleToMember, deleteMessage, sendMessageToMember, removeRolToMember } = require('../../../../discord-services'); -const winston = require('winston'); -const Activity = require('../../activities/activity'); -const Console = require('../../../UI/Console/console'); -const { StringPrompt } = require('advanced-discord.js-prompts'); -const Feature = require('../../../UI/Console/feature'); -const firebaseUtil = require('../../../../db/firebase/firebaseUtil') - -/** - * @class TeamFormation - * - * The team formation class represents the team formation activity. It helps teams and prospects - * find each other by adding their respective information to a catalogue of sorts. Admins have the - * ability to customize the messages sent, emojis used, and if they want users to be notified of new - * posts in the catalogue. - * - */ -class TeamFormation extends Activity { - static defaultTeamForm = - "Team Member(s): \nTeam Background: \nObjective: \nFun Fact About Team: \nLooking For: "; - static defaultProspectForm = - "Name: \nSchool: \nPlace of Origin: \nSkills: \nFun Fact: \nDeveloper or Designer?:"; - static defaultTeamColor = "#60c2e6"; - static defaultProspectColor = "#d470cd"; - - /** - * Creates the team role and returns it. - * @param {RoleManager} roleManager - * @returns {Promise} - * @static - * @async - */ - static async createTeamRole(roleManager) { - winston.loggers - .get(roleManager.guild.id) - .verbose(`Team formation team role has been created!`, { - event: "Team Formation", - }); - return await roleManager.create({ - data: { - name: "tf-team-leader", - color: TeamFormation.defaultTeamColor, - }, - }); - } - - /** - * Creates the prospect role and returns it. - * @param {RoleManager} roleManager - * @returns {Promise} - * @static - * @async - */ - static async createProspectRole(roleManager) { - winston.loggers - .get(roleManager.guild.id) - .verbose(`Team formation prospect role has been created!`, { - event: "Team Formation", - }); - return await roleManager.create({ - data: { - name: "tf-prospect", - color: TeamFormation.defaultProspectColor, - }, - }); - } - - /** - * @typedef TeamFormationPartyInfo - * @property {GuildEmoji | ReactionEmoji} emoji - the emoji used to add this party to the team formation - * @property {Role} role - the role given to the users of this party - * @property {String} [form] - the form added to the signup embed for users to respond to. Will not be added if signupEmbed given! - * @property {MessageEmbed} [signupEmbed] - the embed sent to users when they sign up, must include the form! - */ - - /** - * @typedef TeamFormationChannels - * @property {TextChannel} info - the info channel where users read about this activity - * @property {TextChannel} teamCatalogue - the channel where team info is posted - * @property {TextChannel} prospectCatalogue - the channel where prospect info is posted - */ - - /** - * @callback SignupEmbedCreator - * @param {String} teamEmoji - the emoji used by teams to sign up - * @param {String} prospectEmoji - the emoji used by prospects to sign up - * @param {Boolean} isNotificationEnabled - true if parties will be notified when the other party has a new post - * @return {MessageEmbed} - */ - - /** - * @typedef TeamFormationInfo - * @property {TeamFormationPartyInfo} teamInfo - * @property {TeamFormationPartyInfo} prospectInfo - * @property {Guild} guild - * @property {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @property {Collection} activityRoles - * @property {Boolean} [isNotificationsEnabled] - * @property {SignupEmbedCreator} [signupEmbedCreator] - */ - - /** - * Create a new team formation. - * @param {TeamFormationInfo} teamFormationInfo - the team formation information - */ - constructor(teamFormationInfo) { - super({ - activityName: "Team Formation", - guild: teamFormationInfo.guild, - roleParticipants: teamFormationInfo.activityRoles, - initBotInfo: teamFormationInfo.initBotInfo, - }); - - if (!teamFormationInfo?.teamInfo || !teamFormationInfo?.prospectInfo) - throw new Error("Team and prospect info must be given!"); - this.validatePartyInfo(teamFormationInfo.teamInfo); - this.validatePartyInfo(teamFormationInfo.prospectInfo); - if (!teamFormationInfo?.guild) - throw new Error("A guild is required for a team formation!"); - - /** - * The team information, those teams willing to join will use this. - * @type {TeamFormationPartyInfo} - */ - this.teamInfo = { - emoji: teamFormationInfo.teamInfo.emoji, - role: teamFormationInfo.teamInfo.role, - form: teamFormationInfo.teamInfo?.form || TeamFormation.defaultTeamForm, - signupEmbed: teamFormationInfo.teamInfo?.signupEmbed, - }; - - /** - * The prospect info, those solo users that want to join a team will use this info. - * @type {TeamFormationPartyInfo} - */ - this.prospectInfo = { - emoji: teamFormationInfo.prospectInfo.emoji, - role: teamFormationInfo.prospectInfo.role, - form: - teamFormationInfo.prospectInfo?.form || - TeamFormation.defaultProspectForm, - signupEmbed: teamFormationInfo.prospectInfo?.signupEmbed, - }; - - /** - * The channels that a team formation activity needs. - * @type {TeamFormationChannels} - */ - this.channels = {}; - - /** - * True if the parties will be notified when the opposite party has a new post. - * @type {Boolean} - */ - this.isNotificationEnabled = - teamFormationInfo?.isNotificationsEnabled || false; - - /** - * A creator of the info embed in case you want it to be different. - * @type {SignupEmbedCreator} - */ - this.signupEmbedCreator = teamFormationInfo?.signupEmbedCreator || null; - - winston.loggers - .get(this.guild.id) - .event(`A Team formation has been created!`, { event: "Team Formation" }); - winston.loggers - .get(this.guild.id) - .verbose(`A Team formation has been created!`, { - event: "Team Formation", - data: { teamFormationInfo: teamFormationInfo }, - }); - } - - /** - * Validates a TeamFormationPartyInfo object - * @param {TeamFormationPartyInfo} partyInfo - the party info to validate - * @private - */ - validatePartyInfo(partyInfo) { - if ( - !partyInfo?.emoji && - typeof partyInfo.emoji != (GuildEmoji || ReactionEmoji) - ) - throw new Error( - "A Discord emoji is required for a TeamFormationPartyInfo" - ); - if (!partyInfo?.role && typeof partyInfo.role != Role) - throw new Error("A Discord Role is required in a TeamFormationPartyInfo"); - if (partyInfo.signupEmbed && typeof partyInfo.signupEmbed != MessageEmbed) - throw new Error("The message embed must be a Discord Message Embed"); - if (partyInfo.form && typeof partyInfo.form != "string") - throw new Error("The form must be a string!"); - } - - async init() { - await super.init(); - await this.createChannels(); - } - - /** - * Will create the TeamFormationChannels object with new channels to use with a new TeamFormation - * @async - */ - async createChannels() { - this.room.channels.category.setName("🏅Team Formation"); - this.room.channels.generalText.delete(); - this.room.channels.generalVoice.delete(); - - this.channels.info = await this.room.addRoomChannel({ - name: "👀team-formation", - permissions: [ - { - id: this.initBotInfo.roleIDs.everyoneRole, - permissions: { SEND_MESSAGES: false }, - }, - ], - isSafe: true, - }); - - this.channels.prospectCatalogue = await this.room.addRoomChannel({ - name: "🙋🏽prospect-catalogue", - info: { - topic: - "Information about users looking to join teams can be found here. Happy hunting!!!", - }, - permissions: [ - { - id: this.initBotInfo.roleIDs.everyoneRole, - permissions: { SEND_MESSAGES: false }, - }, - ], - isSafe: true, - }); - - this.channels.teamCatalogue = await this.room.addRoomChannel({ - name: "💼team-catalogue", - info: { - topic: - "Channel for teams to post about themselves and who they are looking for! Expect people to send you private messages.", - }, - permissions: [ - { - id: this.initBotInfo.roleIDs.everyoneRole, - permissions: { SEND_MESSAGES: false }, - }, - ], - isSafe: true, - }); - - winston.loggers - .get(this.guild.id) - .verbose(`Team formation channels have been created!`, { - event: "Team Formation", - }); - } - - /** - * Will start the activity! - * @param {SignupEmbedCreator} [signupEmbedCreator] - embed creator for the sign in - * @async - */ - async start(signupEmbedCreator = null) { - let embed; - - if (signupEmbedCreator) { - embed = signupEmbedCreator( - this.teamInfo.emoji, - this.prospectInfo.emoji, - this.isNotificationEnabled - ); - } else { - embed = new MessageEmbed() - .setColor((await firebaseUtil.getInitBotInfo(this.guild.id)).colors.embedColor) - .setTitle("Team Formation Information") - .setDescription( - "Welcome to the team formation section! If you are looking for a team or need a few more members to complete your ultimate group, you are in the right place!" - ) - .addField( - "How does this work?", - "* Once you react to this message, I will send you a template you need to fill out and send back to me via DM. \n* Then I will post your information in the channels below. \n* Then, other members, teams, or yourself can browse these channels and reach out via DM!" - ) - .addField( - "Disclaimer!!", - "By participating in this activity, you consent to other server members sending you a DM." - ) - .addField( - "Teams looking for new members", - "React with " + - this.teamInfo.emoji.toString() + - " and the bot will send you instructions." - ) - .addField( - "Prospects looking for a team", - "React with " + - this.prospectInfo.emoji.toString() + - " and the bot will send you instructions." - ); - } - - let signupMsg = await this.channels.info.send(embed); - signupMsg.react(this.teamInfo.emoji); - signupMsg.react(this.prospectInfo.emoji); - - winston.loggers - .get(this.guild.id) - .event( - `The team formation has started. ${ - signupEmbedCreator - ? "A custom embed creator was used" - : "The default embed was used." - }`, - { event: "Team Formation" } - ); - winston.loggers - .get(this.guild.id) - .verbose( - `The team formation has started. ${ - signupEmbedCreator - ? "A custom embed creator was used" - : "The default embed was used." - }`, - { event: "Team Formation", data: { embed: embed } } - ); - - const signupCollector = signupMsg.createReactionCollector( - (reaction, user) => - !user.bot && - (reaction.emoji.name === this.teamInfo.emoji.name || - reaction.emoji.name === this.prospectInfo.emoji.name) - ); - - signupCollector.on("collect", (reaction, user) => { - let isTeam = reaction.emoji.name === this.teamInfo.emoji.name; - winston.loggers - .get(this.guild.id) - .userStats( - `The user ${user.id} is signing up to the team formation as a ${ - isTeam ? "team" : "prospect" - }.`, - { event: "Team Formation" } - ); - this.reachOutToUser(user, isTeam); - }); - } - - /** - * Will reach out to the user to ask for the form response to add to the catalogue. - * @param {User} user - the user joining the team formation activity - * @param {Boolean} isTeam - true if the user represents a team, else false - * @async - */ - async reachOutToUser(user, isTeam) { - let logger = winston.loggers.get(this.guild.id); - - let console = new Console({ - title: `Team Formation - ${isTeam ? "Team Format" : "Prospect Format"}`, - description: - "We are very excited for you to find your perfect " + - (isTeam ? "team members." : "team.") + - "\n* Please **copy and paste** the following format in your next message. " + - "\n* Try to respond to all the sections! \n* Once you are ready to submit, react to this message with 🇩 and then send me your information!\n" + - "* Once you fill your team, please come back and click the ⛔ emoji.", - channel: await user.createDM(), - guild: this.guild, - }); - - if (this.isNotificationEnabled) - console.addField( - "READ THIS!", - "As soon as you submit your form, you will be notified of every new " + - (isTeam ? "available prospect." : "available team.") + - " Once you close your form, you will stop receiving notifications!" - ); - - await console.addField( - "Format:", - isTeam - ? this.teamInfo.form || TeamFormation.defaultTeamForm - : this.prospectInfo.form || TeamFormation.defaultProspectForm - ); - - await console.addFeature( - Feature.create({ - name: "Send completed form", - description: - "React to this emoji, wait for my prompt, and send the finished form.", - emoji: "🇩", - callback: async (user, reaction, stopInteracting, console) => { - // gather and send the form from the user - try { - var catalogueMsg = await this.gatherForm(user, isTeam); - logger.verbose( - `I was able to get the user's team formation response: ${catalogueMsg.cleanContent}`, - { event: "Team Formation" } - ); - } catch (error) { - logger.warning( - `While waiting for a user's team formation response I found an error: ${error}`, - { event: "Team Formation" } - ); - user.dmChannel - .send( - "You have canceled the prompt. You can try again at any time!" - ) - .then((msg) => msg.delete({ timeout: 10000 })); - stopInteracting(); - return; - } - - // confirm the post has been received - sendEmbedToMember( - user, - { - title: "Team Formation", - description: - "Thanks for sending me your information, you should see it pop up in the respective channel under the team formation category." + - `Once you find your ${ - isTeam ? "members" : "ideal team" - } please react to my original message with ⛔ so I can remove your post. Good luck!!!`, - }, - 15 - ); - logger.event( - `The user ${user.id} has successfully sent their information to the team formation feature.`, - { event: "Team Formation" } - ); - - // add role to the user - addRoleToMember( - this.guild.member(user), - isTeam ? this.teamInfo.role : this.prospectInfo.role - ); - - // add remove post feature - await console.addFeature( - Feature.create({ - name: "Done with team formation!", - description: - "React with this emoji if you are done with team formation.", - emoji: "⛔", - callback: (user, reaction, stopInteracting, console) => { - // remove message sent to channel - deleteMessage(catalogueMsg); - - // confirm deletion - sendMessageToMember( - user, - "This is great! You are all set! Have fun with your new team! Your message has been deleted.", - true - ); - - removeRolToMember( - this.guild.member(user), - isTeam ? this.teamInfo.role : this.prospectInfo.role - ); - - logger.event( - `The user ${user.id} has found a team and has been removed from the team formation feature.`, - { event: "Team Formation" } - ); - - console.delete(); - }, - }) - ); - - console.removeFeature("🇩"); - - stopInteracting(); - }, - }) - ); - - console.sendConsole(); - } - - /** - * Will gather the form from a user to add to the catalogues and send it to the correct channel. - * @param {User} user - the user being prompted - * @param {Boolean} isTeam - true if the user is a team looking for members - * @returns {Promise} - the catalogue message - * @async - * @throws Error if user cancels or takes too long to respond to prompt - */ - async gatherForm(user, isTeam) { - var formContent = await StringPrompt.single({ - prompt: - "Please send me your completed form, if you do not follow the form your post will be deleted!", - channel: user.dmChannel, - userId: user.id, - time: 30, - cancelable: true, - }); - - const embed = new MessageEmbed() - .setTitle("Information about them can be found below:") - .setDescription(formContent + "\nDM me to talk -> <@" + user.id + ">") - .setColor( - isTeam ? this.teamInfo.role.hexColor : this.prospectInfo.role.hexColor - ); - - let channel = isTeam - ? this.channels.teamCatalogue - : this.channels.prospectCatalogue; - - let catalogueMsg = await channel.send( - (this.isNotificationEnabled - ? "<@&" + - (isTeam ? this.prospectInfo.role.id : this.teamInfo.role.id) + - ">, " - : "") + - "<@" + - user.id + - (isTeam - ? "> and their team are looking for more team members!" - : "> is looking for a team to join!"), - { embed: embed } - ); - - winston.loggers - .get(channel.guild.id) - .verbose( - `A message with the user's information has been sent to the channel ${channel.name} with id ${channel.id}.`, - { event: "Team Formation", data: embed } - ); - - return catalogueMsg; - } -} -module.exports = TeamFormation; \ No newline at end of file diff --git a/classes/Bot/Features/Team_Roulette/team.js b/classes/Bot/Features/Team_Roulette/team.js deleted file mode 100644 index 7fb734e3..00000000 --- a/classes/Bot/Features/Team_Roulette/team.js +++ /dev/null @@ -1,178 +0,0 @@ -const Discord = require('discord.js'); -const winston = require('winston'); - -/** - * A Team represents a real life team with members. Teams can merge together, have channels and each team has a unique ID. - */ -class Team { - - constructor(teamNumber) { - /** - * The team ID - * @type {Number} - */ - this.id = teamNumber; - - /** - * All the team members - * @type {Discord.Collection} - - */ - this.members = new Discord.Collection(); - - /** - * The team's text channel if any - * @type {Discord.TextChannel | null} - */ - this.textChannel; - - /** - * The team leader. - * @type {Discord.Snowflake} - ID of the team leader - */ - this.leader; - - /** - * True if the team has been complete at least once. - * @type {Boolean} - */ - this.hasBeenComplete = false; - - /** - * True if the team has been deleted, else false. - * @type {Boolean} - */ - this.deleted = false; - } - - /** - * Create a text channel for this team and add all the team members. - * Will notify the members of the channel creation - * @param {Discord.ChannelManager} channelManager - the channel manager to create the text channel - * @param {Discord.CategoryChannel} category - the category where to create the channel - * @async - * @returns {Promise} - */ - async createTextChannel(channelManager, category) { - this.textChannel = await channelManager.create('Team-' + this.id, { - type: 'text', - topic: 'Welcome to your new team, good luck!', - parent: category, - }); - - let usersMentions = ''; - - this.members.forEach((user, id) => { - usersMentions += '<@' + id + '>, '; - this.addUserToTextChannel(user); - }); - - this.textChannel.send(usersMentions).then(msg => msg.delete({timeout: 5000})); - - return this.textChannel; - } - - /** - * Merge two teams. Team with a text channel, if any will be kept. New - * members will be added to the text channel, if any. - * @param {Team} team - team to merge into this team - * @async - */ - async mergeTeam(team) { - if (this?.textChannel || !team?.textChannel) { - team.members.forEach((user, id) => { - this.members.set(id, user); - this.addUserToTextChannel(user); - }); - return this; - } else { - return await team.mergeTeam(this); - } - } - - /** - * Add a user to the team's text channel by giving them permission. - * Will also introduce them to the team. - * @param {Discord.User} user - * @private - * @async - */ - async addUserToTextChannel(user) { - if (this?.textChannel) { - await this.textChannel.createOverwrite(user.id, { - 'VIEW_CHANNEL' : true, - 'SEND_MESSAGES' : true, - }); - this.textChannel.send('Hello <@' + user.id + '>, welcome to the team!').then(msg => msg.delete({timeout: 30000})); - } - } - - /** - * Add a new user to the team. - * @param {Discord.User | Discord.GuildMember} user - the user to add to the team - * @async - */ - async addTeamMember(user) { - if (!this.members.has(user.id)) { - this.members.set(user.id, user); - await this.addUserToTextChannel(user); - - if (this.members.size === 1) { - this.leader = user.id; - - } - } - - if (this.size() === 4) this.hasBeenComplete = true; - } - - /** - * Removes a user from the team. - * @param {Discord.User} user - the user to remove from the team - * @returns {Number} - the new size of this team - */ - removeTeamMember(user) { - this.members.delete(user.id); - if (this?.textChannel) this.textChannel.createOverwrite(user.id, { - VIEW_CHANNEL: false, - SEND_MESSAGES: false, - }); - - // if user is the team leader appoint another team member - if (this.leader === user.id) { - this.leader = this.members.first().id; - } - - return this.size(); - } - - /** - * Return the length of the members collection. - * @returns {Number} - */ - size() { - return this.members.size; - } - - /** - * True if the team has 4 members, false otherwise. - */ - isComplete() { - return this.size() === 4; - } - - /** - * Returns a string with the team id and all the team members. - * @returns {String} - */ - toString() { - let teamMemberString = ''; - - this.members.forEach((user, key) => { - teamMemberString += user.username + ', '; - }); - - return 'Team ' + this.id + ': ' + teamMemberString; - } - -} -module.exports = Team; \ No newline at end of file diff --git a/classes/Bot/Features/Ticket_System/ticket-manager.js b/classes/Bot/Features/Ticket_System/ticket-manager.js deleted file mode 100644 index 5c3e2839..00000000 --- a/classes/Bot/Features/Ticket_System/ticket-manager.js +++ /dev/null @@ -1,335 +0,0 @@ -const { Collection, GuildEmoji, ReactionEmoji, TextChannel, Guild, Role, User } = require('discord.js'); -const Ticket = require('./ticket'); -const BotGuildModel = require('../../bot-guild'); -const Console = require('../../../UI/Console/console'); -const Feature = require('../../../UI/Console/feature'); -const { sendMsgToChannel } = require('../../../../discord-services'); -const winston = require('winston'); -const { StringPrompt, MessagePrompt } = require('advanced-discord.js-prompts'); -const Activity = require('../../activities/activity'); - - -/** - * Represents a real life ticket system that can be used in any setting. It is very versatile so it can be - * used with one or many helper types, can edit options, embeds, etc. - * @class - */ -class TicketManager { - - /** - * All the information needed for tickets in this ticket manager - * @typedef SystemWideTicketInfo - * @property {GarbageCollectorInfo} garbageCollectorInfo - the garbage collector information for each tickets - * @property {Boolean} isAdvancedMode Information about the system being advanced. Advanced mode will create a category with channels for - * the users and the helpers. Regular will not create anything and expects the helper to DM the user or users. - */ - /** - * @typedef GarbageCollectorInfo - * @property {Boolean} isEnabled - if the garbage collector is enabled for this ticket system - * @property {Number} inactivePeriod - number of minutes a ticket channel will be inactive before bot starts to delete it - * @property {Number} bufferTime - number of minutes the bot will wait for a response before deleting ticket - */ - - /** - * @typedef TicketCreatorInfo - * @property {TextChannel} channel - the channel where users can create a ticket - * @property {Console} console - the console used to let users create tickets - */ - - /** - * @typedef TicketDispatcherInfo - * @property {TextChannel} channel - the channel where tickets are dispatched to - * @property {GuildEmoji | ReactionEmoji} takeTicketEmoji - emoji for mentors to accept/take a ticket, can be a unicode emoji string - * @property {GuildEmoji | ReactionEmoji} joinTicketEmoji - emoji for mentors to join a taken ticket, can be a unicode emoji string - * @property {NewTicketEmbedCreator} embedCreator - function to create a Discord MessageEmbed - * @property {ReminderInfo} reminderInfo - the reminder information - * @property {MainHelperInfo} mainHelperInfo - */ - /** - * @typedef ReminderInfo - * @property {Boolean} isEnabled - is this feature enabled - * @property {Number} time - how long should I wait to remind helpers - * @property {Collection} reminders - the timeout reminders mapped by the ticket ID - */ - /** - * @callback NewTicketEmbedCreator - * @param {Ticket} ticket - * @returns {MessageEmbed} - */ - /** - * @typedef MainHelperInfo - * @property {Role} role - * @property {GuildEmoji | ReactionEmoji} emoji - can be a unicode emoji string - */ - - - /** - * @constructor - * @param {Activity} parent - * @param {Object} args - * @param {TicketCreatorInfo} args.ticketCreatorInfo - * @param {TicketDispatcherInfo} args.ticketDispatcherInfo - * @param {SystemWideTicketInfo} args.systemWideTicketInfo - */ - constructor(parent, { ticketCreatorInfo, ticketDispatcherInfo, systemWideTicketInfo }) { - - /** - * The tickets in this ticket system. - * @type {Collection} - - */ - this.tickets = new Collection(); - - /** - * The number of tickets created. - * Must be separate as tickets.length since we use this to assign IDs to tickets. - */ - this.ticketCount = 0; - - /** - * The parent of this ticket-system. It must be paired with a cave or an activity. - * @type {Activity} - */ - this.parent = parent; - - /** - * @type {TicketCreatorInfo} - */ - this.ticketCreatorInfo = { - channel: null, - console: null, - }; - - /** - * @type {TicketDispatcherInfo} - */ - this.ticketDispatcherInfo = { - channel: null, - takeTicketEmoji: null, - joinTicketEmoji: null, - embedCreator: null, // function - reminderInfo: { - isEnabled: null, - time: null, - reminders: new Collection(), - }, - mainHelperInfo: { - role: null, - emoji: null, - }, - }; - - /** - * @type {SystemWideTicketInfo} - */ - this.systemWideTicketInfo = { - garbageCollectorInfo: { - isEnabled : false, - inactivePeriod : null, - bufferTime : null, - }, - isAdvancedMode: false, - }; - - /** - * Information about the system being multi role, if its the case, it needs a - * Multi Role Selector. - */ - this.multiRoleInfo = { - isEnabled : false, - multiRoleSelector : null, - }; - - this.validateTicketSystemInfo({ ticketCreatorInfo, ticketDispatcherInfo, systemWideTicketInfo }); - } - /** - * - * @param {Object} param0 - * @private - */ - validateTicketSystemInfo({ ticketCreatorInfo, ticketDispatcherInfo, systemWideTicketInfo }) { - this.ticketCreatorInfo = ticketCreatorInfo; - this.ticketDispatcherInfo = ticketDispatcherInfo; - this.systemWideTicketInfo = systemWideTicketInfo; - this.ticketDispatcherInfo.reminderInfo.reminders = new Collection(); - } - - /** - * Sends the ticket creator console. - * @param {String} title - the ticket creator console title - * @param {String} description - the ticket creator console description - * @param {String} [color] - the ticket creator console color, hex - * @async - */ - async sendTicketCreatorConsole(title, description, color) { - /** @type {Console.Feature[]} */ - let featureList = [ - Feature.create({ - name: 'General Ticket', - description: 'A general ticket aimed to all helpers.', - emoji: this.ticketDispatcherInfo.mainHelperInfo.emoji, - callback: (user, reaction, stopInteracting, console) => this.startTicketCreationProcess(user, this.ticketDispatcherInfo.mainHelperInfo.role, console.channel).then(() => stopInteracting()), - }) - ]; - - let features = new Collection(featureList.map(feature => [feature.emojiName, feature])); - - this.ticketCreatorInfo.console = new Console({ title, description, channel: this.ticketCreatorInfo.channel, features, color, guild: this.parent.guild }); - await this.ticketCreatorInfo.console.sendConsole(); - } - - /** - * Adds a new type of ticket, usually a more focused field, there must be a role associated - * to this new type of ticket. - * @param {Role} role - role to add - * @param {String} typeName - * @param {GuildEmoji | ReactionEmoji} emoji - */ - addTicketType(role, typeName, emoji) { - this.ticketCreatorInfo.console.addFeature( - Feature.create({ - name: `Question about ${typeName}`, - description: '---------------------------------', - emoji: emoji, - callback: (user, reaction, stopInteracting, console) => { - this.startTicketCreationProcess(user, role, console.channel).then(() => stopInteracting()); - } - }) - ); - } - - /** - * Prompts a user for more information to create a new ticket for them. - * @param {User} user - the user creating a ticket - * @param {Role} role - * @param {TextChannel | DMChannel} - * @async - */ - async startTicketCreationProcess(user, role, channel) { - // check if role has mentors in it - if (role.members.size <= 0) { - sendMsgToChannel(channel, user.id, 'There are no mentors available with that role. Please request another role or the general role!', 10); - winston.loggers.get(this.parent.initBotInfo.id).userStats(`The cave ${this.parent.name} received a ticket from user ${user.id} but was canceled due to no mentor having the role ${role.name}.`, { event: 'Ticket Manager' }); - return; - } - - try { - var promptMsg = await MessagePrompt.prompt({prompt: 'Please send ONE message with: \n* A one liner of your problem ' + - '\n* Mention your team members using @friendName (example: @John).', channel, userId: user.id, cancelable: true, time: 45}); - } catch (error) { - winston.loggers.get(this.parent.initBotInfo.id).warning(`New ticket was canceled due to error: ${error}`, { event: 'Ticket Manager' }); - return; - } - - let hackers = new Collection(); - hackers.set(user.id, user); - if (promptMsg.mentions.users.size > 0) hackers = hackers.concat(promptMsg.mentions.users); - - this.newTicket(hackers, promptMsg.cleanContent, role); - } - - /** - * Adds a new ticket. - * @param {Collection} hackers - * @param {String} question - * @param {Role} roleRequested - * @private - */ - newTicket(hackers, question, roleRequested) { - let ticket = new Ticket(hackers, question, roleRequested, this.ticketCount, this); - this.tickets.set(ticket.id, ticket); - - this.setReminder(ticket); - - this.ticketCount ++; - - ticket.setStatus('new'); - } - - /** - * Sets a reminder to a ticket only if reminders are on. - * @param {Ticket} ticket - * @private - */ - setReminder(ticket) { - // if reminders are on, set a timeout to reminder the main role of this ticket if the ticket is still new - if (this.ticketDispatcherInfo.reminderInfo.isEnabled) { - let timeout = setTimeout(() => { - if (ticket.status === Ticket.STATUS.new) { - ticket.consoles.ticketManager.changeColor('#ff5736'); - this.ticketDispatcherInfo.channel.send(`Hello <@&${this.ticketDispatcherInfo.mainHelperInfo.role.id}> ticket number ${ticket.id} still needs help!`) - .then(msg => msg.delete({ timeout: (this.ticketDispatcherInfo.reminderInfo.time * 60 * 1000)/2 })); - // sets another timeout - this.setReminder(ticket); - } - }, this.ticketDispatcherInfo.reminderInfo.time * 60 * 1000); - - this.ticketDispatcherInfo.reminderInfo.reminders.set(ticket.id, timeout); - } - } - - /** - * Return the number of tickets in this ticket system. - * @returns {Number} - */ - getTicketCount() { - return this.tickets.size; - } - - /** - * Removes all the tickets from this ticket manager. - * @param {Number[]} [excludeTicketIds=[]] - tickets to be excluded - */ - removeAllTickets(excludeTicketIds = []) { - // exclude the tickets - let ticketsToRemove; - if (excludeTicketIds.length > 0) ticketsToRemove = this.tickets.filter((ticket, ticketId) => excludeTicketIds.includes(ticketId)); - else ticketsToRemove = this.tickets; - - ticketsToRemove.forEach((ticket, ticketId) => { - this.removeTicket(ticketId); - }); - } - - /** - * Removes tickets by their ids - * @param {Number[]} ticketIds - ticket ids to remove - */ - removeTicketsById(ticketIds) { - ticketIds.forEach(ticketId => { - this.removeTicket(ticketId); - }); - } - - /** - * Removes all tickets older than the given age. - * @param {Number} minAge - the minimum age in minutes - * @throws Error when used and advanced mode is turned off - */ - removeTicketsByAge(minAge) { - // only usable when advanced mode is turned on - if (!this.systemWideTicketInfo.isAdvancedMode) throw new Error('Remove by age is only available when advanced mode is on!'); - this.tickets.forEach((ticket, ticketId, tickets) => { - let now = Date.now(); - - let timeDif = now - ticket.room.timeCreated; - - if (timeDif > minAge * 50 * 1000) { - this.removeTicket(ticketId); - } - }); - } - - /** - * Removes a ticket, deletes the ticket's channels too! - * @param {Number} ticketId - the ticket id to remove - */ - removeTicket(ticketId) { - // remove the reminder for this ticket if reminders are on - if (this.ticketDispatcherInfo.reminderInfo.isEnabled && this.ticketDispatcherInfo.reminderInfo.reminders.has(ticketId)) { - clearTimeout(this.ticketDispatcherInfo.reminderInfo.reminders.get(ticketId)); - this.ticketDispatcherInfo.reminderInfo.reminders.delete(ticketId); - } - this.tickets.get(ticketId).setStatus(Ticket.STATUS.closed, 'ticket manager closed the ticket'); - } -} -module.exports = TicketManager; \ No newline at end of file diff --git a/classes/Bot/Features/Ticket_System/ticket.js b/classes/Bot/Features/Ticket_System/ticket.js deleted file mode 100644 index 353389ee..00000000 --- a/classes/Bot/Features/Ticket_System/ticket.js +++ /dev/null @@ -1,440 +0,0 @@ -const { Collection, User, Role } = require('discord.js'); -const winston = require('winston'); -const discordServices = require('../../../../discord-services'); -const Console = require('../../../UI/Console/console'); -const Feature = require('../../../UI/Console/feature'); -const Room = require('../../../UI/Room/room'); -const TicketManager = require('./ticket-manager'); - -class Ticket { - - /** - * @typedef TicketConsoles - * @property {Console} groupLeader - * @property {Console} ticketManager - Message sent to incoming ticket channel for helpers to see. - * @property {Console} ticketRoom - The message with the information embed sent to the ticket channel once the ticket is open. - */ - - /** - * @typedef TicketGarbageInfo - * @property {Number} noHelperInterval - Interval ID for when there are no more helpers in the ticket - * @property {Boolean} mentorDeletionSequence - Flag to check if a deletion sequence has already been triggered by all mentors leaving the ticket; if so, there will not be - * another sequence started for inactivity - * @property {Boolean} exclude - Flag for whether this ticket is excluded from automatic garbage collection - */ - - /** - * @param {Collection} hackers - * @param {String} question - * @param {Role} requesterRole - * @param {Number} ticketNumber - * @param {TicketManager} ticketManager - */ - constructor(hackers, question, requestedRole, ticketNumber, ticketManager) { - - /** - * Ticket number - * @type {Number} - */ - this.id = ticketNumber; - - /** - * The room this ticket will be solved in. - * @type {Room} - */ - this.room = ticketManager.systemWideTicketInfo.isAdvancedMode ? - new Room(ticketManager.parent.guild, ticketManager.parent.initBotInfo, `Ticket-${ticketNumber}`, new Collection(), hackers.clone()) : - null; - - /** - * Question from hacker - * @type {String} - */ - this.question = question; - - /** - * @type {Role} - */ - this.requestedRole = requestedRole; - - /** - * All the group members, group leader should be the first one! - * @type {Collection} - - * Must clone the Map since we edit it. - */ - this.group = hackers.clone(); - - /** - * Mentors who join the ticket - * @type {Collection} - - */ - this.helpers = new Collection(); - - /** - * All the consoles sent out. - * GroupLeader -> sent via DM to leader, they can cancel the ticket from there - * ticketManager -> sent to the helper channel - * ticketRoom -> sent to the ticket room once created for users to leave - * @type {TicketConsoles} - */ - this.consoles = { - groupLeader: null, - ticketManager: null, - ticketRoom: null, - }; - - /** - * Garbage collector info. - * @type {TicketGarbageInfo} - */ - this.garbageCollectorInfo = { - noHelperInterval: null, - mentorDeletionSequence: false, - exclude: false, - }; - - /** - * The status of this ticket - * @type {Ticket.STATUS} - */ - this.status = null; - - /** - * @type {TicketManager} - */ - this.ticketManager = ticketManager; - } - - /** - * This function is called by the ticket's Cave class to change its status between include/exclude for automatic garbage collection. - * If a previously excluded ticket is re-included, the bot starts listening for inactivity as well. - * @param {Boolean} exclude - true if ticket is now excluded from garbage collection, false if not - */ - async includeExclude(exclude) { - // oldExclude saves the previous inclusion status of the ticket - var oldExclude = this.garbageCollectorInfo.exclude; - // set excluded variable to new status - this.garbageCollectorInfo.exclude = exclude; - - // if this ticket was previously excluded and is now included, start the listener for inactivity - if (oldExclude && !exclude) { - this.startChannelActivityListener(); - } - } - - /** - * Change the status of this ticket. - * @param {String} status - one of Ticket.STATUS - * @param {String} [reason] - the reason for the change - * @param {User} [user] - user involved with the status change - * @async - */ - async setStatus(status, reason = '', user) { - this.status = status; - - switch(status) { - case Ticket.STATUS.new: - // let user know that ticket was submitted and give option to remove ticket - await this.contactGroupLeader(); - - this.newStatusCallback(); - break; - - case Ticket.STATUS.taken: - if (this.ticketManager.systemWideTicketInfo.isAdvancedMode) await this.advancedTakenStatusCallback(user); - else await this.basicTakenStatusCallback(user); - break; - case Ticket.STATUS.closed: - this.delete(reason); - break; - } - } - - /** - * The new ticket status callback creates the ticket manager helper console and sends it to the incoming tickets channel. - * @private - */ - async newStatusCallback() { - const ticketManagerMsgEmbed = this.ticketManager.ticketDispatcherInfo.embedCreator(this); - - this.consoles.ticketManager = new Console({ - title: ticketManagerMsgEmbed.title, - description: ticketManagerMsgEmbed.description, - channel: this.ticketManager.ticketDispatcherInfo.channel, - guild: this.ticketManager.parent.guild, - color: '#fff536' - }); - - ticketManagerMsgEmbed.fields.forEach((embedField => { - this.consoles.ticketManager.addField(embedField.name, embedField.value, embedField.inline); - })); - - let joinTicketFeature = Feature.create({ - name: 'Can you help them?', - description: 'If so, react to this message with the emoji!', - emoji: this.ticketManager.ticketDispatcherInfo.takeTicketEmoji, - callback: (user, reaction, stopInteracting) => { - if (this.status === Ticket.STATUS.new) { - this.setStatus(Ticket.STATUS.taken, 'helper has taken the ticket', user); - } - stopInteracting(); - } - }); - - this.consoles.ticketManager.addFeature(joinTicketFeature); - - this.consoles.ticketManager.sendConsole(`<@&${this.requestedRole.id}>`); - } - - /** - * Contacts the group leader and sends a console with the ability to remove the ticket. - * @private - */ - async contactGroupLeader() { - let removeTicketEmoji = '⚔️'; - this.consoles.groupLeader = new Console({ - title: 'Ticket was Successful!', - description: `Your ticket to the ${this.ticketManager.parent.name} group was successful! It is ticket number ${this.id}`, - channel: await this.group.first().createDM(), - guild: this.ticketManager.parent.guild, - features: new Collection([ - [removeTicketEmoji, Feature.create({ - name: 'Remove the ticket', - description: 'React to this message if you don\'t need help any more!', - emoji: removeTicketEmoji, - callback: (user, reaction, stopInteracting) => { - // make sure user can only close the ticket if no one has taken the ticket - if (this.status === Ticket.STATUS.new) this.setStatus(Ticket.STATUS.closed, 'group leader closed the ticket'); - }, - })] - ]), - options: { max: 1 } - }); - this.consoles.groupLeader.sendConsole(); - } - - /** - * Callback for status change to taken when ticket manager is NOT in advanced mode. - * @param {User} helper - the user who is taking the ticket - */ - async basicTakenStatusCallback(helper) { - this.addHelper(helper); - - // edit ticket manager helper console with mentor information - await this.consoles.ticketManager.addField('This ticket is being handled!', `<@${helper.id}> is helping this team!`); - await this.consoles.ticketManager.changeColor('#36c3ff'); - - // update dm with user to reflect that their ticket has been accepted - this.consoles.groupLeader.addField('Your ticket has been taken by a helper!', 'Expect a DM from a helper soon!'); - this.consoles.groupLeader.stopConsole(); - } - - /** - * Callback for status change for when the ticket is taken by a helper. - * @param {User} helper - the helper user - * @private - */ - async advancedTakenStatusCallback(helper) { - await this.room.init(); - - // add helper and clear the ticket reminder timeout - this.addHelper(helper); - - // edit ticket manager helper console with mentor information - await this.consoles.ticketManager.addField('This ticket is being handled!', `<@${helper.id}> is helping this team!`); - await this.consoles.ticketManager.changeColor('#36c3ff'); - - let takeTicketFeature = Feature.create({ - name: 'Still want to help?', - description: `Click the ${this.ticketManager.ticketDispatcherInfo.joinTicketEmoji.toString()} emoji to join the ticket!`, - emoji: this.ticketManager.ticketDispatcherInfo.joinTicketEmoji, - callback: (user, reaction, stopInteracting) => { - if (this.status === Ticket.STATUS.taken) this.helperJoinsTicket(user); - stopInteracting(); - } - }); - await this.consoles.ticketManager.addFeature(takeTicketFeature); - - // update dm with user to reflect that their ticket has been accepted - this.consoles.groupLeader.addField('Your ticket has been taken by a helper!', 'Please go to the corresponding channel and read the instructions there.'); - this.consoles.groupLeader.stopConsole(); - - // send message mentioning all the parties involved so they get a notification - let notificationMessage = '<@' + helper.id + '> ' + this.group.array().join(' '); - this.room.channels.generalText.send(notificationMessage).then(msg => msg.delete({ timeout: 15000 })); - - let leaveTicketEmoji = '👋🏽'; - - this.consoles.ticketRoom = new Console({ - title: 'Original Question', - description: `<@${this.group.first().id}> has the question: ${this.question}`, - channel: this.room.channels.generalText, - color: this.ticketManager.parent.initBotInfo.colors.embedColor, - guild: this.ticketManager.parent.guild, - }); - - this.consoles.ticketRoom.addField('Thank you for helping this team.', `<@${helper.id}> best of luck!`); - this.consoles.ticketRoom.addFeature( - Feature.create({ - name: 'When done:', - description: `React to this message with ${leaveTicketEmoji} to lose access to these channels!`, - emoji: leaveTicketEmoji, - callback: (user, reaction, stopInteracting) => { - // delete the mentor or the group member that is leaving the ticket - this.helpers.delete(user.id); - this.group.delete(user.id); - - this.room.removeUserAccess(user); - - // if all hackers are gone, delete ticket channels - if (this.group.size === 0) { - this.setStatus(Ticket.STATUS.closed, 'no users on the ticket remaining'); - } - - // tell hackers all mentors are gone and ask to delete the ticket if this has not been done already - else if (this.helpers.size === 0 && !this.garbageCollectorInfo.mentorDeletionSequence && !this.garbageCollectorInfo.exclude) { - this.garbageCollectorInfo.mentorDeletionSequence = true; - this.askToDelete('mentor'); - } - - stopInteracting(); - } - }) - ); - - this.consoles.ticketRoom.sendConsole(); - - //create a listener for inactivity in the text channel - this.startChannelActivityListener(); - } - - /** - * Callback for collector for when a new helper joins the ticket. - * @param {User} helper - the new helper user - * @private - */ - helperJoinsTicket(helper) { - this.addHelper(helper, this.garbageCollectorInfo.noHelperInterval); - - discordServices.sendMsgToChannel(this.room.channels.generalText, helper.id, 'Has joined the ticket!', 10); - - // update the ticket manager and ticket room embeds with the new mentor - this.consoles.ticketManager.addField('More hands on deck!', '<@' + helper.id + '> Joined the ticket!'); - this.consoles.ticketRoom.addField('More hands on deck!', '<@' + helper.id + '> Joined the ticket!'); - } - - /** - * Adds a helper to the ticket. - * @param {User} user - the user to add to the ticket as a helper - * @param {NodeJS.Timeout} [timeoutId] - the timeout to clear due to this addition - * @private - */ - addHelper(user, timeoutId) { - this.helpers.set(user.id, user); - if (this.room) this.room.giveUserAccess(user); - if (timeoutId) clearTimeout(timeoutId); - } - - /** - * Main deletion sequence: mentions and asks hackers if ticket can be deleted, and deletes if there is no response or indicates that - * it will check in again later if someone does respond - * @param {String} reason - 'mentor' if this deletion sequence was initiated by the last mentor leaving, 'inactivity' if initiated by - * inactivity in the text channel - * @private - */ - async askToDelete(reason) { - // assemble message to send to hackers to verify if they still need the ticket - let msgText = `${this.group.array().map(user => '<@' + user.id + '>').join(' ')} `; - if (reason === 'inactivity') { - msgText += `${this.helpers.array().map(user => '<@' + user.id + '>').join(' ')} Hello! I detected some inactivity on this channel and wanted to check in.\n`; - } else if (reason === 'mentor') { - msgText += 'Hello! Your mentor(s) has/have left the ticket.\n'; - } - - let warning = await this.room.channels.generalText.send(`${msgText} If the ticket has been solved, please click the 👋 emoji above - to leave the channel. If you need to keep the channel, please click the emoji below, - **otherwise this ticket will be deleted in ${this.ticketManager.systemWideTicketInfo.garbageCollectorInfo.bufferTime} minutes**.`); - - await warning.react('🔄'); - - // reaction collector to listen for someone to react with the emoji for more time - const deletionCollector = warning.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === '🔄', { time: this.ticketManager.systemWideTicketInfo.garbageCollectorInfo.bufferTime * 60 * 1000, max: 1 }); - - deletionCollector.on('end', async (collected) => { - // if a channel has already been deleted by another process, stop this deletion sequence - if (collected.size === 0 && !this.garbageCollectorInfo.exclude && this.status != Ticket.STATUS.closed) { // checks to see if no one has responded and this ticket is not exempt - this.setStatus(Ticket.STATUS.closed, 'inactivity'); - } else if (collected.size > 0) { - await this.room.channels.generalText.send('You have indicated that you need more time. I\'ll check in with you later!'); - - // set an interval to ask again later - //this.garbageCollectorInfo.noHelperInterval = setInterval(() => this.askToDelete(reason), this.ticketManager.systemWideTicketInfo.garbageCollectorInfo.inactivePeriod * 60 * 1000); - this.startChannelActivityListener(); - } - }); - } - - /** - * Uses a message collector to see if there is any activity in the room's text channel. When the collector ends, if it collected - * no messages and there is no one on the voice channels then ask to delete and listen once again. - * @async - * @private - */ - async startChannelActivityListener() { - // message collector that stops when there are no messages for inactivePeriod minutes - const activityListener = this.room.channels.generalText.createMessageCollector(m => !m.author.bot, { idle: this.ticketManager.systemWideTicketInfo.garbageCollectorInfo.inactivePeriod * 60 * 1000 }); - activityListener.on('end', async collected => { - if (collected.size === 0 && this.room.channels.generalVoice.members.size === 0 && this.status === Ticket.STATUS.taken) { - await this.askToDelete('inactivity'); - - // start listening again for inactivity in case they ask for more time - //this.startChannelActivityListener(); - } else { - this.startChannelActivityListener(); - } - }); - } - - /** - * Deletes the ticket, the room and the intervals. - * @param {String} reason - the reason to delete the ticket - * @private - */ - delete(reason) { - // update ticketManager msg and let user know the ticket is closed - this.consoles.ticketManager.addField( - 'Ticket Closed', - `This ticket has been closed${reason ? ' due to ' + reason : '!! Good job!'}` - ); - this.consoles.ticketManager.changeColor('#43e65e'); - this.consoles.ticketManager.stopConsole(); - - this.consoles.groupLeader.addField( - 'Ticket Closed!', - `Your ticket was closed due to ${reason}. If you need more help, please request another ticket!` - ); - this.consoles.groupLeader.stopConsole(); - - // delete the room, clear intervals - if (this.room) this.room.delete(); - clearInterval(this.garbageCollectorInfo.noHelperInterval); - - if (this.consoles?.ticketRoom) this.consoles.ticketRoom.stopConsole(); - } -} - -/** - * The possible status of the ticket. - * @enum {String} - * @static - */ -Ticket.STATUS = { - /** Ticket is open for someone to take. */ - new: 'new', - /** Ticket has been dealt with and is closed. */ - closed: 'closed', - /** Ticket is being handled by someone. */ - taken: 'taken', -}; - -module.exports = Ticket; diff --git a/classes/Bot/Features/Verification/verification.js b/classes/Bot/Features/Verification/verification.js deleted file mode 100644 index 8460929a..00000000 --- a/classes/Bot/Features/Verification/verification.js +++ /dev/null @@ -1,116 +0,0 @@ -const { GuildMember, Guild } = require('discord.js'); -const discordServices = require('../../../../discord-services'); -const firebaseUtil = require('../../../../db/firebase/firebaseUtil'); -const winston = require('winston'); - -/** - * @class Verification - */ -class Verification { - - /** - * Verifies a guild member into a guild. - * @param {GuildMember} member - member to verify - * @param {String} email - email to verify with - * @param {Guild} guild - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @async - * @static - * @throws Error if email is not valid! - */ - static async verify(member, email, guild, initBotInfo) { - if (!discordServices.validateEmail(email)) { - throw new Error('Email is not valid!!'); - } - - let logger = winston.loggers.get(guild.id); - - // try to get member types, error will mean no email was found - try { - let types = await firebaseUtil.verify(email, member.id, member.guild.id); - } catch (error) { - logger.warning(`The email provided (${email}) by user ${member.id} was not found, got an error: ${error}`, { event: 'Verification' }); - discordServices.sendEmbedToMember(member, { - title: 'Verification Failure', - description: 'The email provided was not found! If you need assistance ask an admin for help!', - }); - discordServices.discordLog(guild, `VERIFY FAILURE : <@${member.id}> Verified email: ${email} but was a failure, I could not find that email!`); - return; - } - - // check for types, if no types it means they are already verified with those types - if (types.length === 0) { - logger.warning(`An email was found but the user ${member.id} is already set as verified. Email used: ${email}`, { event: 'Verification' }); - discordServices.sendEmbedToMember(member, { - title: 'Verification Warning', - description: 'We found your email, but you are already verified! If this is not the case let an admin know!', - color: '#fc1403', - }); - discordServices.discordLog(guild, `VERIFY WARNING : <@${member.id}> Verified email: ${email} but he was already verified for all types!`); - return; - } - - let correctTypes = []; - - // check for correct types with botGuild verification info and give the roles - types.forEach((type, index, array) => { - if (initBotInfo.verification.verificationRoles.has(type)) { - let roleId = initBotInfo.verification.verificationRoles.get(type); - logger.verbose(`User ${member.id} has type ${type} in list index ${index} and it was found, he got the role ${roleId}`, { event: 'Verification' }); - discordServices.addRoleToMember(member, roleId); - correctTypes.push(type); - } else { - logger.error(`User ${member.id} has type ${type} in list index ${index} and it was not found in the botGuild verification roles map.`, { event: 'Verification' }); - } - - }); - - // extra check to see if types were found, give stamp role if available and let user know of success - if (correctTypes.length > 0) { - discordServices.replaceRoleToMember(member, initBotInfo.verification.guestRoleID, initBotInfo.roleIDs.memberRole); - if (initBotInfo.stamps.isEnabled) discordServices.addRoleToMember(member, initBotInfo.stamps.stamp0thRoleId); - discordServices.sendEmbedToMember(member, { - title: `${guild.name} Verification Success`, - description: `You have been verified as a ${correctTypes.join()}, good luck and have fun!`, - color: initBotInfo.colors.specialDMEmbedColor, - }); - discordServices.discordLog(guild, `VERIFY SUCCESS : <@${member.id}> Verified email: ${email} successfully as ${correctTypes.join()}.`); - logger.event(`User ${member.id} was verified with email ${email} successfully as ${correctTypes.join()}.`, { event: 'Verification' }); - } else { - discordServices.sendEmbedToMember(member, { - title: 'Verification Error', - description: 'There has been an error, contact an admin ASAP!', - color: '#fc1403', - }); - discordServices.discordLog(guild, `VERIFY ERROR : <@${member.id}> Verified email: ${email} had types available, but I could not find them on the botGuild!`); - logger.error(`User ${member.id} Verified email: ${email} had types available, but I could not find them on the botGuild!`, { event: 'Verification' }); - } - } - - - /** - * Will attend the user and give it the attendee role. - * @param {GuildMember} member - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - */ - static async attend(member, initBotInfo) { - try { - // wait for attend to end, then give role - await firebaseUtil.attend(member.id, member.guild.id); - discordServices.addRoleToMember(member, initBotInfo.attendance.attendeeRoleID); - discordServices.sendEmbedToMember(member, { - title: 'Attendance Success', - description: 'You have been marked as attending, thank you and good luck!', - color: initBotInfo.colors.specialDMEmbedColor, - }); - discordServices.discordLog(member.guild, `ATTEND SUCCESS : <@${member.id}> has been marked as attending!`); - winston.loggers.get(initBotInfo.id).event(`User ${member.id} was marked as attending!`, { event: 'Verification' }); - } catch (error) { - // email was not found, let admins know! - discordServices.discordLog(member.guild, `ATTEND WARNING : <@${member.id}> tried to attend but I could not find his discord ID! He might be an impostor!!!`); - winston.loggers.get(initBotInfo.id).warning(`User ${member.id} could not be marked as attending, I could not find his discord ID, he could be an impostor! Got the error: ${error}`, { event: 'Verification' }); - } - } - -} -module.exports = Verification; \ No newline at end of file diff --git a/classes/Bot/activities/activity.js b/classes/Bot/activities/activity.js deleted file mode 100644 index 42681103..00000000 --- a/classes/Bot/activities/activity.js +++ /dev/null @@ -1,451 +0,0 @@ -const { Guild, Collection, Role, CategoryChannel, TextChannel, MessageEmbed, GuildMember, PermissionOverwriteOption } = require('discord.js'); -const winston = require('winston'); -const firebaseUtil = require('../../../db/firebase/firebaseUtil'); -const BotGuildModel = require('../bot-guild'); -const { shuffleArray, sendMsgToChannel } = require('../../../discord-services'); -const StampsManager = require('../Features/Stamps/stamps-manager'); -const Room = require('../../UI/Room/room'); -const Console = require('../../UI/Console/console'); -const { StringPrompt, RolePrompt, ListPrompt } = require('advanced-discord.js-prompts'); -const Feature = require('../../UI/Console/feature'); - -/** - * @typedef ActivityInfo - * @property {string} activityName - the name of this activity! - * @property {Guild} guild - the guild where the new activity lives - * @property {Collection} roleParticipants - roles allowed to view activity - * @property {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - */ - -/** - * An object with a role and its permissions - * @typedef RolePermission - * @property {String} id - the role snowflake - * @property {PermissionOverwriteOption} permissions - the permissions to set to that role - */ - -/** - * @typedef ActivityFeature - * @property {String} emoji - the emoji as a string - * @property {String} name - * @property {String} description - * @property {Function} callback - */ - -/** - * An activity is a overarching class for any kind of activity. An activity consists of a - * category with voice and text channels. - * Activities have features admins can run from the admin console by reacting to a message (console). - * The activity can be private to specified roles or public to all users. - * @class - */ -class Activity { - - /** - * Prompts a user for the roles that can have access to an activity. - * @param {TextChannel} channel - the channel to prompt in - * @param {String} userId - the user id to prompt - * @param {Boolean} [isStaffAuto=false] - true if staff are added automatically - * @returns {Promise>} - * @async - * @static - */ - static async promptForRoleParticipants(channel, userId, isStaffAuto = false) { - let allowedRoles = new Collection(); - - try { - allowedRoles = await RolePrompt.multi({ - prompt: `What roles${isStaffAuto ? ', aside from Staff,' : ''} will be allowed to view this activity? (Type "cancel" if none)`, - channel, userId, cancelable: true - }); - } catch (error) { - // nothing given is an empty collection viewable to admins only - } - - // add staff role - if (isStaffAuto) { - let botGuildData = await firebaseUtil.getInitBotInfo( - channel.guild.id - ); - let staffRoleId = botGuildData.roleIDs.staffRole; - allowedRoles.set(staffRoleId, channel.guild.roles.resolve(staffRoleId)); - } - - return allowedRoles; - } - - /** - * Constructor for an activity, will create the category, voice and text channel. - * @constructor - * @param {ActivityInfo} ActivityInfo - */ - constructor({ activityName, guild, roleParticipants, initBotInfo }) { - /** - * The name of this activity. - * @type {string} - */ - this.name = activityName; - - /** - * The guild this activity is in. - * @type {Guild} - */ - this.guild = guild; - - /** - * The room this activity lives in. - * @type {Room} - */ - this.room = new Room(guild, initBotInfo, activityName, roleParticipants); - - /** - * The admin console with activity features. - * @type {Console} - */ - this.adminConsole = new Console({ - title: `Activity ${activityName} Console`, - description: 'This activity\'s information can be found below, you can also find the features available.', - channel: guild.channels.resolve(initBotInfo.channelIDs.adminConsole), - guild: this.guild, - }); - - /** - * The Firestore bot info object - * @type {FirebaseFirestore.DocumentData | null | undefined} - */ - this.initBotInfo = initBotInfo; - - winston.loggers.get(guild.id).event(`An activity named ${this.name} was created.`, { data: { permissions: roleParticipants } }); - } - - - /** - * Initialize this activity by creating the channels, adding the features and sending the admin console. - * @async - * @returns {Promise} - */ - async init() { - await this.room.init(); - - this.addDefaultFeatures(); - - await this.adminConsole.sendConsole(); - - winston.loggers.get(this.guild.id).event(`The activity ${this.name} was initialized.`, { event: 'Activity' }); - return this; - } - - - /** - * Adds the default features to the activity, these features are available to all activities. - * @protected - */ - addDefaultFeatures() { - let localFeatures = [ - { - name: 'Add Channel', - description: 'Add one channel to the activity.', - emoji: '⏫', - callback: (user, reaction, stopInteracting, console) => this.addChannel(console.channel, user.id).then(() => stopInteracting()), - }, - { - name: 'Remove Channel', - description: 'Remove a channel, decide from a list.', - emoji: '⏬', - callback: (user, reaction, stopInteracting, console) => this.removeChannel(console.channel, user.id).then(() => stopInteracting()), - }, - { - name: 'Delete', - description: 'Delete this activity and its channels.', - emoji: '⛔', - callback: (user, reaction, stopInteracting, console) => this.delete(console.channel, user.id), - }, - { - name: 'Archive', - description: 'Archive the activity, text channels are saved.', - emoji: '💼', - callback: (user, reaction, stopInteracting, console) => { - let archiveCategory = this.guild.channels.resolve(this.initBotInfo.channelIDs.archiveCategory); - this.archive(archiveCategory); - } - }, - { - name: 'Callback', - description: 'Move all users in the activity\'s voice channels back to a specified voice channel.', - emoji: '🔃', - callback: (user, reaction, stopInteracting, console) => this.voiceCallBack(console.channel, user.id).then(() => stopInteracting()), - }, - { - name: 'Shuffle', - description: 'Shuffle all members from one channel to all others in the activity.', - emoji: '🌬️', - callback: (user, reaction, stopInteracting, console) => this.shuffle(console.channel, user.id).then(() => stopInteracting()), - }, - { - name: 'Role Shuffle', - description: 'Shuffle all the members with a specific role from one channel to all others in the activity.', - emoji: '🦜', - callback: (user, reaction, stopInteracting, console) => this.roleShuffle(console.channel, user.id).then(() => stopInteracting()), - }, - { - name: 'Distribute Stamp', - description: 'Send a emoji collector for users to get a stamp.', - emoji: '🏕️', - callback: (user, reaction, stopInteracting, console) => this.distributeStamp(console.channel, user.id).then(() => stopInteracting()), - }, - { - name: 'Rules Lock', - description: 'Lock the activity behind rules, users must agree to the rules to access the channels.', - emoji: '🔒', - callback: (user, reaction, stopInteracting, console) => this.ruleValidation(console.channel, user.id).then(() => stopInteracting()), - } - ]; - - localFeatures.forEach(feature => this.adminConsole.addFeature(Feature.create(feature))); - } - - /** - * FEATURES FROM THIS POINT DOWN. - */ - - /** - * Add a channel to the activity, prompts user for info and name. - * @param {TextChannel} channel - channel to prompt user for specified voice channel - * @param {String} userId - user to prompt for specified voice channel - * @async - */ - async addChannel(channel, userId) { - // voice or text - let option = await ListPrompt.singleReactionPicker({ - prompt: 'What type of channel do you want?', - channel, - userId, - }, [ - { - name: 'voice', - description: 'A voice channel', - emojiName: '🔊' - }, - { - name: 'text', - description: 'A text channel', - emojiName: '✍️', - } - ]); - // channel name - let name = await StringPrompt.single({ prompt: 'What is the name of the channel?', channel, userId }); - - return await this.room.addRoomChannel({ name, info: { type: option.name } }); - } - - /** - * Removes a channel from the activity, the user will decide which. Wont delete channels in the safeChannel map. - * @param {TextChannel} channel - channel to prompt user for specified voice channel - * @param {String} userId - user to prompt for specified voice channel - * @async - */ - async removeChannel(channel, userId) { - /** @type {TextChannel} channel to remove */ - let removeChannel = await ListPrompt.singleListChooser({ - prompt: 'What channel should be removed?', - channel: channel, - userId: userId - }, this.room.channels.category.children.array()); - - try { - this.room.removeRoomChannel(removeChannel); - } catch (error) { - sendMsgToChannel(channel, userId, 'Can\'t remove that channel!', 10); - return; - } - - winston.loggers.get(this.guild.id).event(`The activity ${this.name} lost a channel named ${removeChannel.name}`, { event: 'Activity' }); - } - - /** - * Archive the activity. Move general text channel to archive category, remove all remaining channels - * and remove the category. - * @param {CategoryChannel} archiveCategory - the category where the general text channel will be moved to - * @async - */ - async archive(archiveCategory) { - await this.room.archive(archiveCategory); - - this.adminConsole.delete(); - - winston.loggers.get(this.guild.id).event(`The activity ${this.name} was archived!`, { event: 'Activity' }); - } - - /** - * Delete all the channels and the category. Remove the workshop from firebase. - * @async - */ - async delete() { - await this.room.delete(); - - this.adminConsole.delete(); - - winston.loggers.get(this.guild.id).event(`The activity ${this.name} was deleted!`, { event: 'Activity' }); - } - - /** - * Move all users back to a specified voice channel from the activity's voice channels. - * @param {TextChannel} channel - channel to prompt user for specified voice channel - * @param {String} userId - user to prompt for specified voice channel - */ - async voiceCallBack(channel, userId) { - /** @type {VoiceChannel} */ - let mainChannel = await ListPrompt.singleListChooser({ - prompt: 'What channel should people be moved to?', - channel: channel, - userId: userId - }, this.room.channels.voiceChannels.array()); - - this.room.channels.voiceChannels.forEach(channel => { - channel.members.forEach(member => member.voice.setChannel(mainChannel)); - }); - - winston.loggers.get(this.guild.id).event(`Activity named ${this.name} had its voice channels called backs to channel ${mainChannel.name}.`, { event: 'Activity' }); - } - - /** - * @callback ShuffleFilter - * @param {GuildMember} member - * @returns {Boolean} - true if filtered - /** - * Shuffle all the general voice members on all other voice channels - * @param {TextChannel} channel - channel to prompt user for specified voice channel - * @param {String} userId - user to prompt for specified voice channel - * @param {ShuffleFilter} [filter] - filter the users to shuffle - * @async - */ - async shuffle(channel, userId, filter) { - /** @type {VoiceChannel} */ - let mainChannel = await ListPrompt.singleListChooser({ - prompt: 'What channel should I move people from?', - channel: channel, - userId: userId - }, this.room.channels.voiceChannels.array()); - - let members = mainChannel.members; - if (filter) members = members.filter(member => filter(member)); - - let memberList = members.array(); - shuffleArray(memberList); - - let channels = this.room.channels.voiceChannels.filter(channel => channel.id != mainChannel.id).array(); - - let channelsLength = channels.length; - let channelIndex = 0; - memberList.forEach(member => { - try { - member.voice.setChannel(channels[channelIndex % channelsLength]); - channelIndex++; - } catch (error) { - winston.loggers.get(this.guild.id).warning(`Could not set a users voice channel when shuffling an activity by role. Error: ${error}`, { event: 'Activity' }); - } - }); - - winston.loggers.get(this.guild.id).event(`Activity named ${this.name} had its voice channel members shuffled around!`, { event: 'Activity' }); - } - - /** - * Shuffles users with a specific role throughout the activity's voice channels - * @param {TextChannel} channel - channel to prompt user for specified voice channel - * @param {String} userId - user to prompt for specified voice channel - * @async - */ - async roleShuffle(channel, userId) { - try { - var role = await RolePrompt.single({ prompt: 'What role would you like to shuffle?', channel, userId }); - } catch (error) { - winston.loggers.get(this.guild.id).warning(`User canceled a request when asking for a role for role shuffle. Error: ${error}.`, { event: 'Activity' }); - } - - this.shuffle(channel, userId, (member) => member.roles.cache.has(role.id)); - } - - /** - * Will let hackers get a stamp for attending the activity. - * @param {TextChannel} channel - channel to prompt user for specified voice channel - * @param {String} userId - user to prompt for specified voice channel - */ - async distributeStamp(channel, userId) { - - if (!this.initBotInfo.stamps.isEnabled) { - sendMsgToChannel(channel, userId, 'The stamp system is not enabled in this server!', 10); - return; - } - - // The users already seen by this stamp distribution. - let seenUsers = new Collection(); - - const promptEmbed = new MessageEmbed() - .setColor(this.initBotInfo.colors.embedColor) - .setTitle('React within ' + this.initBotInfo.stamps.stampCollectionTime + ' seconds of the posting of this message to get a stamp for ' + this.name + '!'); - - // send embed to general text or prompt for channel - let promptMsg; - if ((await this.room.channels.generalText.fetch(true))) promptMsg = await this.room.channels.generalText.send(promptEmbed); - else { - let stampChannel = await ListPrompt.singleListChooser({ - prompt: 'What channel should the stamp distribution go?', - channel: channel, - userId: userId - }, this.room.channels.textChannels.array()); - promptMsg = await stampChannel.send(promptEmbed); - } - - promptMsg.react('👍'); - - // reaction collector, time is needed in milliseconds, we have it in seconds - const collector = promptMsg.createReactionCollector((reaction, user) => !user.bot, { time: (1000 * this.initBotInfo.stamps.stampCollectionTime) }); - - collector.on('collect', async (reaction, user) => { - // grab the member object of the reacted user - const member = this.guild.member(user); - - if (!seenUsers.has(user.id)) { - StampsManager.parseRole(member, this.name, this.initBotInfo); - seenUsers.set(user.id, user.username); - } - }); - - // edit the message to closed when the collector ends - collector.on('end', () => { - winston.loggers.get(this.guild.id).event(`Activity named ${this.name} stamp distribution has stopped.`, { event: 'Activity' }); - if (!promptMsg.deleted) { - promptMsg.edit(promptEmbed.setTitle('Time\'s up! No more responses are being collected. Thanks for participating in ' + this.name + '!')); - } - }); - } - - /** - * Will lock the channels behind an emoji collector. - * @param {TextChannel} channel - channel to prompt user for specified voice channel - * @param {String} userId - user to prompt for specified voice channel - */ - async ruleValidation(channel, userId) { - - let rulesChannel = await this.room.lockRoom(); - - let rules = await StringPrompt.single({ prompt: 'What are the activity rules?', channel, userId }); - - let joinEmoji = '🚗'; - - const embed = new MessageEmbed().setTitle('Activity Rules').setDescription(rules).addField('To join the activity:', `React to this message with ${joinEmoji}`).setColor(this.initBotInfo.colors.embedColor); - - const embedMsg = await rulesChannel.send(embed); - - embedMsg.react(joinEmoji); - - const collector = embedMsg.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === joinEmoji); - - collector.on('collect', (reaction, user) => { - this.room.giveUserAccess(user); - rulesChannel.updateOverwrite(user.id, { VIEW_CHANNEL: false }); - }); - } -} - -module.exports = Activity; \ No newline at end of file diff --git a/classes/Bot/activities/cave.js b/classes/Bot/activities/cave.js deleted file mode 100644 index 6196eded..00000000 --- a/classes/Bot/activities/cave.js +++ /dev/null @@ -1,420 +0,0 @@ -const { Guild, Collection, Role, TextChannel, MessageEmbed, GuildEmoji, ReactionEmoji, CategoryChannel } = require('discord.js'); -const { sendMsgToChannel, addRoleToMember, removeRolToMember } = require('../../../discord-services'); -const Room = require('../../UI/Room/room'); -const Console = require('../../UI/Console/console'); -const Feature = require('../../UI/Console/feature'); -const TicketManager = require('../Features/Ticket_System/ticket-manager'); -const Activity = require('./activity'); -const { StringPrompt, NumberPrompt, SpecialPrompt } = require('advanced-discord.js-prompts'); - -/** - * @typedef CaveOptions - * @property {String} name - the name of the cave category - * @property {String} preEmojis - any pre name emojis - * @property {String} preRoleText - the text to add before every role name, not including '-' - * @property {String} color - the role color to use for this cave - * @property {Role} role - the role associated with this cave - * @property {Emojis} emojis - object holding emojis to use in this cave - * @property {Times} times - object holding times to use in this cave - * @property {Collection} publicRoles - the roles that can request tickets - */ - -/** - * @typedef Emojis - * @property {GuildEmoji | ReactionEmoji} joinTicketEmoji - emoji for mentors to accept a ticket - * @property {GuildEmoji | ReactionEmoji} giveHelpEmoji - emoji for mentors to join an ongoing ticket - * @property {GuildEmoji | ReactionEmoji} requestTicketEmoji - emoji for hackers to request a ticket - * @property {GuildEmoji | ReactionEmoji} addRoleEmoji - emoji for Admins to add a mentor role - * @property {GuildEmoji | ReactionEmoji} deleteChannelsEmoji - emoji for Admins to force delete ticket channels - * @property {GuildEmoji | ReactionEmoji} excludeFromAutoDeleteEmoji - emoji for Admins to opt tickets in/out of garbage collector - */ - -/** - * @typedef Times - * @property {Number} inactivePeriod - number of minutes a ticket channel will be inactive before bot starts to delete it - * @property {Number} bufferTime - number of minutes the bot will wait for a response before deleting ticket - * @property {Number} reminderTime - number of minutes the bot will wait before reminding mentors of unaccepted tickets - */ - -/** - * @typedef SubRole - * @property {String} name - the role name - * @property {String} id - the role id (snowflake) - * @property {Number} activeUsers - number of users with this role - */ - -/** - * @typedef CaveChannels - * @property {TextChannel} roleSelection - */ - -class Cave extends Activity { - - /** - * @constructor - * @param {CaveOptions} caveOptions - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Guild} guild - */ - constructor(caveOptions, initBotInfo, guild) { - super({ - activityName: caveOptions.name, - guild: guild, - roleParticipants: new Collection([[caveOptions.role.id, caveOptions.role]]), - initBotInfo, - }); - - /** - * @type {CaveOptions} - */ - this.caveOptions; - this.validateCaveOptions(caveOptions); - - /** - * The cave sub roles, keys are the emoji name, holds the subRole - * @type {Map} - - */ - this.subRoles = new Map(); - - /** - * @type {TicketManager} - */ - this.ticketManager; - - /** - * The channels needed for a cave. - * @type {CaveChannels} - */ - this.channels = {}; - - /** - * The public room for this cave. - * @type {Room} - */ - this.publicRoom = new Room(guild, initBotInfo, `👉🏽👈🏽${caveOptions.name} Help`, caveOptions.publicRoles); - - /** - * The console where cave members can get sub roles. - * @type {Console} - */ - this.subRoleConsole; - } - - /** - * Validates and set the cave options. - * @param {CaveOptions} caveOptions - the cave options to validate - * @param {Discord.Guild} guild - the guild where this cave is happening - * @private - */ - validateCaveOptions(caveOptions) { - if (typeof caveOptions.name != 'string' && caveOptions.name.length === 0) throw new Error('caveOptions.name must be a non empty string'); - if (typeof caveOptions.preEmojis != 'string') throw new Error('The caveOptions.preEmojis must be a string of emojis!'); - if (typeof caveOptions.preRoleText != 'string' && caveOptions.preRoleText.length === 0) throw new Error('The caveOptions.preRoleText must be a non empty string!'); - if (typeof caveOptions.color != 'string' && caveOptions.color.length === 0) throw new Error('The caveOptions.color must be a non empty string!'); - if (!(caveOptions.role instanceof Role)) throw new Error('The caveOptions.role must be Role object!'); - for (const emoji in caveOptions.emojis) { - if (!(caveOptions.emojis[emoji] instanceof GuildEmoji) && !(caveOptions.emojis[emoji] instanceof ReactionEmoji)) throw new Error('The ' + emoji + 'must be a GuildEmoji or ReactionEmoji!'); - } - this.caveOptions = caveOptions; - } - - async init() { - await super.init(); - - this.channels.roleSelection = await this.room.addRoomChannel({ - name: `📝${this.name}-role-selector`, - info: { - topic: 'Sign yourself up for specific roles! New roles will be added as requested, only add yourself to one if you feel comfortable responding to questions about the topic.', - }, - isSafe: true, - }); - this.subRoleConsole = new Console({ - title: 'Choose your sub roles!', - description: 'Choose sub roles you are comfortable answering questions for! Remove your reaction to loose the sub role.', - channel: this.channels.roleSelection, - guild: this.guild, - }); - this.subRoleConsole.sendConsole(); - - for (var i = 0; i < 3; i++) { - this.room.addRoomChannel({ - name: `🗣️ Room ${i}`, - info: { type: 'voice' }, - }); - } - - await this.publicRoom.init(); - - this.ticketManager = new TicketManager(this, { - ticketCreatorInfo: { - channel: await this.publicRoom.addRoomChannel({ - name: '🎫request-ticket', - isSafe: true, - }), - }, - ticketDispatcherInfo: { - channel: await this.room.addRoomChannel({ - name: '📨incoming-tickets', - isSafe: true, - }), - takeTicketEmoji: this.caveOptions.emojis.giveHelpEmoji, - joinTicketEmoji: this.caveOptions.emojis.joinTicketEmoji, - reminderInfo: { - isEnabled: true, - time: this.caveOptions.times.reminderTime, - }, - mainHelperInfo: { - role: this.caveOptions.role, - emoji: this.caveOptions.emojis.requestTicketEmoji, - }, - embedCreator: (ticket) => new MessageEmbed() - .setTitle(`New Ticket - ${ticket.id}`) - .setDescription(`<@${ticket.group.first().id}> has a question: ${ticket.question}`) - .addField('They are requesting:', `<@&${ticket.requestedRole.id}>`) - .setTimestamp(), - }, - systemWideTicketInfo: { - garbageCollectorInfo: { - isEnabled: true, - inactivePeriod: this.caveOptions.times.inactivePeriod, - bufferTime: this.caveOptions.times.bufferTime - }, - isAdvancedMode: true, - } - }); - - await this.ticketManager.sendTicketCreatorConsole('Get some help from our mentors!', - 'To submit a ticket to the mentors please react to this message with the appropriate emoji. **If you are unsure, select a general ticket!**'); - } - - addDefaultFeatures() { - /** @type {Console.Feature[]} */ - let localFeatures = [ - Feature.create({ - name: 'Add Sub-Role', - description: 'Add a new sub-role cave members can select and users can use to ask specific tickets.', - emoji: this.caveOptions.emojis.addRoleEmoji, - callback: (user, reaction, stopInteracting, console) => this.addSubRoleCallback(console.channel, user.id).then(() => stopInteracting()), - }), - Feature.create({ - name: 'Delete Ticket Channels', - description: 'Get the ticket manager to delete ticket rooms to clear up the server.', - emoji: this.caveOptions.emojis.deleteChannelsEmoji, - callback: (user, reaction, stopInteracting, console) => this.deleteTicketChannelsCallback(console.channel, user.id).then(() => stopInteracting()), - }), - Feature.create({ - name: 'Include/Exclude Tickets', - description: 'Include or exclude tickets from the automatic garbage collector.', - emoji: this.caveOptions.emojis.excludeFromAutoDeleteEmoji, - callback: (user, reaction, stopInteracting, console) => this.includeExcludeCallback(console.channel, user.id).then(() => stopInteracting()), - }), - ]; - - localFeatures.forEach(feature => this.adminConsole.addFeature(feature)); - - super.addDefaultFeatures(); - } - - /** - * Prompts a user for information to create a new sub role for this cave. - * @param {TextChannel} channel - * @param {String} userId - * @returns {Promise} - * @async - */ - async addSubRoleCallback(channel, userId) { - let roleName = await StringPrompt.single({ prompt: 'What is the name of the new role?', channel, userId }); - - let emojis = new Map(); - this.subRoles.forEach((subRole, emojiName, map) => { - emojis.set(emojiName, subRole.name); - }); - - let reaction = await SpecialPrompt.singleRestrictedReaction({ prompt: 'What emoji do you want to associate with this new role?', channel, userId }, emojis); - let emoji = reaction.emoji; - - // search for possible existing role - let findRole = this.guild.roles.cache.find(role => role.name.toLowerCase() === `${this.caveOptions.preRoleText}-${roleName}`.toLowerCase()); - let useOld; - if (findRole) useOld = await SpecialPrompt.boolean({ prompt: 'I have found a role with the same name! Would you like to use that one? If not I will create a new one.', channel, userId }); - - let role; - if (useOld) role = findRole; - else role = await this.guild.roles.create({ - data: { - name: `${this.caveOptions.preRoleText}-${roleName}`, - color: this.caveOptions.color, - } - }); - - this.addSubRole(role, emoji); - - try { - let addPublic = await SpecialPrompt.boolean({ prompt: 'Do you want me to create a public text channel?', channel, userId }); - if (addPublic) this.publicRoom.addRoomChannel({ name: roleName }); - } catch { - // do nothing - } - - return role; - } - - /** - * Will prompt the user for more information to delete some, all, or a few tickets. - * @param {TextChannel} channel - * @param {String} userId - * @async - */ - async deleteTicketChannelsCallback(channel, userId) { - let type = await StringPrompt.restricted({ - prompt: 'Type "all" if you would like to delete all tickets before x amount of time or type "some" to specify which tickets to remove.', - channel, - userId, - }, ['all', 'some']); - - switch (type) { - case 'all': { - let age = await NumberPrompt.single({ prompt: 'Enter how old, in minutes, a ticket has to be to remove. Send 0 if you want to remove all of them. Careful - this cannot be undone!', channel, userId }); - this.ticketManager.removeTicketsByAge(age); - sendMsgToChannel(channel, userId, `All tickets over ${age} have been deleted!`); - break; - } - case ('some'): { - let subtype = await StringPrompt.restricted({ - prompt: 'Would you like to remove all tickets except for some tickets you specify later or would you like to remove just some tickets. Type all or some respectively.', - channel, - userId - }, ['all', 'some']); - - switch (subtype) { - case 'all': { - let ticketMentions = await NumberPrompt.multi({ - prompt: 'In one message write the numbers of the tickets to not delete! (Separated by spaces, ex 1 2 13).', - channel, - userId - }); - this.ticketManager.removeAllTickets(ticketMentions); - break; - } - case 'some': { - let ticketMentions = await NumberPrompt.multi({ - prompt: 'In one message type the ticket numbers you would like to remove! (Separated by spaces, ex. 1 23 3).', - channel, - userId, - }); - this.ticketManager.removeTicketsById(ticketMentions); - break; - } - } - } - } - } - - /** - * Will prompt the user for channel numbers to include or exclude from the garbage collector. - * @param {TextChannel} channel - * @param {String} userId - */ - async includeExcludeCallback(channel, userId) { - let type = await StringPrompt.restricted({ - prompt: 'Would you like to include tickets on the automatic garbage collector or exclude tickets? Respond with include or exclude respectively.', - channel, - userId, - }, ['include', 'exclude']); - - let tickets = await NumberPrompt.multi({ - prompt: `Type the ticket numbers you would like to ${type} separated by spaces.`, - channel, - userId, - }); - - tickets.forEach((ticketNumber) => { - let ticket = this.ticketManager.tickets.get(ticketNumber); - ticket?.includeExclude(type === 'exclude' ? true : false); - }); - } - - /** - * Adds a subRole. - * @param {Role} role - the role to add - * @param {GuildEmoji} emoji - the emoji associated to this role - * @param {Number} [currentActiveUsers=0] - number of active users with this role - * @private - */ - addSubRole(role, emoji, currentActiveUsers = 0) { - /** @type {SubRole} */ - let subRoleName = role.name.substring(this.caveOptions.preRoleText.length + 1); - let subRole = { - name: subRoleName, - id: role.id, - activeUsers: currentActiveUsers, - }; - - // add to list of emojis being used - this.subRoles.set(emoji.name, subRole); - - // add to subRole selector console - this.subRoleConsole.addFeature( - Feature.create({ - name: `-> If you know ${subRoleName}`, - description: '---------------------------------', - emoji: emoji, - callback: (user, reaction, stopInteracting, console) => { - let member = this.guild.member(user); - addRoleToMember(member, role); - sendMsgToChannel(console.channel, user.id, `You have received the ${subRoleName} role!`, 10); - stopInteracting(); - }, - removeCallback: (user, reaction, stopInteracting, console) => { - let member = this.guild.member(user); - removeRolToMember(member, role); - sendMsgToChannel(console.channel, user.id, `You have lost the ${subRoleName} role!`, 10); - stopInteracting(); - }, - }) - ); - - this.ticketManager.addTicketType(role, subRole.name, emoji); - } - - /** - * Prompts user if they want to delete sub-roles - * @param {Channel} channel - * @param {Integer} userId - */ - async deleteSubRoles(channel, userId) { - if (this.subRoles.size > 0) { - let ans = await SpecialPrompt.boolean({ prompt: 'Do you want to delete all the sub-roles?', channel, userId }); - if (ans) { - this.subRoles.forEach((subRole) => { - let role = this.guild.roles.cache.find(role => role.id === subRole.id); - role.delete(); - }); - } - } - } - /** - * Deletes all the tickets rooms, public channels and private channels. - * @override - */ - delete(channel, userId) { - this.deleteSubRoles(channel, userId); - this.publicRoom.delete(); - this.ticketManager.removeAllTickets(); - super.delete(); - } - - /** - * Removes private channels and archives the public channels. - * It also deletes the ticket rooms. - * @override - * @param {CategoryChannel} archiveCategory - */ - archive(archiveCategory) { - this.room.delete(); - this.publicRoom.archive(archiveCategory); - this.ticketManager.removeAllTickets(); - super.archive(); - } -} -module.exports = Cave; \ No newline at end of file diff --git a/classes/Bot/activities/coffee-chats.js b/classes/Bot/activities/coffee-chats.js deleted file mode 100644 index 3a6e7b43..00000000 --- a/classes/Bot/activities/coffee-chats.js +++ /dev/null @@ -1,230 +0,0 @@ -const Activity = require('./activity'); -const { TextChannel, GuildMember, Collection, VoiceChannel } = require('discord.js'); -const winston = require('winston'); -const { sendMsgToChannel } = require('../../../discord-services'); -const Console = require('../../UI/Console/console'); -const { MemberPrompt, ListPrompt } = require('advanced-discord.js-prompts'); -const Feature = require('../../UI/Console/feature'); - -/** - * A CoffeeChat is a special activity where teams get shuffled around voice channels to talk with other teams or members like mentors. - * Users can join the activity by reacting to a message. Groups are of unlimited size but must be pre-made. The activity will not create groups. - * Admins get additional features to run this activity. These features let shuffle the groups around the available voice channels. - * @extends Activity - */ -class CoffeeChats extends Activity { - - /** - * Basic constructor for a coffee chats. - * @param {Activity.ActivityInfo} activityInfo - * @param {Number} numOfTeams - */ - constructor(activityInfo, numOfTeams) { - super(activityInfo); - - /** - * A collection of the groups that will attend this coffee chat. - * @type {Collection} - - */ - this.teams = new Collection(); - - /** - * The number of groups available in this coffee chat - * @type {Number} - */ - this.numOfTeams = numOfTeams || 0; - - /** - * The channel where users join the activity. - * @type {TextChannel} - */ - this.joinActivityChannel; - - /** - * The main voice channel where everyone starts. - * @type {VoiceChannel} - */ - this.mainVoiceChannel; - - winston.loggers.get(this.guild.id).event(`The activity ${this.name} was created as a coffee chats.`, {event: 'Activity'}); - } - - /** - * Initializes the activity by creating the necessary channels. - * @returns {Promise} - * @param {TextChannel} channel - * @param {String} userId - * @override - */ - async init(channel, userId) { - await super.init(); - - /** @type {VoiceChannel} */ - this.mainVoiceChannel = await ListPrompt.singleListChooser({ - prompt: 'What channel will the teams join first before being shuffled?', - channel: channel, - userId: userId - }, this.room.channels.voiceChannels.array()); - this.room.channels.safeChannels.set(this.mainVoiceChannel.id, this.mainVoiceChannel); - - for (var i = 0; i < this.numOfTeams; i++) { - this.room.addRoomChannel({name: `voice-${i}`, info: {type: 'voice'}}); - } - - this.joinActivityChannel = await this.room.addRoomChannel({ - name: '☕' + 'join-activity', - info: { - type: 'text', - topic: 'This channel is only intended to add your team to the activity list! Please do not use it for anything else!', - }, - isSafe: true - }); - - this.sendJoinActivityConsole(); - - return this; - } - - - /** - * @override - */ - addDefaultFeatures() { - /** @type {Console.Feature[]} */ - let localFeatures = [ - { - name: 'Team Shuffle', - description: 'Shuffle all the teams from the main voice channel to the other channels.', - emoji: '👨‍👩‍👧‍👦', - callback: (user, reaction, stopInteracting, console) => { - this.groupShuffle(); - sendMsgToChannel(console.channel, user.id, 'The teams have been shuffled!'); - stopInteracting(); - }, - }, - { - name: 'Reset Teams', - description: 'Remove all the signed up teams.', - emoji: '🗜️', - callback: (user, reaction, stopInteracting, console) => { - this.resetTeams(); - sendMsgToChannel(console.channel, user.id, 'The teams have been deleted so new teams can join!'); - stopInteracting(); - }, - }, - { - name: 'Add Team Slot', - description: 'Adds a team slot and a voice channel for them.', - emoji: '☝️', - callback: (user, reaction, stopInteracting, console) => { - this.addTeamSlot(); - sendMsgToChannel(console.channel, user.id, 'A new team slot has been added!'); - stopInteracting(); - }, - } - ]; - - localFeatures.forEach(feature => this.adminConsole.addFeature(Feature.create(feature))); - - super.addDefaultFeatures(); - } - - - /** - * Will send the console for users to join the activity as a group. - * @private - * @async - */ - async sendJoinActivityConsole() { - // reaction to use - var emoji = '⛷️'; - - let joinActivityConsole = new Console({ - title: `${this.name}'s Join Console!`, - description: 'To join this activity read below! This activity is first come, first serve so get in quick!', - channel: this.joinActivityChannel, - guild: this.guild, - }); - - joinActivityConsole.addFeature(Feature.create({ - name: 'Join the activity!', - description: `React to this message with ${emoji} and follow my instructions!`, - emoji: emoji, - callback: async (user, reaction, stopInteracting, console) => { - // check to make sure there are spots left - if (this.teams.size > this.numOfTeams) { - sendMsgToChannel(this.joinActivityChannel, user.id, 'Sorry, but the activity is full! Check back again later for a new cycle!', 10); - return; - } - let members; - try { - members = await MemberPrompt.multi({prompt: 'Who are you team members? Let me know in ONE message! Type cancel if you are joining solo.', channel: this.joinActivityChannel, userId: user.id}); - } catch (error) { - members = new Collection(); - } - - // add team captain to members list - members.set(user.id, this.guild.member(user)); - - // add the team to the team list - this.teams.set(this.teams.size, members.array()); - - this.joinActivityChannel.send('<@' + user.id + '> Your team has been added to the activity! Make sure you follow the instructions in the main channel.').then(msg => { - msg.delete({ timeout: 5000 }); - }); - - stopInteracting(); - } - })); - - joinActivityConsole.sendConsole(); - } - - /** - * FEATURES FROM THIS POINT DOWN. - */ - - - /** - * Shuffle users from general voice to all other voice channel. Groups will stay on the same voice channel. - */ - groupShuffle() { - let channels = this.room.channels.voiceChannels; - let voiceChannels = channels.filter(voiceChannel => voiceChannel.id != this.mainVoiceChannel.id).array(); - - // loop over the groups and channels at the same time using an index, add users for each group in a single voice channel - for (var index = 0; index < this.teams.size; index++) { - this.teams.get(index).forEach(member => { - try { - if (member.voice.channel) - member.voice.setChannel(voiceChannels[index % voiceChannels.length]); - } catch (error) { - // do nothing, sad! - winston.loggers.get(this.guild.id).warning(`For activity named ${this.name} I could not pull in user ${member.id} into the voice channel ${voiceChannels[index].name}.`, { event: 'Coffee Chats' }); - } - }); - } - - winston.loggers.get(this.guild.id).event(`Activity named ${this.name} had its groups shuffled.`, { event: 'Coffee Chats' }); - } - - - /** - * Resets the teams to have no teams. - */ - resetTeams() { - this.teams = new Collection(); - } - - - /** - * Add a team slot to the activity and adds a voice channel for them. - */ - addTeamSlot() { - this.numOfTeams += 1; - this.room.addRoomChannel({name: `voice-${this.numOfTeams - 1}`, info: { type: 'voice' }}); // -1 because we start from 0 - } - -} - -module.exports = CoffeeChats; \ No newline at end of file diff --git a/classes/Bot/activities/workshop.js b/classes/Bot/activities/workshop.js deleted file mode 100644 index 14b57863..00000000 --- a/classes/Bot/activities/workshop.js +++ /dev/null @@ -1,504 +0,0 @@ -const { Role, Collection, TextChannel, VoiceChannel, GuildCreateChannelOptions, MessageEmbed, Message } = require('discord.js'); -const winston = require('winston'); -const { randomColor, sendMessageToMember, sendMsgToChannel } = require('../../../discord-services'); -const Console = require('../../UI/Console/console'); -const Room = require('../../UI/Room/room'); -const TicketManager = require('../Features/Ticket_System/ticket-manager'); -const Activity = require('./activity'); -const { StringPrompt, SpecialPrompt, ListPrompt } = require('advanced-discord.js-prompts'); -const Feature = require('../../UI/Console/feature'); - - -/** - * @typedef PollInfo - * @property {String} type - * @property {String} title - * @property {String} question - * @property {String} emojiName - must be unicode emoji! - * @property {Collection} responses - - */ - -/** - * A workshop is an activity with a TA system to help users with questions. - * The TA system has two options, regular or advanced. Regular option involves TAs reaching out via DMs to users while advanced option - * involves users joining a voice channel to receive help. The advanced option is only recommended with knowledgeable discord users. - * It also has polls the TAs can send to learn basic knowledge from the audience. - * @extends Activity - */ -class Workshop extends Activity { - - /** - * - * @constructor - * @param {Activity.ActivityInfo} - * @param {Boolean} [isLowTechSolution=true] - * @param {Collection} [TARoles] - roles with TA permissions - */ - constructor({activityName, guild, roleParticipants, initBotInfo}, isLowTechSolution = true, TARoles) { - super({activityName, guild, roleParticipants, initBotInfo}); - - /** - * @type {Collection} - roles with TA permissions - */ - this.TARoles = TARoles || new Collection(); - - /** - * True if the assistance protocol is low tech. - * @type {Boolean} - */ - this.isLowTechSolution = isLowTechSolution; - - /** - * The channel where hackers can ask questions. - * @type {TextChannel} - */ - this.assistanceChannel; - - /** - * The channels only available to TAs - * @type {Collection} - - */ - this.TAChannels = new Collection(); - - /** - * TA Console where assistance calls are sent. - * @type {TextChannel} - */ - this.TAConsole; - - /** - * The message where we show the wait list live. - * @type {Message} - */ - this.waitListEmbedMsg; - - /** - * wait list Collection - * @type {Collection} - - */ - this.waitlist = new Collection(); - - /** - * The polls available. - * @type {Collection} - - */ - this.polls = new Collection; - - /** - * The ticket manager. - * @type {TicketManager} - */ - this.ticketManager; - } - - - /** - * Initializes the workshop and adds the ta console, ta banter and assistance channel. - * @override - */ - async init() { - await super.init(); - - this.TAConsole = await this.addTAChannel('_🧑🏽‍🏫ta-console', { - type: 'text', - topic: 'The TA console, here TAs can chat, communicate with the workshop lead, look at the wait list, and send polls!', - }, [], true); - - this.addTAChannel('_ta-banter', { - topic: 'For TAs to talk without cluttering the console.', - }); - - this.assistanceChannel = await this.room.addRoomChannel({ - name: '🙋🏽assistance', - info: { - type: 'text', - topic: 'For hackers to request help from TAs for this workshop, please don\'t send any other messages!' - }, - isSafe: true, - }); - - this.initBotInfo.blackList.set(this.assistanceChannel.id, 3000); - this.initBotInfo.save(); - - if (this.isLowTechSolution) { - this.ticketManager = new TicketManager(this, { - ticketCreatorInfo: { - channel: this.assistanceChannel, - }, - ticketDispatcherInfo: { - channel: await this.room.addRoomChannel({ - name: '_Incoming Tickets', - isSafe: true, - }), - takeTicketEmoji: '👍', - joinTicketEmoji: '☝️', - reminderInfo: { - isEnabled: true, - time: 5 - }, - mainHelperInfo: { - role: this.TARoles.first(), - emoji: '✋', - }, - embedCreator: (ticket) => new MessageEmbed() - .setTitle(`New Ticket - ${ticket.id}`) - .setDescription(`<@${ticket.group.first().id}> has a question: ${ticket.question}`) - .setTimestamp(), - }, - systemWideTicketInfo: { - garbageCollectorInfo: { - isEnabled: false, - }, - isAdvancedMode: false, - } - }, this.guild, this.initBotInfo); - } - - winston.loggers.get(this.guild.id).event(`The activity ${this.name} was transformed to a workshop.`, {event: 'Activity'}); - - return this; - } - - - /** - * Adds extra workshop features, plus the regular features. Also adds default polls. - * @override - */ - addDefaultFeatures() { - this.addDefaultPolls(); - - /** @type {Console.Feature[]} */ - let localFeatures = []; - - this.polls.forEach((pollInfo) => localFeatures.push({ - name: pollInfo.title, - description: `Asks the question: ${pollInfo.title} - ${pollInfo.question}`, - emoji: pollInfo.emojiName, - callback: (user, reaction, stopInteracting, console) => this.sendPoll(pollInfo.type).then(() => stopInteracting()), - })); - - localFeatures.forEach(feature => this.adminConsole.addFeature(Feature.create(feature))); - - super.addDefaultFeatures(); - } - - - /** - * Adds the default polls to the polls list. - * @protected - */ - addDefaultPolls() { - /** @type {PollInfo[]} */ - let localPolls = [ - { - title: 'Speed Poll!', - type: 'Speed Poll', - emojiName: '🏎️', - question: 'Please react to this poll!', - responses: new Collection([['🐢', 'Too Slow?'], ['🐶', 'Just Right?'], ['🐇', 'Too Fast?']]), - }, - { - title: 'Difficulty Poll!', - type: 'Difficulty Poll', - emojiName: '✍️', - question: 'Please react to this poll! If you need help, go to the assistance channel!', - responses: new Collection([['🐢', 'Too Hard?'], ['🐶', 'Just Right?'], ['🐇', 'Too Easy?']]), - }, - { - title: 'Explanation Poll!', - type: 'Explanation Poll', - emojiName: '🧑‍🏫', - question: 'Please react to this poll!', - responses: new Collection([['🐢', 'Hard to understand?'], ['🐶', 'Meh explanations?'], ['🐇', 'Easy to understand?']]), - } - ]; - - localPolls.forEach(pollInfo => this.polls.set(pollInfo.type, pollInfo)); - } - - - /** - * Will send all the consoles the workshop needs to work. - * @async - */ - async sendConsoles() { - let mentorColor = randomColor(); - - const TAInfoEmbed = new MessageEmbed() - .setTitle('TA Information') - .setDescription('Please read this before the workshop starts!') - .setColor(mentorColor); - this.isLowTechSolution ? TAInfoEmbed.addField('Ticketing System is turned on!', `* Tickets will be sent to <#${this.ticketManager.ticketDispatcherInfo.channel.id}> - \n* React to the ticket message and send the user a DM by clicking on their name`) : - TAInfoEmbed.addField('Advanced Voice Channel System is turned on!', `* Users who need help will be listed in a message on channel <#${this.TAConsole}> - \n* Users must be on the general voice channel to receive assistance - \n* You must be on a private voice channel to give assistance - \n* When you react to the message, the user will be moved to your voice channel so you can give assistance - \n* Once you are done, move the user back to the general voice channel`); - this.TAConsole.send(TAInfoEmbed); - - // Console for TAs to send polls and stamp distribution - let TAPollingConsole = new Console({ - title: 'Polling and Stamp Console', - description: 'Here are some common polls you might want to use!', - channel: this.TAConsole, - guild: this.guild, - }); - this.polls.forEach((pollInfo) => TAPollingConsole.addFeature(Feature.create({ - name: pollInfo.title, - description: `Asks the question: ${pollInfo.title} - ${pollInfo.question}`, - emoji: pollInfo.emojiName, - callback: (user, reaction, stopInteracting, console) => this.sendPoll(pollInfo.type).then(() => stopInteracting()), - }))); - TAPollingConsole.addFeature(Feature.create({ - name: 'Stamp Distribution', - description: 'Activate a stamp distribution on the activity\'s text channel', - emoji: '📇', - callback: (user, reaction, stopInteracting, console) => { - this.distributeStamp(this.room.channels.generalText); - stopInteracting(); - } - })); - TAPollingConsole.sendConsole(); - - if (this.isLowTechSolution) { - await this.ticketManager.sendTicketCreatorConsole('Get some help from the Workshop TAs!', - 'React to this message with the emoji and write a quick description of your question. A TA will reach out via DM soon.'); - this.ticketManager.ticketCreatorInfo.console.addField('Simple or Theoretical Questions', 'If you have simple or theory questions, ask them in the main banter channel!'); - } else { - // embed message for TA console - const incomingTicketsEmbed = new MessageEmbed() - .setColor(mentorColor) - .setTitle('Hackers in need of help waitlist') - .setDescription('* Make sure you are on a private voice channel not the general voice channel \n* To get the next hacker that needs help click 🤝'); - this.TAConsole.send(incomingTicketsEmbed).then(message => this.incomingTicketsHandler(message)); - - // where users can request assistance - const outgoingTicketEmbed = new MessageEmbed() - .setColor(this.initBotInfo.colors.embedColor) - .setTitle(this.name + ' Help Desk') - .setDescription('Welcome to the ' + this.name + ' help desk. There are two ways to get help explained below:') - .addField('Simple or Theoretical Questions', 'If you have simple or theory questions, ask them in the main banter channel!') - .addField('Advanced Question or Code Assistance', 'If you have a more advanced question, or need code assistance, click the 🧑🏽‍🏫 emoji for live TA assistance! Join the ' + this.room.channels.generalVoice.name || Room.voiceChannelName + ' voice channel if not already there!'); - this.assistanceChannel.send(outgoingTicketEmbed).then(message => this.outgoingTicketHandler(message)); - } - } - - - /** - * Adds a channel to the activity, ask if it will be for TAs or not. - * @param {TextChannel} channel - channel to prompt user - * @param {String} userId - user to prompt for channel info - * @override - */ - async addChannel(channel, userId) { - // ask if it will be for TA - let isTa = await SpecialPrompt.boolean({ prompt: 'Is this channel for TAs?', channel, userId }); - - if (isTa) { - /** @type {TextChannel} */ - let newChannel = await super.addChannel(channel, userId); - this.getTAChannelPermissions().forEach(rolePermission => newChannel.updateOverwrite(rolePermission.id, rolePermission.permissions)); - this.TAChannels.set(newChannel.name, newChannel); - } else { - super.addChannel(channel, userId); - } - } - - - /** - * Creates a channel only available to TAs. - * @param {String} name - * @param {GuildCreateChannelOptions} info - * @returns {Promise} - * @async - */ - async addTAChannel(name, info) { - let channel = await this.room.addRoomChannel({name, info, permissions: this.getTAChannelPermissions()}); - this.TAChannels.set(channel.name, channel); - return channel; - } - - - /** - * Returns the perms for a TA Channel - * @protected - * @returns {Activity.RolePermission[]} - */ - getTAChannelPermissions() { - /** The permissions for the TA channels */ - let TAChannelPermissions = [ - { id: this.initBotInfo.roleIDs.everyoneRole, permissions: { VIEW_CHANNEL: false } }, - ]; - - // add regular activity members to the TA perms list as non tas, so they cant see that channel - this.room.rolesAllowed.forEach(role => { - TAChannelPermissions.push({id: role.id, permissions: {VIEW_CHANNEL: false}}); - - }); - - // Loop over ta roles, give them voice channel perms and add them to the TA permissions list - this.TARoles.forEach(role => { - TAChannelPermissions.push({id: role.id, permissions: {VIEW_CHANNEL: true}}); - }); - - return TAChannelPermissions; - } - - - /** - * FEATURES: - */ - - - /** - * Send a poll to the general text channel - * @param {String} type - the type of poll to send - * @async - */ - async sendPoll(type, channel, userId) { - let poll = this.polls.get(type); - if (!poll) throw new Error('No poll was found of that type!'); - - // create poll - let description = poll.question + '\n\n'; - for (const key of poll.responses.keys()) { - description += '**' + poll.responses.get(key) + '->** ' + key + '\n\n'; - } - - let qEmbed = new MessageEmbed() - .setColor(this.initBotInfo.colors.embedColor) - .setTitle(poll.title) - .setDescription(description); - - // send poll to general text or prompt for channel - let pollChannel; - if ((await this.room.channels.generalText.fetch(true))) pollChannel = this.room.channels.generalText; - else pollChannel = ListPrompt.singleListChooser({ - prompt: 'What channel should the poll go to?', - channel: channel, - userId: userId - }, this.room.channels.textChannels.array()); - - pollChannel.send(qEmbed).then(msg => { - poll.responses.forEach((value, key) => msg.react(key)); - }); - - winston.loggers.get(this.guild.id).event(`Activity named ${this.name} sent a poll with title: ${poll.title} and question ${poll.question}.`, { event: 'Workshop' }); - } - - /** - * Creates and handles with the emoji reactions on the incoming ticket console embed - * @param {Message} message - */ - incomingTicketsHandler(message) { - message.pin(); - message.react('🤝'); - - this.waitListEmbedMsg = message; - - // add reaction to get next in this message! - const getNextCollector = message.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === '🤝'); - - getNextCollector.on('collect', async (reaction, user) => { - // remove the reaction - reaction.users.remove(user.id); - - // check that there is someone to help - if (this.waitlist.size === 0) { - this.TAConsole.send('<@' + user.id + '> No one to help right now!').then(msg => msg.delete({ timeout: 5000 })); - return; - } - - // if pullInFunctionality is turned off then then just remove from list - if (this.isLowTechSolution) { - // remove hacker from wait list - let hackerKey = this.waitlist.firstKey(); - this.waitlist.delete(hackerKey); - - } else { - // grab the ta and their voice channel - var ta = message.guild.member(user.id); - var taVoice = ta.voice.channel; - - // check that the ta is in a voice channel - if (taVoice === null || taVoice === undefined) { - this.TAConsole.send('<@' + user.id + '> Please join a voice channel to assist hackers.').then(msg => msg.delete({ timeout: 5000 })); - return; - } - - // get next user - let hackerKey = this.waitlist.firstKey(); - this.waitlist.delete(hackerKey); - var hacker = message.guild.member(hackerKey); - - // if status mentor in use there are no hackers in list - if (hacker === undefined) { - this.TAConsole.send('<@' + user.id + '> There are no hackers in need of help!').then(msg => msg.delete({ timeout: 5000 })); - return; - } - - try { - await hacker.voice.setChannel(taVoice); - sendMessageToMember(hacker, 'TA is ready to help you! You are with them now!', true); - this.TAConsole.send('<@' + user.id + '> A hacker was moved to your voice channel! Thanks for your help!!!').then(msg => msg.delete({ timeout: 5000 })); - } catch (err) { - sendMessageToMember(hacker, 'A TA was ready to talk to you, but we were not able to pull you to their voice ' + - 'voice channel. Try again and make sure you are in the general voice channel!'); - this.TAConsole.send('<@' + user.id + '> We had someone that needed help, but we were unable to move them to your voice channel. ' + - 'They have been notified and skipped. Please help someone else!').then(msg => msg.delete({ timeout: 8000 })); - } - } - - // remove hacker from the embed list - this.waitListEmbedMsg.edit(this.waitListEmbedMsg.embeds[0].spliceFields(0, 1)); - }); - } - - /** - * Creates and handles with the emoji reactions on the outgoing ticket console embed - * @param {Message} message - */ - outgoingTicketHandler(message) { - message.pin(); - message.react('🧑🏽‍🏫'); - - // filter collector and event handler for help emoji from hackers - const helpCollector = message.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === '🧑🏽‍🏫'); - - helpCollector.on('collect', async (reaction, user) => { - // remove the emoji - reaction.users.remove(user.id); - - // check that the user is not already on the wait list - if (this.waitlist.has(user.id)) { - sendMessageToMember(user, 'You are already on the TA wait list! A TA will get to you soon!', true); - return; - } else { - var position = this.waitlist.size; - // add user to wait list - this.waitlist.set(user.id, user.username); - } - - let oneLiner = await StringPrompt.single({prompt: 'Please send to this channel a one-liner of your problem or question. You have 20 seconds to respond', channel: this.assistanceChannel, userId: user.id }); - - const hackerEmbed = new MessageEmbed() - .setColor(this.initBotInfo.colors.embedColor) - .setTitle('Hey there! We got you signed up to talk to a TA!') - .setDescription('You are number: ' + position + ' in the wait list.') - .addField(!this.isLowTechSolution ? 'JOIN THE VOICE CHANNEL!' : 'KEEP AN EYE ON YOUR DMs', - !this.isLowTechSolution ? 'Sit tight in the voice channel. If you are not in the voice channel when its your turn you will be skipped, and we do not want that to happen!' : - 'A TA will reach out to you soon via DM! Have your question ready and try to keep up with the workshop until then!'); - - sendMessageToMember(user, hackerEmbed); - - // update message embed with new user in list - this.waitListEmbedMsg.edit(this.waitListEmbedMsg.embeds[0].addField(user.username, '<@' + user.id + '> has the question: ' + oneLiner)); - - // send a quick message to let ta know a new user is on the wait list - this.TAConsole.send('A new hacker needs help!').then(msg => msg.delete({timeout: 3000})); - }); - } -} - -module.exports = Workshop; \ No newline at end of file diff --git a/classes/Bot/bot-guild.d.ts b/classes/Bot/bot-guild.d.ts deleted file mode 100644 index 24d442a2..00000000 --- a/classes/Bot/bot-guild.d.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Document } from 'mongoose' -import Cave from './activities/cave' - -/** - * @interface BotGuild - */ -interface BotGuild extends Document { - /** - * The basic roles for any botGuild. Given as snowflakes (ids). - */ - roleIDs: { - memberRole: String, - staffRole: String, - adminRole: String, - everyoneRole: String, - }, - - channelIDs: { - adminConsole: String, - adminLog: String, - botSpamChannel: String, - }, - - mentorTickets: { - // ticketNumber: Number, - incomingTicketsChannel: String, - mentorRoleSelectionChannel: String, - requestTicketChannel: String - }, - - verification: { - /** @required */ - isEnabled: Boolean, - guestRoleID: String, - welcomeSupportChannelID: String, - /** */ - verificationRoles: Map - }, - - stamps: { - /** @required */ - isEnabled: Boolean, - /** */ - stampRoleIDs: Map, - /** The first stamp role Id given to all users */ - stamp0thRoleId: Number, - stampCollectionTime: Number, - }, - - /** @hexColor */ - embedColor: String, - /** - * The botGuild id must equal the guild id - * @required - */ - _id: String, - - /** - * True if the bot has been set up and its ready to hack! - */ - isSetUpComplete: Boolean, - - /** - * Will set the minimum required information for the bot to work on this guild. - * @param {BotGuildInfo} botGuildInfo - * @param {CommandoClient} client - * @returns {Promise} - * @async - */ - async readyUp(client, botGuildInfo); - - setUpVerification(guild, guestRoleId, types, welcomeSupportChannel); - - /** - * Staff role permissions. - * @static - */ - static staffPermissions: String[]; - - /** - * Admin role permissions. - * @static - */ - static adminPermissions: String[]; - - /** - * The regular member perms. - * @static - */ - static memberPermissions: String[]; -} - -let botGuild: BotGuild; - -export = botGuild; \ No newline at end of file diff --git a/classes/Bot/bot-guild.js b/classes/Bot/bot-guild.js deleted file mode 100644 index 7784d61e..00000000 --- a/classes/Bot/bot-guild.js +++ /dev/null @@ -1,260 +0,0 @@ -const { Collection, TextChannel, Role, MessageEmbed, CategoryChannel, Guild } = require('discord.js'); -// const { CommandoClient, CommandoGuild } = require('discord.js-commando'); -const {SapphireClient} = require('@sapphire/framework') -const winston = require('winston'); -const discordServices = require('../../discord-services'); - -/** - * @class - */ -class BotGuild { - - - /** - * Staff role permissions. - * @type {String[]} - */ - static staffPermissions = ['VIEW_CHANNEL', 'CHANGE_NICKNAME', 'MANAGE_NICKNAMES', - 'KICK_MEMBERS', 'BAN_MEMBERS', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES', 'ADD_REACTIONS', 'USE_EXTERNAL_EMOJIS', 'MANAGE_MESSAGES', - 'READ_MESSAGE_HISTORY', 'CONNECT', 'STREAM', 'SPEAK', 'PRIORITY_SPEAKER', 'USE_VAD', 'MUTE_MEMBERS', 'DEAFEN_MEMBERS', 'MOVE_MEMBERS']; - - /** - * Admin role permissions. - * @type {String[]} - */ - static adminPermissions = ['ADMINISTRATOR']; - - /** - * The regular member perms. - * @type {String[]} - */ - static memberPermissions = ['VIEW_CHANNEL', 'CHANGE_NICKNAME', 'SEND_MESSAGES', 'ADD_REACTIONS', 'READ_MESSAGE_HISTORY', - 'CONNECT', 'SPEAK', 'STREAM', 'USE_VAD']; - - /** - * @typedef RoleIDs - * @property {String} memberRole - regular guild member role ID - * @property {String} staffRole - the staff role ID - * @property {String} adminRole - the admin role ID - * @property {String} everyoneRole - the everyone role ID - */ - - /** - * @typedef ChannelIDs - * @property {String} adminConsole - the admin console channel ID - * @property {String} adminLog - the admin log channel ID - * @property {String} botSupportChannel - the bot support channel ID - */ - - /** - * @typedef VerificationInfo - * @property {Boolean} isEnabled - true if verification is enabled - * @property {String} isVerifiedRoleID - the verified role ID that holds basic permissions - * @property {String[]} isVerifiedRolePermissions - the permissions for the isVerified role - * @property {String} guestRoleID - the guest role ID used for verification - * @property {String} welcomeChannelID - the welcome channel where users learn to verify - * @property {String} welcomeSupportChannelID - the support channel where the bot can contact users - */ - - /** - * @typedef AttendanceInfo - * @property {Boolean} isEnabled - true if attendance is enabled in this guild - * @property {String} attendeeRoleID - the attendee role ID used for attendance - */ - - /** - * @typedef StampInfo - * @property {Boolean} isEnabled - true if stamps are enabled - * @property {Collection} stampRoleIDs - - * @property {Number} stampCollectionTime - time given to users to collect password stamps - */ - - /** - * @typedef ReportInfo - * @property {Boolean} isEnabled - true if the report functionality is enabled - * @property {String} incomingReportChannelID - channel where reports are sent - */ - - /** - * @typedef AnnouncementInfo - * @property {Boolean} isEnabled - * @property {String} announcementChannelID - */ - - /** - * @typedef BotGuildInfo - * @property {RoleIDs} roleIDs - * @property {ChannelIDs} channelIDs - */ - - /** - * Will set the minimum required information for the bot to work on this guild. - * @param {BotGuildInfo} botGuildInfo - * @param {SapphireClient} client - * @returns {Promise} - * @async - */ - async readyUp(guild, botGuildInfo) { - this.roleIDs = botGuildInfo.roleIDs; - this.channelIDs = botGuildInfo.channelIDs; - this.embedColor = botGuildInfo.embedColor; - - let adminRole = await guild.roles.resolve(this.roleIDs.adminRole); - // try giving the admins administrator perms - try { - if (!adminRole.permissions.has('ADMINISTRATOR')) - { - adminRole.setPermissions(adminRole.permissions.add(['ADMINISTRATOR'])); - await adminRole.setMentionable(true); - } - } catch { - discordServices.discordLog(guild, 'Was not able to give administrator privileges to the role <@&' + adminRole.id + '>. Please help me!') - } - - // staff role set up - let staffRole = await guild.roles.resolve(this.roleIDs.staffRole); - staffRole.setMentionable(true); - staffRole.setHoist(true); - staffRole.setPermissions(staffRole.permissions.add(BotGuild.staffPermissions)); - - // regular member role setup - let memberRole = await guild.roles.resolve(this.roleIDs.memberRole); - memberRole.setMentionable(true); - memberRole.setPermissions(memberRole.permissions.add(BotGuild.memberPermissions)); - - // mentor role setup - let mentorRole = await guild.roles.resolve(this.roleIDs.mentorRole); - mentorRole.setMentionable(true); - mentorRole.setPermissions(mentorRole.permissions.add(BotGuild.memberPermissions)); - - // change the everyone role permissions, we do this so that we can lock rooms. For users to see the server when - // verification is off, they need to get the member role when greeted by the bot! - // guild.roles.everyone.setPermissions(0); // no permissions for anything like the guest role - - this.isSetUpComplete = true; - - winston.loggers.get(this.id).event(`The botGuild has run the ready up function.`, {event: "Bot Guild"}); - - return this; - } - - /** - * @typedef VerificationChannels - * @property {String} welcomeChannelID - * @property {String} welcomeChannelSupportID - */ - - /** - * @typedef TypeInfo - * @property {String} type - * @property {String} roleId - */ - - /** - * Will set up the verification process. - * @param {SapphireClient} client - * @param {String} guestRoleId - * @param {TypeInfo[]} types - * @param {VerificationChannels} [verificationChannels] - * @return {Promise} - * @async - */ - async setUpVerification(guild, guestRoleId, types, welcomeSupportChannel) { - // guestRole.setMentionable(false); - // guestRole.setPermissions(0); - this.verification.guestRoleID = guestRoleId; - - this.verification.welcomeSupportChannelID = welcomeSupportChannel; - // add the types to the type map. - types.forEach(async (type, index, list) => { - try { - await guild.roles.resolve(type.roleId); - this.verification.verificationRoles.set(type.name.toLowerCase(), type.roleId); - } catch (error) { - throw new Error('The given verification role ID is not valid for this guild!'); - } - }); - - this.verification.isEnabled = true; - // guild.setCommandEnabled('verify', true); - return this; - } - - /** - * Creates the stamps roles and adds them to this BotGuild. If stamps roles are given - * then no roles are created! - * @param {SapphireClient} client - * @param {Number} [stampAmount] - amount of stamps to create - * @param {Number} [stampCollectionTime] - time given to users to send password to get stamp - * @param {String[]} [stampRoleIDs] - current stamp roles to use - * @returns {Promise} - * @async - */ - // async setUpStamps(client, stampAmount = 0, stampCollectionTime = 60, stampRoleIDs = []) { - // let guild = await client.guilds.resolve(this.id); - - // if (stampRoleIDs.length > 0) { - // stampRoleIDs.forEach((ID, index, array) => { - // this.addStamp(ID, index); - // }); - // winston.loggers.get(this.id).event(`The botGuild has set up the stamp functionality. The stamp roles were given. Stamp collection time is set at ${stampCollectionTime}.` [{stampIds: stampRoleIDs}]); - // } else { - // for (let i = 0; i < stampAmount; i++) { - // let role = await guild.roles.create({ - // data: { - // name: 'Stamp Role #' + i, - // hoist: true, - // color: discordServices.randomColor(), - // } - // }); - - // this.addStamp(role.id, i); - // } - // winston.loggers.get(this.id).event(`The botGuild has set up the stamp functionality. Stamps were created by me, I created ${stampAmount} stamps. Stamp collection time is set at ${stampCollectionTime}.`, {event: "Bot Guild"}); - // } - - // this.stamps.stampCollectionTime = stampCollectionTime; - // this.stamps.isEnabled = true; - - // // this.setCommandStatus(client); - - // return this; - // } - - /** - * Adds a stamp to the stamp collection. Does not save the mongoose document! - * @param {String} roleId - * @param {Number} stampNumber - */ - addStamp(roleId, stampNumber) { - if (stampNumber === 0) this.stamps.stamp0thRoleId = roleId; - this.stamps.stampRoleIDs.set(roleId, stampNumber); - winston.loggers.get(this.id).event(`The botGuild has added a stamp with number ${stampNumber} linked to role id ${roleId}`, {event: "Bot Guild"}); - } - - /** - * Will enable and disable the appropriate commands by looking at what is enabled in the botGuild. - * @param {SapphireClient} client - * @async - */ - async setCommandStatus(client) { - /** @type {SapphireClient.Guild} */ - let guild = await client.guilds.resolve(this.id); - - // guild.setGroupEnabled('verification', this.verification.isEnabled); - // guild.setGroupEnabled('attendance', this.attendance.isEnabled); - - // guild.setGroupEnabled('stamps', this.stamps.isEnabled); - - // guild.setCommandEnabled('report', this.report.isEnabled); - // guild.setCommandEnabled('ask', this.ask.isEnabled); - // guild.setGroupEnabled('hacker_utility', this.ask.isEnabled || this.report.isEnabled); - - // client.registry.groups.forEach((group, key, map) => { - // if (group.id.startsWith('a_')) guild.setGroupEnabled(group, this.isSetUpComplete); - // }); - - winston.loggers.get(guild.id).verbose(`Set the command status of guild ${guild.name} with id ${guild.id}`); - } -} -module.exports = BotGuild; \ No newline at end of file diff --git a/classes/UI/Console/console.js b/classes/UI/Console/console.js deleted file mode 100644 index b34c03df..00000000 --- a/classes/UI/Console/console.js +++ /dev/null @@ -1,284 +0,0 @@ -const { Collection, Message, TextChannel, MessageEmbed, DMChannel, MessageReaction, User, ReactionCollectorOptions, ReactionCollector, Guild, GuildEmoji, ReactionEmoji } = require('discord.js'); -const { randomColor } = require('../../../discord-services'); -const getEmoji = require('get-random-emoji'); -const Feature = require('./feature'); - -/** - * @typedef ConsoleInfo - * @property {String} title - the console title - * @property {String} description - the description of the console - * @property {TextChannel | DMChannel} channel - the channel this console lives in - * @property {Collection} [features] - the collection of features mapped by emoji name - * @property {Collection} [fields] - a collection of fields - * @property {String} [color] - console color in hex - * @property {ReactionCollectorOptions} [options={}] collector options - */ - -/** - * The console class represents a Discord UI console. A console is an embed with options users - * can interact with by reacting with emojis. - * @class - */ -class Console { - - /** - * @constructor - * @param {ConsoleInfo} args - */ - constructor({title, description, channel, features = new Collection(), fields = new Collection(), color = randomColor(), options = {}}) { - - /** - * @type {String} - */ - this.title = title; - - /** - * @type {String} - */ - this.description = description; - - /** - * The fields this console has, not including feature fields. - * - * @type {Collection} - */ - this.fields = new Collection(); - - /** - * @type {String} - hex color - */ - this.color = color; - - /** - * @type {Collection} - - */ - this.features = new Collection(); - - /** - * @type {ReactionCollector} - */ - this.collector; - - /** - * The collector options. - * @type {ReactionCollectorOptions} - */ - this.collectorOptions = options; - this.collectorOptions.dispose = true; - - /** - * Users the console is interacting with; - * @type {Collection} - - */ - this.interacting = new Collection(); - - /** - * The message holding the console. - * @type {Message} - */ - this.message; - - /** - * The channel this console lives in. - * @type {TextChannel | DMChannel} - */ - this.channel = channel; - - features.forEach(feature => this.addFeature(feature)); - fields.forEach((name, description) => this.addField(name, description)); - } - - /** - * Sends the console to a channel - * @param {String} [messageText] - text to add to the message used to send the embed - * @async - */ - async sendConsole(messageText = '') { - let embed = new MessageEmbed().setColor(this.color) - .setTimestamp() - .setTitle(this.title) - .setDescription(this.description); - - this.features.forEach(feature => embed.addField(feature.getFieldName(this.channel?.guild?.emojis), feature.getFieldValue())); - this.fields.forEach((description, name) => embed.addField(name, description)); - - this.message = await this.channel.send(messageText ,embed); - - this.features.forEach(feature => { - this.message.react(feature.emojiName).catch((reason) => { - // the emoji is probably custom we need to find it! - let emoji = this.message.guild.emojis.cache.find(guildEmoji => guildEmoji.name === feature.emojiName); - this.message.react(emoji); - }); - }); - - this.createReactionCollector(this.message); - } - - /** - * Creates the reaction collector in the message. - * @param {Message} message - */ - createReactionCollector(message) { - // make sure we don't have two collectors going! - if (this.collector) this.stopConsole(); - - this.collector = message.createReactionCollector((reaction, user) => !user.bot && - this.features.has(reaction.emoji.id || reaction.emoji.name) && - !this.interacting.has(user.id), - this.collectorOptions); - - this.collector.on('collect', (reaction, user) => { - this.interacting.set(user.id, user); - let feature = this.features.get(reaction.emoji.id || reaction.emoji.name); - feature?.callback(user, reaction, () => this.stopInteracting(user), this); - if (this.channel.type != 'dm' && !feature?.removeCallback) - reaction.users.remove(user); - }); - - this.collector.on('remove', (reaction, user) => { - let feature = this.features.get(reaction.emoji.id || reaction.emoji.name); - if (feature && feature?.removeCallback) { - this.interacting.set(user.id, user); - feature?.removeCallback(user, reaction, () => this.stopInteracting(user), this); - } - }); - } - - /** - * Adds a feature to this console. - * @param {Feature} feature - the feature to add - * @async - */ - async addFeature(feature) { - if (!(feature instanceof Feature)) { - throw Error(`The given feature is not a Feature object! Given object: ${feature}`); - } - - // if the channel is a DM channel, we can't use custom emojis, so if the emoji is a custom emoji, its an ID, - // we will grab a random emoji and use that instead - if (this.channel.type === 'dm' && !isNaN(parseInt(feature.emojiName))) { - feature.emojiName = getEmoji(); - } - - this.features.set(feature.emojiName, feature); - - if (this.message) { - await this.message.edit(this.message.embeds[0].addField(feature.getFieldName(this.channel?.guild?.emojis), feature.getFieldValue())); - this.message.react(feature.emojiName).catch(reason => { - // the emoji is probably custom we need to find it! - let emoji = this.message.guild.emojis.cache.find(guildEmoji => guildEmoji.name === feature.emojiName); - this.message.react(emoji); - }); - } - } - - /** - * Removes a feature from this console. - * @param {String | Feature} identifier - feature name, feature emojiName or feature - */ - removeFeature(identifier) { - if (typeof identifier === 'string') { - let isDone = this.features.delete(identifier); - if (!isDone) { - let feature = this.features.find(feature => feature.name === identifier); - this.features.delete(feature.emojiName); - } - } else if (typeof identifier === 'object') { - this.features.delete(identifier?.emojiName); - } else { - throw Error(`Was not given an identifier to work with when deleting a feature from this console ${this.title}`); - } - } - - /** - * Adds a field to this console without adding a feature. - * @param {String} name - the new field name - * @param {String} value - the description on this field - * @param {Boolean} [inline] - * @async - */ - async addField(name, value, inline) { - this.fields.set(name, value); - if(this.message) await this.message.edit(this.message.embeds[0].addField(name, value, inline)); - } - - /** - * Changes the console's color. - * @param {String} color - the new color in hex - * @async - */ - async changeColor(color) { - await this.message.edit(this.message.embeds[0].setColor(color)); - } - - /** - * Stop the console from interacting with any users. - */ - stopConsole() { - this.collector?.stop(); - } - - /** - * Deletes this console from discord. - */ - delete() { - this.stopConsole(); - this.message?.delete(); - } - - /** - * Callback for users to call when the user interacting with the console is done. - * @param {User} user - the user that stopped interacting with this console. - * @private - */ - stopInteracting(user) { - this.interacting.delete(user.id); - } - - /** - * Creates a JSON representation of this object - * @returns {JSON} representation of this object as JSON - */ - toJSON() { - return { - 'title': this.title, - 'description': this.description, - 'features': [...this.features], - 'fields': [...this.fields], - 'color': this.color, - 'collectorOptions': JSON.stringify(this.collectorOptions), - 'messageId': this.message?.id, - 'interacting': [...this.interacting], - 'channelId': this.channel.id, - }; - } - - /** - * Creates a Console from JSON data. - * @param {JSON} json the json data - * @param {Guild} guild the guild where this console lives - * @returns {Console} - */ - static async fromJSON(json, guild) { - let console = new Console( - { - title: json['title'], - description: json['description'], - channel: guild.channels.resolve(json['channelId']), - features: new Collection(json['features'].forEach(feature => Feature.create(feature))), //TODO: test - color: json['color'], - options: JSON.parse(json['collectorOptions']), - fields: new Collection(json['fields']), - } - ); - - if (json['messageId'] != undefined) { - let message = await console.channel.messages.fetch(json['messageId']); - console.message = message; - console.createReactionCollector(console.message); - } - } - -} -module.exports = Console; \ No newline at end of file diff --git a/classes/UI/Console/feature.js b/classes/UI/Console/feature.js deleted file mode 100644 index 0f152c55..00000000 --- a/classes/UI/Console/feature.js +++ /dev/null @@ -1,114 +0,0 @@ -const { GuildEmojiManager, User, MessageReaction } = require('discord.js'); -const Console = require('./console'); - - -/** - * The function to be called when a feature is activated. - * @callback FeatureCallback - * @param {User} user - the user that reacted - * @param {MessageReaction} reaction - the reaction - * @param {StopInteractingCallback} stopInteracting - callback to let the console know the user has - * stopped interacting. - * @param {Console} console - the console this feature is working on - * @async - */ - -/** - * The function used to signal the console the user interacting has finished. - * @callback StopInteractingCallback - * @param {User} user - */ - -/** - * A feature is an object with information to make an action from a console. - * The emojiName can be either a custom emoji ID or a unicode emoji name. - * @class Feature - */ -class Feature { - - - /** - * Creates a feature object when you have a GuildEmoji or a ReactionEmoji. - * Used for when adding features programmatically! - * @param {Object} args - * @param {String} args.name - * @param {String} args.description - * @param {GuildEmoji | ReactionEmoji | String} args.emoji - * @param {FeatureCallback} args.callback - * @param {FeatureCallback} [args.removeCallback] - * @returns {Feature} - */ - static create({name, description, emoji, callback, removeCallback}) { - return new Feature({ - name, - emojiName: (typeof emoji === 'string') ? emoji : emoji.id || emoji.name, - description, - callback, - removeCallback, - }); - } - - /** - * - * @param {Object} args arguments - * @param {String} args.name the name of the feature - * @param {String} args.emojiName the name of the emoji - * @param {String} args.description the description of the feature - * @param {FeatureCallback} args.callback the callback for when the feature is activated - * @param {FeatureCallback} [args.removeCallback=undefined] the callback for when the feature is deactivated - */ - constructor({name, emojiName, description, callback, removeCallback = undefined}) { - - /** - * @type {String} - */ - this.name = name; - - /** - * @type {String} - */ - this.emojiName = emojiName; - - /** - * @type {String} - */ - this.description = description; - - /** - * @type {FeatureCallback} - */ - this.callback = callback; - - /** - * @type {FeatureCallback} - */ - this.removeCallback = removeCallback; - - } - - /** - * Returns a string with the emoji and the feature name: - * - Feature 1 - * @param {GuildEmojiManager} [guildEmojiManager] not needed if console is DM - * @returns {String} - */ - getFieldName(guildEmojiManager) { - let emoji; - - if (guildEmojiManager != undefined) { - emoji = guildEmojiManager.resolve(this.emojiName); - } - - return `${emoji ? emoji.toString() : this.emojiName} - ${this.name}`; - } - - /** - * Returns the feature's value string for when adding it to a embed field. - * @returns {String} - */ - getFieldValue() { - return this.description; - } - -} -module.exports = Feature; \ No newline at end of file diff --git a/classes/UI/Room/room.js b/classes/UI/Room/room.js deleted file mode 100644 index fc56e670..00000000 --- a/classes/UI/Room/room.js +++ /dev/null @@ -1,322 +0,0 @@ -const winston = require('winston'); -const { GuildCreateChannelOptions, User, Guild, Collection, Role, CategoryChannel, VoiceChannel, TextChannel, OverwriteResolvable, PermissionOverwriteOption } = require('discord.js'); -const { deleteChannel } = require('../../../discord-services'); - -/** - * @typedef RoomChannels - * @property {CategoryChannel} category - * @property {TextChannel} generalText - * @property {VoiceChannel} generalVoice - * @property {TextChannel} nonLockedChannel - * @property {Collection} voiceChannels - - * @property {Collection} textChannels - - * @property {Collection} safeChannels - , channels that can not be removed - */ - -/** - * An object with a role and its permissions - * @typedef RolePermission - * @property {String} id - the role snowflake - * @property {PermissionOverwriteOption} permissions - the permissions to set to that role - */ - -/** - * The room class represents a room where things can occur, a room - * consists of a category with voice and text channels. As well as roles - * or users allowed to see the room. - */ -class Room { - - /** - * @param {Guild} guild - the guild in which the room lives - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {String} name - name of the room - * @param {Collection} [rolesAllowed=Collection()] - the participants able to view this room - * @param {Collection} [usersAllowed=Collection()] - the individual users allowed to see the room - */ - constructor(guild, initBotInfo, name, rolesAllowed = new Collection(), usersAllowed = new Collection()) { - - /** - * The name of this room. Will remove all leading and trailing whitespace and - * switch spaces for '-'. Will also replace all character except for numbers, letters and '-' - * and make it lowercase. - * @type {string} - */ - this.name = name.split(' ').join('-').trim().replace(/[^0-9a-zA-Z-]/g, '').toLowerCase(); - - /** - * The guild this activity is in. - * @type {Guild} - */ - this.guild = guild; - - /** - * Roles allowed to view the room. - * @type {Collection} - */ - this.rolesAllowed = rolesAllowed; - - /** - * Users allowed to view the room. - * @type {Collection} - */ - this.usersAllowed = usersAllowed; - - /** - * All the channels this room has! - * @type {RoomChannels} - */ - this.channels = { - category: null, - generalVoice: null, - generalText: null, - nonLockedChannel: null, - voiceChannels: new Collection(), - textChannels: new Collection(), - safeChannels: new Collection(), - }; - - /** - * The Firestore bot info object - * @type {FirebaseFirestore.DocumentData | null | undefined} - */ - this.initBotInfo = initBotInfo; - - /** - * True if the room is locked, false otherwise. - * @type {Boolean} - */ - this.locked = false; - - /** - * The time this room was created. - * @type {Number} - */ - this.timeCreated = Date.now(); - - } - - /** - * Initialize this activity by creating the channels, adding the features and sending the admin console. - * @param {Object} [args] - channels already created to add to this room - * @param {CategoryChannel} args.category - * @param {TextChannel} args.textChannel - * @param {VoiceChannel} args.voiceChannel - * @async - * @returns {Promise} - */ - async init(args) { - if (args?.category) this.channels.category = args.category; - else { - let position = this.guild.channels.cache.filter(channel => channel.type === 'category').size; - this.channels.category = await this.createCategory(position); - } - - if (args?.textChannel) { - this.channels.generalText = args.textChannel; - this.addExcisingChannel(args.textChannel); - } - else this.channels.generalText = await this.addRoomChannel({ - name: this.name.length < 12 ? `${this.name}-banter` : Room.mainTextChannelName, - info: { - parent: this.channels.category, - type: 'text', - topic: 'A general banter channel to be used to communicate with other members, mentors, or staff. The !ask command is available for questions.', - } - }); - - if (args?.voiceChannel) { - this.channels.generalVoice = args.voiceChannel; - this.addExcisingChannel(args.voiceChannel); - } - else this.channels.generalVoice = await this.addRoomChannel({ - name: this.name.length < 12 ? `${this.name}-room` : Room.mainVoiceChannelName, - info: { - parent: this.channels.category, - type: 'voice', - } - }); - - winston.loggers.get(this.guild.id).event(`The room ${this.name} was initialized.`, {event: 'Room'}); - return this; - } - /** - * Helper function to create the category - * @param {Number} position - the position of this category on the server - * @returns {Promise} - a category with the activity name - * @async - * @private - */ - async createCategory(position) { - /** @type {OverwriteResolvable[]} */ - let overwrites = [ - { - id: this.initBotInfo.roleIDs.everyoneRole, - deny: ['VIEW_CHANNEL'], - }]; - this.rolesAllowed.each(role => overwrites.push({ id: role.id, allow: ['VIEW_CHANNEL'] })); - this.usersAllowed.each(user => overwrites.push({ id: user.id, allow: ['VIEW_CHANNEL'] })); - return this.guild.channels.create(this.name, { - type: 'category', - position: position >= 0 ? position : 0, - permissionOverwrites: overwrites - }); - } - - /** - * Adds a channels to the room. - * @param {Object} args - * @param {String} args.name - name of the channel to create - * @param {GuildCreateChannelOptions} [args.info={}] - one of voice or text - * @param {RolePermission[]} [args.permissions=[]] - the permissions per role to be added to this channel after creation. - * @param {Boolean} [args.isSafe=false] - true if the channel is safe and cant be removed - * @async - */ - async addRoomChannel({name, info = {}, permissions = [], isSafe = false}) { - info.parent = info?.parent || this.channels.category; - info.type = info?.type || 'text'; - - let channel = await this.guild.channels.create(name, info); - - permissions.forEach(rolePermission => channel.updateOverwrite(rolePermission.id, rolePermission.permissions)); - - // add channel to correct list - if (info.type == 'text') this.channels.textChannels.set(channel.id, channel); - else this.channels.voiceChannels.set(channel.id, channel); - - if (isSafe) this.channels.safeChannels.set(channel.id, channel); - - winston.loggers.get(this.guild.id).event(`The activity ${this.name} had a channel named ${name} added to it of type ${info?.type || 'text'}.`, {event: 'Activity'}); - - return channel; - } - - /** - * Removes a channel from the room. - * @param {VoiceChannel | TextChannel} channelToRemove - * @param {Boolean} [isForced=false] - is the deletion forced?, if so then channel will be removed even if its safeChannels - * @async - */ - async removeRoomChannel(channelToRemove, isForced = false) { - if (isForced && this.channels.safeChannels.has(channelToRemove.id)) throw Error('Can\'t remove that channel.'); - - if (channelToRemove.type === 'text') this.channels.textChannels.delete(channelToRemove.id); - else this.channels.voiceChannels.delete(channelToRemove.id); - - this.channels.safeChannels.delete(channelToRemove.id); - - deleteChannel(channelToRemove); - winston.loggers.get(this.guild.id).event(`The room ${this.name} lost a channel named ${channelToRemove.name}`, { event: 'Room' }); - } - - /** - * Deletes the room. - * @async - */ - async delete() { - // only delete channels if they were created! - if (this.channels?.category) { - var listOfChannels = this.channels.category.children.array(); - for (var i = 0; i < listOfChannels.length; i++) { - await deleteChannel(listOfChannels[i]); - } - - await deleteChannel(this.channels.category); - } - winston.loggers.get(this.guild.id).event(`The room ${this.name} was deleted!`, {event: 'Room'}); - } - - /** - * Archive the activity. Move general text channel to archive category, remove all remaining channels - * and remove the category. - * @param {CategoryChannel} archiveCategory - the category where the general text channel will be moved to - * @async - */ - async archive(archiveCategory) { - // move all text channels to the archive and rename with activity name - // remove all voice channels in the category one at a time to not get a UI glitch - - this.channels.category.children.forEach(async (channel, key) => { - this.initBotInfo.blackList.delete(channel.id); - if (channel.type === 'text') { - let channelName = channel.name; - await channel.setName(`${this.name}-${channelName}`); - await channel.setParent(archiveCategory); - } else deleteChannel(channel); - }); - - await deleteChannel(this.channels.category); - - this.initBotInfo.save(); - - winston.loggers.get(this.guild.id).event(`The activity ${this.name} was archived!`, {event: 'Activity'}); - } - - /** - * Locks the room for all roles except for a text channel. To gain access users must be allowed access - * individually. - * @returns {Promise} - channel available to roles - */ - async lockRoom() { - // set category private - this.rolesAllowed.forEach((role, key) => this.channels.category.updateOverwrite(role, { VIEW_CHANNEL: false })); - - /** @type {TextChannel} */ - this.channels.nonLockedChannel = await this.addRoomChannel({ - name: 'Activity Rules START HERE', - info: { type: 'text' }, - permissions: this.rolesAllowed.map((role, key) => ({ id: role.id, permissions: { VIEW_CHANNEL: true, SEND_MESSAGES: false, }})), - isSafe: true}); - this.channels.safeChannels.set(this.channels.nonLockedChannel.id, this.channels.nonLockedChannel); - - this.locked = true; - - return this.channels.nonLockedChannel; - } - - /** - * Gives access to the room to a role. - * @param {Role} role - role to give access to - */ - giveRoleAccess(role) { - this.rolesAllowed.set(role.id, role); - - if (this.locked) { - this.channels.nonLockedChannel.updateOverwrite(role.id, { VIEW_CHANNEL: true, SEND_MESSAGES: false }); - } else { - this.channels.category.updateOverwrite(role.id, { VIEW_CHANNEL: true }); - } - } - - /** - * Gives access to a user - * @param {User} user - user to give access to - */ - giveUserAccess(user) { - this.usersAllowed.set(user.id, user); - this.channels.category.updateOverwrite(user.id, { VIEW_CHANNEL: true, SEND_MESSAGES: true }); - } - - /** - * Removes access to a user to see this room. - * @param {User} user - the user to remove access to - */ - removeUserAccess(user) { - this.usersAllowed.delete(user.id); - this.channels.category.updateOverwrite(user.id, { VIEW_CHANNEL: false }); - } - - /** - * @param {TextChannel | VoiceChannel} channel - */ - addExcisingChannel(channel) { - if (channel.type === 'text') this.channels.textChannels.set(channel.id, channel); - else if (channel.type === 'voice') this.channels.voiceChannels.set(channel.id, channel); - } -} - -Room.voiceChannelName = '🔊Room-'; -Room.mainTextChannelName = '🖌️activity-banter'; -Room.mainVoiceChannelName = '🗣️activity-room'; - -module.exports = Room; \ No newline at end of file diff --git a/classes/permission-command.js b/classes/permission-command.js deleted file mode 100644 index f3269389..00000000 --- a/classes/permission-command.js +++ /dev/null @@ -1,210 +0,0 @@ -const Discord = require('discord.js'); -const { Command } = require('@sapphire/framework'); -// const { Command, CommandoMessage, CommandoClientOptions, CommandInfo } = require('discord.js-commando'); -const firebaseUtil = require('../db/firebase/firebaseUtil'); -const discordServices = require('../discord-services'); -const winston = require('winston'); - -/** - * The PermissionCommand is a custom command that extends the Sapphire Command class. - * This Command subclass adds role and channel permission checks before the command is run. - * @extends Command - */ -class PermissionCommand extends Command { - - /** - * Our custom command information for validation - * @typedef {Object} CommandPermissionInfo - * @property {string} role - the role this command can be run by, one of FLAGS - * @property {string} channel - the channel where this command can be run, one of FLAGS - * @property {string} roleMessage - the message to be sent for an incorrect role - * @property {string} channelMessage - the message to be sent for an incorrect channel - * @property {Boolean} dmOnly - true if this command can only be used on a DM - */ - - /** - * Constructor for our custom command, calls the parent constructor. - * @param {Command.Context} context - the context of the command - * @param {Command.Options} options - additional command options - * @param {CommandPermissionInfo} permissionInfo - the custom information for this command - */ - constructor(context, options, permissionInfo) { - super(context, { - ...options - }); - - /** - * The permission info - * @type {CommandPermissionInfo} - * @private - */ - this.permissionInfo = this.validateInfo(permissionInfo); - } - - /** - * - * @param {Command.Registry} registry - */ - registerApplicationCommands(registry) { - throw new Error('You need to implement the registerApplicationCommands method!'); - } - - /** - * Adds default values if not found on the object. - * @param {CommandPermissionInfo} permissionInfo - * @returns {CommandPermissionInfo} - * @private - */ - validateInfo(permissionInfo) { - // Make sure permissionInfo is an object, if not given then create empty object - if (typeof permissionInfo != 'object') permissionInfo = {}; - if (!permissionInfo?.channelMessage) permissionInfo.channelMessage = 'Hi, the command you just used is not available on that channel!'; - if (!permissionInfo?.roleMessage) permissionInfo.roleMessage = 'Hi, the command you just used is not available to your current role!'; - permissionInfo.dmOnly = permissionInfo?.dmOnly ?? false; - return permissionInfo; - } - - /** - * - * @param {Command.ChatInputInteraction} interaction - */ - async chatInputRun(interaction) { - /** @type {FirebaseFirestore.DocumentData | null | undefined} */ - let initBotInfo; - if (interaction.guild) { - initBotInfo = await firebaseUtil.getInitBotInfo(interaction.guildId); - } - else initBotInfo = null; - - // check for DM only, when true, all other checks should not happen! - if (this.permissionInfo.dmOnly) { - if (interaction.channel.type != 'DM') { - winston.loggers. - get(initBotInfo?.id || 'main'). - warning(`User ${interaction.user.id} tried to run a permission command ${this.name} that is only available in DMs in the channel ${interaction.channel.name}.`); - return interaction.reply({ - content: 'The command you just tried to use is only usable via DM!', - ephemeral: true - }); - } - } else { - // Make sure it is only used in the permitted channel - if (this.permissionInfo?.channel) { - let channelID = initBotInfo.channelIDs[this.permissionInfo.channel]; - - if (channelID && interaction.channelId != channelID) { - winston.loggers.get(initBotInfo?.id || 'main').warning(`User ${interaction.user.id} tried to run a permission command ${this.name} that is only available in the channel ${this.permissionInfo.channel}, in the channel ${interaction.channel.name}.`); - return interaction.reply({ - content: this.permissionInfo.channelMessage, - ephemeral: true - }); - } - } - // Make sure only the permitted role can call it - else if (this.permissionInfo?.role) { - - let roleID = initBotInfo.roleIDs[this.permissionInfo.role]; - - // if staff role then check for staff and admin, else check the given role - if (roleID && (roleID === initBotInfo.roleIDs.staffRole && - (!discordServices.checkForRole(interaction.member, roleID) && !discordServices.checkForRole(interaction.member, initBotInfo.roleIDs.adminRole))) || - (roleID != initBotInfo.roleIDs.staffRole && !discordServices.checkForRole(interaction.member, roleID))) { - winston.loggers.get(initBotInfo?.id || 'main').warning(`User ${interaction.user.id} tried to run a permission command ${this.name} that is only available for members with role ${this.permissionInfo.role}, but he has roles: ${interaction.member.roles.cache.array().map((role) => role.name)}`); - return interaction.reply({ - content: this.permissionInfo.roleMessage, - ephemeral: true - }); - } - } - } - this.runCommand(initBotInfo, interaction, args, fromPattern, result); - } - - // /** - // * Run command used by Command class. Has the permission checks and runs the child runCommand method. - // * @param {Discord.Message} message - // * @param {Object|string|string[]} args - // * @param {boolean} fromPattern - // * @param {Promise>} result - // * @override - // * @private - // */ - // async run(message, args, fromPattern, result){ - - // // delete the message - // discordServices.deleteMessage(message); - - // /** @type {FirebaseFirestore.DocumentData | null | undefined} */ - // let initBotInfo; - // if (message?.guild) initBotInfo = await firebaseUtil.getInitBotInfo(message.guild.id); - // else initBotInfo = null; - - // // check for DM only, when true, all other checks should not happen! - // if (this.permissionInfo.dmOnly) { - // if (message.channel.type != 'dm') { - // discordServices.sendEmbedToMember(message.member, { - // title: 'Error', - // description: 'The command you just tried to use is only usable via DM!', - // }); - // winston.loggers.get(initBotInfo?.id || 'main').warning(`User ${message.author.id} tried to run a permission command ${this.name} that is only available in DMs in the channel ${message.channel.name}.`); - // return; - // } - // } else { - // // Make sure it is only used in the permitted channel - // if (this.permissionInfo?.channel) { - // let channelID = initBotInfo.channelIDs[this.permissionInfo.channel]; - - // if (channelID && message.channel.id != channelID) { - // discordServices.sendMessageToMember(message.member, this.permissionInfo.channelMessage, true); - // winston.loggers.get(initBotInfo?.id || 'main').warning(`User ${message.author.id} tried to run a permission command ${this.name} that is only available in the channel ${this.permissionInfo.channel}, in the channel ${message.channel.name}.`); - // return; - // } - // } - // // Make sure only the permitted role can call it - // else if (this.permissionInfo?.role) { - - // let roleID = initBotInfo.roleIDs[this.permissionInfo.role]; - - // // if staff role then check for staff and admin, else check the given role - // if (roleID && (roleID === initBotInfo.roleIDs.staffRole && - // (!discordServices.checkForRole(message.member, roleID) && !discordServices.checkForRole(message.member, initBotInfo.roleIDs.adminRole))) || - // (roleID != initBotInfo.roleIDs.staffRole && !discordServices.checkForRole(message.member, roleID))) { - // discordServices.sendMessageToMember(message.member, this.permissionInfo.roleMessage, true); - // winston.loggers.get(initBotInfo?.id || 'main').warning(`User ${message.author.id} tried to run a permission command ${this.name} that is only available for members with role ${this.permissionInfo.role}, but he has roles: ${message.member.roles.cache.array().map((role) => role.name)}`); - // return; - // } - // } - // } - // this.runCommand(initBotInfo, message, args, fromPattern, result); - // } - - - /** - * Required class by children, will throw error if not implemented! - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Command.ChatInputInteraction} interaction - * @param {Object} args - * @param {Boolean} fromPattern - * @param {Promise<*>} result - * @abstract - * @protected - */ - runCommand(initBotInfo, interaction, args, fromPattern, result) { - throw new Error('You need to implement the runCommand method!'); - } -} - -/** - * String permission flags used for command permissions. - * * ADMIN_ROLE : only admins can use this command - * * STAFF_ROLE : staff and admin can use this command - * * ADMIN_CONSOLE : can only be used in the admin console - * @enum {String} - */ -PermissionCommand.FLAGS = { - ADMIN_ROLE: 'adminRole', - STAFF_ROLE: 'staffRole', - ADMIN_CONSOLE: 'adminConsole', -}; - -module.exports = PermissionCommand; \ No newline at end of file diff --git a/commands/a_activity/discord-contests.js b/commands/a_activity/discord-contests.js deleted file mode 100644 index 0a2c259b..00000000 --- a/commands/a_activity/discord-contests.js +++ /dev/null @@ -1,340 +0,0 @@ -const { Command } = require('@sapphire/framework'); -const { discordLog, checkForRole } = require('../../discord-services'); -const { Message, MessageEmbed, Snowflake, MessageActionRow, MessageButton } = require('discord.js'); -const { getQuestion, lookupById, saveToLeaderboard, retrieveLeaderboard } = require('../../db/firebase/firebaseUtil'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); - -/** - * The DiscordContests class handles all functions related to Discord contests. It will ask questions in set intervals and pick winners - * based on keywords for those questions that have correct answers. For other questions it will tag staff and staff will be able to tell - * it the winner. It can also be paused and un-paused, and questions can be removed. - * - * Note: all answers are case-insensitive but any extra or missing characters will be considered incorrect. - * @category Commands - * @subcategory Activity - * @extends Command - * @guildonly - */ -class DiscordContests extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Start discord contests.' - }); - } - - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addIntegerOption(option => - option.setName('interval') - .setDescription('Time (minutes) between questions') - .setRequired(true)) - .addRoleOption(option => - option.setName('notify') - .setDescription('Role to notify when a question drops') - .setRequired(true)) - .addBooleanOption(option => - option.setName('start_question_now') - .setDescription('True to start first question now, false to start it after one interval') - .setRequired(false)) - ), - { - idHints: '1051737343729610812' - }; - } - - /** - * Stores a map which keeps the questions (strings) as keys and an array of possible answers (strings) as values. It iterates through - * each key in order and asks them in the Discord channel in which it was called at the given intervals. It also listens for emojis - * that tell it to pause, resume, or remove a specified question. - * @param {Command.ChatInputInteraction} interaction - */ - async chatInputRun(interaction) { - // helpful prompt vars - let channel = interaction.channel; - let userId = interaction.user.id; - let guild = interaction.guild; - const initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - // let botSpamChannel = guild.channels.resolve(this.botGuild.channelIDs.botSpamChannel); - let adminLog = await guild.channels.fetch(initBotInfo.channelIDs.adminLog); - let adminConsole = await guild.channels.fetch(initBotInfo.channelIDs.adminConsole); - - let interval; - - //ask user for time interval between questions - let timeInterval = interaction.options.getInteger('interval') * 60000; - let startNow = interaction.options.getBoolean('start_question_now'); - let roleId = interaction.options.getRole('notify'); - - if (!guild.members.cache.get(userId).roles.cache.has(initBotInfo.roleIDs.staffRole) && !guild.members.cache.get(userId).roles.cache.has(initBotInfo.roleIDs.adminRole)) { - interaction.reply({ content: 'You do not have permissions to run this command!', ephemeral: true }); - return; - } - - if (Object.values(initBotInfo.roleIDs).includes(roleId.id) || initBotInfo.verification.roles.some((r) => r.roleId === roleId.id)) { - interaction.reply({ content: 'This role cannot be used! Please pick a role that is specifically for Discord Contest notifications!', ephemeral: true }); - return; - } - // try { - // let num = await NumberPrompt.single({prompt: 'What is the time interval between questions in minutes (integer only)? ', channel, userId, cancelable: true}); - // timeInterval = 1000 * 60 * num; - - // // ask user whether to start asking questions now(true) or after 1 interval (false) - // var startNow = await SpecialPrompt.boolean({prompt: 'Type "yes" to start first question now, "no" to start one time interval from now. ', channel, userId, cancelable: true}); - - // // id of role to mention when new questions come out - // var roleId = (await RolePrompt.single({prompt: 'What role should I notify with a new Discord contest is available?', channel, userId})).id; - // } catch (error) { - // channel.send('<@' + userId + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000})); - // return; - // } - - //paused keeps track of whether it has been paused - var paused = false; - - /** - * array of winners' ids - * @type {Array} - */ - const winners = []; - - var string = 'Discord contests starting soon! Answer questions for a chance to win prizes!'; - - const row = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('play') - .setLabel('Play') - .setStyle('PRIMARY'), - ) - .addComponents( - new MessageButton() - .setCustomId('pause') - .setLabel('Pause') - .setStyle('PRIMARY'), - ) - .addComponents( - new MessageButton() - .setCustomId('refresh') - .setLabel('Refresh leaderboard') - .setStyle('PRIMARY'), - ); - - - // const playFilter = i => i.customId == 'play' && !i.user.bot && (guild.members.cache.get(userId).roles.cache.has(this.botGuild.roleIDs.staffRole) || guild.members.cache.get(userId).roles.cache.has(this.botGuild.roleIDs.adminRole)); - // const playCollector = adminConsole.createMessageComponentCollector(playFilter); - // playCollector.on('collect', async i => { - // console.log('play collector') - // if (paused) { - // sendQuestion(this.botGuild); - // interval = setInterval(sendQuestion, timeInterval, this.botGuild); - // paused = false; - // await guild.channels.resolve(this.botGuild.channelIDs.adminLog).send('Discord contest restarted by <@' + i.user.id + '>!'); - // await i.reply('<@&' + i.user.id + '> Discord contest has been un-paused!'); - // } - // }); - - const startEmbed = new MessageEmbed() - .setColor(initBotInfo.embedColor) - .setTitle(string) - .setDescription('Note: Short-answer questions are non-case sensitive but any extra or missing symbols will be considered incorrect.') - .addFields([{name: 'Click the 🍀 emoji below to be notified when a new question drops!', value: 'You can un-react to stop.'}]); - - const leaderboard = new MessageEmbed() - .setTitle('Leaderboard'); - - let pinnedMessage = await channel.send({ content: '<@&' + roleId + '>', embeds: [startEmbed, leaderboard] }); - pinnedMessage.pin(); - pinnedMessage.react('🍀'); - - const roleSelectionCollector = pinnedMessage.createReactionCollector({ filter: (reaction, user) => !user.bot, dispose: true}); - roleSelectionCollector.on('collect', (reaction, user) => { - if (reaction.emoji.name === '🍀') { - guild.members.cache.get(user.id).roles.add(roleId); - } - }); - roleSelectionCollector.on('remove', (reaction, user) => { - if (reaction.emoji.name === '🍀') { - guild.members.cache.get(user.id).roles.remove(roleId); - } - }); - - interaction.reply({ content: 'Discord contest has been started!', ephemeral: true }); - const controlPanel = await adminConsole.send({ content: 'Discord contests control panel. Status: Active', components: [row] }); - adminLog.send('Discord contests started by <@' + userId + '>'); - const filter = i => !i.user.bot && (guild.members.cache.get(i.user.id).roles.cache.has(initBotInfo.roleIDs.staffRole) || guild.members.cache.get(i.user.id).roles.cache.has(initBotInfo.roleIDs.adminRole)); - const collector = controlPanel.createMessageComponentCollector({filter}); - collector.on('collect', async i => { - if (i.customId == 'refresh') { - await i.reply({ content: 'Leaderboard refreshed!', ephemeral: true }); - await updateLeaderboard(null); - } else if (interval != null && !paused && i.customId == 'pause') { - clearInterval(interval); - paused = true; - await i.reply({ content: 'Discord contests has been paused!', ephemeral: true }); - await controlPanel.edit({ content: 'Discord contests control panel. Status: Paused'}); - } else if (paused && i.customId == 'play') { - await sendQuestion(initBotInfo); - interval = setInterval(sendQuestion, timeInterval, initBotInfo); - paused = false; - await i.reply({ content: 'Discord contests has been un-paused!', ephemeral: true }); - await controlPanel.edit({ content: 'Discord contests control panel. Status: Active'}); - } else { - await i.reply({ content: 'Wrong button or wrong permissions!', ephemeral: true }); - } - }); - - //starts the interval, and sends the first question immediately if startNow is true - if (startNow) { - await sendQuestion(initBotInfo); - } - interval = setInterval(sendQuestion, timeInterval, initBotInfo); - - async function updateLeaderboard(memberId) { - if (memberId) { - await saveToLeaderboard(guild.id, memberId); - } - const winnersList = await retrieveLeaderboard(guild.id); - var leaderboardString = ''; - winnersList.forEach(winner => { - leaderboardString += '<@' + winner.memberId + '>: '; - if (winner.points > 1) { - leaderboardString += winner.points + ' points\n'; - } else if (winner.points == 1) { - leaderboardString += '1 point\n'; - } - }); - const newLeaderboard = new MessageEmbed(leaderboard).setDescription(leaderboardString); - pinnedMessage.edit({ embeds: [startEmbed, newLeaderboard] }); - } - - /** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * sendQuestion is the function that picks and sends the next question, then picks the winner by matching participants' messages - * against the answer(s) or receives the winner from Staff. Once it reaches the end it will notify Staff in the Logs channel and - * list all the winners in order. - */ - async function sendQuestion(initBotInfo) { - //get question's parameters from db - let data = await getQuestion(guild.id); - - //sends results to Staff after all questions have been asked and stops looping - if (data === null) { - discordLog(guild, '<@&' + initBotInfo.roleIDs.staffRole + '> Discord contests have ended!'); - clearInterval(interval); - return; - } - - /** @type {string} */ - let question = data.question; - /** @type {string[] | undefined} */ - let answers = data.answers; - let needAllAnswers = data.needAllAnswers; - - const qEmbed = new MessageEmbed() - .setTitle('A new Discord Contest Question:') - .setDescription(question); - - - const row = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('winner') - .setLabel('Select winner') - .setStyle('PRIMARY'), - ); - - - await channel.send({ content: '<@&' + roleId + '>', embeds: [qEmbed] }); - if (!answers?.length) { - //send message to console - const questionMsg = await adminConsole.send({ content: '<@&' + initBotInfo.roleIDs.staffRole + '>' + 'need manual review!', embeds: [qEmbed], components: [row] }); - - const filter = i => !i.user.bot && i.customId === 'winner' && (guild.members.cache.get(i.user.id).roles.cache.has(initBotInfo.roleIDs.staffRole) || guild.members.cache.get(i.user.id).roles.cache.has(initBotInfo.roleIDs.adminRole)); - const collector = await questionMsg.createMessageComponentCollector({ filter }); - - collector.on('collect', async i => { - const winnerRequest = await i.reply({ content: '<@' + i.user.id + '> Mention the winner in your next message!', fetchReply: true }); - - const winnerFilter = message => message.author.id === i.user.id; // error? - const winnerCollector = await adminConsole.createMessageCollector({ filter: winnerFilter, max: 1 }); - winnerCollector.on('collect', async m => { - if (m.mentions.members.size > 0) { - const member = await m.mentions.members.first(); - const memberId = await member.user.id; - await m.delete(); - await questionMsg.delete(); - await i.editReply('<@' + memberId + '> has been recorded!'); - row.components[0].setDisabled(true); - // row.components[0].setDisabled(); - await channel.send('Congrats <@' + memberId + '> for the best answer to the last question!'); - // winners.push(memberId); - await updateLeaderboard(memberId); - collector.stop(); - await recordWinner(memberId); - } else { - await m.delete(); - // await winnerRequest.deleteReply(); - let errorMsg = await i.editReply({ content: 'Message does not include a user mention!' }); - setTimeout(function () { - errorMsg.delete(); - }, 5000); - } - }); - }); - } else { - //automatically mark answers - const filter = m => !m.author.bot && (initBotInfo.verification.isEnabled ? checkForRole(m.member, initBotInfo.verification.roles.find((r) => r.name === 'hacker')?.roleId) : checkForRole(m.member, initBotInfo.roleIDs.memberRole)); - const collector = channel.createMessageCollector({ filter, time: timeInterval * 0.75 }); - - collector.on('collect', async m => { - if (!needAllAnswers) { - // for questions that have numbers as answers, the answer has to match at least one of the correct answers exactly - if (!isNaN(answers[0])) { - if (answers.some(correctAnswer => m.content === correctAnswer)) { - await channel.send('Congrats <@' + m.author.id + '> for getting the correct answer! The answer key is ' + answers.join(' or ') + '.'); - // winners.push(m.author.id); - await updateLeaderboard(m.author.id); - collector.stop(); - recordWinner(m.member.id); - } - } else if (answers.some(correctAnswer => m.content.toLowerCase().includes(correctAnswer.toLowerCase()))) { - //for most questions, an answer that contains at least once item of the answer array is correct - await channel.send('Congrats <@' + m.author.id + '> for getting the correct answer! The answer key is ' + answers.join(' or ') + '.'); - // winners.push(m.author.id); - await updateLeaderboard(m.author.id); - collector.stop(); - recordWinner(m.member.id); - } - } else { - //check if all answers in answer array are in the message - if (answers.every((answer) => m.content.toLowerCase().includes(answer.toLowerCase()))) { - await channel.send('Congrats <@' + m.author.id + '> for getting the correct answer! The answer key is ' + answers.join(', ') + '.'); - // winners.push(m.author.id); - await updateLeaderboard(m.author.id); - collector.stop(); - recordWinner(m.member.id); - } - } - }); - - collector.on('end', async () => { - await channel.send('Answers are no longer being accepted. Stay tuned for the next question!'); - }); - } - } - - async function recordWinner(memberId) { - try { - let email = await lookupById(guild.id, memberId); - discordLog(guild, `Discord contest winner: <@${memberId}> - ${email}`); - } catch (error) { - console.log(error); - } - } - } -} -module.exports = DiscordContests; diff --git a/commands/a_activity/hide-unhide.js b/commands/a_activity/hide-unhide.js deleted file mode 100644 index 104aaddb..00000000 --- a/commands/a_activity/hide-unhide.js +++ /dev/null @@ -1,85 +0,0 @@ -// // Discord.js commando requirements -// const { Command } = require('discord.js-commando'); -// const discordServices = require('../../discord-services'); - -// // Command export -// module.exports = class HideUnhide extends Command { -// constructor(client) { -// super(client, { -// name: 'hide-unhide', -// group: 'a_activity', -// memberName: 'hide or unhide an activity', -// description: 'Will add or remove permissions to everyone except staff to see the activity.', -// guildOnly: true, -// args: [ -// { -// key: 'activityName', -// prompt: 'the workshop name', -// type: 'string', -// }, -// { -// key: 'toHide', -// prompt: 'should the activity be hidden?', -// type: 'boolean', -// }, -// { -// key: 'categoryChannelKey', -// prompt: 'snowflake of the activity\'s category', -// type: 'string', -// default: '', -// }, -// ], -// }); -// } - -// // Run function -> command body -// async run(message, {activityName, toHide, categoryChannelKey}) { -// discordServices.deleteMessage(message); - -// // make sure command is only used in the admin console -// // only members with the staff tag can run this command! -// if (!(discordServices.checkForRole(message.member, discordServices.roleIDs.staffRole))) { -// discordServices.replyAndDelete(message, 'You do not have permission for this command, only staff can use it!'); -// return; -// } - -// // get category -// var category; -// if (categoryChannelKey === '') { -// category = await message.guild.channels.cache.find(channel => channel.type === 'category' && channel.name.endsWith(activityName)); -// } else { -// category = message.guild.channels.resolve(categoryChannelKey); -// } - - -// // if no category then report failure and return -// if (category === undefined) { -// // if the category does not exist -// discordServices.replyAndDelete(message,'The workshop named: ' + activityName +', does not exist! Did not remove voice channels.'); -// return; -// } - -// // NOTE: -// // * It appears that the discord api takes a LONG time to change the name once the category has been changed a few times -// // * For our purposes, we are okay with hiding it only initially and then un-hiding, after that no more hiding allowed -// // * Will make sure this rule is followed in !new-activity console -// // * UPDATE: it appears the problem was using category.name inside the setName method, using a simple variable solved the issue - - -// // update overwrites -// if (toHide) { -// // category = await category.setName('HIDDEN-' + category.name); -// category.updateOverwrite(discordServices.roleIDs.memberRole, {VIEW_CHANNEL: false}); -// category.updateOverwrite(discordServices.roleIDs.mentorRole, {VIEW_CHANNEL: false}); -// category.updateOverwrite(discordServices.roleIDs.sponsorRole, {VIEW_CHANNEL: false}); -// } else { -// // category = await category.setName(category.name.replace('HIDDEN-', '')); -// category.updateOverwrite(discordServices.roleIDs.memberRole, {VIEW_CHANNEL: true}); -// category.updateOverwrite(discordServices.roleIDs.mentorRole, {VIEW_CHANNEL: true}); -// category.updateOverwrite(discordServices.roleIDs.sponsorRole, {VIEW_CHANNEL: true}); -// } - -// // report success of channel deletions -// discordServices.replyAndDelete(message,'Workshop session named: ' + activityName + ' has changed visibility.'); -// } -// }; \ No newline at end of file diff --git a/commands/a_activity/new-activity.js b/commands/a_activity/new-activity.js deleted file mode 100644 index 72bbd553..00000000 --- a/commands/a_activity/new-activity.js +++ /dev/null @@ -1,51 +0,0 @@ -const PermissionCommand = require('../../classes/permission-command'); -const { Message } = require('discord.js'); -const Activity = require('../../classes/Bot/activities/activity'); - -/** - * Creates a new activity and prompts the user for any information. - * Look at the [activity]{@link Activity} class to learn what an activity is. - * @category Commands - * @subcategory Activity - * @extends PermissionCommand - * @guildonly - */ -class NewActivity extends PermissionCommand { - constructor(client) { - super(client, { - name: 'new-activity', - group: 'a_activity', - memberName: 'create a new activity', - description: 'Will create a new activity.', - guildOnly: true, - args: [ - { - key: 'activityName', - prompt: 'the activity name, can use emojis!', - type: 'string', - }, - ], - }, - { - channel: PermissionCommand.FLAGS.ADMIN_CONSOLE, - channelMessage: 'This command can only be used in the admin console!', - role: PermissionCommand.FLAGS.ADMIN_ROLE, - roleMessage: 'You do not have permission for this command, only admins can use it!', - }); - } - - /** - * @param {BotGuildModel} botGuild - * @param {Message} message - the message in which the command was run - * @param {Object} args - * @param {String} args.activityName - */ - async runCommand(botGuild, message, {activityName}) { - - let allowedRoles = await Activity.promptForRoleParticipants(message.channel, message.author.id, true); - let activity = new Activity({ activityName, guild: message.guild, roleParticipants: allowedRoles, botGuild}); - await activity.init(); - - } -} -module.exports = NewActivity; \ No newline at end of file diff --git a/commands/a_activity/new-coffee-chats.js b/commands/a_activity/new-coffee-chats.js deleted file mode 100644 index 41e55225..00000000 --- a/commands/a_activity/new-coffee-chats.js +++ /dev/null @@ -1,62 +0,0 @@ -const { replyAndDelete } = require('../../discord-services'); -const { Message } = require('discord.js'); -const Activity = require('../../classes/Bot/activities/activity'); -const PermissionCommand = require('../../classes/permission-command'); -const CoffeeChats = require('../../classes/Bot/activities/coffee-chats'); - -/** - * Creates a new coffee chats activity. - * See the [coffee chats]{@link CoffeeChats} class to learn what a coffee chats activity is. - * @category Commands - * @subcategory Activity - * @extends PermissionCommand - * @guildonly - */ -class NewCoffeeChats extends PermissionCommand { - constructor(client) { - super(client, { - name: 'new-coffee-chats', - group: 'a_activity', - memberName: 'initialize coffee chat functionality for activity', - description: 'Will initialize the coffee chat functionality for the given workshop.', - guildOnly: true, - args: [ - { - key: 'activityName', - prompt: 'the activity name, can use emojis!', - type: 'string', - }, - { - key: 'numOfGroups', - prompt: 'number of groups to participate in coffee chat', - type: 'integer' - }, - ], - }, - { - channel: PermissionCommand.FLAGS.ADMIN_CONSOLE, - channelMessage: 'This command can only be used in the admin console!', - role: PermissionCommand.FLAGS.ADMIN_ROLE, - roleMessage: 'You do not have permission for this command, only admins can use it!', - }); - } - - /** - * Required class by children, should contain the command code. - * @param {Message} message - the message that has the command - * @param {Activity} activity - the activity for this activity command - * @param {Object} args - * @param {String} args.activityName - * @param {Number} args.numOfGroups - */ - async runCommand(botGuild, message, { activityName, numOfGroups }) { - - let roleParticipants = await Activity.promptForRoleParticipants(message.channel, message.author.id, true); - - let coffeeChats = await new CoffeeChats({activityName: activityName, guild: message.guild, roleParticipants: roleParticipants, botGuild: botGuild}, numOfGroups).init(message.channel, message.author.id); - - // report success of coffee chat creation - replyAndDelete(message,'Activity named: ' + activityName + ' now has coffee chat functionality.'); - } -} -module.exports = NewCoffeeChats; \ No newline at end of file diff --git a/commands/a_activity/new-workshop.js b/commands/a_activity/new-workshop.js deleted file mode 100644 index 09801b56..00000000 --- a/commands/a_activity/new-workshop.js +++ /dev/null @@ -1,76 +0,0 @@ -const { replyAndDelete } = require('../../discord-services'); -const { Message, Collection } = require('discord.js'); -const Workshop = require('../../classes/Bot/activities/workshop'); -const PermissionCommand = require('../../classes/permission-command'); -const Activity = require('../../classes/Bot/activities/activity'); -const { SpecialPrompt, RolePrompt } = require('advanced-discord.js-prompts'); - -/** - * Creates a new Workshop and prompts the user for any information. - * Take a look at the [workshop]{@link Workshop} class to learn what a workshop activity is. - * @category Commands - * @subcategory Activity - * @extends PermissionCommand - * @guildonly - */ -class NewWorkshop extends PermissionCommand { - constructor(client) { - super(client, { - name: 'new-workshop', - group: 'a_activity', - memberName: 'initialize workshop functionality for activity', - description: 'Will initialize the workshop functionality for the given workshop. General voice channel will be muted for all hackers.', - guildOnly: true, - args: [ - { - key: 'activityName', - prompt: 'the activity name, can use emojis!', - type: 'string', - }, - ], - }, - { - channel: PermissionCommand.FLAGS.ADMIN_CONSOLE, - channelMessage: 'This command can only be used in the admin console!', - role: PermissionCommand.FLAGS.ADMIN_ROLE, - roleMessage: 'You do not have permission for this command, only admins can use it!', - }); - } - - /** - * Required class by children, should contain the command code. - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - the message that has the command - * @param {Object} args - * @param {String} args.activityName - the activity for this activity command - */ - async runCommand(initBotInfo, message, {activityName}) { - - - // prompt user for roles that will be allowed to see this activity. - let roleParticipants = await Activity.promptForRoleParticipants(message.channel, message.author.id, true); - - - // prompt user for roles that will have access to the TA side of the workshop - var TARoles = new Collection(); - try { - TARoles = await RolePrompt.multi({prompt: 'What roles will have access to the TA portion of this workshop?', channel: message.channel, userId: message.author.id}); - } catch (error) { - // do nothing if canceled - } - - let isLowTechSolution = await SpecialPrompt.boolean({ prompt: `Would you like to use the low tech solution? Else the high tech solution will be used. - \n Low tech solution involves TAs reaching out to users via DM. - \n High tech solution involves users and TAs being in voice channels and being moved to a common voice channel to give/receive assistance. - \n We recommend the low tech solution!`, channel: message.channel, userId: message.author.id}); - - - let workshop = await new Workshop({activityName, guild: message.guild, roleParticipants, initBotInfo: initBotInfo}, isLowTechSolution,TARoles).init(); - - workshop.sendConsoles(); - - // report success of workshop creation - replyAndDelete(message, 'Activity named: ' + activityName + ' was created as a workshop!'); - } -} -module.exports = NewWorkshop; \ No newline at end of file diff --git a/commands/a_boothing/e-room-directory.js b/commands/a_boothing/e-room-directory.js deleted file mode 100644 index 27f369cd..00000000 --- a/commands/a_boothing/e-room-directory.js +++ /dev/null @@ -1,121 +0,0 @@ -const PermissionCommand = require('../../classes/permission-command'); -const { Message, MessageEmbed, Role, Collection} = require('discord.js'); -const { deleteMessage } = require('../../discord-services'); -const winston = require('winston'); -const { StringPrompt, RolePrompt, SpecialPrompt } = require('advanced-discord.js-prompts'); - -/** - * Shows an embed with a link used for activities happening outside discord. Initial intent was to be used for - * sponsor booths. A specified role can open and close the rooms as they want. When rooms open, a specified role is notified. - * @category Commands - * @subcategory Boothing - * @extends PermissionCommand - * @guildonly - */ -class ERoomDirectory extends PermissionCommand { - constructor(client) { - super(client, { - name: 'e-room-directory', - group: 'a_boothing', - memberName: 'keep track of booths', - description: 'Sends embeds to booth directory to notify hackers of booth statuses', - guildOnly: true, - }, - { - role: PermissionCommand.FLAGS.STAFF_ROLE, - roleMessage: 'This command can only be ran by staff!', - }); - } - - /** - * Sends an embed same channel with the sponsor's name and link to their Zoom boothing room. The embed has 2 states: Open and Closed. - * In the Closed state the embed will be red and say the booth is closed, which is the default, and the bot will react to the embed with - * a door emoji at the beginning. In the Open state the embed will be green and say the booth is open. Any time a staff or sponsor clicks - * on that emoji, the embed changes to the other state. When a booth goes from Closed to Open, it will also notify a role (specified by - * the user) that it is open. - * - * @param {Message} message - messaged that called this command - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - */ - async runCommand(initBotInfo, message) { - - // helpful vars - let channel = message.channel; - let userId = message.author.id; - - try { - var sponsorName = await StringPrompt.single({prompt: 'What is the room name?', channel, userId, cancelable: true}); - - var link = await StringPrompt.single({prompt: 'What is the room link? We will add no words to it! (ex. is Currently Open).', channel, userId, cancelable: true}); - - //ask user for role and save its id in the role variable - var role = (await RolePrompt.single({prompt: 'What role will get pinged when the rooms open?', channel, userId})).id; - } catch (error) { - channel.send('<@' + userId + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000})); - return; - } - - /** - * prompt for roles that can open/close the room - * @type {Collection} - */ - var roomRoles; - try { - roomRoles = await RolePrompt.multi({ prompt: 'What other roles can open/close the room? (Apart form staff).', channel, userId, cancelable: true }); - } catch (error) { - // do nothing as this is fine - winston.loggers.get(message.guild.id).warning(`Got an error: ${error} but I let it go since we expected it from the prompt.`, { event: 'E-Room-Directory Command' }); - } - // add staff role - roomRoles.set(initBotInfo.roleIDs.staffRole, message.guild.roles.resolve(initBotInfo.roleIDs.staffRole)); - - // prompt user for emoji to use - let emoji = await SpecialPrompt.singleEmoji({prompt: 'What emoji do you want to use to open/close the room?', channel, userId}); - - //variable to keep track of state (Open vs Closed) - var closed = true; - //embed for closed state - const embed = new MessageEmbed() - .setColor('#FF0000') - .setTitle(sponsorName + ' is Currently Closed') - .setDescription('Room link: ' + link); - - //send closed embed at beginning (default is Closed) - channel.send(embed).then((msg) => { - msg.pin(); - msg.react(emoji); - - //only listen for the door react from users that have one of the roles in the room roles collection - const emojiFilter = (reaction, user) => { - let member = message.guild.member(user); - return !user.bot && reaction.emoji.name === emoji.name && roomRoles.some(role => member.roles.cache.has(role.id)); - }; - const emojiCollector = msg.createReactionCollector(emojiFilter); - - var announcementMsg; - - emojiCollector.on('collect', async (reaction, user) => { - reaction.users.remove(user); - if (closed) { - //embed for open state - const openEmbed = new MessageEmbed() - .setColor('#008000') - .setTitle(sponsorName + ' \'s Booth is Currently Open') - .setDescription('Please visit this Zoom link to join: ' + link); - //change to open state embed if closed is true - msg.edit(openEmbed); - closed = false; - //notify people of the given role that booth is open and delete notification after 5 mins - announcementMsg = await channel.send('<@&' + role + '> ' + sponsorName + ' \'s booth has just opened!'); - announcementMsg.delete({timeout: 300 * 1000}); - } else { - //change to closed state embed if closed is false - msg.edit(embed); - closed = true; - deleteMessage(announcementMsg); - } - }); - }); - } -} -module.exports = ERoomDirectory; diff --git a/commands/a_start_commands/start-channel-creation.js b/commands/a_start_commands/start-channel-creation.js deleted file mode 100644 index e12c816c..00000000 --- a/commands/a_start_commands/start-channel-creation.js +++ /dev/null @@ -1,128 +0,0 @@ -const PermissionCommand = require('../../classes/permission-command'); -const { sendEmbedToMember, replyAndDelete } = require('../../discord-services'); -const { Message, MessageEmbed } = require('discord.js'); -const { StringPrompt, MessagePrompt } = require('advanced-discord.js-prompts'); -const ChannelPrompt = require('advanced-discord.js-prompts/prompts/channel-prompt'); - -/** - * The start channel creation command lets users create private channels for them to use. - * Users can create voice or text channels, invite as many people as they want when created and name the channels whatever they want. - * A category and channel is created for this feature. The new channels are created inside this category. - * Users can delete the channel by reacting to a message in their DMs with the bot. - * THERE IS A LIMIT OF CHANNELS! Categories can only have up to 50 channels, if you expect more than 50 channels please DO NOT USE THIS FEATURE. - * @category Commands - * @subcategory Start-Commands - * @extends PermissionCommand - * @guildonly - */ -class StartChannelCreation extends PermissionCommand { - constructor(client) { - super(client, { - name: 'start-channel-creation', - group: 'a_start_commands', - memberName: 'start channel creation', - description: 'Send a message with emoji collector, for each emoji bot will ask type and other friends invited and create the private channel.', - guildOnly: true, - }, - { - role: PermissionCommand.FLAGS.STAFF_ROLE, - roleMessage: 'Hey there, the !start-channel-creation command is only for staff!', - channel: PermissionCommand.FLAGS.ADMIN_CONSOLE, - channelMessage: 'Hey there, the !start-channel-creation command is only available in the admin console channel.', - }); - } - - /** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - the message in which the command was run - */ - async runCommand(initBotInfo, message) { - - try { - // grab current channel - var channel = await ChannelPrompt.single({prompt: 'What channel do you want to use? The channel\'s category will be used to create the new channels.', channel: message.channel, userId: message.author.id}); - } catch (error) { - message.channel.send('<@' + message.author.id + '> The command has been canceled due to the prompt cancel.').then(msg => msg.delete({timeout: 5000})); - return; - } - - // grab channel creation category and update permissions - var category = channel.parent; - category.updateOverwrite(initBotInfo.roleIDs.everyoneRole, { - VIEW_CHANNEL: false, - }); - - channel.updateOverwrite(initBotInfo.roleIDs.everyoneRole, { - VIEW_CHANNEL: true, - }); - - - // create and send embed message to channel with emoji collector - const msgEmbed = new MessageEmbed() - .setColor(initBotInfo.colors.embedColor) - .setTitle('Private Channel Creation') - .setDescription('Do you need a private channel to work with your friends? Or a voice channel to get to know a mentor, here you can create private text or voice channels.' + - ' However do know that server admins will have access to these channels, and the bot will continue to monitor for bad language, so please follow the rules!') - .addField('Ready for a channel of your own?', 'Just react this message with any emoji and the bot will ask you a few simple questions.'); - - var cardMessage = await channel.send(msgEmbed); - cardMessage.pin(); - - // main collector works with any emoji - var mainCollector = cardMessage.createReactionCollector(m => true); - - mainCollector.on('collect', async (reaction, user) => { - // helpful vars - let userId = user.id; - - try { - let channelType = await StringPrompt.restricted({prompt: 'Do you want a "voice" or "text" channel?', channel, userId, time: 20, cancelable: true}, ['voice', 'text']); - - let guests = (await MessagePrompt.prompt({prompt: 'Please tag all the invited users to this private ' + channelType + ' channel. Type "none" if no guests are welcomed.', channel, userId, time: 60, cancelable: true})).mentions.members; - - let channelName = await StringPrompt.single({prompt: 'What do you want to name the channel? If you don\'t care then send "default"!', channel, userId, time: 30}); - - // if channelName is default then use default - if (channelName === 'default') { - channelName = user.username + '-private-channel'; - } - - // create channel - message.guild.channels.create(channelName, { - type: channelType, - parent: category - }).then(async newChannel => { - newChannel.updateOverwrite(user, { - VIEW_CHANNEL : true, - }); - - // add guests - guests.each(mem => newChannel.updateOverwrite(mem.user, { - VIEW_CHANNEL : true, - })); - - // DM to creator with emoji collector - let dmMsg = await sendEmbedToMember(user, { - title: 'Channel Creation', - description: 'Your private channel ' + channelName + - ' has been created, when you are done with it, please react to this message with 🚫 to delete the channel.', - }); - dmMsg.react('🚫'); - - const deleteFilter = (react, user) => !user.bot && react.emoji.name === '🚫'; - dmMsg.awaitReactions(deleteFilter, {max: 1}).then(reacts => { - newChannel.delete(); - dmMsg.delete(); - sendEmbedToMember(user, { - title: 'Channel Creation', - description: 'Private channel has been deleted successfully!', - }, true); - }); - }); - } catch (error) { - channel.send('<@' + user.id + '> The channel creation was canceled due to a timeout or prompt cancel. Try again!').then(msg => msg.delete({timeout: 8000})); - } - }); - } -} -module.exports = StartChannelCreation; diff --git a/commands/a_start_commands/start-mentor-cave.js b/commands/a_start_commands/start-mentor-cave.js deleted file mode 100644 index 7c50fd4d..00000000 --- a/commands/a_start_commands/start-mentor-cave.js +++ /dev/null @@ -1,904 +0,0 @@ -const { Command } = require('@sapphire/framework'); -const { MessageEmbed, Guild, Role } = require('discord.js'); -const { discordLog } = require('../../discord-services'); -const { - MessageSelectMenu, - Modal, - TextInputComponent, - Message, - MessageActionRow, - MessageButton, - GuildBasedChannel -} = require('discord.js'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); -const { Client } = require('discord.js'); - -//TODO: allow staff to add more roles -const htmlCssEmoji = '💻'; -const jsTsEmoji = '🕸️'; -const pythonEmoji = '🐍'; -const sqlEmoji = '🐬'; -const reactEmoji = '⚛️'; -const noSqlEmoji = '🔥'; -const javaEmoji = '☕'; -const cEmoji = '🎮'; -const cSharpEmoji = '💼'; -const reduxEmoji = '☁️'; -const figmaEmoji = '🎨'; -const unityEmoji = '🧊'; -const rustEmoji = '⚙️'; -const awsEmoji = '🙂'; -const ideationEmoji = '💡'; -const pitchingEmoji = '🎤'; - -let emojisMap = new Map([ - [htmlCssEmoji, 'HTML/CSS'], - [jsTsEmoji, 'JavaScript/TypeScript'], - [pythonEmoji, 'Python'], - [sqlEmoji, 'SQL'], - [reactEmoji, 'React'], - [noSqlEmoji, 'NoSQL'], - [javaEmoji, 'Java'], - [cEmoji, 'C/C++'], - [cSharpEmoji, 'C#'], - [reduxEmoji, 'Redux'], - [figmaEmoji, 'Figma'], - [unityEmoji, 'Unity'], - [rustEmoji, 'Rust'], - [awsEmoji, 'AWS'], - [ideationEmoji, 'Ideation'], - [pitchingEmoji, 'Pitching'] -]); - -/** - * The start mentor cave command creates a cave for mentors. To know what a cave is look at [cave]{@link Cave} class. - * @category Commands - * @subcategory Start-Commands - * @extends PermissionCommand - * @guildonly - */ -class StartMentorCave extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Start mentor cave' - }); - } - - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName('start-mentor-cave') - .setDescription(this.description) - .addIntegerOption(option => - option.setName('inactivity_time') - .setDescription('How long (minutes) before bot asks users to delete ticket channels') - .setRequired(true)) - .addIntegerOption(option => - option.setName('unanswered_ticket_time') - .setDescription('How long (minutes) shall a ticket go unaccepted before the bot sends a reminder to all mentors?') - .setRequired(true)) - .addRoleOption(option => - option.setName('request_ticket_role') - .setDescription('Tag the role that is allowed to request tickets') - .setRequired(true)) - .addChannelOption(option => - option.setName('mentor_role_selection_channel') - .setDescription('Tag the channel where mentors can select their specialties') - .setRequired(false)) - .addChannelOption(option => - option.setName('incoming_tickets_channel') - .setDescription('Tag the channel where mentor tickets will be sent') - .setRequired(false)) - .addChannelOption(option => - option.setName('request_ticket_channel') - .setDescription('Tag the channel where hackers can request tickets') - .setRequired(false)), - { idHints: '1051737344937566229' }); - } - - /** - * - * @param {Command.ChatInputInteraction} interaction - */ - async chatInputRun(interaction) { - try { - // helpful prompt vars - let userId = interaction.user.id; - let guild = interaction.guild; - this.initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - - if (!guild.members.cache.get(userId).roles.cache.has(this.initBotInfo.roleIDs.staffRole) && !guild.members.cache.get(userId).roles.cache.has(this.initBotInfo.roleIDs.adminRole)) { - await interaction.reply({ content: 'You do not have permissions to run this command!', ephemeral: true }); - return; - } - - // const additionalMentorRole = interaction.options.getRole('additional_mentor_role'); - const publicRole = interaction.options.getRole('request_ticket_role'); - // const bufferTime = inactivePeriod / 2; - const reminderTime = interaction.options.getInteger('unanswered_ticket_time'); - - const mentorRoleSelectionChannelId = - this.initBotInfo.mentorTickets?.mentorRoleSelectionChannel; - const incomingTicketsChannelId = - this.initBotInfo.mentorTickets?.incomingTicketsChannel; - const requestTicketChannelId = - this.initBotInfo.mentorTickets?.requestTicketChannel; - - const mentorRoleSelectionChannel = interaction.options.getChannel( - 'mentor_role_selection_channel' - ) ?? (mentorRoleSelectionChannelId - ? await guild.channels.fetch(mentorRoleSelectionChannelId) - : null); - - const incomingTicketsChannel = interaction.options.getChannel( - 'incoming_tickets_channel' - ) ?? (incomingTicketsChannelId - ? await guild.channels.fetch(incomingTicketsChannelId) - : null); - - const requestTicketChannel = interaction.options.getChannel( - 'request_ticket_channel' - ) ?? (requestTicketChannelId - ? await guild.channels.fetch(requestTicketChannelId) - : null); - - if (!mentorRoleSelectionChannel || !incomingTicketsChannel || !requestTicketChannel) { - await interaction.reply({ content: 'Please enter all 3 channels!', ephemeral: true }); - return; - } - - if ( - mentorRoleSelectionChannel.id != mentorRoleSelectionChannelId || - incomingTicketsChannel.id != incomingTicketsChannelId || - requestTicketChannel.id != requestTicketChannelId - ) { - await interaction.deferReply(); - await firebaseUtil - .getFactotumSubCol() - .doc(guild.id) - .set({ - mentorTickets: { - mentorRoleSelectionChannel: mentorRoleSelectionChannel.id, - incomingTicketsChannel: incomingTicketsChannel.id, - requestTicketChannel: requestTicketChannel.id, - }, - }, - { merge: true }); - await interaction.editReply({ content: 'Mentor cave activated!', ephemeral: true }); - } else { - await interaction.reply({ content: 'Mentor cave activated!', ephemeral: true }); - } - - discordLog(guild, 'Mentor cave started by <@' + userId + '>'); - - // these are all old code that create channels rather than using existing channels - // let overwrites = - // [{ - // id: this.botGuild.roleIDs.everyoneRole, - // deny: ['VIEW_CHANNEL'], - // }, - // { - // id: this.botGuild.roleIDs.mentorRole, - // allow: ['VIEW_CHANNEL'], - // }, - // { - // id: this.botGuild.roleIDs.staffRole, - // allow: ['VIEW_CHANNEL'], - // }]; - - // if (additionalMentorRole) { - // overwrites.push({ - // id: additionalMentorRole, - // allow: ['VIEW_CHANNEL'] - // }); - // } - - // let mentorCategory = await channel.guild.channels.create('Mentors', - // { - // type: 'GUILD_CATEGORY', - // permissionOverwrites: overwrites - // } - // ); - - // let announcementsOverwrites = overwrites; - // announcementsOverwrites.push( - // { - // id: this.botGuild.roleIDs.mentorRole, - // deny: ['SEND_MESSAGES'], - // allow: ['VIEW_CHANNEL'] - // }); - - // await channel.guild.channels.create('mentors-announcements', - // { - // type: 'GUILD_TEXT', - // parent: mentorCategory, - // permissionOverwrites: announcementsOverwrites - // } - // ); - - // const mentorRoleSelectionChannel = await channel.guild.channels.create('mentors-role-selection', - // { - // type: 'GUILD_TEXT', - // topic: 'Sign yourself up for specific roles! New roles will be added as requested, only add yourself to one if you feel comfortable responding to questions about the topic.', - // parent: mentorCategory - // } - // ); - - const mentorRoleColour = guild.roles.cache.find(role => role.id === this.initBotInfo.roleIDs.mentorRole).hexColor; - for (let value of emojisMap.values()) { - const findRole = guild.roles.cache.find(role => role.name.toLowerCase() === `M-${value}`.toLowerCase()); - if (!findRole) { - await guild.roles.create( - { - name: `M-${value}`, - color: mentorRoleColour, - } - ); - } - } - - const roleSelection = makeRoleSelectionEmbed(); - - /** @type {Message} */ - const roleSelectionMsg = await mentorRoleSelectionChannel.send({ embeds: [roleSelection] }); - for (const key of emojisMap.keys()) { - roleSelectionMsg.react(key); - } - - listenToRoleReactions(guild, roleSelectionMsg); - - const mentorCaveDoc = firebaseUtil.getSavedMessagesSubCol(guild.id).doc('mentor-cave'); - - mentorCaveDoc.set({ - roleReactions: { - channelId: roleSelectionMsg.channelId, - messageId: roleSelectionMsg.id - } - }, {merge: true}); - - // channel.guild.channels.create('mentors-general', - // { - // type: 'GUILD_TEXT', - // topic: 'Private chat between all mentors and organizers', - // parent: mentorCategory - // } - // ); - - // const incomingTicketChannel = await channel.guild.channels.create('incoming-tickets', - // { - // type: 'GUILD_TEXT', - // topic: 'Tickets from hackers will come in here!', - // parent: mentorCategory - // } - // ); - - // const mentorHelpCategory = await channel.guild.channels.create('Mentor-help', - // { - // type: 'GUILD_CATEGORY', - // permissionOverwrites: [ - // { - // id: this.botGuild.verification.guestRoleID, - // deny: ['VIEW_CHANNEL'], - // }, - // ] - // } - // ); - - // channel.guild.channels.create('quick-questions', - // { - // type: 'GUILD_TEXT', - // topic: 'ask questions for mentors here!', - // parent: mentorHelpCategory - // } - // ); - - // const requestTicketChannel = await channel.guild.channels.create('request-ticket', - // { - // type: 'GUILD_TEXT', - // topic: 'request 1-on-1 help from mentors here!', - // parent: mentorHelpCategory, - // permissionOverwrites: [ - // { - // id: publicRole, - // allow: ['VIEW_CHANNEL'], - // deny: ['SEND_MESSAGES'] - // }, - // { - // id: this.botGuild.roleIDs.staffRole, - // allow: ['VIEW_CHANNEL'] - // }, - // { - // id: this.botGuild.roleIDs.everyoneRole, - // deny: ['VIEW_CHANNEL'] - // } - // ] - // } - // ); - - const requestTicketEmbed = new MessageEmbed() - .setTitle('Need 1:1 mentor help?') - .setDescription('Select a technology you need help with and follow the instructions!'); - - const selectMenuRow = makeSelectMenuRow(); - - /** @type {Message} */ - const requestTicketConsole = await requestTicketChannel.send({ embeds: [requestTicketEmbed], components: [selectMenuRow] }); - - listenToRequestConsole( - this.initBotInfo, - guild, - requestTicketConsole, - publicRole, - reminderTime, - incomingTicketsChannel - ); - - mentorCaveDoc.set({ - requestTicketConsole: { - channelId: requestTicketConsole.channelId, - messageId: requestTicketConsole.id, - publicRoleId: publicRole.id, - reminderTime - } - }, {merge: true}); - - const adminEmbed = new MessageEmbed() - .setTitle('Mentor Cave Console') - .setColor(this.initBotInfo.embedColor); - - const adminRow = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('addRole') - .setLabel('Add Mentor Role') - .setStyle('PRIMARY'), - ); - - - const adminConsole = await guild.channels.fetch(this.initBotInfo.channelIDs.adminConsole); - - /** @type {Message} */ - const adminControls = await adminConsole.send({ embeds: [adminEmbed], components: [adminRow] }); - - listenToAdminControls( - this.initBotInfo, - guild, - adminControls, - adminConsole, - roleSelectionMsg, - requestTicketConsole - ); - - mentorCaveDoc.set({ - adminControls: { - channelId: adminControls.channelId, - messageId: adminControls.id, - } - }, {merge: true}); - - } catch (error) { - // winston.loggers.get(interaction.guild.id).warning(`An error was found but it was handled by not setting up the mentor cave. Error: ${error}`, { event: 'StartMentorCave Command' }); - } - } - - async deleteTicketChannels(ticketText, ticketVoice, ticketCategory, ticketMsg, embed) { - await ticketMsg.edit({ embeds: [embed] }); - ticketText.delete(); - ticketVoice.delete(); - ticketCategory.delete(); - } - - async startChannelActivityListener(ticketText, ticketVoice, ticketCategory, ticketMsg, inactivePeriod, bufferTime) { - // message collector that stops when there are no messages for inactivePeriod minutes - if (ticketText.parentId && ticketVoice.parentId) { - const activityListener = ticketText.createMessageCollector({ filter: m => !m.author.bot, idle: inactivePeriod * 60 * 1000 }); - activityListener.on('end', async collected => { - if (!ticketText.parentId || !ticketVoice.parentId) return; - if (collected.size === 0 && ticketVoice.members.size === 0 && ticketMsg.embeds[0].color != '#90EE90') { - const remainingMembers = await ticketCategory.members.filter(member => !member.roles.cache.has(this.initBotInfo.roleIDs.adminRole) && !member.user.bot).map(member => member.id); - const msgText = '<@' + remainingMembers.join('><@') + '> Hello! I detected some inactivity in this channel. If you are done and would like to leave this ticket, please go to the pinned message and click the "Leave" button. If you would like to keep this channel a little longer, please click the button below.\n**If no action is taken in the next ' + bufferTime + ' minutes, the channels for this ticket will be deleted automatically.**'; - const row = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('keepChannels') - .setLabel('Keep Channels') - .setStyle('PRIMARY'), - ); - const warning = await ticketText.send({ content: msgText, components: [row] }); - - warning.awaitMessageComponent({ filter: i => !i.user.bot, time: bufferTime * 60 * 1000 }) - .then(interaction => { - interaction.reply('You have indicated that you need more time. I\'ll check in with you later!'); - const disabledButtonRow = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('keepChannels') - .setLabel('Keep Channels') - .setDisabled(true) - .setStyle('PRIMARY'), - ); - warning.edit({ components: [disabledButtonRow] }); - this.startChannelActivityListener(ticketText, ticketVoice, ticketCategory, ticketMsg, inactivePeriod, bufferTime); - }) - .catch(() => { - if (!ticketText.parentId || !ticketVoice.parentId || ticketMsg.embeds[0].color == '#90EE90') return; - if (ticketVoice.members.size === 0) { - this.deleteTicketChannels(ticketText, ticketVoice, ticketCategory, ticketMsg, ticketMsg.embeds[0].setColor('#90EE90').addFields([{ name: 'Ticket closed', value: 'Deleted due to inactivity' }])); - } else { - this.startChannelActivityListener(ticketText, ticketVoice, ticketCategory, ticketMsg, inactivePeriod, bufferTime); - } - }); - } else { - this.startChannelActivityListener(ticketText, ticketVoice, ticketCategory, ticketMsg, inactivePeriod, bufferTime); - } - }); - } - } - - /** - * Checks Firebase for existing stored listeners - - * restores the listeners if they exist, otherwise does nothing - * @param {Guild} guild - */ - async tryRestoreReactionListeners(guild) { - const savedMessagesSubCol = firebaseUtil.getSavedMessagesSubCol(guild.id); - const mentorCaveDoc = await savedMessagesSubCol.doc('mentor-cave').get(); - if (!mentorCaveDoc.exists) return 'Saved messages doc for mentor cave does not exist'; - - const mentorCaveData = mentorCaveDoc.data(); - if (mentorCaveData.extraEmojis) { - for (const [emoji, name] of Object.entries(mentorCaveData.extraEmojis)) { - emojisMap.set(emoji, name); - } - } - - // Get role reaction listener saved details - const { - messageId: reactionMessageId, - channelId: reactionChannelId - } = mentorCaveData.roleReactions; - const reactionChannel = await guild.channels.fetch(reactionChannelId); - if (!reactionChannel) return 'Saved role reactions message info not found'; - - // Get request ticket console saved details - const { - channelId: consoleChannelId, - messageId: consoleMessageId, - publicRoleId, - reminderTime - } = mentorCaveData.requestTicketConsole; - const consoleChannel = await guild.channels.fetch(consoleChannelId); - if (!consoleChannel) return 'Saved request ticket console info not found'; - - // Get admin controls saved details - const { - channelId: controlsChannelId, - messageId: controlsMessageId - } = mentorCaveData.adminControls; - const adminControlsChannel = await guild.channels.fetch(controlsChannelId); - if (!adminControlsChannel) return 'Saved admin controls info not found'; - - - try { - // Restore role reactions listener - /** @type {Message} */ - const roleSelectionMsg = await reactionChannel.messages.fetch(reactionMessageId); - await listenToRoleReactions(guild, roleSelectionMsg); - - // Restore request console listener - /** @type {Message} */ - const requestTicketConsole = await consoleChannel.messages.fetch(consoleMessageId); - const publicRole = await guild.roles.fetch(publicRoleId); - const initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - const incomingTicketsChannelId = initBotInfo.mentorTickets?.incomingTicketsChannel; - const incomingTicketsChannel = await guild.channels.fetch(incomingTicketsChannelId); - if (incomingTicketsChannelId && incomingTicketsChannel) { - listenToRequestConsole( - initBotInfo, - guild, - requestTicketConsole, - publicRole, - reminderTime, - incomingTicketsChannel - ); - } - - // Restore admin controls listener - const adminControls = await adminControlsChannel.messages.fetch(controlsMessageId); - const adminConsole = await guild.channels.fetch(initBotInfo.channelIDs.adminConsole); - listenToAdminControls( - initBotInfo, - guild, - adminControls, - adminConsole, - roleSelectionMsg, - requestTicketConsole - ); - } catch (e) { - // message doesn't exist anymore - return 'Error: ' + e; - } - } -} - -function makeRoleSelectionEmbed() { - const fields = []; - for (const [key, value] of emojisMap) { - fields.push({ name: key + ' --> ' + value, value: '\u200b' }); - } - - const roleSelection = new MessageEmbed() - .setTitle('Choose what you would like to help hackers with! You can un-react to deselect a role.') - .setDescription('Note: You will be notified every time a hacker creates a ticket in one of your selected categories!') - .addFields(fields); - - return roleSelection; -} - -function makeSelectMenuRow() { - const options = []; - for (const value of emojisMap.values()) { - options.push({ label: value, value: value }); - } - options.push({ label: 'None of the above', value: 'None of the above' }); - - const selectMenuRow = new MessageActionRow() - .addComponents( - new MessageSelectMenu() - .setCustomId('ticketType') - .addOptions(options) - ); - return selectMenuRow; -} - -/** - * - * @param {Guild} guild - * @param {Message} roleSelectionMsg - */ -async function listenToRoleReactions(guild, roleSelectionMsg) { - const collector = roleSelectionMsg.createReactionCollector({ filter: (reaction, user) => !user.bot, dispose: true }); - collector.on('collect', async (reaction, user) => { - if (emojisMap.has(reaction.emoji.name)) { - const value = emojisMap.get(reaction.emoji.name); - const findRole = guild.roles.cache.find(role => role.name.toLowerCase() === `M-${value}`.toLowerCase()); - await guild.members.cache.get(user.id).roles.add(findRole); - } - }); - - collector.on('remove', async (reaction, user) => { - if (emojisMap.has(reaction.emoji.name)) { - const member = guild.members.cache.get(user.id); - const value = emojisMap.get(reaction.emoji.name); - const findRole = member.roles.cache.find(role => role.name.toLowerCase() === `M-${value}`.toLowerCase()); - if (findRole) await guild.members.cache.get(user.id).roles.remove(findRole); - } - }); -} - -/** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Guild} guild - * @param {Message} requestTicketConsole - * @param {MessageEmbed} requestTicketEmbed - * @param {MessageActionRow} selectMenuRow - * @param {Role} publicRole - * @param {number} reminderTime - * @param {GuildBasedChannel} incomingTicketsChannel - */ -function listenToRequestConsole( - initBotInfo, - guild, - requestTicketConsole, - publicRole, - reminderTime, - incomingTicketsChannel -) { - const selectMenuFilter = i => !i.user.bot; - const selectMenuCollector = requestTicketConsole.createMessageComponentCollector({filter: selectMenuFilter}); - selectMenuCollector.on('collect', async (i) => { - if (i.customId === 'ticketType') { - // requestTicketConsole.edit({ embeds: [requestTicketEmbed], components: [selectMenuRow] }); - if (!guild.members.cache.get(i.user.id).roles.cache.has(publicRole.id)) { - await i.reply({ content: 'You do not have permissions to request tickets!', ephemeral: true }); - return; - } - const modal = new Modal() - .setCustomId('ticketSubmitModal') - .setTitle(i.values[0] === 'None of the above' ? 'Request a general mentor ticket' : 'Request a ticket for ' + i.values[0]) - .addComponents([ - new MessageActionRow().addComponents( - new TextInputComponent() - .setCustomId('ticketDescription') - .setLabel('Brief description of your problem') - .setMaxLength(300) - .setStyle('PARAGRAPH') - .setPlaceholder('Describe your problem here') - .setRequired(true), - ), - new MessageActionRow().addComponents( - new TextInputComponent() - .setCustomId('location') - .setLabel('Where would you like to meet your mentor?') - .setPlaceholder('Help your mentor find you!') - .setMaxLength(300) - .setStyle('PARAGRAPH') - .setRequired(true), - ) - ]); - await i.showModal(modal); - - const submitted = await i.awaitModalSubmit({ time: 300000, filter: j => j.user.id === i.user.id }) - .catch(() => { - }); - - if (submitted) { - const role = i.values[0] === 'None of the above' ? initBotInfo.roleIDs.mentorRole : guild.roles.cache.find(role => role.name.toLowerCase() === `M-${i.values[0]}`.toLowerCase()).id; - const description = submitted.fields.getTextInputValue('ticketDescription'); - const location = submitted.fields.getTextInputValue('location'); - // const helpFormat = submitted.fields.getTextInputValue('helpFormat'); - const mentorCaveDoc = firebaseUtil.getSavedMessagesSubCol(guild.id).doc('mentor-cave'); - const ticketNumber = (await mentorCaveDoc.get()).data()?.ticketCount ?? 1; - await mentorCaveDoc.set({ ticketCount: ticketNumber + 1 }, { merge: true }); - const newTicketEmbed = new MessageEmbed() - .setTitle('Ticket #' + ticketNumber) - .setColor('#d3d3d3') - .addFields([ - { - name: 'Problem description', - value: description - }, - { - name: 'Where to meet', - value: location - }, - // { - // name: 'OK with being helped online?', - // value: helpFormat - // } - ]); - const ticketAcceptanceRow = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('acceptIrl') - .setLabel('Accept ticket (in-person)') - .setStyle('PRIMARY'), - ); - // .addComponents( - // new MessageButton() - // .setCustomId('acceptOnline') - // .setLabel('Accept ticket (online) - Only use if hackers are OK with it!') - // .setStyle('PRIMARY'), - // ); - - const ticketMsg = await incomingTicketsChannel.send({ content: '<@&' + role + '>, requested by <@' + submitted.user.id + '>', embeds: [newTicketEmbed], components: [ticketAcceptanceRow] }); - submitted.reply({ content: 'Your ticket has been submitted!', ephemeral: true }); - const ticketReminder = setTimeout(() => { - ticketMsg.reply('<@&' + role + '> ticket ' + ticketNumber + ' still needs help!'); - }, reminderTime * 60000); - - const confirmationEmbed = new MessageEmbed() - .setTitle('Your ticket is number ' + ticketNumber) - .addFields([ - { - name: 'Problem description', - value: description - }, - { - name: 'Where to meet', - value: location - } - ]); - const deleteTicketRow = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('deleteTicket') - .setLabel('Delete ticket') - .setStyle('DANGER'), - ); - const ticketReceipt = await submitted.user.send({ embeds: [confirmationEmbed], content: 'You will be notified when a mentor accepts your ticket!', components: [deleteTicketRow] }); - const deleteTicketCollector = ticketReceipt.createMessageComponentCollector({ filter: i => !i.user.bot, max: 1 }); - deleteTicketCollector.on('collect', async deleteInteraction => { - await ticketMsg.edit({ embeds: [ticketMsg.embeds[0].setColor('#FFCCCB').addFields([{ name: 'Ticket closed', value: 'Deleted by hacker' }])], components: [] }); - clearTimeout(ticketReminder); - deleteInteraction.reply('Ticket deleted!'); - ticketReceipt.edit({ components: [] }); - }); - - const ticketAcceptFilter = i => !i.user.bot && i.isButton(); - const ticketAcceptanceCollector = ticketMsg.createMessageComponentCollector({ filter: ticketAcceptFilter }); - ticketAcceptanceCollector.on('collect', async acceptInteraction => { - const inProgressTicketEmbed = ticketMsg.embeds[0].setColor('#0096FF').addFields([{ name: 'Helped by:', value: '<@' + acceptInteraction.user.id + '>' }]); - if (acceptInteraction.customId === 'acceptIrl' || acceptInteraction.customId === 'acceptOnline') { - await ticketReceipt.edit({ components: [] }); - clearTimeout(ticketReminder); - ticketMsg.edit({ embeds: [inProgressTicketEmbed], components: [] }); - } - if (acceptInteraction.customId === 'acceptIrl') { - // TODO: mark as complete? - submitted.user.send('Your ticket number ' + ticketNumber + ' has been accepted by a mentor! They will be making their way to you shortly.'); - acceptInteraction.reply({ content: 'Thanks for accepting their ticket! Please head to their stated location. If you need to contact them, you can click on their username above to DM them!', ephemeral: true }); - } - // if (acceptInteraction.customId === 'acceptOnline') { - // submitted.user.send('Your ticket number ' + ticketNumber + ' has been accepted by a mentor! You should have gotten a ping from a new private channel. You can talk to your mentor there!'); - // acceptInteraction.reply({ content: 'Thanks for accepting their ticket! You should get a ping from a private channel for this ticket! You can help them there.', ephemeral: true }); - // let ticketChannelOverwrites = - // [{ - // id: this.botGuild.roleIDs.everyoneRole, - // deny: ['VIEW_CHANNEL'], - // }, - // { - // id: acceptInteraction.user.id, - // allow: ['VIEW_CHANNEL'], - // }, - // { - // id: submitted.user.id, - // allow: ['VIEW_CHANNEL'], - // }]; - - // let ticketCategory = await channel.guild.channels.create('Ticket-#' + ticketNumber, - // { - // type: 'GUILD_CATEGORY', - // permissionOverwrites: ticketChannelOverwrites - // } - // ); - - // const ticketText = await channel.guild.channels.create('ticket-' + ticketNumber, - // { - // type: 'GUILD_TEXT', - // parent: ticketCategory - // } - // ); - - // const ticketVoice = await channel.guild.channels.create('ticket-' + ticketNumber + '-voice', - // { - // type: 'GUILD_VOICE', - // parent: ticketCategory - // } - // ); - - // const ticketChannelEmbed = new MessageEmbed() - // .setColor(this.botGuild.colors.embedColor) - // .setTitle('Ticket description') - // .setDescription(submitted.fields.getTextInputValue('ticketDescription')); - - // const ticketChannelButtons = new MessageActionRow() - // .addComponents( - // new MessageButton() - // .setCustomId('addMembers') - // .setLabel('Add Members to Channels') - // .setStyle('PRIMARY'), - // ) - // .addComponents( - // new MessageButton() - // .setCustomId('leaveTicket') - // .setLabel('Leave') - // .setStyle('DANGER'), - // ); - // const ticketChannelInfoMsg = await ticketText.send({ content: `<@${acceptInteraction.user.id}><@${submitted.user.id}> These are your very own private channels! It is only visible to the admins of the server and any other users (i.e. teammates) you add to this channel with the button labeled "Add Members to Channels" below ⬇️. Feel free to discuss anything in this channel or the attached voice channel. **Please click the "Leave" button below when you are done to leave these channels**\n\n**Note: these channels may be deleted if they appear to be inactive for a significant period of time, even if not everyone has left**`, embeds: [ticketChannelEmbed], components: [ticketChannelButtons] }); - // ticketChannelInfoMsg.pin(); - - // const ticketChannelCollector = ticketChannelInfoMsg.createMessageComponentCollector({ filter: notBotFilter }); - // ticketChannelCollector.on('collect', async ticketInteraction => { - // if (ticketInteraction.customId === 'addMembers') { - // ticketInteraction.reply({ content: 'Tag the users you would like to add to the channel! (You can mention them by typing @ and then paste in their username with the tag)', ephemeral: true, fetchReply: true }) - // .then(() => { - // const awaitMessageFilter = i => i.user.id === ticketInteraction.user.id; - // ticketInteraction.channel.awaitMessages({ awaitMessageFilter, max: 1, time: 60000, errors: ['time'] }) - // .then(async collected => { - // if (collected.first().mentions.members.size === 0) { - // await ticketInteraction.followUp({ content: 'You have not mentioned any users! Click the button again to try again.' }); - // } else { - // var newMembersArray = []; - // collected.first().mentions.members.forEach(member => { - // ticketCategory.permissionOverwrites.edit(member.id, { VIEW_CHANNEL: true }); - // newMembersArray.push(member.id); - // }); - // ticketInteraction.channel.send('<@' + newMembersArray.join('><@') + '> Welcome to the channel! You have been invited to join the discussion for this ticket. Check the pinned message for more details.'); - // } - // }) - // .catch(collected => { - // ticketInteraction.followUp({ content: 'Timed out. Click the button again to try again.', ephemeral: true }); - // }); - // }); - // } else if (ticketInteraction.customId === 'leaveTicket' && guild.members.cache.get(ticketInteraction.user.id).roles.cache.has(this.botGuild.roleIDs.adminRole) ) { - // await ticketCategory.permissionOverwrites.edit(ticketInteraction.user.id, { VIEW_CHANNEL: false }); - // ticketInteraction.reply({ content: 'Successfully left the channel!', ephemeral: true }); - // if (ticketCategory.members.filter(member => !member.roles.cache.has(this.botGuild.roleIDs.adminRole) && !member.user.bot).size === 0) { - // const leftTicketEmbed = ticketMsg.embeds[0].setColor('#90EE90').addFields([{ name: 'Ticket closed', value: 'Everyone has left the ticket' }]); - // await this.deleteTicketChannels(ticketText, ticketVoice, ticketCategory, ticketMsg, leftTicketEmbed); - // } - // } else { - // ticketInteraction.reply({ content: 'You are an admin, you cannot leave this channel!', ephemeral: true }); - // } - // }); - // this.startChannelActivityListener(ticketText, ticketVoice, ticketCategory, ticketMsg, inactivePeriod, bufferTime); - // } - }); - } - - } - }); -} - -/** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Guild} guild - * @param {Message} adminControls - * @param {GuildBasedChannel} adminConsole - * @param {Message} roleSelectionMsg - * @param {Message} requestTicketConsole - */ -function listenToAdminControls( - initBotInfo, - guild, - adminControls, - adminConsole, - roleSelectionMsg, - requestTicketConsole -) { - const mentorRoleColour = guild.roles.cache.find(role => role.id === initBotInfo.roleIDs.mentorRole).hexColor; - const adminCollector = adminControls.createMessageComponentCollector({ filter: i => !i.user.bot && i.member.roles.cache.has(initBotInfo.roleIDs.adminRole) }); - adminCollector.on('collect', async (adminInteraction) => { - if (adminInteraction.customId === 'addRole') { - const askForRoleName = await adminInteraction.reply({ content: `<@${adminInteraction.user.id}> name of role to add? Type "cancel" to cancel this operation.`, fetchReply: true }); - const roleNameCollector = adminConsole.createMessageCollector({ filter: m => m.author.id === adminInteraction.user.id, max: 1 }); - let roleName; - roleNameCollector.on('collect', async collected => { - if (collected.content.toLowerCase() != 'cancel') { - roleName = collected.content.replace(/\s+/g, '-').toLowerCase(); - const roleExists = guild.roles.cache.filter(role => { - role.name === `M-${roleName}`; - }).size != 0; - if (!roleExists) { - await guild.roles.create( - { - name: `M-${roleName}`, - color: mentorRoleColour, - } - ); - } - - const askForEmoji = await adminConsole.send(`<@${adminInteraction.user.id}> React to this message with the emoji for the role!`); - const emojiCollector = askForEmoji.createReactionCollector({ filter: (reaction, user) => user.id === adminInteraction.user.id }); - emojiCollector.on('collect', (collected) => { - if (emojisMap.has(collected.emoji.name)) { - adminConsole.send(`<@${adminInteraction.user.id}> Emoji is already used in another role. Please react again.`).then(msg => { - setTimeout(() => msg.delete(), 5000); - }); - } else { - emojiCollector.stop(); - firebaseUtil.getSavedMessagesSubCol(guild.id).doc('mentor-cave').set({ - extraEmojis: { - [collected.emoji.name]: roleName - } - }, { merge: true }); - emojisMap.set(collected.emoji.name, roleName); - adminConsole.send(`<@${adminInteraction.user.id}> Role added!`).then(msg => { - setTimeout(() => msg.delete(), 5000); - }); - roleSelectionMsg.edit({ embeds: [new MessageEmbed(makeRoleSelectionEmbed())] }); - roleSelectionMsg.react(collected.emoji.name); - - const selectMenuOptions = makeSelectMenuRow().components[0].options; - const newSelectMenuRow = - new MessageActionRow().addComponents( - new MessageSelectMenu() - .setCustomId('ticketType') - .addOptions(selectMenuOptions) - ); - requestTicketConsole.edit({ components: [newSelectMenuRow] }); - askForEmoji.delete(); - } - }); - } - askForRoleName.delete(); - collected.delete(); - - }); - } - }); -} - -module.exports = StartMentorCave; \ No newline at end of file diff --git a/commands/a_start_commands/start-team-formation.js b/commands/a_start_commands/start-team-formation.js deleted file mode 100644 index e24d7c5c..00000000 --- a/commands/a_start_commands/start-team-formation.js +++ /dev/null @@ -1,87 +0,0 @@ -// Discord.js commando requirements -const PermissionCommand = require('../../classes/permission-command'); -const { Message } = require('discord.js'); -const TeamFormation = require('../../classes/Bot/Features/Team_Formation/team-formation'); -const Activity = require('../../classes/Bot/activities/activity'); -const { sendMsgToChannel } = require('../../discord-services'); -const { StringPrompt, SpecialPrompt, RolePrompt } = require('advanced-discord.js-prompts'); - -/** - * The team formation activity is the most basic team formation activity available. This activity works like a menu or directory of available teams and solo participants. - * To join, a participant reacts to a message. The bot then sends instructions via DM, including a set of questions the user must respond to and send back to the bot. The responses - * are then sent to either a looking-for-team channel or looking-for-members channel. Other parties can then browse these channels and create teams over DMs. Members cannot send - * messages to these channels. - * There is an option for users in the activity to be notified of new posts of interest. For example, a team leader will get notified of new solo participants looking for a team. - * When someone finds a team, they can go back to their DMs with the bot and react to a message to remove their post from the channels and stop receiving notifications of new posts. - * @category Commands - * @subcategory Start-Commands - * @extends PermissionCommand - * @guildonly - */ -class StartTeamFormation extends PermissionCommand { - constructor(client) { - super(client, { - name: 'start-team-formation', - group: 'a_start_commands', - memberName: 'start team formation', - description: 'Send a message with emoji collector, one emoji for recruiters, one emoji for team searchers. Instructions will be sent via DM.', - guildOnly: true, - }, - { - role: PermissionCommand.FLAGS.ADMIN_ROLE, - roleMessage: 'Hey there, the !start-team-formation command is only for admins!', - channel: PermissionCommand.FLAGS.ADMIN_CONSOLE, - channelMessage: 'Hey there, the !start-team-formation command is only available in the admin console.', - }); - } - - /** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - the message in which the command was run - */ - async runCommand(initBotInfo, message) { - // helpful prompt vars - let channel = message.channel; - let userId = message.author.id; - - let activityRoles = await Activity.promptForRoleParticipants(channel, userId, true); - - try { - - var teamFormation = new TeamFormation({ - teamInfo: { - emoji: await SpecialPrompt.singleEmoji({prompt: 'What emoji do you want to use for teams to sign up?', channel, userId}), - role: (await SpecialPrompt.boolean({prompt: 'Have you created the role teams will get when they sign up? If not its okay, I will create it for you!', channel, userId})) ? - await RolePrompt.single({prompt: 'What role should team users get?', channel, userId}) : - await TeamFormation.createTeamRole(message.guild.roles), - form: (await SpecialPrompt.boolean({ prompt: `Would you like to use the default form?: ${TeamFormation.defaultTeamForm}\n else you create your own!`, channel, userId})) ? - TeamFormation.defaultTeamForm : await StringPrompt.single({ prompt: 'Please send your form for teams now:', channel, userId }), - }, - prospectInfo: { - emoji: await SpecialPrompt.singleEmoji({prompt: 'What emoji do you want to use for prospects to sign up?', channel, userId}), - role: (await SpecialPrompt.boolean({prompt: 'Have you created the role prospects will get when they sign up? Worry not if you don\'t I can create it for you!', channel, userId})) ? - await RolePrompt.single({prompt: 'What role should prospects get?', channel, userId}) : - await TeamFormation.createProspectRole(message.guild.roles), - form: (await SpecialPrompt.boolean({ prompt: `Would you like to use the default form?: ${TeamFormation.defaultProspectForm}\n else you create your own!`, channel, userId})) ? - TeamFormation.defaultProspectForm : await StringPrompt.single({ prompt: 'Please send your form for teams now:', channel, userId }), - }, - guild: message.guild, - initBotInfo, - activityRoles, - isNotificationsEnabled: await SpecialPrompt.boolean({prompt: 'Do you want to notify users when the opposite party has a new post?', channel, userId}), - }); - - } catch (error) { - console.log(error); - message.channel.send('<@' + message.author.id + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000})); - return; - } - - await teamFormation.init(); - - sendMsgToChannel(message.channel, userId, `The team formation activity is ready to go! <#${teamFormation.channels.info.id}>`, 10); - - await teamFormation.start(); - } -} -module.exports = StartTeamFormation; diff --git a/commands/a_start_commands/start-team-roulette.js b/commands/a_start_commands/start-team-roulette.js deleted file mode 100644 index 03ce8313..00000000 --- a/commands/a_start_commands/start-team-roulette.js +++ /dev/null @@ -1,473 +0,0 @@ -const PermissionCommand = require('../../classes/permission-command'); -const { sendEmbedToMember } = require('../../discord-services'); -const { TextChannel, Snowflake, Message, MessageEmbed, Collection, GuildChannelManager, User } = require('discord.js'); -const Team = require('../../classes/Bot/Features/Team_Roulette/team'); -const { MemberPrompt, SpecialPrompt, ChannelPrompt } = require('advanced-discord.js-prompts'); - -/** - * The team roulette activity is a special type of team formation activity. Users can join the activity by reacting to a message embed (console). They can join - * as a solo or a group of up to 3 members (them included). The bot will then create teams of 4 as they become available. - * When a team is created, the new team members are invited to a text channel only available to them. Users can leave the team and the bot will - * add a new member from the list (if any available). - * Admins can check the list of users waiting on a team by reacting to a message embed (console) sent to the admin channel. - * @category Commands - * @subcategory Start-Commands - * @extends PermissionCommand - */ -class StartTeamRoulette extends PermissionCommand { - constructor(client) { - super(client, { - name: 'start-team-roulette', - group: 'a_start_commands', - memberName: 'start team roulette', - description: 'Send a message with emoji collector, solos, duos or triplets can join to get assigned a random team.', - guildOnly: true, - }, - { - role: PermissionCommand.FLAGS.ADMIN_ROLE, - roleMessage: 'Hey there, the !start-team-roulette command is only for staff!', - channel: PermissionCommand.FLAGS.ADMIN_CONSOLE, - channelMessage: 'Hey there, th !start-team-roulette command is only available on the admin console.', - }); - - // collection of reaction collectors listening for team leaders to delete teams; used for scope so collectors can be stopped - // when a team forms - this.destroyCollectors = new Collection(); - } - - /** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - the message in which the command was run - */ - async runCommand(initBotInfo, message) { - - this.initBotInfo = initBotInfo; - - /** - * The solo join emoji. - * @type {String} - an emoji string - */ - this.soloEmoji = '🏃🏽'; - - /** - * The non solo join emoji. - * @type {String} - an emoji string - */ - this.teamEmoji = '👯'; - - /** - * The team list from which to create teams. - * @type {Collection>} - - */ - this.teamList = new Collection(); - - /** - * The current team number. - * @type {Number} - */ - this.teamNumber = 0; - - /** - * All the users that have participated in the activity. - * @type {Collection} - */ - this.participants = new Collection(); - - /** - * Channel used to send information about team roulette. - * @type {TextChannel} - */ - this.textChannel; - - this.initList(); - - try { - // ask for channel to use, this will also give us the category to use - this.textChannel = await this.getOrCreateChannel(message.channel, message.author.id, message.guild.channels); - } catch (error) { - message.channel.send('<@' + message.author.id + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000})); - return; - } - - // create and send embed message to channel with emoji collector - const msgEmbed = new MessageEmbed() - .setColor(this.initBotInfo.colors.embedColor) - .setTitle('Team Roulette Information') - .setDescription('Welcome to the team roulette section! If you are looking to join a random team, you are in the right place!') - .addField('How does this work?', 'Reacting to this message will get you or your team on a list. I will try to assign you a team of 4 as fast as possible. When I do I will notify you on a private text channel with your new team!') - .addField('Disclaimer!!', 'By participating in this activity, you will be assigned a random team with random hackers! You can only use this activity once!') - .addField('If you are solo', 'React with ' + this.soloEmoji + ' and I will send you instructions.') - .addField('If you are in a team of two or three', 'React with ' + this.teamEmoji + ' and I will send you instructions.'); - - var cardMessage = await this.textChannel.send(msgEmbed); - cardMessage.react(this.soloEmoji); - cardMessage.react(this.teamEmoji); - - // collect form reaction collector and its filter - const emojiFilter = (reaction, user) => !user.bot && (reaction.emoji.name === this.soloEmoji || reaction.emoji.name === this.teamEmoji); - var mainCollector = cardMessage.createReactionCollector(emojiFilter); - - mainCollector.on('collect', async (reaction, teamLeaderUser) => { - // creator check - if (this.participants.has(teamLeaderUser.id)) { - sendEmbedToMember(teamLeaderUser, { - title: 'Team Roulette', - description: 'You are already signed up on the team roulette activity!', - }, true); - return; - } - - // add team or solo to their team - let newTeam = new Team(this.teamNumber); - this.teamNumber ++; - - // add team leader - newTeam.addTeamMember(teamLeaderUser); - - // the emoji used to leave a team - let leaveTeamEmoji = '👎'; - // the emoji used to remove a team from the roulette - let destroyTeamEmoji = '🛑'; - - if (reaction.emoji.name === this.teamEmoji) { - - try { - var groupMembers = await MemberPrompt.multi({ - prompt: 'Please mention all your current team members in one message.', - channel: this.textChannel, userId: teamLeaderUser.id, - time: 30, - }); - } catch (error) { - reaction.users.remove(newTeam.leader); - return; - } - - // remove any self mentions - groupMembers.delete(newTeam.leader); - - // check if they have more than 4 team members - if (groupMembers.size > 2) { - sendEmbedToMember(teamLeaderUser, { - title: 'Team Roulette', - description: 'You just tried to use the team roulette, but you mentioned more than 2 members. That should mean you have a team of 4 already! If you mentioned yourself by accident, try again!', - }, true); - return; - } - - // delete any mentions of users already in the activity. - groupMembers.forEach((teamMember, index) => { - if (this.participants.has(teamMember.id)) { - sendEmbedToMember(teamLeaderUser, { - title: 'Team Roulette', - description: 'We had to remove ' + teamMember.username + ' from your team roulette team because he already participated in the roulette.', - }, true); - } else { - // push member to the team list and activity list - newTeam.addTeamMember(teamMember); - this.participants.set(teamMember.id, teamMember); - - sendEmbedToMember(teamMember, { - title: 'Team Roulette', - description: 'You have been added to ' + teamLeaderUser.username + ' team roulette team! I will ping you as soon as I find a team for all of you!', - color: '#57f542', - fields: [{ - title:'Leave the team', - description: 'To leave the team please react to this message with ' + leaveTeamEmoji, - }] - }).then(memberMsg => { - memberMsg.react(leaveTeamEmoji); - - // reaction to leave the team only works before the team has been completed!! - memberMsg.awaitReactions((reaction, user) => !user.bot && !newTeam.hasBeenComplete && !newTeam.deleted && reaction.emoji.name === leaveTeamEmoji, {max: 1}).then(reactions => { - // remove member from list - let newSize = this.removeMemberFromTeam(newTeam, teamMember); - - memberMsg.delete(); - - // add team without users to correct teamList and notify team leader - if(newSize > 0) { - this.teamList.get(newSize).push(newTeam); - sendEmbedToMember(newTeam.members.get(newTeam.leader), { - title: 'Team Roulette', - description: teamMember.username + ' has left the team, but worry not, we are still working on getting you a team!', - }); - } - - }); - }); - } - }); - } - - this.teamList.get(newTeam.size()).push(newTeam); - - // add team leader to activity list and notify of success - this.participants.set(teamLeaderUser.id, teamLeaderUser); - let leaderDM = await sendEmbedToMember(teamLeaderUser, { - title: 'Team Roulette', - description: 'You' + (reaction.emoji.name === this.teamEmoji ? ' and your team' : '') + ' have been added to the roulette. I will get back to you as soon as I have a team for you!', - color: '#57f542', - fields: [{ - title: 'Destroy your team', - description: 'If you want to leave the roulette queue react to this message with ' + destroyTeamEmoji + '\n' - + 'Note that once you destroy your team, you will have to re-join the roulette and wait for longer!', - }] - }); - leaderDM.react(destroyTeamEmoji); - - // reaction to destroy the team only works before the team is completed - const destroyTeamCollector = leaderDM.createReactionCollector((reaction,user) => !user.bot && !newTeam.hasBeenComplete && reaction.emoji.name === destroyTeamEmoji, {max: 1}); - this.destroyCollectors.set(teamLeaderUser.id, destroyTeamCollector); - destroyTeamCollector.on('collect', (reaction, leader) => { - // remove team from team list - this.teamList.get(newTeam.size()).splice(this.teamList.get(newTeam.size()).indexOf(newTeam), 1); - - // remove leader DM - leaderDM.delete(); - - // mark the team as deleted - newTeam.deleted = true; - - // notify users of team deletion - newTeam.members.forEach(user => { - this.participants.delete(user.id); - if (user.id === leader.id) { - sendEmbedToMember(leader, { - title: 'Team Roulette', - description: 'Your team has been removed from the roulette!', - }, true); - } else { - sendEmbedToMember(user, { - title: 'Team Roulette', - description: 'Your team with <@' + newTeam.leader + '> has been destroyed!', - }); - } - }); - }); - this.runTeamCreator(message.guild.channels); - }); - } - - /** - * Ask user if new channels are needed, if so create them, else ask for current channels to use for TR. - * @param {TextChannel} promptChannel - channel to prompt on - * @param {Snowflake} promptId - user's ID to prompt - * @param {GuildChannelManager} guildChannelManager - manager to create channels - * @async - * @returns {Promise} - * @throws Throws an error if the user cancels either of the two Prompts, the command should quit! - */ - async getOrCreateChannel(promptChannel, promptId, guildChannelManager) { - let needChannel = await SpecialPrompt.boolean({prompt: 'Do you need a new channel and category or have you created one already?', channel: promptChannel, userId: promptId}); - - let channel; - - if (needChannel) { - let category = await guildChannelManager.create('Team Roulette', { - type: 'category', - }); - - channel = await guildChannelManager.create('team-roulette-info', { - type: 'text', - topic: 'Channel should only be used for team roulette.', - parent: category, - }); - } else { - channel = await ChannelPrompt.single({prompt: 'What channel would you like to use for team roulette, this channels category will be used for the new team channels.', channel: promptChannel, userId: promptId}); - channel.bulkDelete(100, true); - } - - // let user know everything is good to go - let listEmoji = '📰'; - - const adminEmbed = new MessageEmbed() - .setColor(this.initBotInfo.colors.embedColor) - .setTitle('Team Roulette Console') - .setDescription('Team roulette is ready and operational! <#' + channel.id + '>.') - .addField('Check the list!', 'React with ' + listEmoji + ' to get a message with the roulette team lists.'); - - let adminEmbedMsg = await promptChannel.send(adminEmbed); - adminEmbedMsg.react(listEmoji); - - // emoji reaction to send team roulette information - let adminEmbedMsgCollector = adminEmbedMsg.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === listEmoji); - adminEmbedMsgCollector.on('collect', (reaction, user) => { - reaction.users.remove(user.id); - - let infoEmbed = new MessageEmbed() - .setColor(this.initBotInfo.colors.embedColor) - .setTitle('Team Roulette Information') - .setDescription('These are all the teams that are still waiting.'); - - // loop over each list type and add them to one field - this.teamList.forEach((teams, key) => { - let teamListString = ''; - - teams.forEach((team, index) => { - teamListString += team.toString() + ' ; '; - }); - - infoEmbed.addField('Lists of size: ' + key, '[ ' + teamListString + ' ]'); - }); - - promptChannel.send(infoEmbed); - }); - - // add channel to black list - this.initBotInfo.blackList.set(channel.id, 5000); - this.initBotInfo.save(); - return channel; - } - - - /** - * Will remove the team member from the team, notify the user of success, and remove the team from the teamList - * @param {Team} team - the team to remove user from - * @param {User} teamMember - the user to remove from the team - * @returns {Number} - the new size of the team - */ - removeMemberFromTeam(team, teamMember) { - // remove the team from the list - if (!team.isComplete()) this.teamList.get(team.size()).splice(this.teamList.get(team.size()).indexOf(team), 1); - - // remove user from team and notify - this.participants.delete(teamMember.id); - let newSize = team.removeTeamMember(teamMember); - sendEmbedToMember(teamMember, { - title: 'Team Roulette', - description: 'You have been removed from the team!' - }, true); - return newSize; - } - - /** - * Will try to create a team and set them up for success! - * @param {GuildChannelManager} channelManager - * @async - */ - async runTeamCreator(channelManager) { - // call the team creator - let team = await this.findTeam(); - - // if no team then just return - if (!team) return; - - // if team does NOT have a text channel - if (!team?.textChannel) { - // disable the ability to destroy a team after team has been formed - team.members.forEach((user,id) => { - if (this.destroyCollectors.has(id)) { - this.destroyCollectors.get(id).stop(); - this.destroyCollectors.delete(id); - } - }); - - let privateChannelCategory = this.textChannel.parent; - - await team.createTextChannel(channelManager, privateChannelCategory); - - let leaveEmoji = '👋'; - - const infoEmbed = new MessageEmbed() - .setColor(this.initBotInfo.colors.embedColor) - .setTitle('WELCOME TO YOUR NEW TEAM!!!') - .setDescription('This is your new team, please get to know each other by creating a voice channel in a new Discord server or via this text channel. Best of luck!') - .addField('Leave the Team', 'If you would like to leave this team react to this message with ' + leaveEmoji); - - let teamCard = await team.textChannel.send(infoEmbed); - - let teamCardCollection = teamCard.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === leaveEmoji); - - teamCardCollection.on('collect', (reaction, exitUser) => { - // remove user from team - this.removeMemberFromTeam(team, exitUser); - - // search for more members depending on new team size - if (team.size()) { - team.textChannel.send('<@' + exitUser.id + '> Has left the group, but worry not, we are working on getting you more members!'); - - this.teamList.get(team.size()).push(team); - this.runTeamCreator(channelManager); - } - }); - } - } - - - /** - * Will try to create teams with the current groups signed up! - * @param {Number} teamSize - the size of the new team - * @private - * @returns {Promise} - * @async - */ - async findTeam(teamSize) { - let newTeam; - - if (teamSize === 3) newTeam = await this.assignGroupOf3(); - else if (teamSize === 2) newTeam = await this.assignGroupOf2(); - else { - if (this.teamList.get(3).length >=1) newTeam = await this.assignGroupOf3(); - else if (this.teamList.get(2).length >= 1) newTeam = await this.assignGroupOf2(); - else newTeam = await this.assignGroupsOf1(); - } - return newTeam; - } - - - /** - * Will assign a team of 3 with a team of 1. - * @returns {Promise} - * @requires this.groupList to have a team of 3. - * @async - */ - async assignGroupOf3() { - let listOf1 = this.teamList.get(1); - if (listOf1.length === 0) return null; - let teamOf3 = this.teamList.get(3).shift(); - return await teamOf3.mergeTeam(listOf1.shift()); - } - - /** - * Will assign a team of 2 with a team of 2 or two of 1 - * @returns {Promise} - * @requires this.groupList to have a team of 2 - * @async - */ - async assignGroupOf2() { - let listOf2 = this.teamList.get(2); - if (listOf2.length >= 2) { - return listOf2.shift().mergeTeam(listOf2.shift()); - } else { - let listOf1 = this.teamList.get(1); - if (listOf1.length <= 1) return null; - return await (await listOf2.shift().mergeTeam(listOf1.shift())).mergeTeam(listOf1.shift()); - } - } - - /** - * Assigns 4 groups of 1 together. - * @returns {Promise} - * @async - */ - async assignGroupsOf1() { - let groupOf1 = this.teamList.get(1); - if (groupOf1.length < 4) return null; - else return await (await (await groupOf1.shift().mergeTeam(groupOf1.shift())).mergeTeam(groupOf1.shift())).mergeTeam(groupOf1.shift()); - } - - - /** - * Initializes the team list by creating three key value pairs. - * 1 -> empty array - * 2 -> empty array - * 3 -> empty array - * @private - */ - initList() { - this.teamList.set(1, []); - this.teamList.set(2, []); - this.teamList.set(3, []); - } -} -module.exports = StartTeamRoulette; diff --git a/commands/a_utility/change-pre-fix.js b/commands/a_utility/change-pre-fix.js deleted file mode 100644 index be1d6eee..00000000 --- a/commands/a_utility/change-pre-fix.js +++ /dev/null @@ -1,43 +0,0 @@ -/** DEPRECATED */ - -const { Message } = require('discord.js'); -const PermissionCommand = require('../../classes/permission-command'); -const { StringPrompt } = require('advanced-discord.js-prompts'); - -/** - * Gives admin the ability to change the prefix used in the guild by the bot. - * @category Commands - * @subcategory Admin-Utility - * @extends PermissionCommand - * @guildonly - */ -class ChangePreFix extends PermissionCommand { - constructor(client) { - super(client, { - name: 'change-prefix', - group: 'a_utility', - memberName: 'change guild prefix', - description: 'Change the prefix used in this guild by the bot.', - guildOnly: true, - }, { - role: PermissionCommand.FLAGS.STAFF_ROLE, - }); - } - - /** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - */ - async runCommand(initBotInfo, message) { - let options = ['!', '#', '$', '%', '&', '?', '|', '°']; - - let prefix = StringPrompt.restricted({ prompt: 'What would you like to use as the prefix?', channel: message.channel, userId: message.author.id }, options); - - initBotInfo.prefix = prefix; - initBotInfo.save(); - - message.guild.commandPrefix = prefix; - - } -} -module.exports = ChangePreFix; \ No newline at end of file diff --git a/commands/a_utility/clear-chat.js b/commands/a_utility/clear-chat.js deleted file mode 100644 index 4b0f25ed..00000000 --- a/commands/a_utility/clear-chat.js +++ /dev/null @@ -1,46 +0,0 @@ -const { Command } = require('@sapphire/framework'); -const { discordLog } = require('../../discord-services'); - -class ClearChat extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Clear most recent 100 messages younger than 2 weeks.' - }); - } - - /** - * - * @param {Command.Registry} registry - */ - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addBooleanOption((option) => - option // - .setName('keep_pinned') - .setDescription('If true any pinned messages will not be removed')) - ); - } - - /** - * @param {Command.ChatInputInteraction} interaction - */ - async chatInputRun (interaction) { - const keepPinned = interaction.options.getBoolean('keep_pinned', false); - - if (keepPinned) { - // other option is to get all channel messages, filter of the pined channels and pass those to bulkDelete, might be to costly? - var messagesToDelete = interaction.channel.messages.cache.filter(msg => !msg.pinned); - await interaction.channel.bulkDelete(messagesToDelete, true).catch(console.error); - } else { - // delete messages and log to console - await interaction.channel.bulkDelete(100, true).catch(console.error); - } - discordLog(interaction.guild, 'CHANNEL CLEAR ' + interaction.channel.name); - return interaction.reply({ content: 'Messages successfully deleted' }); - } -} -module.exports = ClearChat; diff --git a/commands/a_utility/organizer-check-in.js b/commands/a_utility/organizer-check-in.js deleted file mode 100644 index bbda818c..00000000 --- a/commands/a_utility/organizer-check-in.js +++ /dev/null @@ -1,165 +0,0 @@ -const { Command } = require('@sapphire/framework'); -const { Interaction, MessageEmbed, Guild, Message, MessageActionRow, MessageButton } = require('discord.js'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); - -/** - * The organizer-check-in command lets organizers check in/out for attendance. It uses the organizerAttendance - * field in the InitBotInfo document to store the list of organizers present. organizerAttendance is a map of - * organizer username to organizer display name. - * @category Commands - * @subcategory Admin-Utility - * @extends Command - */ -class OrganizerCheckIn extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Let organizers check in/out for attendance.' - }); - } - - /** - * - * @param {Command.Registry} registry - */ - registerApplicationCommands(registry) { - registry.registerChatInputCommand( - (builder) => - builder.setName(this.name).setDescription(this.description), - { - idHints: '1344923545154617357', - } - ); - } - - /** - * - * @param {Interaction} interaction - */ - async chatInputRun(interaction) { - const guild = interaction.guild; - this.initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - const userId = interaction.user.id; - if (!guild.members.cache.get(userId).roles.cache.has(this.initBotInfo.roleIDs.staffRole) && !guild.members.cache.get(userId).roles.cache.has(this.initBotInfo.roleIDs.adminRole)) { - interaction.reply({ content: 'You do not have permissions to run this command!', ephemeral: true }); - return; - } - - const row = new MessageActionRow().addComponents( - new MessageButton() - .setCustomId('check-in') - .setLabel('Check In') - .setStyle('PRIMARY') - ).addComponents( - new MessageButton() - .setCustomId('check-out') - .setLabel('Check Out') - .setStyle('PRIMARY') - ); - - const embed = generateAttendanceEmbed(this.initBotInfo); - let checkInPanel = await interaction.channel.send({ - content: 'Make sure to check in/out when you enter and leave the venue!', - components: [row], - embeds: [embed] - }); - interaction.reply({content: 'Organizer check-in started!', ephemeral: true}); - - await listenToReactions(guild, checkInPanel); - - const savedMessagesCol = firebaseUtil.getSavedMessagesSubCol(guild.id); - await savedMessagesCol.doc('organizer-check-in').set({ - messageId: checkInPanel.id, - channelId: checkInPanel.channel.id, - }); - } - - /** - * Checks Firebase for an existing stored panel listener - - * restores the listeners for the panel if it exists, otherwise does nothing - * @param {Guild} guild - */ - async tryRestoreReactionListeners(guild) { - const savedMessagesSubCol = firebaseUtil.getSavedMessagesSubCol(guild.id); - const organizerCheckInDoc = await savedMessagesSubCol.doc('organizer-check-in').get(); - if (organizerCheckInDoc.exists) { - const { messageId, channelId } = organizerCheckInDoc.data(); - const channel = await this.container.client.channels.fetch(channelId); - if (channel) { - try { - const initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - - /** @type {Message} */ - const message = await channel.messages.fetch(messageId); - const updatedEmbed = generateAttendanceEmbed(initBotInfo); - await message.edit({ embeds: [updatedEmbed] }); - await listenToReactions(guild, message); - } catch (e) { - // message doesn't exist anymore - return e; - } - } else { - return 'Saved message channel does not exist'; - } - } else { - return 'No existing saved message for organizer check-in command'; - } - } -} - -/** - * Generates an embed with the current list of organizers present - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @returns {MessageEmbed} - */ -function generateAttendanceEmbed(initBotInfo) { - /** @type {string[]} */ - const organizerAttendance = Object.values(initBotInfo.organizerAttendance ?? {}); - organizerAttendance.sort(); - const formattedMessage = organizerAttendance.map(organizer => `- ${organizer}`).join('\n'); - - const embed = new MessageEmbed() - .setColor('#0DEFE1') - .setTitle('Organizers Present') - .setDescription(formattedMessage); - return embed; -} - -/** - * Adds button listeners to a message to check in/out for organizer attendance - * @param {Guild} guild - * @param {Message} message - */ -async function listenToReactions(guild, message) { - const initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - - const filter = (i) => - !i.user.bot && - (guild.members.cache - .get(i.user.id) - .roles.cache.has(initBotInfo.roleIDs.staffRole) || - guild.members.cache - .get(i.user.id) - .roles.cache.has(initBotInfo.roleIDs.adminRole)); - - // create collector - const collector = message.createMessageComponentCollector({ filter }); - collector.on('collect', async i => { - const newInitBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - if (!newInitBotInfo.organizerAttendance) newInitBotInfo.organizerAttendance = {}; - - const user = guild.members.cache.get(i.user.id); - if (i.customId == 'check-in') { - newInitBotInfo.organizerAttendance[user.user.username] = user.displayName; - await i.reply({ content: `Checked in ${user.displayName}`, ephemeral: true }); - } else if (i.customId == 'check-out') { - delete newInitBotInfo.organizerAttendance[user.user.username]; - await i.reply({ content: `Checked out ${user.displayName}`, ephemeral: true }); - } - await firebaseUtil.updateOrganizerAttendance(guild.id, newInitBotInfo.organizerAttendance); - const updatedEmbed = generateAttendanceEmbed(newInitBotInfo); - await message.edit({ embeds: [updatedEmbed] }); - }); -} - -module.exports = OrganizerCheckIn; \ No newline at end of file diff --git a/commands/a_utility/pronouns.js b/commands/a_utility/pronouns.js deleted file mode 100644 index 8a9423cf..00000000 --- a/commands/a_utility/pronouns.js +++ /dev/null @@ -1,198 +0,0 @@ -const { Command } = require('@sapphire/framework'); -const { Interaction, MessageEmbed, PermissionFlagsBits, Guild, Message, MessageManager } = require('discord.js'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); - -const emojis = ['1️⃣', '2️⃣', '3️⃣', '4️⃣']; - -/** - * The pronouns command sends a role reaction console for users to select a pronoun role out of 4 options: - * * she/her - * * he/him - * * they/them - * * other pronouns - * @category Commands - * @subcategory Admin-Utility - * @extends Command - */ -class Pronouns extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Start pronoun selector.' - }); - } - - /** - * - * @param {Command.Registry} registry - */ - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description), - { - idHints: '1051737347441569813' - }); - } - - /** - * - * @param {Interaction} interaction - */ - async chatInputRun(interaction) { - const guild = interaction.guild; - this.initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - const userId = interaction.user.id; - if (!guild.members.cache.get(userId).roles.cache.has(this.initBotInfo.roleIDs.staffRole) && !guild.members.cache.get(userId).roles.cache.has(this.initBotInfo.roleIDs.adminRole)) { - interaction.reply({ content: 'You do not have permissions to run this command!', ephemeral: true }); - return; - } - - const { sheRole, heRole, theyRole, otherRole } = await getPronounRoles(guild); - - // check to make sure all 4 roles are available - if (!sheRole || !heRole || !theyRole || !otherRole) { - interaction.reply('Could not find all four roles! Make sure the role names are exactly like stated on the documentation.'); - return; - } - - let embed = new MessageEmbed() - .setColor('#0DEFE1') - .setTitle('Set your pronouns by reacting to one or more of the emojis!') - .setDescription( - `${emojis[0]} she/her\n` - + `${emojis[1]} he/him\n` - + `${emojis[2]} they/them\n` - + `${emojis[3]} other pronouns\n`); - - let messageEmbed = await interaction.channel.send({embeds: [embed]}); - emojis.forEach(emoji => messageEmbed.react(emoji)); - interaction.reply({content: 'Pronouns selector started!', ephemeral: true}); - - listenToReactions(guild, messageEmbed); - - const savedMessagesCol = firebaseUtil.getSavedMessagesSubCol(guild.id); - await savedMessagesCol.doc('pronouns').set({ - messageId: messageEmbed.id, - channelId: messageEmbed.channel.id, - }); - } - - /** - * Checks Firebase for an existing stored reaction listener - - * restores the listeners for the reaction if it exists, otherwise does nothing - * @param {Guild} guild - */ - async tryRestoreReactionListeners(guild) { - const savedMessagesSubCol = firebaseUtil.getSavedMessagesSubCol(guild.id); - const pronounDoc = await savedMessagesSubCol.doc('pronouns').get(); - if (pronounDoc.exists) { - const { messageId, channelId } = pronounDoc.data(); - const channel = await this.container.client.channels.fetch(channelId); - if (channel) { - try { - /** @type {Message} */ - const message = await channel.messages.fetch(messageId); - listenToReactions(guild, message); - } catch (e) { - // message doesn't exist anymore - return e; - } - } else { - return 'Saved message channel does not exist'; - } - } else { - return 'No existing saved message for pronouns command'; - } - } -} - -/** - * - * @param {Guild} guild - */ -async function getPronounRoles(guild) { - const sheRole = guild.roles.cache.find(role => role.name === 'she/her') || await createRole(guild, 'she/her', '#99AAB5'); - const heRole = guild.roles.cache.find(role => role.name === 'he/him') || await createRole(guild, 'he/him', '#99AAB5'); - const theyRole = guild.roles.cache.find(role => role.name === 'they/them') || await createRole(guild, 'they/them', '#99AAB5'); - const otherRole = guild.roles.cache.find(role => role.name === 'other pronouns') || await createRole(guild, 'other pronouns', '#99AAB5'); - return { sheRole, heRole, theyRole, otherRole }; -} - -/** - * Creates a role in the guild with the specified name and color. - * @param {Guild} guild - * @param {string} name - * @param {string} color - */ -async function createRole(guild, name, color) { - try { - const role = await guild.roles.create({ - name: name, - color: color, - reason: `Creating ${name} role for pronoun selector` - }); - return role; - } catch (error) { - console.error(`Failed to create role ${name}:`, error); - throw new Error(`Could not create role ${name}`); - } -} - -/** - * Adds reaction listeners to a message to add/remove pronoun rules from users upon reacting - * @param {Guild} guild - * @param {Message} message - */ -async function listenToReactions(guild, message) { - const { sheRole, heRole, theyRole, otherRole } = await getPronounRoles(guild); - - let filter = (reaction, user) => { - return user.bot != true && emojis.includes(reaction.emoji.name); - }; - - // create collector - const reactionCollector = message.createReactionCollector({filter, dispose: true}); - - // on emoji reaction - reactionCollector.on('collect', async (reaction, user) => { - if (reaction.emoji.name === emojis[0]) { - const member = guild.members.cache.get(user.id); - await member.roles.add(sheRole); - } - if (reaction.emoji.name === emojis[1]) { - const member = guild.members.cache.get(user.id); - await member.roles.add(heRole); - } - if (reaction.emoji.name === emojis[2]) { - const member = guild.members.cache.get(user.id); - await member.roles.add(theyRole); - } - if (reaction.emoji.name === emojis[3]) { - const member = guild.members.cache.get(user.id); - await member.roles.add(otherRole); - } - }); - - reactionCollector.on('remove', async (reaction, user) => { - if (reaction.emoji.name === emojis[0]) { - const member = guild.members.cache.get(user.id); - await member.roles.remove(sheRole); - } - if (reaction.emoji.name === emojis[1]) { - const member = guild.members.cache.get(user.id); - await member.roles.remove(heRole); - } - if (reaction.emoji.name === emojis[2]) { - const member = guild.members.cache.get(user.id); - await member.roles.remove(theyRole); - } - if (reaction.emoji.name === emojis[3]) { - const member = guild.members.cache.get(user.id); - await member.roles.remove(otherRole); - } - }); -} - -module.exports = Pronouns; \ No newline at end of file diff --git a/commands/a_utility/role-selector.js b/commands/a_utility/role-selector.js deleted file mode 100644 index 421b6236..00000000 --- a/commands/a_utility/role-selector.js +++ /dev/null @@ -1,299 +0,0 @@ -// Discord.js commando requirements -const PermissionCommand = require('../../classes/permission-command'); -const { Command } = require('@sapphire/framework'); -const { Message, MessageEmbed, MessageActionRow, Modal, TextInputComponent, MessageSelectMenu, Guild } = require('discord.js'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); - -const newTransferEmoji = '🆕'; -const emojisMap = new Map(); - -/** - * Make a message embed (console) available on the channel for users to react and un-react for roles. Staff can dynamically add - * roles to the console. Users can react to get the role, then un-react to loose the role. - * @category Commands - * @subcategory Admin-Utility - * @extends PermissionCommand - */ -class RoleSelector extends PermissionCommand { - constructor(context, options) { - super(context, { - ...options, - name: 'role-selector', - description: 'Will let users transfer roles. Useful for sponsor reps that are also mentors!', - }, { - role: PermissionCommand.FLAGS.STAFF_ROLE, - roleMessage: 'Hey there, the command !role-selector is only available to staff!', - }); - } - - /** - * - * @param {Command.Registry} registry - */ - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description), - { idHints: '1262581324657725521' }); - } - - /** - * - * @param {Command.ChatInputInteraction} interaction - */ - async chatInputRun(interaction) { - const guild = interaction.guild; - const userId = interaction.user.id; - const initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - - if (!guild.members.cache.get(userId).roles.cache.has(initBotInfo.roleIDs.staffRole) && !guild.members.cache.get(userId).roles.cache.has(initBotInfo.roleIDs.adminRole)) { - await interaction.reply({ content: 'You do not have permissions to run this command!', ephemeral: true }); - return; - } - - const savedMessagesCol = firebaseUtil.getSavedMessagesSubCol(guild.id); - const roleSelectorDoc = await savedMessagesCol.doc('role-selector').get(); - const roleSelectorData = roleSelectorDoc.data(); - if (roleSelectorData?.emojis) { - for (const [emoji, name] of Object.entries( - roleSelectorData.emojis - )) { - emojisMap.set(emoji, name); - } - } - - let embed = await makeRoleSelectionEmbed(); - - let messageEmbed = await interaction.channel.send({embeds: [embed] }); - messageEmbed.react(newTransferEmoji); - - for (const key of emojisMap.keys()) { - messageEmbed.react(key); - } - - listenToRoleSelectorReaction(initBotInfo, guild, messageEmbed); - - savedMessagesCol.doc('role-selector').set({ - messageId: messageEmbed.id, - channelId: messageEmbed.channelId - }, { merge: true }); - - interaction.reply({content: 'Role selector created!', ephemeral: true}); - } - - /** - * Checks Firebase for an existing stored reaction listener - - * restores the listeners for the reaction if it exists, otherwise does nothing - * @param {Guild} guild - */ - async tryRestoreReactionListeners(guild) { - const savedMessagesCol = firebaseUtil.getSavedMessagesSubCol(guild.id); - const roleSelectorDoc = await savedMessagesCol.doc('role-selector').get(); - if (roleSelectorDoc.exists) { - const { messageId, channelId, emojis } = roleSelectorDoc.data(); - - if (emojis) { - for (const [emoji, name] of Object.entries(emojis)) { - emojisMap.set(emoji, name); - } - } - const channel = await this.container.client.channels.fetch(channelId); - if (channel) { - try { - /** @type {Message} */ - const message = await channel.messages.fetch(messageId); - const initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - listenToRoleSelectorReaction(initBotInfo, guild, message); - } catch (e) { - // message doesn't exist anymore - return e; - } - } else { - return 'Saved message channel does not exist'; - } - } else { - return 'No existing saved message for role-selector command'; - } - } -} - -async function makeRoleSelectionEmbed() { - const roleSelectionEmbed = new MessageEmbed() - .setTitle('Role Selector!') - .setDescription( - 'React to the specified emoji to get the role, un-react to remove the role.' - ); - - if (emojisMap.size > 0) { - roleSelectionEmbed.setFields( - Array.from(emojisMap).map(([k, v]) => ({name: k, value: `${v.title} - ${v.description}`})) - ); - } - - return roleSelectionEmbed; -} - -/** - * - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Guild} guild - * @param {Message} messageEmbed - */ -async function listenToRoleSelectorReaction( - initBotInfo, - guild, - messageEmbed -) { - const reactionCollector = messageEmbed.createReactionCollector({ - dispose: true, - }); - - reactionCollector.on('collect', async (reaction, user) => { - if (user.bot) return; - if ( - !guild.members.cache - .get(user.id) - .roles.cache.has(initBotInfo.roleIDs.staffRole) && - !guild.members.cache - .get(user.id) - .roles.cache.has(initBotInfo.roleIDs.adminRole) - ) { - await user.send({ - content: 'You do not have permissions to run this command!', - ephemeral: true, - }); - return; - } - if (reaction.emoji.name === newTransferEmoji) { - const allRoles = await guild.roles.fetch(); - const roleSelectRow = new MessageActionRow().setComponents( - new MessageSelectMenu() - .setOptions( - ...allRoles - .map((r) => ({ - label: r.name, - value: r.id, - })) - .slice(-25) - ) - .setCustomId('transfer_role') - ); - const roleSelectEmbed = new MessageEmbed() - .setTitle('New role selector') - .setDescription( - 'Select the role that you want to add to the role selector embed:' - ); - - const roleSelectMenu = await user.send({ - components: [roleSelectRow], - embeds: [roleSelectEmbed], - }); - - const roleSelectListener = roleSelectMenu.createMessageComponentCollector(); - roleSelectListener.on('collect', async (i) => { - const role = await guild.roles.fetch(i.values[0]); - if (i.customId === 'transfer_role') { - if ( - !guild.members.cache - .get(i.user.id) - .roles.cache.has(initBotInfo.roleIDs.staffRole) && - !guild.members.cache - .get(i.user.id) - .roles.cache.has(initBotInfo.roleIDs.adminRole) - ) { - await i.reply({ - content: 'You do not have permissions to run this command!', - ephemeral: true, - }); - return; - } - const newReactionModal = new Modal() - .setTitle('New role reaction for ' + role.name) - .setCustomId('new_role') - .setComponents( - new MessageActionRow().setComponents( - new TextInputComponent() - .setLabel('What is the transfer title?') - .setStyle('SHORT') - .setRequired(true) - .setCustomId('transfer_title') - ), - new MessageActionRow().setComponents( - new TextInputComponent() - .setLabel('What is the transfer description?') - .setStyle('SHORT') - .setRequired(true) - .setCustomId('transfer_desc') - ) - ); - await i.showModal(newReactionModal); - - const submitted = await i - .awaitModalSubmit({ - time: 300000, - filter: (j) => j.user.id === i.user.id, - }) - .catch(() => {}); - - if (submitted) { - await roleSelectMenu.delete(); - const title = submitted.fields.getTextInputValue('transfer_title'); - const description = submitted.fields.getTextInputValue('transfer_desc'); - - await submitted.reply('New role selector details successfully submitted!'); - - const askForEmoji = await user.send('React to this message with the emoji for the role!'); - const emojiCollector = askForEmoji.createReactionCollector(); - emojiCollector.on('collect', async (reaction, user) => { - const savedMessagesCol = firebaseUtil.getSavedMessagesSubCol(guild.id); - const roleSelectorData = (await savedMessagesCol.doc('role-selector').get()).data(); - if (roleSelectorData?.emojis && reaction.emoji.name in roleSelectorData.emojis) { - user.send('Emoji is already used in another role. Please react again.').then(msg => { - setTimeout(() => msg.delete(), 5000); - }); - } else { - emojiCollector.stop(); - firebaseUtil.getSavedMessagesSubCol(guild.id).doc('role-selector').set({ - emojis: { - [reaction.emoji.name]: { - title, - description, - roleId: role.id - } - } - }, { merge: true }); - emojisMap.set(reaction.emoji.name, { title, description, roleId: role.id }); - user.send('Role added!').then(msg => { - setTimeout(() => msg.delete(), 5000); - }); - messageEmbed.edit({ embeds: [new MessageEmbed(await makeRoleSelectionEmbed())] }); - messageEmbed.react(reaction.emoji.name); - - askForEmoji.delete(); - } - }); - } - } - }); - } else { - if (emojisMap.has(reaction.emoji.name)) { - const value = emojisMap.get(reaction.emoji.name); - const findRole = await guild.roles.cache.get(value.roleId); - await guild.members.cache.get(user.id).roles.add(findRole); - } - } - }); - reactionCollector.on('remove', async (reaction, user) => { - if (emojisMap.has(reaction.emoji.name)) { - const member = guild.members.cache.get(user.id); - const value = emojisMap.get(reaction.emoji.name); - const findRole = await member.roles.cache.get(value.roleId); - if (findRole) - await guild.members.cache.get(user.id).roles.remove(findRole); - } - }); -} - -module.exports = RoleSelector; - diff --git a/commands/a_utility/self-care.js b/commands/a_utility/self-care.js deleted file mode 100644 index 5e7f04a8..00000000 --- a/commands/a_utility/self-care.js +++ /dev/null @@ -1,148 +0,0 @@ -const { Command } = require('@sapphire/framework'); -const { discordLog } = require('../../discord-services'); -const { MessageEmbed, MessageActionRow, MessageButton } = require('discord.js'); -const { getReminder } = require('../../db/firebase/firebaseUtil'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); -const { Message } = require('discord.js'); - -/** - * The self care command will send pre made reminders from firebase to the command channel. These reminders are self - * care reminders. Will prompt a role to mention with each reminder. We recommend that be an opt-in role. - * @category Commands - * @subcategory Admin-Utility - * @extends Command - */ -class SelfCareReminders extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Sends self-care reminders at designated times.', - }); - } - - /** - * - * @param {Command.Registry} registry - */ - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addIntegerOption(option => - option.setName('interval') - .setDescription('Time (minutes) between reminders') - .setRequired(true)) - .addRoleOption(option => - option.setName('notify') - .setDescription('Role to notify when a reminder drops') - .setRequired(false)) - .addBooleanOption(option => - option.setName('start_reminder_now') - .setDescription('True to start first reminder now, false to start it after one interval') - .setRequired(false)) - ), - { - idHints: '1052699103143927859' - }; - } - - /** - * - * @param {Command.ChatInputInteraction} interaction - */ - async chatInputRun(interaction) { - let interval; - - let channel = interaction.channel; - let userId = interaction.user.id; - let guild = interaction.guild; - let initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - let adminConsole = await guild.channels.fetch(initBotInfo.channelIDs.adminConsole); - - let timeInterval = interaction.options.getInteger('interval') * 60000; - let startNow = interaction.options.getBoolean('start_reminder_now'); - let roleId = interaction.options.getRole('notify'); - - if (!guild.members.cache.get(userId).roles.cache.has(initBotInfo.roleIDs.staffRole) && !guild.members.cache.get(userId).roles.cache.has(initBotInfo.roleIDs.adminRole)) { - interaction.reply({ message: 'You do not have permissions to run this command!', ephemeral: true }); - return; - } - - // keeps track of whether it has been paused - let paused = false; - - const startEmbed = new MessageEmbed() - .setColor(initBotInfo.embedColor) - .setTitle('To encourage healthy hackathon habits, we will be sending hourly self-care reminders!'); - - interaction.reply({ content: 'Self-care reminders started!', ephemeral: true }); - - roleId ? interaction.channel.send({ content: '<@&' + roleId + '>', embeds: [startEmbed] }) : interaction.channel.send({ embeds: [startEmbed] }); - - const row = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('play') - .setLabel('Play') - .setStyle('PRIMARY'), - ) - .addComponents( - new MessageButton() - .setCustomId('pause') - .setLabel('Pause') - .setStyle('PRIMARY'), - ); - - /** @type {Message} */ - const controlPanel = await adminConsole.send({ content: 'Self care reminders started by <@' + userId + '>', components: [row] }); - const isAdminOrStaffFilter = (i) => !i.user.bot && (guild.members.cache.get(i.user.id).roles.cache.has(initBotInfo.roleIDs.staffRole) || guild.members.cache.get(i.user.id).roles.cache.has(initBotInfo.roleIDs.adminRole)); - const collector = controlPanel.createMessageComponentCollector({ filter: isAdminOrStaffFilter }); - collector.on('collect', async (i) => { - if (interval != null && !paused && i.customId == 'pause') { - clearInterval(interval); - paused = true; - discordLog(guild, 'Self care reminders paused by <@' + i.user.id + '>!'); - await i.reply({ content: 'Self care reminders has been paused!', ephemeral: true }); - } else if (paused && i.customId == 'play') { - await sendReminder(initBotInfo); - interval = setInterval(sendReminder, timeInterval, initBotInfo); - paused = false; - discordLog(guild, 'Self care reminders restarted by <@' + i.user.id + '>!'); - await i.reply({ content: 'Self care reminders has been un-paused!', ephemeral: true }); - } else { - await i.reply({ content: 'Wrong button or wrong permissions!', ephemeral: true }); - } - }); - - - //starts the interval, and sends the first reminder immediately if startNow is true - if (startNow) { - sendReminder(initBotInfo); - } - interval = setInterval(sendReminder, timeInterval, initBotInfo); - - // sendReminder is the function that picks and sends the next reminder - async function sendReminder(initBotInfo) { - //get reminders parameters from db - const data = await getReminder(guild.id); - - //report in admin logs that there are no more messages - //TODO: consider having it just loop through the db again? - if (data === null) { - discordLog(guild, '<@&' + initBotInfo.roleIDs.staffRole + '> HI, PLEASE FEED ME more self-care messages!!'); - clearInterval(interval); - return; - } - - let reminder = data.reminder; - - const qEmbed = new MessageEmbed() - .setColor(initBotInfo.embedColor) - .setTitle(reminder); - - roleId ? channel.send({ content: 'Hey <@&' + roleId + '> remember:', embeds: [qEmbed] }) : channel.send({ embeds: [qEmbed] }); - } - } -} -module.exports = SelfCareReminders; diff --git a/commands/a_utility/set-bot-activity.js b/commands/a_utility/set-bot-activity.js deleted file mode 100644 index c54791fc..00000000 --- a/commands/a_utility/set-bot-activity.js +++ /dev/null @@ -1,42 +0,0 @@ -const PermissionCommand = require('../../classes/permission-command'); -const { discordLog } = require('../../discord-services'); -const { Message } = require('discord.js'); - -/** - * The !set-bot-activity will set the bot's activity. - * @category Commands - * @subcategory Admin-Utility - * @extends PermissionCommand - */ -class SetBotActivity extends PermissionCommand { - constructor(client) { - super(client, { - name: 'set-bot-activity', - group: 'a_utility', - memberName: 'set bot activity', - description: 'Sets the bot activity.', - guildOnly: true, - args: [{ - key: 'status', - prompt: 'the bot status', - type: 'string', - default: 'nwplus.github.io/Factotum', - }] - }, - { - role: PermissionCommand.FLAGS.STAFF_ROLE, - roleMessage: 'Hey there, the command !set-bot-activity is only available to Staff!', - }); - } - - /** - * @param {Message} message - the command message - * @param {Object} args - * @param {String} args.status - */ - - async runCommand(initBotInfo, message, {status}) { - this.client.user.setActivity(status, {type: 'PLAYING'}); - } -} -module.exports = SetBotActivity; diff --git a/commands/attendance/attend.js b/commands/attendance/attend.js deleted file mode 100644 index 551b7a4f..00000000 --- a/commands/attendance/attend.js +++ /dev/null @@ -1,64 +0,0 @@ -const PermissionCommand = require('../../classes/permission-command'); -const { Message } = require('discord.js'); -const { checkForRole, sendEmbedToMember } = require('../../discord-services'); -const Verification = require('../../classes/Bot/Features/Verification/verification'); - -/** - * Attends the user who runs this command. The user must have the guild ID. Can only be run - * via DMs. - * @category Commands - * @subcategory Verification - * @extends PermissionCommand - * @dmonly - */ -class Attend extends PermissionCommand { - constructor(client) { - super(client, { - name: 'attend', - group: 'attendance', - memberName: 'hacker attendance', - description: 'Will mark a hacker as attending and upgrade role to Attendee. Can only be called once!', - args: [ - { - key: 'guildId', - prompt: 'Please provide the server ID, ask admins for it!', - type: 'integer', - }, - ], - }, - { - dmOnly: true - }); - } - - /** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - * @param {Object} args - * @param {String} args.guildId - */ - async runCommand(initBotInfo, message, { guildId }) { - // check if the user needs to attend, else warn and return - if (checkForRole(message.author, initBotInfo.attendance.attendeeRoleID)) { - sendEmbedToMember(message.author, { - title: 'Attend Error', - description: 'You do not need to attend! Happy hacking!!!' - }, true); - return; - } - - let guild = this.client.guilds.cache.get(guildId); - if (!guild) { - sendEmbedToMember(message.author, { - title: 'Attendance Failure', - description: 'The given server ID is not valid. Please try again!', - }); - return; - } - let member = guild.member(message.author.id); - - // call the firebase services attendHacker function - Verification.attend(member, initBotInfo); - } -} -module.exports = Attend; \ No newline at end of file diff --git a/commands/attendance/start-attend.js b/commands/attendance/start-attend.js deleted file mode 100644 index 8a99c4a9..00000000 --- a/commands/attendance/start-attend.js +++ /dev/null @@ -1,108 +0,0 @@ -const PermissionCommand = require('../../classes/permission-command'); -const { checkForRole, sendEmbedToMember } = require('../../discord-services'); -const { MessageEmbed, Message } = require('discord.js'); -const Verification = require('../../classes/Bot/Features/Verification/verification'); -const BotGuildModel = require('../../classes/Bot/bot-guild'); -const { StringPrompt, SpecialPrompt, ChannelPrompt } = require('advanced-discord.js-prompts'); - -/** - * StartAttend makes a new channel called #attend, or uses an existing channel of the user's choice, as the channel where an embed - * is sent for users to react and get attend. Users don't need to send any information to attend. - * @category Commands - * @subcategory Verification - * @extends PermissionCommand - * @guildonly - */ -class StartAttend extends PermissionCommand { - constructor(client) { - super(client, { - name: 'start-attend', - group: 'attendance', - memberName: 'initiate attend process', - description: 'identifies/makes a channel to be used for !attend and notifies people', - guildOnly: true, - }, - { - channel: PermissionCommand.FLAGS.ADMIN_CONSOLE, - channelMessage: 'This command can only be used in the admin console!', - role: PermissionCommand.FLAGS.ADMIN_ROLE, - roleMessage: 'Hey there, the command !start-attend is only available to Admins!', - }); - } - - /** - * If existsChannel is true, asks user to indicate the channel to use. Else asks user to indicate the category under which the - * channel should be created, and then creates it. In both cases it will send an embed containing the instructions for hackers to - * check in. - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - message containing command - */ - async runCommand(initBotInfo, message) { - var channel; - - // register the attend command just in case its needed - message.guild.setCommandEnabled('attend', true); - - try { - let existsChannel = await SpecialPrompt.boolean({prompt: 'Is there already a channel that exists that hackers will be using !attend in?', channel: message.channel, userId: message.author.id, cancelable: true}); - - if (existsChannel) { - //ask user to mention channel to be used for !attend - channel = await ChannelPrompt.single({prompt: 'Please mention the channel to be used for the !attend command. ', channel: message.channel, userId: message.author.id, cancelable: true}); - } else { - //ask user for category to create new attend channel under - var categoryName = await StringPrompt.single({prompt: 'What category do you want the new attend channel under? ', channel: message.channel, userId: message.author.id, cancelable: true}); - - let category = message.guild.channels.cache.find(c => c.type == 'category' && c.name.toLowerCase() == categoryName.toLowerCase()); - if (!category) { - message.channel.send('Invalid category name. Please try the command again.') - .then((msg) => msg.delete({timeout: 3000})); - return; - } - - //create the channel - channel = await message.guild.channels.create('attend', { - parent: category, - topic: 'Channel to attend the event!', - }); - } - } catch (error) { - message.channel.send('<@' + message.author.id + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000})); - return; - } - - channel.updateOverwrite(initBotInfo.roleIDs.everyoneRole, { SEND_MESSAGES: false }); - - //send embed with information and tagging hackers - let attendEmoji = '🔋'; - - const embed = new MessageEmbed() - .setColor(initBotInfo.colors.embedColor) - .setTitle('Hey there!') - .setDescription('In order to indicate that you are participating, please react to this message with ' + attendEmoji) - .addField('Do you need assistance?', 'Head over to the support channel and ping the admins!'); - let embedMsg = await channel.send('<@&' + initBotInfo.roleIDs.memberRole + '>', {embed: embed}); - embedMsg.pin(); - embedMsg.react(attendEmoji); - initBotInfo.blackList.set(channel.id, 1000); - initBotInfo.save(); - - // reaction collector to attend hackers - let embedMsgCollector = embedMsg.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === attendEmoji); - - embedMsgCollector.on('collect', (reaction, user) => { - let member = message.guild.member(user.id); - - // check if user needs to attend - if (!checkForRole(member, initBotInfo.attendance.attendeeRoleID)) { - Verification.attend(member, initBotInfo); - } else { - sendEmbedToMember(member, { - title: 'Attend Error', - description: 'You do not need to attend, you are already attending or you are not a hacker!' - }, true); - } - }); - } -} -module.exports = StartAttend; diff --git a/commands/essentials/help.js b/commands/essentials/help.js deleted file mode 100644 index b29d717b..00000000 --- a/commands/essentials/help.js +++ /dev/null @@ -1,82 +0,0 @@ -// Discord.js commando requirements -const { Command, CommandoGuild } = require('discord.js-commando'); -const { deleteMessage, checkForRole } = require('../../discord-services'); -const { MessageEmbed } = require('discord.js'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); - -/** - * The help command shows all the available commands for the user via DM message. - * @category Commands - * @subcategory Essentials - * @extends Command - */ -class Help extends Command { - constructor(client) { - super(client, { - name: 'help', - group: 'essentials', - memberName: 'help user', - description: 'Will send available commands depending on role!', - hidden: true, - }); - } - - /** - * @param {Message} message - */ - async run(message) { - - let initBotInfo = await firebaseUtil.getInitBotInfo(message.guild.id); - - /** @type {CommandoGuild} */ - let guild = message.guild; - - /** @type {Command[]} */ - var commands = []; - - var commandGroups; - - // if message on DM then send hacker commands - if (message.channel.type === 'dm') { - commandGroups = this.client.registry.findGroups('utility', true); - } else { - deleteMessage(message); - - if ((checkForRole(message.member, initBotInfo.roleIDs.staffRole))) { - commandGroups = this.client.registry.groups; - } else { - commandGroups = this.client.registry.findGroups('utility', true); - } - } - - // add all the commands from the command groups - commandGroups.forEach((value) => { - if (guild.isGroupEnabled(value)) { - value.commands.forEach((command, index) => { - if (guild.isCommandEnabled(command)) commands.push(command); - }); - } - }); - - var length = commands.length; - - const textEmbed = new MessageEmbed() - .setColor(initBotInfo.colors.embedColor) - .setTitle('Commands Available for you') - .setDescription('All other interactions with me will be via emoji reactions!') - .setTimestamp(); - - // add each command as a field in the embed - for (var i = 0; i < length; i++) { - let command = commands[i]; - if (command.format != null) { - textEmbed.addField(this.client.commandPrefix + command.name, command.description + ', arguments: ' + command.format); - } else { - textEmbed.addField(this.client.commandPrefix + command.name, command.description + ', no arguments'); - } - } - - message.author.send(textEmbed); - } -} -module.exports = Help; diff --git a/commands/essentials/init-bot.js b/commands/essentials/init-bot.js deleted file mode 100644 index ca290a25..00000000 --- a/commands/essentials/init-bot.js +++ /dev/null @@ -1,233 +0,0 @@ -const { Command } = require('@sapphire/framework'); -const { TextChannel, Snowflake, Guild, ColorResolvable, Role, Permissions, PermissionsBitField, PermissionFlagsBits } = require('discord.js'); -const { sendMsgToChannel, addRoleToMember, discordLog, } = require('../../discord-services'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); -const winston = require('winston'); -const fetch = require('node-fetch'); -const { MessagePrompt, StringPrompt, NumberPrompt, SpecialPrompt, RolePrompt, ChannelPrompt } = require('advanced-discord.js-prompts'); - -/** - * The InitBot command initializes the bot on the guild. It will prompt the user for information needed - * to set up the bot. It is only usable by server administrators. It can only be run once. - * @category Commands - * @subcategory Essentials - * @extends Command - */ -class InitBot extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Configurations for this guild.' - }); - } - - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName('init-bot') - .setDescription(this.description) - .addChannelOption(option => - option.setName('admin_console') - .setDescription('Mention the admin console channel') - .setRequired(true)) - .addRoleOption(option => - option.setName('admin') - .setDescription('Mention the admin role') - .setRequired(true)) - .addChannelOption(option => - option.setName('admin_log') - .setDescription('Mention the admin log channel') - .setRequired(true)) - .addRoleOption(option => - option.setName('staff') - .setDescription('Mention the staff role') - .setRequired(true)) - .addRoleOption(option => - option.setName('member') - .setDescription('Mention the member (general participant) role') - .setRequired(true)) - .addRoleOption(option => - option.setName('mentor') - .setDescription('Mention the mentor role') - .setRequired(true)) - .addBooleanOption(option => - option.setName('use_verification') - .setDescription('Whether verification will be used') - .setRequired(true)) - .addRoleOption(option => - option.setName('guest') - .setDescription('Mention the guest role.') - .setRequired(true)) - .addChannelOption(option => - option.setName('welcome_support_channel') - .setDescription('Mention the channel for verification issues (must be viewable to guests)!') - .setRequired(true)) - .addAttachmentOption(option => - option.setName('verification_roles') - .setDescription('File: array of objects! Each role string in the participants\' database and corresponding role ID.') - .setRequired(true)) - // .addBooleanOption(option => - // option.setName('use_stamps') - // .setDescription('Whether stamps will be used') - // .setRequired(true)) - // .addIntegerOption(option => - // option.setName('number_of_stamps') - // .setDescription('Number of stamps **if stamps is on**') - // .setRequired(false)) - // .addIntegerOption(option => - // option.setName('stamp_time') - // .setDescription('Time, in seconds, each stamp is open for claiming **if stamps is on**') - // .setRequired(false)) - // .addRoleOption(option => - // option.setName('0th_stamp_role') - // .setDescription('Mention the starting stamp role **if stamps is on**') - // .setRequired(false)) - .addStringOption(option => - option.setName('embed_colour') - .setDescription('Hex code of embed colour') - .setRequired(false)) - .addStringOption(option => - option.setName('hackathon_name') - .setDescription('Optional: Name of the hackathon') - .setRequired(false)) - ), - { - idHints: '1051737348502728764' - }; - } - - /** - * @param {Message} message - */ - async chatInputRun(interaction) { - await firebaseUtil.connect('Factotum'); - - await interaction.deferReply({ ephemeral: true }); - // easy constants to use - var channel = interaction.channel; - const userId = interaction.user.id; - /** @type {CommandoGuild} */ - const guild = interaction.guild; - const everyoneRole = interaction.guild.roles.everyone; - - const botGuildRef = firebaseUtil.getFactotumSubCol().doc(guild.id); - const hackathonName = interaction.options.getString('hackathon_name') || 'unspecified hackathon'; - - // make sure the user had manage server permission - if (!interaction.member.permissionsIn(interaction.channel).has('ADMINISTRATOR')) { - await interaction.followUp({ content: 'You do not have permissions to run this command!', ephemeral: true }); - return; - } - - const botGuildDoc = await botGuildRef.get(); - if (botGuildDoc.exists && botGuildDoc.data().isSetUpComplete) { - await interaction.followUp({ content: 'This server is already set up!', ephemeral: true }); - return; - } - - const initBotInfoRef = firebaseUtil.getFactotumSubCol(); - const duplicateQuery = await initBotInfoRef.where('hackathonName', '==', hackathonName).get(); - - if (!duplicateQuery.empty && hackathonName !== 'unspecified hackathon') { - await interaction.followUp({ - content: `The hackathon name "${hackathonName}" is already in use. Please use a different name or leave it unspecified.`, - ephemeral: true, - }); - return; - } - - const adminConsole = interaction.options.getChannel('admin_console'); - const admin = interaction.options.getRole('admin'); - const adminLog = interaction.options.getChannel('admin_log'); - const staff = interaction.options.getRole('staff'); - const member = interaction.options.getRole('member'); - const mentor = interaction.options.getRole('mentor'); - const useVerification = interaction.options.getBoolean('use_verification'); - let guest; - let welcomeSupportChannel; - let verificationRoles; - const verification = { - isEnabled: useVerification - }; - if (useVerification) { - guest = interaction.options.getRole('guest').id; - welcomeSupportChannel = interaction.options.getChannel('welcome_support_channel').id; - verificationRoles = interaction.options.getAttachment('verification_roles'); - try { - const response = await fetch(verificationRoles.url); - let res = await response.json(); - verification.roles = res; - verification.guestRoleID = guest; - verification.welcomeSupportChannel = welcomeSupportChannel; - } catch (error) { - console.error('error: ' + error); - await interaction.followUp({ - content: 'An error occurred with the file upload or verification roles upload!', - ephemeral: true, - }); - return; - } - } - // const useStamps = interaction.options.getBoolean('use_stamps'); - // let numberOfStamps; - // let stampTime; - // let firstStampRole; - // const stamps = { - // isEnabled: useStamps - // }; - // if (useStamps) { - // numberOfStamps = interaction.options.getInteger('number_of_stamps'); - // stampTime = interaction.options.getInteger('stamp_time'); - // firstStampRole = interaction.options.getInteger('0th_stamp_role'); - // botGuild.setUpStamps(this.client, numberOfStamps, stampTime, firstStampRole); - // } - const embedColor = interaction.options.getString('embed_colour') || '#26fff4'; - - // ask the user to move our role up the list - await interaction.followUp({content: 'Before we move on, could you please move my role up the role list as high as possible, this will give me the ability to assign roles!', ephemeral: true}); - await botGuildRef.set({ - verification, - embedColor, - roleIDs: { - adminRole: admin.id, - staffRole: staff.id, - everyoneRole: everyoneRole.id, - memberRole: member.id, - mentorRole: mentor.id - }, - channelIDs: { - adminLog: adminLog.id, - adminConsole: adminConsole.id - }, - hackathonName, - isSetUpComplete: true, - }); - - await interaction.followUp(`The bot is set and ready to hack for ${hackathonName}!`); - discordLog(guild, '<@' + userId + '> ran init-bot!'); - } - - /** - * @typedef TypeInfo - * @property {String} type - * @property {String} roleId - */ - - async getValidVerificationTypes(channel, userId, guestRole, memberRole) { - let typeMsg = await MessagePrompt.prompt({ - prompt: `Please tell me the type and mention the role for a verification option. - For example: hacker @hacker . Make sure you add nothing more to the message!`, channel, userId - }); - let type = typeMsg.content.replace(/<(@&?|#)[a-z0-9]*>/, ''); // clean out any snowflakes - type = type.toLowerCase().trim(); - let role = typeMsg.mentions.roles.first(); - - if (role.id === guestRole.id || role.id === memberRole.id) { - sendMsgToChannel(channel, userId, 'Guest and member roles cannot be used for verification. ' + - 'Please try again.', 30); - return await this.getValidVerificationTypes(channel, userId, guestRole, memberRole); - } - return { type, role }; - } -} -module.exports = InitBot; \ No newline at end of file diff --git a/commands/essentials/unknown-command.js b/commands/essentials/unknown-command.js deleted file mode 100644 index 247a4fa8..00000000 --- a/commands/essentials/unknown-command.js +++ /dev/null @@ -1,37 +0,0 @@ -// Discord.js commando requirements -const { Command } = require('discord.js-commando'); -const { deleteMessage } = require('../../discord-services'); -const { Message } = require('discord.js'); - -/** - * This unknown command is used by the bot when a unknown command is run. - * @category Commands - * @subcategory Essentials - * @extends Command - */ -class UnknownCommand extends Command { - constructor(client) { - super(client, { - name: 'unknown-command', - group: 'essentials', - memberName: 'unknown-command', - description: 'Displays help information when an unknown command is used.', - unknown: true, - hidden: true, - }); - } - - /** - * @param {Message} message - */ - async run(message) { - if (message.channel.type === 'dm') { - return; - } else { - deleteMessage(message); - message.reply('This is an unknown command!').then(msg => msg.delete({timeout: 3000})); - } - - } -} -module.exports = UnknownCommand; diff --git a/commands/firebase_scripts/load-questions.js b/commands/firebase_scripts/load-questions.js deleted file mode 100644 index 05325915..00000000 --- a/commands/firebase_scripts/load-questions.js +++ /dev/null @@ -1,57 +0,0 @@ -const { Command } = require('@sapphire/framework'); -require('dotenv').config(); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); -const fetch = require('node-fetch'); - -/** - * Loads discord contest questions from input JSON file - * @category Commands - * @subcategory Admin-Utility - * @extends Command - */ -class LoadQuestions extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Adds Discord Contest questions to database', - }); - } - - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addAttachmentOption(option => - option.setName('questions') - .setDescription('JSON file only! Make sure each question is an object.') - .setRequired(true)) - ), - { - idHints: '1171789829479079976', - }; - } - - async chatInputRun(interaction) { - // const adminSDK = JSON.parse(process.env.NWPLUSADMINSDK); - // let app = FirebaseServices.initializeFirebaseAdmin('factotum', adminSDK, 'https://nwplus-bot.firebaseio.com'); - - const guildId = interaction.guild.id; - const file = interaction.options.getAttachment('questions'); - await interaction.deferReply(); - try { - const response = await fetch(file.url); - const res = await response.json(); - - res.forEach(question => { - const docRef = firebaseUtil.getFactotumSubCol().doc(guildId) - .collection('Questions').doc(); - docRef.set({ ...question, asked: false }); - }); - await interaction.editReply({ content: res + ' questions added!', ephemeral: true }); - } catch (error) { - await interaction.editReply({ content: 'Something went wrong! Error msg: ' + error, ephemeral: true }); - } - } -} -module.exports = LoadQuestions; diff --git a/commands/hacker_utility/ask.js b/commands/hacker_utility/ask.js deleted file mode 100644 index a5cd7753..00000000 --- a/commands/hacker_utility/ask.js +++ /dev/null @@ -1,139 +0,0 @@ -// Discord.js commando requirements -const PermissionCommand = require('../../classes/permission-command'); -const { checkForRole, sendMessageToMember, } = require('../../discord-services'); -const { MessageEmbed, Collection, Message, } = require('discord.js'); - -/** - * The ask command tries to imitate a thread like functionality from slack. Users can ask questions, and then other - * users can respond to the question, the responses are added on the same message embed, to keep the conversation on - * the same message. - * @category Commands - * @subcategory Hacker-Utility - * @extends PermissionCommand - */ -class AskQuestion extends PermissionCommand { - constructor(client) { - super(client, { - name: 'ask', - group: 'hacker_utility', - memberName: 'ask anonymous question with thread', - description: 'Will send the question to the same channel, and add emoji collector for thread like support.', - guildOnly: true, - args: [ - { - key: 'question', - prompt: 'Question to ask', - type: 'string', - default: '', - } - ], - }); - } - - /** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - * @param {Object} args - * @param {String} args.question - */ - async runCommand(initBotInfo, message, {question}) { - - // if question is blank let user know via DM and exit - if (question === '') { - sendMessageToMember(message.member, 'When using the !ask command, add your question on the same message!\n' + - 'Like this: !ask This is a question'); - return; - } - - // get current channel - var curChannel = message.channel; - - // message embed to be used for question - const qEmbed = new MessageEmbed() - .setColor(initBotInfo.colors.questionEmbedColor) - .setTitle('Question from ' + message.author.username) - .setDescription(question); - - // send message and add emoji collector - curChannel.send(qEmbed).then(async (msg) => { - - // list of users currently responding - var onResponse = new Collection(); - - msg.react('🇷'); // respond emoji - msg.react('✅'); // answered emoji! - msg.react('⏫'); // up vote emoji - msg.react('⛔'); // delete emoji - - // filter and collector - const emojiFilter = (reaction, user) => !user.bot && (reaction.emoji.name === '🇷' || reaction.emoji.name === '✅' || reaction.emoji.name === '⛔'); - const collector = msg.createReactionCollector(emojiFilter); - - collector.on('collect', async (reaction, user) => { - // delete the reaction - reaction.users.remove(user.id); - - // add response to question - if (reaction.emoji.name === '🇷') { - // make sure user is not already responding - if (onResponse.has(user.id)) { - return; - } else { - onResponse.set(user.id, user.username); - } - - // prompt the response - curChannel.send('<@' + user.id + '> Please send your response within 15 seconds! If you want to cancel write cancel.').then(prompt => { - // filter and message await only one - // only user who reacted this message will be able to add a reply to it - curChannel.awaitMessages(m => m.author.id === user.id, {max: 1, time: 15000, errors: ['time']}).then((msgs) => { - var response = msgs.first(); - - // if cancel then do nothing - if (response.content.toLowerCase() != 'cancel') { - // if user has a mentor role, they get a special title - if (checkForRole(response.member, initBotInfo.roleIDs.staffRole)) { - msg.edit(msg.embeds[0].addField('🤓 ' + user.username + ' Responded:', response.content)); - } else { - // add a field to the message embed with the response - msg.edit(msg.embeds[0].addField(user.username + ' Responded:', response.content)); - } - } - - // delete messages - prompt.delete(); - response.delete(); - - // remove user from on response list - onResponse.delete(user.id); - }).catch((msgs) => { - prompt.delete(); - curChannel.send('<@' + user.id + '> Time is up! When you are ready to respond, emoji again!').then(msg => msg.delete({timeout: 2000})); - - // remove user from on response list - onResponse.delete(user.id); - }); - }); - } - // check for check-mark emoji and only user who asked the question - else if (reaction.emoji.name === '✅' && user.id === message.author.id) { - // change color - msg.embeds[0].setColor('#80c904'); - // change title and edit embed - msg.edit(msg.embeds[0].setTitle('✅ ANSWERED ' + msg.embeds[0].title)); - } - // remove emoji will remove the message - else if (reaction.emoji.name === '⛔') { - // check that user is staff - if (checkForRole(msg.guild.member(user), initBotInfo.roleIDs.staffRole)) { - msg.delete(); - } else { - sendMessageToMember(user, 'Deleting a question is only available to staff!', true); - } - - } - }); - }); - } -} -module.exports = AskQuestion; \ No newline at end of file diff --git a/commands/hacker_utility/start-report.js b/commands/hacker_utility/start-report.js deleted file mode 100644 index 4585bda6..00000000 --- a/commands/hacker_utility/start-report.js +++ /dev/null @@ -1,146 +0,0 @@ -const { Command } = require('@sapphire/framework'); -// const { deleteMessage, sendMessageToMember, } = require('../../discord-services'); -const { Guild, Message, MessageEmbed, Modal, MessageActionRow, MessageButton, TextInputComponent } = require('discord.js'); -const { discordLog } = require('../../discord-services'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); - -/** - * The report command allows users to report incidents from the server to the admins. Reports are made - * via the bot's DMs and are 100% anonymous. - * @category Commands - * @subcategory Hacker-Utility - * @extends Command - */ -class StartReport extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Will send report format to user via DM for user to send back via DM. Admins will get the report!', - }); - } - - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - ), - { - idHints: '1214159059880517652' - }; - } - - /** - * - * @param {Command.ChatInputInteraction} interaction - */ - async chatInputRun(interaction) { - // const userId = interaction.user.id; - - // const embed = new MessageEmbed() - // .setTitle(`See an issue you'd like to annoymously report at ${interaction.guild.name}? Let our organizers know!`); - - const embed = new MessageEmbed() - .setTitle('Anonymously report users who are not following server or MLH rules. Help makes our community safer!') - .setDescription('Please use the format below, be as precise and accurate as possible. \n ' + - 'Everything you say will be 100% anonymous. We have no way of reaching back to you so again, be as detailed as possible!\n' + - 'Copy paste the format and send it to me in this channel!') - .addFields({ - name: 'Format:', - value: 'User(s) discord username(s) (including discord id number(s)):\n' + - 'Reason for report (one line):\n' + - 'Detailed Explanation:\n' + - 'Name of channel where the incident occurred (if possible):' - }); - // modal timeout warning? - const row = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('report') - .setLabel('Report an issue') - .setStyle('PRIMARY'), - ); - interaction.reply({ content: 'Report started!', ephemeral: true }); - const msg = await interaction.channel.send({ embeds: [embed], components: [row] }); - - await this.listenToReports(interaction.guild, msg); - - const savedMessagesCol = firebaseUtil.getSavedMessagesSubCol(interaction.guild.id); - await savedMessagesCol.doc('report').set({ - messageId: msg.id, - channelId: msg.channel.id, - }); - } - - /** - * - * @param {Guild} guild - * @param {Message} msg - */ - async listenToReports(guild, msg) { - const checkInCollector = msg.createMessageComponentCollector({ filter: i => !i.user.bot}); - - checkInCollector.on('collect', async i => { - const modal = new Modal() - .setCustomId('reportModal') - .setTitle('Report an issue') - .addComponents([ - new MessageActionRow().addComponents( - new TextInputComponent() - .setCustomId('issueMessage') - .setLabel('Reason for report:') - .setMinLength(3) - .setMaxLength(1000) - .setStyle(2) - .setPlaceholder('Type your issue here...') - .setRequired(true), - ), - ]); - await i.showModal(modal); - - const submitted = await i.awaitModalSubmit({ time: 300000, filter: j => j.user.id === i.user.id }) - .catch(error => { - }); - - if (submitted) { - const issueMessage = submitted.fields.getTextInputValue('issueMessage'); - - try { - discordLog(guild, `<@&${guild.roleIDs.staffRole}> New anonymous report:\n\n ${issueMessage}`); - } catch { - discordLog(guild, `New anonymous report:\n\n ${issueMessage}`); - } - submitted.reply({ content: 'Thank you for taking the time to report users who are not following server or MLH rules. You help makes our community safer!', ephemeral: true }); - return; - } - }); - } - - /** - * - * @param {Guild} guild - */ - async tryRestoreReactionListeners(guild) { - const savedMessagesCol = firebaseUtil.getSavedMessagesSubCol(guild.id); - const reportDoc = await savedMessagesCol.doc('report').get(); - if (reportDoc.exists) { - const { messageId, channelId } = reportDoc.data(); - const channel = await this.container.client.channels.fetch(channelId); - if (channel) { - try { - /** @type {Message} */ - const message = await channel.messages.fetch(messageId); - this.listenToReports(guild, message); - } catch (e) { - // message doesn't exist anymore - return e; - } - } else { - return 'Saved message channel does not exist'; - } - } else { - return 'No existing saved message for pronouns command'; - } - } -} -module.exports = StartReport; \ No newline at end of file diff --git a/commands/stamps/change-stamp-time.js b/commands/stamps/change-stamp-time.js deleted file mode 100644 index 90e20928..00000000 --- a/commands/stamps/change-stamp-time.js +++ /dev/null @@ -1,47 +0,0 @@ -const PermissionCommand = require('../../classes/permission-command'); -const { replyAndDelete } = require('../../discord-services'); -const { Message } = require('discord.js'); - -/** - * Change the time users get to react to get a stamp from activity stamp distributions. It defaults to 60 seconds. - * @category Commands - * @subcategory Stamps - * @extends PermissionCommand - */ -class ChangeStampTime extends PermissionCommand { - constructor(client) { - super(client, { - name: 'change-stamp-time', - group: 'stamps', - memberName: 'new stamp time', - description: 'Will set the given seconds as the new stamp time for activities.', - guildOnly: true, - args: [ - { - key: 'newTime', - prompt: 'new time for stamp collectors to use', - type: 'integer', - }, - ], - }, - { - role: PermissionCommand.FLAGS.STAFF_ROLE, - roleMessage: 'Hey there, the command !change-stamp-time is only available to staff!', - }); - } - - /** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - * @param {Object} args - * @param {Number} args.newTime - */ - async runCommand(initBotInfo, message, {newTime}) { - - initBotInfo.stamps.stampCollectionTime = newTime; - initBotInfo.save(); - - replyAndDelete(message, 'Stamp collection will now give hackers ' + newTime + ' seconds to collect stamp.'); - } -} -module.exports = ChangeStampTime; diff --git a/commands/stamps/password-stamp.js b/commands/stamps/password-stamp.js deleted file mode 100644 index bd2d2cd9..00000000 --- a/commands/stamps/password-stamp.js +++ /dev/null @@ -1,163 +0,0 @@ -const { sendEmbedToMember, sendMessageToMember, deleteMessage } = require('../../discord-services'); -const { MessageEmbed, Message, Snowflake, Collection } = require('discord.js'); -const PermissionCommand = require('../../classes/permission-command'); -const StampsManager = require('../../classes/Bot/Features/Stamps/stamps-manager'); -const { StringPrompt, ChannelPrompt } = require('advanced-discord.js-prompts'); - -/** - * Sends a reaction collector for users to react, send a password and receive a stamp. Used to give out stamps for activities that don't have - * an activity instance. The user who starts the password stamp must give the activity name, password, and stop time defaults to 120 seconds. Users - * have 3 attempts to get the password right within the stop time. - * @category Commands - * @subcategory Stamps - * @extends PermissionCommand - */ -class PasswordStamp extends PermissionCommand { - constructor(client) { - super(client, { - name: 'password-stamp', - group: 'stamps', - memberName: 'gives stamps requiring passwords', - description: 'gives a stamp to everyone who reacted and gave the correct password', - args: [ - { key: 'activityName', - prompt: 'the workshop/activity name', - type: 'string', - default: '', - }, - { - key: 'password', - prompt: 'the password for hackers to use to get stamp', - type: 'string', - default: '', - }, - { - key: 'stopTime', - prompt: 'time for stamp collector to be open for, in minutes.', - type: 'integer', - default: 120, - } - ], - }, - { - channel: PermissionCommand.FLAGS.ADMIN_CONSOLE, - channelMessage: 'This command is only available on the admin console!', - role: PermissionCommand.FLAGS.STAFF_ROLE, - roleMessage: 'This command can only available to staff!', - }); - } - - /** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - * @param {Object} args - * @param {String} args.activityName - * @param {String} args.password - * @param {Number} args.stopTime - */ - async runCommand(initBotInfo, message, {activityName, password, stopTime}) { - // helpful vars - let channel = message.channel; - let userId = message.author.id; - // check if arguments have been given and prompt for the channel to use - try { - if (activityName === '') { - activityName = await StringPrompt.single({prompt: 'Please respond with the workshop/activity name.', channel, userId, cancelable: true}); - } - - if(password === '') { - password = await StringPrompt.single({prompt: 'Please respond with the password for hackers to use to get stamp.', channel, userId, cancelable: true}); - } - - var targetChannel = await ChannelPrompt.single({prompt: 'What channel do you want to send the stamp collector to? Users should have access to this channel!', channel, userId, cancelable: true}); - } catch (error) { - channel.send('<@' + userId + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000})); - return; - } - - const qEmbed = new MessageEmbed() - .setColor(initBotInfo.colors.embedColor) - .setTitle('React with anything to claim a stamp for attending ' + activityName) - .setDescription('Once you react to this message, check for a DM from this bot. **You can only emoji this message once!**') - .addField('A Password Is Required!', 'Through the Bot\'s DM, you will have 3 attempts in the first 60 seconds to enter the correct password.'); - - targetChannel.send(qEmbed).then((msg) => { - - let emoji = '👍'; - msg.react(emoji); - - /** - * keeps track of which users have already reacted to the message so there are no duplicates - * @type {Collection} - - */ - var seenUsers = new Collection(); - - // filter emoji reaction and collector - const emojiFilter = (reaction, user) => !user.bot && !seenUsers.has(user.id); - const collector = msg.createReactionCollector(emojiFilter, {time: (1000 * stopTime * 60)}); // stopTime is in minutes, multiply to get seconds, then milliseconds - - //send hacker a dm upon reaction - collector.on('collect', async(reaction, user) => { - seenUsers.set(user.id, user.username); - - const member = message.guild.member(user); - - // prompt member for password - var dmMessage = await sendEmbedToMember(user, { - description: 'You have 60 seconds and 3 attempts to type the password correctly to get the ' + activityName + ' stamp.\n' + - 'Please enter the password (leave no stray spaces or anything):', - title: 'Stamp Collector For ' + activityName, - color: '#b2ff2e', - }); - - var correctPassword = false; - var incorrectPasswords = 0; - - const filter = m => user.id === m.author.id; - //message collector for the user's password attempts - const pwdCollector = dmMessage.channel.createMessageCollector(filter,{time: 60000, max: 3}); - - pwdCollector.on('collect', async m => { - //update role and stop collecting if password matches - if (m.content.toLowerCase() === password.toLowerCase()) { - - StampsManager.parseRole(member, activityName, initBotInfo); - - correctPassword = true; - pwdCollector.stop(); - } else if (incorrectPasswords < 2) { - //add 1 to number of incorrect guesses and prompts user to try again - await sendMessageToMember(user, 'Incorrect. Please try again.', true); - } - incorrectPasswords++; - }); - pwdCollector.on('end', collected => { - deleteMessage(dmMessage); - - //show different messages after password collection expires depending on circumstance - if (!correctPassword) { - if (incorrectPasswords < 3) { - sendEmbedToMember(user, { - title: 'Stamp Collector', - description: 'Time\'s up! You took too long to enter the password for the ' + activityName + ' stamp. If you have extenuating circumstances please contact an organizer.', - }); - } else { - sendEmbedToMember(user, { - title: 'Stamp Collector', - description: 'Incorrect. You have no attempts left for the ' + activityName + ' stamp. If you have extenuating circumstances please contact an organizer.', - }); - } - } - }); - }); - - //edits the embedded message to notify people when it stops collecting reacts - collector.on('end', collected => { - if (msg.guild.channels.cache.find(channel => channel.name === targetChannel.name)) { - msg.edit(qEmbed.setTitle('Time\'s up! No more responses are being collected. Thanks for participating in ' + activityName + '\'s booth!')); - } - }); - }); - } -} -module.exports = PasswordStamp; diff --git a/commands/stamps/raffle.js b/commands/stamps/raffle.js deleted file mode 100644 index 1356f4b5..00000000 --- a/commands/stamps/raffle.js +++ /dev/null @@ -1,88 +0,0 @@ -const PermissionCommand = require('../../classes/permission-command'); -const { Message, MessageEmbed } = require('discord.js'); - -/** - * Picks x amount of winners from the stamp contest. The more stamps a user has, the more chances they have of winning. - * @category Commands - * @subcategory Stamps - * @extends PermissionCommand - */ -class Raffle extends PermissionCommand { - constructor(client) { - super(client, { - name: 'raffle', - group: 'stamps', - memberName: 'draw raffle winners', - description: 'parses each hacker for their stamps and draws winners from them, one entry per stamp', - guildOnly: true, - args: [ - { - key: 'numberOfWinners', - prompt: 'number of winners to be selected', - type: 'integer' - }, - ] - }, - { - channel: PermissionCommand.FLAGS.ADMIN_CONSOLE, - channelMessage: 'This command can only be used in the admin console!', - role: PermissionCommand.FLAGS.ADMIN_ROLE, - roleMessage: 'You do not have permission for this command, only admins can use it!', - }); - } - - /** - * Main function which looks at every member's roles, identifies all that end in a number, and adds the member's id that many times - * into an array. Then it chooses random numbers and picks the id corresponding to that index until it has numberOfWinners unique - * winners. - * - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - message used to call the command - * @param {Object} args - * @param {integer} args.numberOfWinners - number of winners to be drawn - */ - async runCommand(initBotInfo, message, {numberOfWinners}) { - - //check that numberOfWinners is less than the number of people with stamp roles or it will infinite loop - let validMembers = message.guild.members.cache.filter(member => member.roles.cache.has(initBotInfo.roleIDs.memberRole)); - var memberCount = validMembers.size; - if (memberCount <= numberOfWinners) { - message.channel.send('Whoa there, you want more winners than hackers!').then((msg) => { - msg.delete({ timeout: 5000 }); - }); - return; - } - - //array to contain the ids - var entries = new Array(); - - validMembers.forEach(member => { - let roleId = member.roles.cache.find(role => initBotInfo.stamps.stampRoleIDs.has(role.id)); - if (!roleId) return; - let stampNumber = initBotInfo.stamps.stampRoleIDs.get(roleId); - - for (let i = 0; i < stampNumber; i++) { - entries.push(member.user.id); - } - }); - - //number of array spaces that are actually occupied by ids - var length = entries.length; - - //set to keep track of winners - let winners = new Set(); - //randomly generate a number and add the corresponding winner into the set - while (winners.size < numberOfWinners) { - let num = Math.floor(Math.random() * length); - let winner = entries[num]; - if (!winners.has(winner)) winners.add(winner); - } - let winnersList = Array.from(winners); - const embed = new MessageEmbed() - .setColor(initBotInfo.colors.embedColor) - .setTitle('The winners of the raffle draw are:') - .setDescription( winnersList.map(id => `<@${id}>`).join(', ')); - await message.channel.send(embed); - } -} -module.exports = Raffle; diff --git a/commands/verification/add-members.js b/commands/verification/add-members.js deleted file mode 100644 index bed2a104..00000000 --- a/commands/verification/add-members.js +++ /dev/null @@ -1,81 +0,0 @@ -const { Command } = require('@sapphire/framework'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); -const { Modal, MessageActionRow, TextInputComponent } = require('discord.js'); -const { addUserData } = require('../../db/firebase/firebaseUtil'); - -class AddMembers extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Start verification prompt in landing channel.' - }); - } - - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addStringOption(option => - option.setName('participantstype') - .setDescription('Type of role of the added participants (i.e. hacker, sponsor)') - .setRequired(true)) - .addBooleanOption(option => - option.setName('overwrite') - .setDescription('Overwrite existing role?') - .setRequired(false)) - ); - } - - async chatInputRun(interaction) { - this.initBotInfo = await firebaseUtil.getInitBotInfo(interaction.guild.id); - const userId = interaction.user.id; - const guild = interaction.guild; - const participantsType = interaction.options.getString('participantstype'); - const overwrite = interaction.options.getBoolean('overwrite') ?? false; - - const userRoles = guild.members.cache.get(userId).roles.cache; - const staffRoleID = this.initBotInfo.roleIDs.staffRole; - const adminRoleID = this.initBotInfo.roleIDs.adminRole; - - if (!userRoles.has(staffRoleID) && !userRoles.has(adminRoleID)) { - return this.error({ message: 'You do not have permissions to run this command!', ephemeral: true }); - } - - const roles = this.initBotInfo.verification.roles; - const roleExists = roles.some(role => role.name === participantsType); - if (!roleExists) { - await interaction.reply({ content: 'The role you entered does not exist!', ephemeral: true }); - return; - } - console.log('PASSED AGAIN'); - const modal = new Modal() - .setCustomId('emailsModal') - .setTitle('Enter all emails to be added as ' + participantsType) - .addComponents([ - new MessageActionRow().addComponents( - new TextInputComponent() - .setCustomId('emails') - .setLabel('Newline-separated Emails') - .setStyle('PARAGRAPH') - .setRequired(true), - ), - ]); - await interaction.showModal(modal); - - const submitted = await interaction.awaitModalSubmit({ time: 300000, filter: j => j.user.id === interaction.user.id }) - .catch(error => { - }); - - if (submitted) { - const emailsRaw = submitted.fields.getTextInputValue('emails'); - console.log(emailsRaw, ' is the raw emails'); - const emails = emailsRaw.split(/[\r?\n|\r|\n|,]+/g).map(email => email.trim()).filter(Boolean); - emails.forEach(email => { - addUserData(email, participantsType, interaction.guild.id, overwrite); - }); - submitted.reply({ content: emails.length + ' emails have been added as ' + participantsType, ephemeral: true }); - } - } -} -module.exports = AddMembers; \ No newline at end of file diff --git a/commands/verification/check-email.js b/commands/verification/check-email.js deleted file mode 100644 index 77444d4d..00000000 --- a/commands/verification/check-email.js +++ /dev/null @@ -1,128 +0,0 @@ -const { checkEmail } = require("../../db/firebase/firebaseUtil"); -const { Command } = require('@sapphire/framework'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); - -class CheckEmail extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Return user information given an email.' - }); - } - - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addStringOption(option => - option.setName('email') - .setDescription('Email to be checked') - .setRequired(true)) - ) - } - - async chatInputRun(interaction) { - this.initBotInfo = await firebaseUtil.getInitBotInfo(interaction.guild.id); - const guild = interaction.guild; - const email = interaction.options.getString('email'); - - if (!guild.members.cache.get(userId).roles.cache.has(this.initBotInfo.roleIDs.staffRole) && !guild.members.cache.get(userId).roles.cache.has(this.initBotInfo.roleIDs.adminRole)) { - return this.error({ message: 'You do not have permissions to run this command!', ephemeral: true }) - } - - let botSpamChannel = guild.channels.resolve(this.initBotInfo.channelIDs.botSpamChannel); - const userData = await checkEmail(email, guild.id); - interaction.reply({content: 'Visit <#' + this.initBotInfo.channelIDs.botSpamChannel + '> for the results', ephemeral: true}); - - if (userData) { - if (userData.discordId && userData.types) { - const roleToString = await Promise.all(userData.types.map(type => (type.type + ': ' + (type.isVerified ? 'verified' : 'not verified')))); - botSpamChannel.send('The user associated with the email "' + email + '" is <@' + userData.discordId + '>. \n' - + 'Their role is:\n' + roleToString.join('\n')) - return; - } else if (userData.discordId) { - botSpamChannel.send('The user associated with the email "' + email + '" is <@' + userData.discordId + '>.'); - return; - } else { - botSpamChannel.send('Hmm. No Discord user is associated with the email "' + email + '" but we do have their email on file.'); - return; - } - } else { - botSpamChannel.send('The email "' + email +'" does not exist in our database'); - return; - } - } -} - -module.exports = CheckEmail; - -// // Discord.js commando requirements -// const PermissionCommand = require('../../classes/permission-command'); -// const firebaseServices = require('../../db/firebase/firebase-services'); -// const BotGuildModel = require('../../classes/Bot/bot-guild'); -// const { Message } = require('discord.js'); - -// /** -// * User can check if a member is in the database by email or name. -// * @category Commands -// * @subcategory Verification -// * @extends PermissionCommand -// */ -// class CheckMember extends PermissionCommand { -// constructor(client) { -// super(client, { -// name: 'check-member', -// group: 'verification', -// memberName: 'check if member is in database', -// description: 'If given email, will tell user if email is valid, or suggest similar emails if not. If given name, returns corresponding email.', -// args: [ -// { -// key: 'emailOrName', -// prompt: 'Please provide the email address or name to check (write names in the format firstName-lastName)', -// type: 'string', -// default: '', -// }, - -// ], -// }, -// { -// role: PermissionCommand.FLAGS.STAFF_ROLE, -// roleMessage: 'Hey there, the !check-member command is only for staff!', -// channel: PermissionCommand.FLAGS.ADMIN_CONSOLE, -// channelMessage: 'Hey there, the !check-member command is only available in the admin console channel.', -// }); -// } - -// /** -// * @param {BotGuildModel} botGuild -// * @param {Message} message -// * @param {Object} args -// * @param {String} args.emailOrName -// */ -// async runCommand(botGuild, message, { emailOrName }) { -// if (emailOrName.split('-').length === 1) { // check for similar emails if given argument is an email -// let result = await firebaseServices.checkEmail(emailOrName, message.guild.id); -// if (result.length > 0) { // if similar emails were found, print them -// let listMembers = ''; -// result.forEach(member => { -// let listMember = member.email + ' (' + member.types.join(', ') + ') '; -// listMembers += listMember; -// }); -// message.channel.send('Here are the results I found similar to ' + emailOrName + ': ' + listMembers); -// } else { // message if no similar emails found -// message.channel.send('No matches to this email were found').then(msg => msg.delete({ timeout: 8000 })); -// } -// } else { // check for members of the given name if argument was a name -// let firstName = emailOrName.split('-')[0]; -// let lastName = emailOrName.split('-')[1]; -// let result = await firebaseServices.checkName(firstName, lastName, message.guild.id); -// if (result != null) { // print email if member was found -// message.channel.send('Email found for ' + firstName + ' ' + lastName + ' is: ' + result); -// } else { // message if member was not found -// message.channel.send('The name does not exist in our database!').then(msg => msg.delete({ timeout: 8000 })); -// } -// } -// } -// } -// module.exports = CheckMember; diff --git a/commands/verification/get-email.js b/commands/verification/get-email.js deleted file mode 100644 index b0e9ef57..00000000 --- a/commands/verification/get-email.js +++ /dev/null @@ -1,46 +0,0 @@ -const { Command } = require('@sapphire/framework'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); -const { lookupById } = require('../../db/firebase/firebaseUtil'); - -class GetEmails extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Check the email of a given user if they have it linked to their Discord ID in our database.' - }); - } - - registerApplicationCommands(registry) { - registry.registerContextMenuCommand((builder) => - builder - .setName(this.name) - .setType(2) - ); - } - async contextMenuRun(interaction) { - const guild = interaction.guild; - const userId = interaction.user.id; - this.initBotInfo = await firebaseUtil.getInitBotInfo(interaction.guild.id); - - if (!guild.members.cache.get(userId).roles.cache.has(this.initBotInfo.roleIDs.staffRole) && !guild.members.cache.get(userId).roles.cache.has(this.initBotInfo.roleIDs.adminRole)) { - return this.error({ message: 'You do not have permissions to run this command!', ephemeral: true }) - } - - let botSpamChannel = guild.channels.resolve(this.initBotInfo.channelIDs.botSpamChannel); - - const email = await lookupById(guild.id, interaction.targetUser.id) - if (email) { - interaction.reply({ content: 'Visit <#' + this.initBotInfo.channelIDs.botSpamChannel + '> for the results', ephemeral: true }); - botSpamChannel.send('<@' + interaction.targetUser.id + '>\'s email is: ' + email); - return; - } else { - interaction.reply({ content: 'Visit <#' + this.initBotInfo.channelIDs.botSpamChannel + '> for the results', ephemeral: true }); - botSpamChannel.send('<@' + interaction.targetUser.id + '>\'s email is not in our database!'); - return; - } - } -} - -module.exports = { - GetEmails -}; diff --git a/commands/verification/start-verification.js b/commands/verification/start-verification.js deleted file mode 100644 index 031f7f5c..00000000 --- a/commands/verification/start-verification.js +++ /dev/null @@ -1,164 +0,0 @@ -// Required imports -const { Command } = require('@sapphire/framework'); -const { MessageEmbed, Modal, MessageActionRow, MessageButton, TextInputComponent } = require('discord.js'); -const firebaseUtil = require('../../db/firebase/firebaseUtil'); -const { discordLog } = require('../../discord-services'); - -/** - * Verification Command - * Starts a verification process in the landing channel. - */ -class StartVerification extends Command { - constructor(context, options) { - super(context, { - ...options, - description: 'Start verification prompt in landing channel.' - }); - } - - registerApplicationCommands(registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - ); - } - - async chatInputRun(interaction) { - this.initBotInfo = await firebaseUtil.getInitBotInfo(interaction.guild.id); - const { guild, user } = interaction; - - if (!guild.members.cache.get(user.id).roles.cache.has(this.initBotInfo.roleIDs.staffRole)) { - return interaction.reply({ content: 'You do not have permissions to run this command!', ephemeral: true }); - } - - const embed = new MessageEmbed().setTitle(`Please click the button below to check-in to the ${interaction.guild.name} server! Make sure you know which email you used to apply to ${interaction.guild.name}!`); - const row = new MessageActionRow() - .addComponents( - new MessageButton() - .setCustomId('verify') - .setLabel('Check-in') - .setStyle('PRIMARY'), - ); - - interaction.reply({ content: 'Verification started!', ephemeral: true }); - const msg = await interaction.channel.send({ - content: 'If you have not already, make sure to enable DMs, emojis, and embeds/link previews in your personal Discord settings! If you have any issues, please find an organizer!', - embeds: [embed], - components: [row] - }); - - this.listenToVerification(guild, msg); - - await firebaseUtil.getSavedMessagesSubCol(interaction.guild.id).doc('startverification').set({ - messageId: msg.id, - channelId: msg.channel.id, - }); - } - - async tryRestoreReactionListeners(guild) { - const savedMessagesCol = firebaseUtil.getSavedMessagesSubCol(guild.id); - const verificationDoc = await savedMessagesCol.doc('startverification').get(); - - if (verificationDoc.exists) { - const { messageId, channelId } = verificationDoc.data(); - const channel = await this.container.client.channels.fetch(channelId); - - if (channel) { - try { - const message = await channel.messages.fetch(messageId); - this.listenToVerification(guild, message); - } catch (e) { - console.error('Failed to fetch verification message:', e); - } - } - } - } - - listenToVerification(guild, msg) { - const collector = msg.createMessageComponentCollector({ filter: i => i.customId === 'verify' && !i.user.bot }); - - collector.on('collect', async i => { - const member = guild.members.cache.get(i.user.id); - this.initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - - // Check if the user has the guest role - if (!member.roles.cache.has(this.initBotInfo.verification.guestRoleID)) { - await i.reply({ content: 'You are not eligible to be checked in! If you don\'t have correct access to the server, please contact an organizer.', ephemeral: true }); - return; - } - - const modal = new Modal() - .setCustomId('verifyModal') - .setTitle('Check-in to gain access to the server!') - .addComponents( - new MessageActionRow().addComponents( - new TextInputComponent() - .setCustomId('email') - .setLabel('Enter the email that you applied with!') - .setMinLength(3) - .setMaxLength(320) - .setStyle('SHORT') - .setPlaceholder('Email Address') - .setRequired(true), - ), - ); - - await i.showModal(modal); - - const submitted = await i.awaitModalSubmit({ time: 300000, filter: j => j.user.id === i.user.id }).catch(console.error); - - if (submitted) { - const email = submitted.fields.getTextInputValue('email'); - let types; - - try { - types = await firebaseUtil.verify(email, submitted.user.id, submitted.guild.id); - } catch { - submitted.reply({ content: 'Your email could not be found! Please try again or ask an admin for help.', ephemeral: true }); - discordLog(guild, `VERIFY FAILURE : <@${submitted.user.id}> Verified email: ${email} but was a failure, I could not find that email!`); - return; - } - - if (types.length === 0) { - submitted.reply({ content: 'You have already verified!', ephemeral: true }); - discordLog(guild, `VERIFY WARNING : <@${submitted.user.id}> Verified email: ${email} but they are already verified for all types!`); - return; - } - - let correctTypes = []; - types.forEach(type => { - const roleObj = this.initBotInfo.verification.roles.find(role => role.name === type); - const member = guild.members.cache.get(submitted.user.id); - let roleId; - - if (type === 'staff') { - roleId = this.initBotInfo.roleIDs.staffRole; - } else if (type === 'mentor') { - roleId = this.initBotInfo.roleIDs.mentorRole; - } else { - roleId = roleObj ? roleObj.roleId : null; - } - - if (member && roleId) { - member.roles.add(roleId); - - if (correctTypes.length === 0) { - member.roles.remove(this.initBotInfo.verification.guestRoleID); - member.roles.add(this.initBotInfo.roleIDs.memberRole); - } - correctTypes.push(type); - } else { - console.warn(`Could not add role: ${roleId} for type: ${type}`); - } - }); - - if (correctTypes.length > 0) { - submitted.reply({ content: `You have successfully verified as a ${correctTypes.join(', ')}!`, ephemeral: true }); - } - } - }); - } -} - -module.exports = StartVerification; \ No newline at end of file diff --git a/commands/verification/verify.js b/commands/verification/verify.js deleted file mode 100644 index 49276c2a..00000000 --- a/commands/verification/verify.js +++ /dev/null @@ -1,85 +0,0 @@ -const PermissionCommand = require('../../classes/permission-command'); -const { sendEmbedToMember, sendMessageToMember, checkForRole, validateEmail } = require('../../discord-services'); -const { Message } = require('discord.js'); -const Verification = require('../../classes/Bot/Features/Verification/verification'); - -/** - * Will verify the user running the command, needs the user's email and guild ID. Can only - * be run through DM. - * @category Commands - * @subcategory Verification - * @extends PermissionCommand - * @dmonly - */ -class Verify extends PermissionCommand { - constructor(client) { - super(client, { - name: 'verify', - group: 'verification', - memberName: 'hacker verification', - description: 'Will verify a guest to its correct role if their email is in our database.', - args: [ - { - key: 'email', - prompt: 'Please provide your email address', - type: 'string', - default: '', - }, - { - key: 'guildId', - prompt: 'Please provide the server ID, ask admins for it!', - type: 'integer', - }, - ], - }, - { - dmOnly: true, - }); - } - - /** - * @param {FirebaseFirestore.DocumentData | null | undefined} initBotInfo - * @param {Message} message - * @param {Object} args - * @param {String} args.email - * @param {String} args.guildId - */ - async runCommand(initBotInfo, message, { email, guildId }) { - - // check if the user needs to verify, else warn and return - if (!checkForRole(member, initBotInfo.roleIDs.guestRole)) { - sendEmbedToMember(member, { - title: 'Verify Error', - description: 'You do not need to verify, you are already more than a guest!' - }, true); - return; - } - - // let user know he has used the command incorrectly and exit - if (!validateEmail(email)) { - sendMessageToMember(message.author, 'You have used the verify command incorrectly! \nPlease write a valid email after the command like this: !verify email@gmail.com'); - return; - } - - let guild = this.client.guilds.cache.get(guildId); - if (!guild) { - sendEmbedToMember(message.author, { - title: 'Verification Failure', - description: 'The given server ID is not valid. Please try again!', - }); - return; - } - let member = guild.member(message.author.id); - - // Call the verify function - try { - Verification.verify(member, email, guild, initBotInfo); - } catch (error) { - sendEmbedToMember(member, { - title: 'Verification Error', - description: 'Email provided is not valid!' - }, true); - } - } -} -module.exports = Verify; \ No newline at end of file diff --git a/db/firebase/admin-json-to-env.js b/db/firebase/admin-json-to-env.js deleted file mode 100644 index 6715b99a..00000000 --- a/db/firebase/admin-json-to-env.js +++ /dev/null @@ -1,7 +0,0 @@ -const fs = require('fs'); - -// add the file path in this requires -const adminSDKConfig = require(''); - -// copy and paste the logs output to the .env file -console.log(JSON.stringify(adminSDKConfig)); diff --git a/db/firebase/firebase-services.js b/db/firebase/firebase-services.js deleted file mode 100644 index d728282c..00000000 --- a/db/firebase/firebase-services.js +++ /dev/null @@ -1,381 +0,0 @@ -const { GuildMember, } = require('discord.js'); -const admin = require('firebase-admin'); - -/** - * The firebase services module has firebase related helper functions. - * @module FirebaseServices - */ - -/** - * All the firebase apps in play stored by their name. - * @type {Map} - */ -const apps = new Map(); -module.exports.apps = apps; - -/** - * Will start an admin connection with the given name - * @param {String} name - name of the connection - * @param {JSON} adminSDK - the JSON file with admin config - * @param {String} databaseURL - the database URL - */ -function initializeFirebaseAdmin(name, adminSDK, databaseURL) { - let app = admin.initializeApp({ - credential: admin.credential.cert(adminSDK), - databaseURL: databaseURL, - }, name); - - apps.set(name, app); - -} -module.exports.initializeFirebaseAdmin = initializeFirebaseAdmin; - -function getFactotumDoc() { - return apps.get('nwPlusBotAdmin').firestore().collection('ExternalProjects').doc('Factotum'); -} - -/** - * @typedef UserType - * @property {String} type - * @property {Boolean} isVerified - * @property {Date} timestamp - */ - -/** - * @typedef FirebaseUser - * @property {String} email - * @property {String} discordId - * @property {UserType[]} types - */ - -/** - * Retrieves a question from the db that has not already been asked at the Discord Contests, then marks the question as having been - * asked in the db. - * @param {String} guildId - the id of the guild - * @returns {Object | null} - the data object of a question or null if no more questions - */ -async function getQuestion(guildId) { - //checks that the question has not been asked - let questionReference = getFactotumDoc().collection('guilds').doc(guildId).collection('questions').where('asked', '==', false).limit(1); - let question = (await questionReference.get()).docs[0]; - //if there exists an unasked question, change its status to asked - if (question != undefined) { - question.ref.update({ - 'asked': true, - }); - return question.data(); - } - return null; -} -module.exports.getQuestion = getQuestion; - -/** - * Retrieves self-care reminder from the db that has not already been sent, - * then marks the reminder as having been asked in the db. - * @param {String} guildId - the guild id - * @returns {Object | null} - the data object of a reminder or null if no more reminders - */ -async function getReminder(guildId) { - //checks that the reminder has not been sent - var qref = getFactotumDoc().collection('guilds').doc(guildId).collection('reminders').where('sent', '==', false).limit(1); - var reminder = (await qref.get()).docs[0]; - //if there reminder unsent, change its status to asked - if (reminder != undefined) { - reminder.ref.update({ - 'sent': true, - }); - return reminder.data(); - } - return null; -} -module.exports.getReminder = getReminder; - - -/** - * @typedef {Object} Member - * @property {String} email - the email of the member - * @property {Boolean} isVerified - whether member has already verified - * @property {String} type - role a member has in the server - */ - -/** - * Checks to see if the input email matches or is similar to emails in the database - * Returns an array of objects containing emails that match or are similar, along with the verification status of each, - * and returns empty array if none match - * @param {String} email - email to check - * @param {String} guildId - the guild id - * @returns {Promise>} - array of members with similar emails to parameter email - */ -async function checkEmail(email, guildId) { - const cleanEmail = email.trim().toLowerCase(); - const docRef = getFactotumDoc().collection('guilds').doc(guildId).collection('members').doc(cleanEmail); - const doc = await docRef.get(); - return doc.data(); - // var foundEmails = []; - // snapshot.forEach(memberDoc => { - // compare each member's email with the given email - // if (memberDoc.get('email') != null) { - // let compare = memberDoc.get('email'); - // if the member's emails is similar to the given email, retrieve and add the email, verification status, and member type of - // the member as an object to the array - // if (compareEmails(email.split('@')[0], compare.split('@')[0])) { - // foundEmails.push({ - // email: compare, - // types: memberDoc.get('types').map(type => type.type), - // }); - // } - - // } - - // }); - // return foundEmails; -} -module.exports.checkEmail = checkEmail; - -/** - * Uses Levenshtein Distance to determine whether two emails are within 5 Levenshtein Distance - * @param {String} searchEmail - email to search for similar emails for - * @param {String} dbEmail - email from db to compare to searchEmail - * @returns {Boolean} - Whether the two emails are similar - * @private - */ -function compareEmails(searchEmail, dbEmail) { - // matrix to track Levenshtein Distance with - var matrix = new Array(searchEmail.length); - var searchEmailChars = searchEmail.split(''); - var dbEmailChars = dbEmail.split(''); - // initialize second dimension of matrix and set all elements to 0 - for (let i = 0; i < matrix.length; i++) { - matrix[i] = new Array(dbEmail.length); - for (let j = 0; j < matrix[i].length; j++) { - matrix[i][j] = 0; - } - } - // set all elements in the top row and left column to increment by 1 - for (let i = 1; i < searchEmail.length; i++) { - matrix[i][0] = i; - } - for (let j = 1; j < dbEmail.length; j++) { - matrix[0][j] = j; - } - // increment Levenshtein Distance by 1 if there is a letter inserted, deleted, or swapped; store the running tally in the corresponding - // element of the matrix - let substitutionCost; - for (let j = 1; j < dbEmail.length; j++) { - for (let i = 1; i < searchEmail.length; i++) { - if (searchEmailChars[i] === dbEmailChars[j]) { - substitutionCost = 0; - } else { - substitutionCost = 1; - } - matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + substitutionCost); - } - } - return matrix[searchEmail.length - 1][dbEmail.length - 1] <= (Math.min(searchEmail.length, dbEmail.length) / 2); -} - -/** - * Finds the email of user with given first and last names - * @param {String} firstName - first name of member to match with database - * @param {String} lastName - last name of member to match with database - * @param {String} guildId - the guild id - * @returns {Promise} - email of given member - * @private - */ -async function checkName(firstName, lastName, guildId) { - const snapshot = (await getFactotumDoc().collection('guilds').doc(guildId).collection('members').get()).docs; // snapshot of Firestore as array of documents - snapshot.forEach(memberDoc => { - if (memberDoc.get('firstName') != null && memberDoc.get('lastName') != null && memberDoc.get('firstName').toLowerCase() === firstName.toLowerCase() - && memberDoc.get('lastName').toLowerCase() === lastName.toLowerCase()) { // for each document, check if first and last names match given names - return memberDoc.get('email'); - } - }); - return null; -} -module.exports.checkName = checkName; - -/** - * Adds a new guild member to the guild's member collection. Email is used as ID, there can be no duplicates. - * @param {String} email - email of member verified - * @param {String[]} types - types this user might verify for - * @param {String} guildId - the guild id - * @param {GuildMember} [member={}] - member verified - * @param {String} [firstName=''] - users first name - * @param {String} [lastName=''] - users last name - * @async - */ -async function addUserData(email, type, guildId, overwrite) { - const cleanEmail = email.trim().toLowerCase(); - var documentRef = getFactotumDoc().collection('guilds').doc(guildId).collection('members').doc(cleanEmail); - const doc = await documentRef.get(); - - if (doc.exists && !overwrite) { - var types = await doc.data().types; - var containsType = false; - types.forEach(existingType => { - if (existingType.type === type) { - containsType = true; - return; - } - }); - if (!containsType) { - types.push({ type: type, isVerified: false }); - } - await documentRef.update({ types: types }); - } else { - let data = { - email: cleanEmail, - types: [{ - isVerified: false, - type: type - }] - }; - await documentRef.set(data); - } -} -module.exports.addUserData = addUserData; - -/** - * Verifies the any event member via their email. - * @param {String} email - the user email - * @param {String} id - the user's discord snowflake - * @param {String} guildId - the guild id - * @returns {Promise} - the types this user is verified - * @async - * @throws Error if the email provided was not found. - */ -async function verify(email, id, guildId) { - let emailLowerCase = email.trim().toLowerCase(); - var userRef = getFactotumDoc().collection('guilds').doc(guildId).collection('members').where('email', '==', emailLowerCase).limit(1); - var user = (await userRef.get()).docs[0]; - if (user) { - let returnTypes = []; - - /** @type {FirebaseUser} */ - var data = user.data(); - - data.types.forEach((value, index, array) => { - if (!value.isVerified) { - value.isVerified = true; - value.VerifiedTimestamp = admin.firestore.Timestamp.now(); - returnTypes.push(value.type); - } - }); - - data.discordId = id; - - user.ref.update(data); - - return returnTypes; - } else { - throw new Error('The email provided was not found!'); - } -} -module.exports.verify = verify; - -/** - * Attends the user via their discord id - * @param {String} id - the user's discord snowflake - * @param {String} guildId - the guild id - * @returns {Promise} - the types this user is verified - * @async - * @throws Error if the email provided was not found. - */ -async function attend(id, guildId) { - var userRef = getFactotumDoc().collection('guilds').doc(guildId).collection('members').where('discordId', '==', id).limit(1); - var user = (await userRef.get()).docs[0]; - - if (user) { - /** @type {FirebaseUser} */ - var data = user.data(); - - data.types.forEach((value, index, array) => { - if (value.isVerified) { - value.isAttending = true; - value.AttendingTimestamp = admin.firestore.Timestamp.now(); - } - }); - - user.ref.update(data); - } else { - throw new Error('The discord id provided was not found!'); - } -} -module.exports.attend = attend; - -/** - * check if codex is set to active (very hacky atm, it's just a document in the "codex" collection with a boolean - * field called "active") - * @param {String} guildId - * @returns boolean of whether codex is set to active - */ - -async function checkCodexActive(guildId) { - var ref = getFactotumDoc().collection('guilds').doc(guildId).collection('codex').doc('active'); - var activeRef = await ref.get(); - const data = activeRef.data(); - return data.active; -} -module.exports.checkCodexActive = checkCodexActive; - -/** - * stores email to firebase collection - * @param {String} guildId - * @param {String} collection - name of collection to store email in - * @param {String} email - user's email - */ - -async function saveToFirebase(guildId, collection, email) { - var ref = getFactotumDoc().collection('guilds').doc(guildId).collection(collection).doc(email.toLowerCase()); - /** @type {FirebaseUser} */ - let data = { - email: email.toLowerCase() - }; - - await ref.set(data); -} -module.exports.saveToFirebase = saveToFirebase; - -async function lookupById(guildId, memberId) { - var userRef = getFactotumDoc().collection('guilds').doc(guildId).collection('members').where('discordId', '==', memberId).limit(1); - var user = (await userRef.get()).docs[0]; - if (user) { - /** @type {FirebaseUser} */ - var data = user.data(); - - return data.email; - } else { - return undefined; - } - -} -module.exports.lookupById = lookupById; - -async function saveToLeaderboard(guildId, memberId) { - var userRef = getFactotumDoc().collection('guilds').doc(guildId).collection('questionsLeaderboard').doc(memberId); - var user = await userRef.get(); - if (user.exists) { - // var data = user.data(); - var data = user.data(); - data.points++; - await userRef.update(data); - } else { - let data = { - memberId: memberId, - points: 1 - }; - await userRef.set(data); - } -} -module.exports.saveToLeaderboard = saveToLeaderboard; - -async function retrieveLeaderboard(guildId) { - var snapshot = (await getFactotumDoc().collection('guilds').doc(guildId).collection('questionsLeaderboard').get()).docs; - let winners = []; - snapshot.forEach(doc => { - winners.push(doc.data()); - }); - winners.sort((a, b) => parseFloat(b.points) - parseFloat(a.points)); - return winners; -} -module.exports.retrieveLeaderboard = retrieveLeaderboard; \ No newline at end of file diff --git a/db/firebase/firebaseUtil.js b/db/firebase/firebaseUtil.js deleted file mode 100644 index 78173f31..00000000 --- a/db/firebase/firebaseUtil.js +++ /dev/null @@ -1,474 +0,0 @@ -const admin = require('firebase-admin'); - -/** - * The firebase services module has firebase related helper functions. - * @module FirebaseServices - */ - -/** - * All the firebase apps in play stored by their name. - * @type {Map} - */ -const apps = new Map(); -module.exports.apps = apps; - -/** - * Will start an admin connection with the given name - * @param {String} name - name of the connection - * @param {JSON} adminSDK - the JSON file with admin config - * @param {String} databaseURL - the database URL - */ -function initializeFirebaseAdmin(name, adminSDK, databaseURL) { - let app = admin.initializeApp({ - credential: admin.credential.cert(adminSDK), - databaseURL: databaseURL, - }, name); - - apps.set(name, app); -} - -/** - * The firebase utility module has some useful mongo related helper functions. - * @module FirebaseUtil - */ - -/** @type {FirebaseFirestore.Firestore | undefined} */ -let _db; - -module.exports = { - apps, - - /** - * Starts a connection to new firestore - */ - async connect(appName) { - if (appName) { - const app = apps.get(appName); - if (!app) { - throw new Error(`No Firebase app initialized with the name ${appName}`); - } - _db = app.firestore(); - } else { - _db = admin.firestore(); - } - console.log('Connected to Firebase Firestore'); - }, - - initializeFirebaseAdmin, - - getDb() { - if (!_db) { - throw new Error('Firestore is not initialized. Call connect() first.'); - } - return _db; - }, - - getBotGuildCol() { - return _db.collection('botGuilds'); - }, - getExternalProjectsCol() { - if (!_db) { - throw new Error('Firestore is not initialized, call connect() first.'); - } - return _db.collection('ExternalProjects'); - }, - getFactotumSubCol() { - const externalProjectsCol = this.getExternalProjectsCol(); - if (!externalProjectsCol) { - throw new Error('ExternalProjects collection is not initialized.'); - } - return externalProjectsCol.doc('Factotum').collection('InitBotInfo'); - }, - /** - * @param {string} guildId - */ - getSavedMessagesSubCol(guildId) { - const factotumSubCol = this.getFactotumSubCol(); - return factotumSubCol.doc(guildId).collection('SavedMessages'); - }, - - /** - * @param {String} appName - * @returns {Firestore} Firestore instance for the given app - */ - getFirestoreInstance(appName) { - const app = apps.get(appName); - if (!app) { - throw new Error(`No Firebase app initialized with the name ${appName}`); - } - return app.firestore(); - }, - - async mongooseConnect() { - // this is no longer needed but keeping for now - return Promise.resolve(); - }, - - // FIREBASE SERVICE FUNCTIONS IMPORTED BELOW - /** - * Retrieves a question from the db that has not already been asked at the Discord Contests, then marks the question as having been - * asked in the db. - * @param {String} guildId - the id of the guild - * @returns {Object | null} - the data object of a question or null if no more questions - */ - async getQuestion(guildId) { - //checks that the question has not been asked - let questionReference = module.exports.getFactotumSubCol().doc(guildId) - .collection('Questions').where('asked', '==', false).limit(1); - let question = (await questionReference.get()).docs[0]; - //if there exists an unasked question, change its status to asked - if (question != undefined) { - question.ref.update({ - 'asked': true, - }); - return question.data(); - } - return null; - }, - - /** - * Retrieves self-care reminder from the db that has not already been sent, - * then marks the reminder as having been asked in the db. - * @param {String} guildId - the guild id - * @returns {Object | null} - the data object of a reminder or null if no more reminders - */ - async getReminder(guildId) { - //checks that the reminder has not been sent - let qref = module.exports.getFactotumSubCol().doc(guildId) - .collection('Reminders').where('sent', '==', false).limit(1); - let reminder = (await qref.get()).docs[0]; - //if there reminder unsent, change its status to asked - if (reminder != undefined) { - reminder.ref.update({ - 'sent': true, - }); - return reminder.data(); - } - return null; - }, - - /** - * Checks to see if the input email matches or is similar to emails in the database - * Returns an array of objects containing emails that match or are similar, along with the verification status of each, - * and returns empty array if none match - * @param {String} email - email to check - * @param {String} guildId - the guild id - * @returns {Promise>} - array of members with similar emails to parameter email - */ - async checkEmail(email, guildId) { - const cleanEmail = email.trim().toLowerCase(); - const docRef = getFactotumDoc().collection('guilds').doc(guildId).collection('members').doc(cleanEmail); - const doc = await docRef.get(); - return doc.data(); - }, - - /** - * Uses Levenshtein Distance to determine whether two emails are within 5 Levenshtein Distance - * @param {String} searchEmail - email to search for similar emails for - * @param {String} dbEmail - email from db to compare to searchEmail - * @returns {Boolean} - Whether the two emails are similar - * @private - */ - compareEmails(searchEmail, dbEmail) { - // matrix to track Levenshtein Distance with - let matrix = new Array(searchEmail.length); - let searchEmailChars = searchEmail.split(''); - let dbEmailChars = dbEmail.split(''); - // initialize second dimension of matrix and set all elements to 0 - for (let i = 0; i < matrix.length; i++) { - matrix[i] = new Array(dbEmail.length); - for (let j = 0; j < matrix[i].length; j++) { - matrix[i][j] = 0; - } - } - // set all elements in the top row and left column to increment by 1 - for (let i = 1; i < searchEmail.length; i++) { - matrix[i][0] = i; - } - for (let j = 1; j < dbEmail.length; j++) { - matrix[0][j] = j; - } - // increment Levenshtein Distance by 1 if there is a letter inserted, deleted, or swapped; store the running tally in the corresponding - // element of the matrix - let substitutionCost; - for (let j = 1; j < dbEmail.length; j++) { - for (let i = 1; i < searchEmail.length; i++) { - if (searchEmailChars[i] === dbEmailChars[j]) { - substitutionCost = 0; - } else { - substitutionCost = 1; - } - matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + substitutionCost); - } - } - return matrix[searchEmail.length - 1][dbEmail.length - 1] <= (Math.min(searchEmail.length, dbEmail.length) / 2); - }, - - /** - * Finds the email of user with given first and last names - * @param {String} firstName - first name of member to match with database - * @param {String} lastName - last name of member to match with database - * @param {String} guildId - the guild id - * @returns {Promise} - email of given member - * @private - */ - async checkName(firstName, lastName, guildId) { - const snapshot = (await getFactotumDoc().collection('guilds').doc(guildId).collection('members').get()).docs; // snapshot of Firestore as array of documents - for (const memberDoc of snapshot) { - if ( - memberDoc.get('firstName') && - memberDoc.get('lastName') && - memberDoc.get('firstName').toLowerCase() === firstName.toLowerCase() && - memberDoc.get('lastName').toLowerCase() === lastName.toLowerCase() - ) { - return memberDoc.get('email'); - } - } - return null; - }, - - /** - * Adds a new guild member to the guild's member collection. Email is used as ID, there can be no duplicates. - * @param {String} email - email of member verified - * @param {String[]} types - types this user might verify for - * @param {String} guildId - the guild id - * @param {GuildMember} [member={}] - member verified - * @param {String} [firstName=''] - users first name - * @param {String} [lastName=''] - users last name - * @async - */ - async addUserData(email, type, guildId, overwrite) { - const cleanEmail = email.trim().toLowerCase(); - const querySnapshot = type === "hacker" ? await getFactotumDoc() - .collection('InitBotInfo') - .doc(guildId) - .collection('applicants') - .where('email', '==', cleanEmail) - .limit(1) - .get() - : await getFactotumDoc() - .collection('InitBotInfo') - .doc(guildId) - .collection('otherRoles') - .where('email', '==', cleanEmail) - .limit(1) - .get() - - let docRef; - - if (!querySnapshot.empty) { - const doc = querySnapshot.docs[0]; - console.log(doc, ' is doc data'); - docRef = doc.ref; - - const types = doc.data().types || []; - const containsType = types.some(existingType => existingType.type === type); - - if (!containsType || overwrite) { - if (!containsType) { - types.push({ type, isVerified: false }); - } - await docRef.update({ types }); - } - } else { - docRef = type=== "hacker" ? getFactotumDoc() - .collection('InitBotInfo') - .doc(guildId) - .collection('applicants') - .doc() - : getFactotumDoc() - .collection('InitBotInfo') - .doc(guildId) - .collection('otherRoles') - .doc(); - - const data = { email: cleanEmail, types: [{ isVerified: false, type }] }; - await docRef.set(data); - } - }, - - /** - * Verifies any event member via their email. - * @param {String} email - The user's email. - * @param {String} id - The user's Discord ID. - * @param {String} guildId - The guild ID. - * @returns {Promise} - The types this user is verified for. - * @async - * @throws Error if the email provided was not found. - */ - async verify(email, id, guildId) { - const emailLowerCase = email.trim().toLowerCase(); - - const userRef = getFactotumDoc() - .collection('InitBotInfo') - .doc(guildId) - .collection('applicants') - .where('email', '==', emailLowerCase) - .limit(1); - const userSnapshot = await userRef.get(); - const user = userSnapshot.docs[0]; - - const otherRolesRef = getFactotumDoc() - .collection('InitBotInfo') - .doc(guildId) - .collection('otherRoles') - .where('email', '==', emailLowerCase) - .limit(1); - const otherRolesUserSnapshot = await otherRolesRef.get(); - const otherRolesUser = otherRolesUserSnapshot.docs[0]; - - if (user || otherRolesUser) { - let returnTypes = []; - let data; - - if (user) { - data = user.data(); - } else { - data = otherRolesUser.data(); - } - - if (data?.types) { - data.types = data.types.map((role) => { - if (!role.isVerified) { - role.isVerified = true; - returnTypes.push(role.type); - } - return role; - }); - - data.VerifiedTimestamp = admin.firestore.Timestamp.now(); - data.discordId = id; - - if (user) { - await user.ref.update(data); - } else if (otherRolesUser) { - await otherRolesUser.ref.update(data); - } - } - - return returnTypes; - } else { - throw new Error('The email provided was not found!'); - } - }, - - - /** - * Attends the user via their discord id - * @param {String} id - the user's discord snowflake - * @param {String} guildId - the guild id - * @returns {Promise} - the types this user is verified - * @async - * @throws Error if the email provided was not found. - */ - async attend(id, guildId) { - let userRef = getFactotumDoc().collection('guilds').doc(guildId).collection('members').where('discordId', '==', id).limit(1); - let user = (await userRef.get()).docs[0]; - - if (user) { - /** @type {FirebaseUser} */ - let data = user.data(); - - data.types.forEach((value, index, array) => { - if (value.isVerified) { - value.isAttending = true; - value.AttendingTimestamp = admin.firestore.Timestamp.now(); - } - }); - - user.ref.update(data); - } else { - throw new Error('The discord id provided was not found!'); - } - }, - - /** - * check if codex is set to active (very hacky atm, it's just a document in the "codex" collection with a boolean - * field called "active") - * @param {String} guildId - * @returns boolean of whether codex is set to active - */ - async checkCodexActive(guildId) { - let ref = getFactotumDoc().collection('guilds').doc(guildId).collection('codex').doc('active'); - let activeRef = await ref.get(); - const data = activeRef.data(); - return data.active; - }, - - /** - * stores email to firebase collection - * @param {String} guildId - * @param {String} collection - name of collection to store email in - * @param {String} email - user's email - */ - async saveToFirebase(guildId, collection, email) { - let ref = getFactotumDoc().collection('guilds').doc(guildId).collection(collection).doc(email.toLowerCase()); - /** @type {FirebaseUser} */ - let data = { - email: email.toLowerCase() - }; - - await ref.set(data); - }, - - async lookupById(guildId, memberId) { - const userRef = module.exports - .getFactotumSubCol() - .doc(guildId) - .collection("applicants") - .where("discordId", "==", memberId) - .limit(1); - const user = (await userRef.get()).docs[0]; - return user ? user.data().email : undefined; - }, - - async saveToLeaderboard(guildId, memberId) { - const userRef = module.exports.getFactotumSubCol().doc(guildId) - .collection('QuestionsLeaderboard').doc(memberId); - const user = await userRef.get(); - if (user.exists) { - const data = user.data(); - data.points++; - await userRef.update(data); - } else { - const data = { memberId, points: 1 }; - await userRef.set(data); - } - }, - - async retrieveLeaderboard(guildId) { - const snapshot = (await module.exports.getFactotumSubCol().doc(guildId).collection('QuestionsLeaderboard').get()).docs; - const winners = snapshot.map(doc => doc.data()).sort((a, b) => b.points - a.points); - return winners; - }, - - async updateOrganizerAttendance(guildId, organizerAttendance) { - const ref = module.exports.getFactotumSubCol().doc(guildId); - await ref.update({ organizerAttendance }); - } - -}; - -function getFactotumDoc() { - return _db.collection('ExternalProjects').doc('Factotum'); -} - -/** - * Gets the InitBotInfo document by guild ID - * @param {string} guildId - */ -async function getInitBotInfo(guildId) { - const doc = await module.exports.getFactotumSubCol().doc(guildId).get(); - return doc.exists ? doc.data() : null; -} - -/** - * Creates a new InitBotInfo document for a new guild - * @param {string} guildId - */ -async function createInitBotInfoDoc(guildId) { - return await module.exports.getFactotumSubCol().doc(guildId).set({}); -} - -module.exports.getInitBotInfo = getInitBotInfo; -module.exports.createInitBotInfoDoc = createInitBotInfoDoc; \ No newline at end of file diff --git a/db/firebase/parser.js b/db/firebase/parser.js deleted file mode 100644 index 8fa192b4..00000000 --- a/db/firebase/parser.js +++ /dev/null @@ -1,93 +0,0 @@ -const csv = require('csv-parser'); -const fs = require('fs'); -require('dotenv').config(); -const firebaseUtil = require('./firebaseUtil'); - -/** - * The firebase parser module has scripts to parse csv data to upload to - * firebase for validation purposes. - * @module FirebaseParser - */ - - -const adminSDK = JSON.parse(process.env.NWPLUSADMINSDK); -let app = firebaseUtil.initializeFirebaseAdmin('factotum', adminSDK, 'https://nwplus-bot.firebaseio.com'); - -// save second argument as type of members to add -let type = process.argv[2]; -if (type == undefined) { - throw new Error('no defined type!'); -} - -// third argument is guild id -let guildId = process.argv[3]; -if (!guildId) throw new Error('The guild id was not defined as the third argument!'); - -// optional fourth argument; if "true", all their previous types will be overwritten by this new one -let overwrite = false; -if (process.argv[4] === 'true') { - overwrite = true; -} - -class Registration { - constructor(email) { - this.email = email; - this.types = []; - } -} - -const results = []; -const all_regs = {}; -fs.createReadStream('registrations.csv') // requires a registrations.csv file in root directory to run - .pipe(csv()) - .on('data', (data) => results.push(data)) - .on('end', () => { - results.forEach((row) => { // grab email from each csv entry and save to all_regs - const email = (row['What is your primary email that can we contact you with?'] || row['Email Address']).toLowerCase(); - - const r = new Registration(email); - all_regs[email] = r; - }); - - let db = firebaseUtil.apps.get('factotum').firestore(); - - var all = db.collection('guilds').doc(guildId).collection('members').get().then(snapshot => { - // get all ids and types of members already in collections and store in idMap - let idMap = new Map(); - snapshot.docs.forEach(doc => idMap.set(doc.id, doc.get('types'))); // keys are doc ids, values are member types - - let iterable = Object.entries(all_regs); - console.log(`found ${iterable.length} registrations total!!`); - - console.log(`found ${idMap.size} existing registrations, actually patching ${iterable.length - idMap.size} new registrations`); - while (iterable.length > 0) { - var batch = db.batch(); - - for (let [key, value] of iterable.splice(0, 500)) { - key = key.toLowerCase(); - var docRef = db.collection('guilds').doc(guildId).collection('members').doc(key); - if (idMap.has(key)) { - // if overwrite is on, replace the Registration's existing types with just the new type - if (overwrite) { - value.types = [{ isVerified: false, type: type }]; - } else { - // else retrieve the Registration's existing types and push the new type - value.types = idMap.get(key); - if (!idMap.get(key).some(role => role.type === type)) { - value.types.push({ isVerified: false, type: type }); - } - } - } else { - // if member is new, just push current type into types array - value.types.push({ isVerified: false, type: type }); - } - batch.set(docRef, Object.assign({}, value)); - } - - batch.commit(); - console.log('batch write success'); - } - console.log('done!'); - }); - - }); \ No newline at end of file diff --git a/db/mongo/BotGuild.d.ts b/db/mongo/BotGuild.d.ts deleted file mode 100644 index a4087bd4..00000000 --- a/db/mongo/BotGuild.d.ts +++ /dev/null @@ -1,98 +0,0 @@ -import {Document, Model } from 'mongoose' -import Cave from '../../classes/Bot/activities/cave' - -/** - * @interface BotGuild - */ -interface BotGuild extends Document { - /** - * The basic roles for any botGuild. Given as snowflakes (ids). - */ - roleIDs: { - memberRole: String, - staffRole: String, - adminRole: String, - everyoneRole: String, - mentorRole: String - }, - - channelIDs: { - adminConsole: String, - adminLog: String, - botSpamChannel: String, - }, - - mentorTickets: { - // ticketNumber: Number, - incomingTicketsChannel: String, - mentorRoleSelectionChannel: String, - requestTicketChannel: String - }, - - verification: { - /** @required */ - isEnabled: Boolean, - guestRoleID: String, - welcomeChannelID: String, - welcomeSupportChannelID: String, - /** */ - verificationRoles: Map - }, - - // stamps: { - // /** @required */ - // isEnabled: Boolean, - // /** */ - // stampRoleIDs: Map, - // /** The first stamp role Id given to all users */ - // stamp0thRoleId: String, - // stampCollectionTime: Number, - // }, - - /** @hexColor */ - embedColor: String, - - /** - * The botGuild id must equal the guild id - * @required - */ - _id: String, - - /** - * True if the bot has been set up and its ready to hack! - */ - isSetUpComplete: Boolean, - - /** - * Will set the minimum required information for the bot to work on this guild. - * @param {BotGuildInfo} botGuildInfo - * @param {CommandoClient} client - * @returns {Promise} - * @async - */ - async readyUp(client, botGuildInfo); - - async setUpVerification(guild, guestRoleId, types, welcomeSupportChannel); - - /** - * Staff role permissions. - * @static - */ - static staffPermissions: String[]; - - /** - * Admin role permissions. - * @static - */ - static adminPermissions: String[]; - - /** - * The regular member perms. - * @static - */ - static memberPermissions: String[]; -} - -let botGuild: Model; - -export = botGuild; \ No newline at end of file diff --git a/db/mongo/BotGuild.js b/db/mongo/BotGuild.js deleted file mode 100644 index 98655042..00000000 --- a/db/mongo/BotGuild.js +++ /dev/null @@ -1,110 +0,0 @@ -const { Schema, model } = require('mongoose'); - -const BotGuildClass = require('../../classes/Bot/bot-guild'); - -/** - * @class BotGuild - */ -const BotGuildSchema = new Schema({ - - roleIDs: { - memberRole: { - type: String, - }, - staffRole: { - type: String, - }, - adminRole: { - type: String, - }, - everyoneRole: { - type: String, - }, - mentorRole: { - type: String, - } - }, - - channelIDs: { - adminConsole: { - type: String, - }, - adminLog: { - type: String, - }, - botSpamChannel: { - type: String, - } - }, - - mentorTickets: { - // ticketNumber: { - // type: Number, - // }, - incomingTicketsChannel: { - type: String, - }, - mentorRoleSelectionChannel: { - type: String, - }, - requestTicketChannel: { - type: String, - } - }, - - verification: { - isEnabled: { - type: Boolean, - default: false, - }, - guestRoleID: { - type: String, - }, - welcomeSupportChannelID: { - type: String, - }, - verificationRoles: { - type: Map, - default: new Map(), - } - }, - - // stamps: { - // isEnabled: { - // type: Boolean, - // default: false, - // }, - // stampRoleIDs: { - // type: Map, - // default: new Map(), - // }, - // stamp0thRoleId: { - // type: String, - // }, - // stampCollectionTime: { - // type: Number, - // default: 60, - // }, - // }, - - embedColor: { - type: String, - default: '#26fff4', - }, - - _id: { - type: String, - required: true, - }, - - isSetUpComplete: { - type: Boolean, - default: false, - }, -}); - -BotGuildSchema.loadClass(BotGuildClass); - -const BotGuild = model('BotGuild', BotGuildSchema); - -module.exports = BotGuild; \ No newline at end of file diff --git a/db/mongo/mongoUtil.js b/db/mongo/mongoUtil.js deleted file mode 100644 index d242f443..00000000 --- a/db/mongo/mongoUtil.js +++ /dev/null @@ -1,46 +0,0 @@ -const { MongoClient, Db, } = require('mongodb'); -const mongoose = require('mongoose'); - -/** - * The mongo utility module has some useful mongo related helper functions. - * @module MongoUtil - */ - -const url = 'mongodb+srv://dev-user:' + process.env.MONGODBPASSWORD + '@cluster-dev.j87rm.mongodb.net/test?retryWrites=true&w=majority&useNewUrlParser=true&useUnifiedTopology=true'; -const mongooseUrl = 'mongodb+srv://dev-user:' + process.env.MONGODBPASSWORD + '@cluster-dev.j87rm.mongodb.net/data'; -/** @type {Db} */ -var _db; - -module.exports = { - - /** - * Starts a connection to MongoDB - */ - async connect() { - const mongoClient = new MongoClient(url); - - await mongoClient.connect(); - - console.log('Connected to mongoDB'); - _db = mongoClient.db('data'); - }, - - /** - * @returns {Db} - */ - getDb() { - return _db; - }, - - /** - * @returns {Collection} - */ - getBotGuildCol() { - return _db.collection('botGuilds'); - }, - - async mongooseConnect() { - _db = await mongoose.connect(mongooseUrl, {useNewUrlParser: true, useUnifiedTopology: true}); - } - -}; \ No newline at end of file diff --git a/discord-services.js b/discord-services.js deleted file mode 100644 index 406300ce..00000000 --- a/discord-services.js +++ /dev/null @@ -1,301 +0,0 @@ -const { GuildMember, TextChannel, Message, User, MessageEmbed, RoleResolvable, Guild } = require('discord.js'); -const winston = require('winston'); -const firebaseUtil = require('./db/firebase/firebaseUtil'); - -/** - * The discord services module has useful discord related functions. - * These functions are helper, discord related functions. - * @module DiscordServices - */ - -/** - * Checks if the member has a role, returns true if it does - * @param {GuildMember} member - member to check role - * @param {String} role - role ID to check for - */ -function checkForRole(member, role) { - winston.loggers.get(member.guild.id).verbose(`A role check was requested. Role ID: ${role}. Member ID: ${member.id}`); - return member.roles.cache.has(role); -} -module.exports.checkForRole = checkForRole; - -/** - * Will send a message to a text channel and ping the user, can be deleted after a timeout. - * @param {TextChannel} channel - the channel to send the message to - * @param {String} userId - the user to tag on the message - * @param {String} message - the message to send - * @param {Number} timeout - timeout before delete if any, in seconds - * @async - * @returns {Promise} - */ -async function sendMsgToChannel(channel, userId, message, timeout = 0) { - let msg = await channel.send('<@' + userId + '> ' + message); - - if (timeout) msg.delete({timeout: timeout * 1000}); // convert to milliseconds - winston.loggers.get(channel.guild.id).verbose(`A message has been sent to the channel ${channel.name} for the user with id ${userId} ${timeout === 0 ? 'with no timeout requested' : 'with a ' + timeout + ' second timeout.'}`); - return msg; -} -module.exports.sendMsgToChannel = sendMsgToChannel; - -/** - * Send a Direct message to a member, option to delete after a few seconds. - * Helps user fix DM issue if the bot can't reach them over DM. - * @param {User | GuildMember} member - the user or member to send a DM to - * @param {String | MessageEmbed} message - the message to send - * @param {Boolean} isDelete - weather to delete message after 60 seconds - * @async - * @return {Promise} - */ -async function sendMessageToMember(member, message, isDelete = false) { - return await member.send(message).then(msg => { - winston.loggers.get(member?.guild?.id || 'main').verbose(`A DM message was sent to user with id ${member.id}.`); - if (isDelete === true) { - msg.delete({timeout: 60000}); - } - return msg; - }).catch(async error => { - if (error.code === 50007) { - winston.loggers.get(member?.guild?.id || 'main').warning(`A DM message was sent to user with id ${member.id} but failed, he has been asked to fix this problem!`); - let initBotInfo; - if (member?.guild) initBotInfo = await firebaseUtil.getInitBotInfo(member.guild.id); - else { - winston.loggers.get(member.guild.id).error('While trying to help a user to get my DMs I could not find a botGuild for which this member is in. I could not help him!'); - throw Error(`I could not help ${member.id} due to not finding the guild he is trying to access. I need a member and not a user!`); - } - - let botSupportChannel = member.guild.channels.resolve(initBotInfo.channelIDs.botSupportChannel); - if (botSupportChannel) botSupportChannel.send('<@' + member.id + '> I couldn\'t reach you :(. Please turn on server DMs, explained in this link: https://support.discord.com/hc/en-us/articles/217916488-Blocking-Privacy-Settings-'); - } else { - throw error; - } - }); - -} -module.exports.sendMessageToMember = sendMessageToMember; - - -/** - * @typedef FieldInfo - * @property {String} title - field title - * @property {String} description - field description - */ - -/** - * @typedef EmbedOptions - * @property {String} title - embed title - * @property {String} description - embed description - * @property {String} color - embed color - * @property {Array} fields - embed fields - */ - -/** - * Sends an embed to a user via DM. Title and description are required, color and fields are optional. - * @param {User | GuildMember} member - member to send embed to - * @param {EmbedOptions} embedOptions - embed information - * @param {Boolean} isDelete - should the message be deleted after some time? - * @async - * @returns {Promise} - */ -async function sendEmbedToMember(member, embedOptions, isDelete = false) { - // check embedOptions - if (embedOptions?.title === undefined || embedOptions?.title === '') throw new Error('A title is needed for the embed!'); - if (embedOptions?.description === undefined || embedOptions?.description === '') throw new Error('A description is needed for the embed!'); - if (embedOptions?.color === undefined || embedOptions?.color === '') embedOptions.color === '#ff0000'; - - let embed = new MessageEmbed().setColor(embedOptions.color) - .setTitle(embedOptions.title) - .setDescription(embedOptions.description) - .setTimestamp(); - - if (embedOptions?.fields) embedOptions.fields.forEach((fieldInfo, index) => embed.addField(fieldInfo.title, fieldInfo.description)); - - return sendMessageToMember(member, embed, isDelete); -} -module.exports.sendEmbedToMember = sendEmbedToMember; - -/** - * Add a role to a member - * @param {GuildMember} member - the guild member to give a role to - * @param {RoleResolvable} addRole - the role to add to the member - */ -function addRoleToMember(member, addRole) { - if (!member?.guild) throw Error('I need a member not a user!!!'); - - let role = member.guild.roles.resolve(addRole); - member.roles.add(addRole).catch(error => { - // try one more time - member.roles.add(addRole).catch(error => { - // now send error to admins - discordLog(member.guild, '@everyone The member <@' + member.id + '> did not get the role <@&' + role.id +'> please help me!'); - winston.loggers.get(member.guild.id).error(`Could not give the member with id ${member.id} the role ${role.name} with id ${role.id}. The following error ocurred: ${error.name} - ${error.message}.`, { event: 'Error', data: error }); - }); - }); - winston.loggers.get(member.guild.id).verbose(`A member with id ${member.id} was given the role ${role.name} with id ${role.id}`); -} -module.exports.addRoleToMember = addRoleToMember; - -/** - * Remove a role to a member - * @param {GuildMember} member - the guild member to give a role to - * @param {RoleResolvable} removeRole - the role to add to the member - */ -function removeRolToMember(member, removeRole) { - let role = member.guild.roles.resolve(removeRole); - member.roles.remove(removeRole).catch(error => { - // try one more time - member.roles.remove(removeRole).catch(error => { - // now send error to admins - discordLog(member.guild, '@everyone The member <@' + member.user.id + '> did not lose the role ' + member.guild.roles.cache.get(removeRole).id + ', please help me!'); - winston.loggers.get(member.guild.id).error(`Could not remove the member with id ${member.id} the role ${role.name} with id ${role.id}. The following error ocurred: ${error.name} - ${error.message}.`); - }); - }); - winston.loggers.get(member.guild.id).verbose(`A member with id ${member.id} lost the role ${role.name} with id ${role.id}`); -} -module.exports.removeRolToMember = removeRolToMember; - -/** - * Replaces one role for the other - * @param {GuildMember} member - member to change roles to - * @param {RoleResolvable} removeRole - role to remove - * @param {RoleResolvable} addRole - role to add - */ -function replaceRoleToMember(member, removeRole, addRole) { - addRoleToMember(member, addRole); - removeRolToMember(member, removeRole); -} -module.exports.replaceRoleToMember = replaceRoleToMember; - -/** - * Log a message on the log channel - * @param {Guild} guild - the guild being used - * @param {String | MessageEmbed} message - message to send to the log channel - * @async - */ -async function discordLog(guild, message) { - let initBotInfo = await firebaseUtil.getInitBotInfo(guild.id); - if (initBotInfo?.channelIDs?.adminLog) { - guild.channels.resolve(initBotInfo.channelIDs.adminLog)?.send(message); - winston.loggers.get(guild.id).silly(`The following was logged to discord: ${message}`); - } - else winston.loggers.get(guild.id).error('I was not able to log something to discord!! I could not find the botGuild or the adminLog channel!'); -} -module.exports.discordLog = discordLog; - -/** - * Reply to message and delete 5 seconds later - * @param {Message} message - the message to reply to - * @param {String} reply - the string to reply - */ -async function replyAndDelete(message, reply) { - var msg = await message.reply(reply); - msg.delete({timeout: 5000}); - winston.loggers.get(message?.guild.id || 'main').verbose(`A message with id ${message.id} is being replied to and then the reply is being deleted.`); -} -module.exports.replyAndDelete = replyAndDelete; - -/** - * Deletes a message if the message hasn't been deleted already - * @param {Message} message - the message to delete - * @param {Number} timeout - the time to wait in milliseconds - * @async - */ -async function deleteMessage(message, timeout = 0) { - if (!message.deleted && message.deletable && message.channel.type != 'dm') { - winston.loggers.get(message.guild.id).verbose(`A message with id ${message.id} in the guild channel ${message.channel.name} with id ${message.channel.id} was deleted.`); - await message.delete({timeout: timeout}).catch(error => console.log(error)); - } else if (message.channel.type === 'dm' && message.author.bot) { - winston.loggers.get('main').verbose(`A message with id ${message.id} in a DM channel with user id ${message.channel.recipient.id} from the bot was deleted.`); - await message.delete({timeout: timeout}).catch(error => console.log(error)); - } else { - winston.loggers.get(message?.guild.id | 'main').warning(`A message with id ${message.id} in a DM channel from user with id ${message.author.id} tried to be deleted but was not possible.`); - } -} -module.exports.deleteMessage = deleteMessage; - -/** - * Delete the given channel if it is not deleted already - * @param {TextChannel} channel - */ -async function deleteChannel(channel) { - if (!channel.deleted && channel.deletable) { - winston.loggers.get(channel.guild.id).verbose(`The channel ${channel.name} with id ${channel.id} was deleted.`); - await channel.delete().catch(error => console.log(error)); - } else { - winston.loggers.get(channel.guild.id).warning(`The channel ${channel?.name} with id ${channel?.id} tried to be deleted but was not possible!`); - } -} -module.exports.deleteChannel = deleteChannel; - -/** - * Returns a random color as a hex string. - * @returns {String} - hex color - */ -function randomColor() { - winston.loggers.get('main').silly('A random color has been used!'); - return Math.floor(Math.random()*16777215).toString(16); -} -module.exports.randomColor = randomColor; - -/** - * Validates an email using a reg exp. - * @param {String} email - the email to validate - * @returns {Boolean} true if valid email, false otherwise - */ -function validateEmail(email) { - winston.loggers.get('main').silly('An email has been validated!'); - - // make email lowercase - email = email.toLowerCase(); - - // regex to validate email - const re = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; - - // let user know he has used the command incorrectly and exit - if (email === '' || !re.test(email)) { - - return false; - } else { - return true; - } -} -module.exports.validateEmail = validateEmail; - -/** - * will shuffle an array as best and fast as possible - * @param {Array<*>} array - array to shuffle - * @private - */ -function shuffleArray(array) { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } -} -module.exports.shuffleArray = shuffleArray; - -/** - * asks user an opt-in question in DM - * @param {GuildMember} member - * @param {BotGuild} botGuild - * @param {String} title - title for embed - * @param {String} description - description for embed - * @param {String} thankYouMessage - followup after user reacts - * @param {String} email - user's email - */ -async function askBoolQuestion(member, botGuild, title, description, thankYouMessage, email) { - const message = await sendEmbedToMember(member, { - title, - description, - color: botGuild.colors.specialDMEmbedColor, - }); - await message.react('👍'); - const filter = (reaction, user) => { - return reaction.emoji.name === '👍' && user.id != message.author.id; - }; - const collector = message.createReactionCollector(filter, { max: 1 }); - collector.on('collect', async (reaction, user) => { - sendMessageToMember(member, thankYouMessage, false); - await firebaseUtil.saveToFirebase(member.guild.id, 'codex', email); - }); -} -module.exports.askBoolQuestion = askBoolQuestion; \ No newline at end of file diff --git a/docs/Activity.html b/docs/Activity.html deleted file mode 100644 index 85315357..00000000 --- a/docs/Activity.html +++ /dev/null @@ -1,2535 +0,0 @@ - - - - - - - - Factotum Documentation Activity - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Activity

-
- - - - - -
- -
- -

Activity(ActivityInfo)

- -
An activity is a overarching class for any kind of activity. An activity consists of a category with voice and text channels. Activities have features admins can run from the admin console by reacting to a message (console). The activity can be private to specified roles or public to all users.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Activity(ActivityInfo) - - -

- - - - -
- Constructor for an activity, will create the category, voice and text channel. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
ActivityInfo - - -ActivityInfo - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 41 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -Console - - - - -

- # - - - adminConsole - - -

- - - - -
- The admin console with activity features. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 99 - -

- -
- - - - - -
- -
- - - - -BotGuildModel - - - - -

- # - - - botGuild - - -

- - - - -
- The mongoose BotGuildModel Object -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 110 - -

- -
- - - - - -
- -
- - - - -Guild - - - - -

- # - - - guild - - -

- - - - -
- The guild this activity is in. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 87 - -

- -
- - - - - -
- -
- - - - -string - - - - -

- # - - - name - - -

- - - - -
- The name of this activity. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 81 - -

- -
- - - - - -
- -
- - - - -Room - - - - -

- # - - - room - - -

- - - - -
- The room this activity lives in. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 93 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - static - - - - - promptForRoleParticipants(channel, userId, isStaffAutoopt) → {Promise.<Collection.<String, Role>>} - - -

- - - - -
- Prompts a user for the roles that can have access to an activity. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
channel - - -TextChannel - - - - - - - - - - - - the channel to prompt in
userId - - -String - - - - - - - - - - - - the user id to prompt
isStaffAuto - - -Boolean - - - - - - <optional>
- - - - - -
- - false - - true if staff are added automatically
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 52 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Collection.<String, Role>> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - addChannel(channel, userId) - - -

- - - - -
- Add a channel to the activity, prompts user for info and name. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 213 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - protected - - - - - addDefaultFeatures() - - -

- - - - -
- Adds the default features to the activity, these features are available to all activities. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 137 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - archive(archiveCategory) - - -

- - - - -
- Archive the activity. Move general text channel to archive category, remove all remaining channels and remove the category. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
archiveCategory - - -CategoryChannel - - - - the category where the general text channel will be moved to
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 267 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - delete() - - -

- - - - -
- Delete all the channels and the category. Remove the workshop from firebase. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 279 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - distributeStamp(channel, userId) - - -

- - - - -
- Will let hackers get a stamp for attending the activity. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 369 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - init() → {Promise.<Activity>} - - -

- - - - -
- Initialize this activity by creating the channels, adding the features and sending the admin console. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 121 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Activity> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - removeChannel(channel, userId) - - -

- - - - -
- Removes a channel from the activity, the user will decide which. Wont delete channels in the safeChannel map. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 243 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - roleShuffle(channel, userId) - - -

- - - - -
- Shuffles users with a specific role throughout the activity's voice channels -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 354 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - ruleValidation(channel, userId) - - -

- - - - -
- Will lock the channels behind an emoji collector. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 424 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - voiceCallBack(channel, userId) - - -

- - - - -
- Move all users back to a specified voice channel from the activity's voice channels. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 292 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/AddMembers.html b/docs/AddMembers.html deleted file mode 100644 index 10481437..00000000 --- a/docs/AddMembers.html +++ /dev/null @@ -1,559 +0,0 @@ - - - - - - - - Factotum Documentation AddMembers - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

AddMembers

-
- - - - - -
- -
- -

AddMembers()

- -
Will prompt the user for a csv file to add members to firebase. The csv file must have the following columns with exactly those names: * email -> the user's email, must be a string * firstName -> the user's first name, must be a string * lastName -> the user's last name, must be a string * types -> the types the user will get, must be a list of strings separated by a comma, spaces are okay, types must be the same ones used when setting up verification
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new AddMembers() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/verification/add-members.js, line 22 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/verification/add-members.js, line 43 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/AskQuestion.html b/docs/AskQuestion.html deleted file mode 100644 index 404134df..00000000 --- a/docs/AskQuestion.html +++ /dev/null @@ -1,610 +0,0 @@ - - - - - - - - Factotum Documentation AskQuestion - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

AskQuestion

-
- - - - - -
- -
- -

AskQuestion()

- -
The ask command tries to imitate a thread like functionality from slack. Users can ask questions, and then other users can respond to the question, the responses are added on the same message embed, to keep the conversation on the same message.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new AskQuestion() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/hacker_utility/ask.js, line 15 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message, args) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - -
args - - -Object - - - -
question - - -String - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/hacker_utility/ask.js, line 40 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Attend.html b/docs/Attend.html deleted file mode 100644 index 3e57a922..00000000 --- a/docs/Attend.html +++ /dev/null @@ -1,610 +0,0 @@ - - - - - - - - Factotum Documentation Attend - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Attend

-
- - - - - -
- -
- -

Attend()

- -
Attends the user who runs this command. The user must have the guild ID. Can only be run via DMs.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Attend() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/attendance/attend.js, line 15 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message, args) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - -
args - - -Object - - - -
guildId - - -String - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/attendance/attend.js, line 41 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/BotGuild.html b/docs/BotGuild.html deleted file mode 100644 index 37d8d003..00000000 --- a/docs/BotGuild.html +++ /dev/null @@ -1,5437 +0,0 @@ - - - - - - - - Factotum Documentation BotGuild - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

BotGuild

-
- - - - - -
- -
- -

BotGuild()

- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new BotGuild() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 9 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -Array.<String> - - - - -

- # - - - adminPermissions - - -

- - - - -
- Admin role permissions. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 24 - -

- -
- - - - - -
- -
- - - - -Array.<String> - - - - -

- # - - - memberPermissions - - -

- - - - -
- The regular member perms. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 30 - -

- -
- - - - - -
- -
- - - - -Array.<String> - - - - -

- # - - - staffPermissions - - -

- - - - -
- Staff role permissions. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 16 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - static - - - - - createAdminChannels(guild, adminRole, everyoneRole) → {Promise.<{TextChannel, TextChannel}>} - - -

- - - - -
- Will create the admin channels with the correct roles. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
guild - - -Guild - - - -
adminRole - - -Role - - - -
everyoneRole - - -Role - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 208 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
- {Admin Console, Admin Log Channel}
- - -
- - -Promise.<{TextChannel, TextChannel}> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - addStamp(roleId, stampNumber) - - -

- - - - -
- Adds a stamp to the stamp collection. Does not save the mongoose document! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
roleId - - -String - - - -
stampNumber - - -Number - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 438 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - readyUp(botGuildInfo, client) → {Promise.<BotGuild>} - - -

- - - - -
- Will set the minimum required information for the bot to work on this guild. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuildInfo - - -BotGuildInfo - - - -
client - - -CommandoClient - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 109 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<BotGuild> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - setCommandStatus(client) - - -

- - - - -
- Will enable and disable the appropriate commands by looking at what is enabled in the botGuild. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
client - - -CommandoClient - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 481 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - setUpAnnouncements(client, announcementChannelID) - - -

- - - - -
- Will set up the firebase announcements. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
client - - -CommandoClient - - - -
announcementChannelID - - -String - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 358 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - setUpAsk(client) - - -

- - - - -
- Will enable the ask command. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
client - - -CommandoClient - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 468 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - setUpAttendance(client, attendeeRoleID) → {Promise.<BotGuild>} - - -

- - - - -
- Sets up the attendance functionality. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
client - - -CommandoClient - - - -
attendeeRoleID - - -String - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 340 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<BotGuild> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - setUpReport(client, incomingReportChannelID) → {Promise.<BotGuild>} - - -

- - - - -
- Enables the report commands and sends the reports to the given channel. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
client - - -CommandoClient - - - -
incomingReportChannelID - - -String - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 451 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<BotGuild> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - setUpStamps(client, stampAmountopt, stampCollectionTimeopt, stampRoleIDsopt) → {Promise.<BotGuild>} - - -

- - - - -
- Creates the stamps roles and adds them to this BotGuild. If stamps roles are given then no roles are created! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
client - - -CommandoClient - - - - - - - - - - - -
stampAmount - - -Number - - - - - - <optional>
- - - - - -
- - 0 - - amount of stamps to create
stampCollectionTime - - -Number - - - - - - <optional>
- - - - - -
- - 60 - - time given to users to send password to get stamp
stampRoleIDs - - -Array.<String> - - - - - - <optional>
- - - - - -
- - current stamp roles to use
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 402 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<BotGuild> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - setUpVerification(client, guestRoleId, types, verificationChannelsopt) → {Promise.<BotGuild>} - - -

- - - - -
- Will set up the verification process. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
client - - -CommandoClient - - - - - - - - - - - -
guestRoleId - - -String - - - - - - - - - - - -
types - - -Array.<TypeInfo> - - - - - - - - - - - -
verificationChannels - - -VerificationChannels - - - - - - <optional>
- - - - - -
- - null - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 259 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<BotGuild> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - validateBotGuildInfo(botGuildInfo) - - -

- - - - -
- Validate the information. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuildInfo - - -BotGuildInfo - - - - the information to validate
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 94 - -

- -
- - - - - - - - - - - - - - - - -
-
-
- - -
- - -
Error if the botGuildInfo is incomplete
- - -
- - -
-
- - - - - - -
- -
-
- - - - - -
- -
- - - - - - - -
- -
- -

BotGuild()

- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new BotGuild() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/mongo/BotGuild.js, line 5 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -Array.<String> - - - - -

- # - - - adminPermissions - - -

- - - - -
- Admin role permissions. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 24 - -

- -
- - - - - -
- -
- - - - -Array.<String> - - - - -

- # - - - memberPermissions - - -

- - - - -
- The regular member perms. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 30 - -

- -
- - - - - -
- -
- - - - -Array.<String> - - - - -

- # - - - staffPermissions - - -

- - - - -
- Staff role permissions. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 16 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - static - - - - - createAdminChannels(guild, adminRole, everyoneRole) → {Promise.<{TextChannel, TextChannel}>} - - -

- - - - -
- Will create the admin channels with the correct roles. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
guild - - -Guild - - - -
adminRole - - -Role - - - -
everyoneRole - - -Role - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 208 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
- {Admin Console, Admin Log Channel}
- - -
- - -Promise.<{TextChannel, TextChannel}> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - addStamp(roleId, stampNumber) - - -

- - - - -
- Adds a stamp to the stamp collection. Does not save the mongoose document! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
roleId - - -String - - - -
stampNumber - - -Number - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 438 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - readyUp(botGuildInfo, client) → {Promise.<BotGuild>} - - -

- - - - -
- Will set the minimum required information for the bot to work on this guild. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuildInfo - - -BotGuildInfo - - - -
client - - -CommandoClient - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 109 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<BotGuild> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - setCommandStatus(client) - - -

- - - - -
- Will enable and disable the appropriate commands by looking at what is enabled in the botGuild. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
client - - -CommandoClient - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 481 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - setUpAnnouncements(client, announcementChannelID) - - -

- - - - -
- Will set up the firebase announcements. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
client - - -CommandoClient - - - -
announcementChannelID - - -String - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 358 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - setUpAsk(client) - - -

- - - - -
- Will enable the ask command. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
client - - -CommandoClient - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 468 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - setUpAttendance(client, attendeeRoleID) → {Promise.<BotGuild>} - - -

- - - - -
- Sets up the attendance functionality. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
client - - -CommandoClient - - - -
attendeeRoleID - - -String - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 340 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<BotGuild> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - setUpReport(client, incomingReportChannelID) → {Promise.<BotGuild>} - - -

- - - - -
- Enables the report commands and sends the reports to the given channel. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
client - - -CommandoClient - - - -
incomingReportChannelID - - -String - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 451 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<BotGuild> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - setUpStamps(client, stampAmountopt, stampCollectionTimeopt, stampRoleIDsopt) → {Promise.<BotGuild>} - - -

- - - - -
- Creates the stamps roles and adds them to this BotGuild. If stamps roles are given then no roles are created! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
client - - -CommandoClient - - - - - - - - - - - -
stampAmount - - -Number - - - - - - <optional>
- - - - - -
- - 0 - - amount of stamps to create
stampCollectionTime - - -Number - - - - - - <optional>
- - - - - -
- - 60 - - time given to users to send password to get stamp
stampRoleIDs - - -Array.<String> - - - - - - <optional>
- - - - - -
- - current stamp roles to use
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 402 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<BotGuild> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - setUpVerification(client, guestRoleId, types, verificationChannelsopt) → {Promise.<BotGuild>} - - -

- - - - -
- Will set up the verification process. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
client - - -CommandoClient - - - - - - - - - - - -
guestRoleId - - -String - - - - - - - - - - - -
types - - -Array.<TypeInfo> - - - - - - - - - - - -
verificationChannels - - -VerificationChannels - - - - - - <optional>
- - - - - -
- - null - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 259 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<BotGuild> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - validateBotGuildInfo(botGuildInfo) - - -

- - - - -
- Validate the information. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuildInfo - - -BotGuildInfo - - - - the information to validate
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 94 - -

- -
- - - - - - - - - - - - - - - - -
-
-
- - -
- - -
Error if the botGuildInfo is incomplete
- - -
- - -
-
- - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Cave.html b/docs/Cave.html deleted file mode 100644 index 651ae6e2..00000000 --- a/docs/Cave.html +++ /dev/null @@ -1,1636 +0,0 @@ - - - - - - - - Factotum Documentation Cave - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Cave

-
- - - - - -
- -
- -

Cave(caveOptions, botGuild, guild)

- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Cave(caveOptions, botGuild, guild) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
caveOptions - - -CaveOptions - - - -
botGuild - - -BotGuildModel - - - -
guild - - -Guild - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 60 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CaveOptions - - - - -

- # - - - caveOptions - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 71 - -

- -
- - - - - -
- -
- - - - -CaveChannels - - - - -

- # - - - channels - - -

- - - - -
- The channels needed for a cave. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 89 - -

- -
- - - - - -
- -
- - - - -Room - - - - -

- # - - - publicRoom - - -

- - - - -
- The public room for this cave. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 95 - -

- -
- - - - - -
- -
- - - - -Console - - - - -

- # - - - subRoleConsole - - -

- - - - -
- The console where cave members can get sub roles. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 101 - -

- -
- - - - - -
- -
- - - - -Map.<String, SubRole> - - - - -

- # - - - subRoles - - -

- - - - -
- The cave sub roles, keys are the emoji name, holds the subRole -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 78 - -

- -
- - - - - -
- -
- - - - -TicketManager - - - - -

- # - - - ticketManager - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 83 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - addSubRoleCallback(channel, userId) → {Promise.<Role>} - - -

- - - - -
- Prompts a user for information to create a new sub role for this cave. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - -
userId - - -String - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 226 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Role> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - archive(archiveCategory) - - -

- - - - -
- Removes private channels and archives the public channels. It also deletes the ticket rooms. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
archiveCategory - - -CategoryChannel - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 399 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - delete() - - -

- - - - -
- Deletes all the tickets rooms, public channels and private channels. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 387 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - deleteTicketChannelsCallback(channel, userId) - - -

- - - - -
- Will prompt the user for more information to delete some, all, or a few tickets. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - -
userId - - -String - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 271 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - includeExcludeCallback(channel, userId) - - -

- - - - -
- Will prompt the user for channel numbers to include or exclude from the garbage collector. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - -
userId - - -String - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 321 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/ChangePreFix.html b/docs/ChangePreFix.html deleted file mode 100644 index 6f7fe39b..00000000 --- a/docs/ChangePreFix.html +++ /dev/null @@ -1,450 +0,0 @@ - - - - - - - - Factotum Documentation ChangePreFix - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

ChangePreFix

-
- - - - - -
- -
- -

ChangePreFix()

- -
Gives admin the ability to change the prefix used in the guild by the bot.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new ChangePreFix() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_utility/change-pre-fix.js, line 9 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_utility/change-pre-fix.js, line 26 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/ChangeStampTime.html b/docs/ChangeStampTime.html deleted file mode 100644 index a49a7163..00000000 --- a/docs/ChangeStampTime.html +++ /dev/null @@ -1,610 +0,0 @@ - - - - - - - - Factotum Documentation ChangeStampTime - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

ChangeStampTime

-
- - - - - -
- -
- -

ChangeStampTime()

- -
Change the time users get to react to get a stamp from activity stamp distributions. It defaults to 60 seconds.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new ChangeStampTime() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/stamps/change-stamp-time.js, line 12 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message, args) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - -
args - - -Object - - - -
newTime - - -Number - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/stamps/change-stamp-time.js, line 40 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/CheckMember.html b/docs/CheckMember.html deleted file mode 100644 index 2d2adb97..00000000 --- a/docs/CheckMember.html +++ /dev/null @@ -1,610 +0,0 @@ - - - - - - - - Factotum Documentation CheckMember - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

CheckMember

-
- - - - - -
- -
- -

CheckMember()

- -
User can check if a member is in the database by email or name.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new CheckMember() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/verification/check-member.js, line 13 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message, args) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - -
args - - -Object - - - -
emailOrName - - -String - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/verification/check-member.js, line 44 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/ClearChat.html b/docs/ClearChat.html deleted file mode 100644 index 77b27e8e..00000000 --- a/docs/ClearChat.html +++ /dev/null @@ -1,610 +0,0 @@ - - - - - - - - Factotum Documentation ClearChat - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

ClearChat

-
- - - - - -
- -
- -

ClearChat()

- -
The clear chat command will clear a channel from at most 100 messages that are at least 2 weeks young. Option to keep pinned messages available.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new ClearChat() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_utility/clear-chat.js, line 14 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message, args) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - - the botGuild instance given from PermissionCommand
message - - -Message - - - - the message in which the command was run
args - - -Object - - - - the command arguments
keepPinned - - -Boolean - - - - if true any pinned messages will not be removed
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_utility/clear-chat.js, line 43 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/CoffeeChats.html b/docs/CoffeeChats.html deleted file mode 100644 index 942331e7..00000000 --- a/docs/CoffeeChats.html +++ /dev/null @@ -1,3123 +0,0 @@ - - - - - - - - Factotum Documentation CoffeeChats - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

CoffeeChats

-
- - - - - -
- -
- -

CoffeeChats(activityInfo, numOfTeams)

- -
A CoffeeChat is a special activity where teams get shuffled around voice channels to talk with other teams or members like mentors. Users can join the activity by reacting to a message. Groups are of unlimited size but must be pre-made. The activity will not create groups. Admins get additional features to run this activity. These features let shuffle the groups around the available voice channels.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new CoffeeChats(activityInfo, numOfTeams) - - -

- - - - -
- Basic constructor for a coffee chats. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
activityInfo - - -Activity.ActivityInfo - - - -
numOfTeams - - -Number - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/coffee-chats.js, line 14 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -Console - - - - -

- # - - - adminConsole - - -

- - - - -
- The admin console with activity features. -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 99 - -

- -
- - - - - -
- -
- - - - -BotGuildModel - - - - -

- # - - - botGuild - - -

- - - - -
- The mongoose BotGuildModel Object -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 110 - -

- -
- - - - - -
- -
- - - - -Guild - - - - -

- # - - - guild - - -

- - - - -
- The guild this activity is in. -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 87 - -

- -
- - - - - -
- -
- - - - -TextChannel - - - - -

- # - - - joinActivityChannel - - -

- - - - -
- The channel where users join the activity. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/coffee-chats.js, line 40 - -

- -
- - - - - -
- -
- - - - -VoiceChannel - - - - -

- # - - - mainVoiceChannel - - -

- - - - -
- The main voice channel where everyone starts. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/coffee-chats.js, line 46 - -

- -
- - - - - -
- -
- - - - -VoiceChannel - - - - -

- # - - - mainVoiceChannel - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/coffee-chats.js, line 62 - -

- -
- - - - - -
- -
- - - - -string - - - - -

- # - - - name - - -

- - - - -
- The name of this activity. -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 81 - -

- -
- - - - - -
- -
- - - - -Number - - - - -

- # - - - numOfTeams - - -

- - - - -
- The number of groups available in this coffee chat -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/coffee-chats.js, line 34 - -

- -
- - - - - -
- -
- - - - -Room - - - - -

- # - - - room - - -

- - - - -
- The room this activity lives in. -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 93 - -

- -
- - - - - -
- -
- - - - -Collection.<Number, Array.<GuildMember>> - - - - -

- # - - - teams - - -

- - - - -
- A collection of the groups that will attend this coffee chat. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/coffee-chats.js, line 28 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - addChannel(channel, userId) - - -

- - - - -
- Add a channel to the activity, prompts user for info and name. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 213 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - protected - - - - - addDefaultFeatures() - - -

- - - - -
- Adds the default features to the activity, these features are available to all activities. -
- - - - - - - - - - - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/coffee-chats.js, line 91 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - addTeamSlot() - - -

- - - - -
- Add a team slot to the activity and adds a voice channel for them. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/coffee-chats.js, line 222 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - archive(archiveCategory) - - -

- - - - -
- Archive the activity. Move general text channel to archive category, remove all remaining channels and remove the category. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
archiveCategory - - -CategoryChannel - - - - the category where the general text channel will be moved to
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 267 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - delete() - - -

- - - - -
- Delete all the channels and the category. Remove the workshop from firebase. -
- - - - - - - - - - - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 279 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - distributeStamp(channel, userId) - - -

- - - - -
- Will let hackers get a stamp for attending the activity. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 369 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - groupShuffle() - - -

- - - - -
- Shuffle users from general voice to all other voice channel. Groups will stay on the same voice channel. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/coffee-chats.js, line 190 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - init(channel, userId) → {Promise.<CoffeeChats>} - - -

- - - - -
- Initializes the activity by creating the necessary channels. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - -
userId - - -String - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/coffee-chats.js, line 58 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<CoffeeChats> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - removeChannel(channel, userId) - - -

- - - - -
- Removes a channel from the activity, the user will decide which. Wont delete channels in the safeChannel map. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 243 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - resetTeams() - - -

- - - - -
- Resets the teams to have no teams. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/coffee-chats.js, line 214 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - roleShuffle(channel, userId) - - -

- - - - -
- Shuffles users with a specific role throughout the activity's voice channels -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 354 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - ruleValidation(channel, userId) - - -

- - - - -
- Will lock the channels behind an emoji collector. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 424 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - voiceCallBack(channel, userId) - - -

- - - - -
- Move all users back to a specified voice channel from the activity's voice channels. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 292 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Console.html b/docs/Console.html deleted file mode 100644 index 227d5f1b..00000000 --- a/docs/Console.html +++ /dev/null @@ -1,2611 +0,0 @@ - - - - - - - - Factotum Documentation Console - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Console

-
- - - - - -
- -
- -

Console(args)

- -
The console class represents a Discord UI console. A console is an embed with options users can interact with by reacting with emojis.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Console(args) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
args - - -ConsoleInfo - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 22 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -TextChannel -| - -DMChannel - - - - -

- # - - - channel - - -

- - - - -
- The channel this console lives in. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 85 - -

- -
- - - - - -
- -
- - - - -ReactionCollector - - - - -

- # - - - collector - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 79 - -

- -
- - - - - -
- -
- - - - -ReactionCollectorOptions - - - - -

- # - - - collectorOptions - - -

- - - - -
- The collector options. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 61 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - color - - -

- - - - -
- - hex color -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 55 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - description - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 38 - -

- -
- - - - - -
- -
- - - - -Collection.<String, Feature> - - - - -

- # - - - features - - -

- - - - -
- - -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 43 - -

- -
- - - - - -
- -
- - - - -Collection.<String, String> - - - - -

- # - - - fields - - -

- - - - -
- The fields this console has, not including feature fields. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 50 - -

- -
- - - - - -
- -
- - - - -Collection.<String, User> - - - - -

- # - - - interacting - - -

- - - - -
- Users the console is interacting with; -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 74 - -

- -
- - - - - -
- -
- - - - -Message - - - - -

- # - - - message - - -

- - - - -
- The message holding the console. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 68 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - title - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 33 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - static - - - - - fromJSON(json, guild) → {Console} - - -

- - - - -
- Creates a Console from JSON data. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
json - - -JSON - - - - the json data
guild - - -Guild - - - - the guild where this console lives
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 259 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Console - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - addFeature(feature) - - -

- - - - -
- Adds a feature to this console. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
feature - - -Feature - - - - the feature to add
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 153 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - addField(name, value, inlineopt) - - -

- - - - -
- Adds a field to this console without adding a feature. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
name - - -String - - - - - - - - - - the new field name
value - - -String - - - - - - - - - - the description on this field
inline - - -Boolean - - - - - - <optional>
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 197 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - changeColor(color) - - -

- - - - -
- Changes the console's color. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
color - - -String - - - - the new color in hex
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 207 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - createReactionCollector(message) - - -

- - - - -
- Creates the reaction collector in the message. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 122 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - delete() - - -

- - - - -
- Deletes this console from discord. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 221 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - removeFeature(identifier) - - -

- - - - -
- Removes a feature from this console. TODO remove from embed too! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
identifier - - -String -| - -Feature - - - - feature name, feature emojiName or feature
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 176 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - sendConsole(messageTextopt) - - -

- - - - -
- Sends the console to a channel -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
messageText - - -String - - - - - - <optional>
- - - - - -
text to add to the message used to send the embed
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 96 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - stopConsole() - - -

- - - - -
- Stop the console from interacting with any users. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 214 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - toJSON() → {JSON} - - -

- - - - -
- Creates a JSON representation of this object -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 239 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
representation of this object as JSON
- - -
- - -JSON - - -
- -
- - -
-
- - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/DiscordContests.html b/docs/DiscordContests.html deleted file mode 100644 index 56947859..00000000 --- a/docs/DiscordContests.html +++ /dev/null @@ -1,563 +0,0 @@ - - - - - - - - Factotum Documentation DiscordContests - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

DiscordContests

-
- - - - - -
- -
- -

DiscordContests()

- -
The DiscordContests class handles all functions related to Discord contests. It will ask questions in set intervals and pick winners based on keywords for those questions that have correct answers. For other questions it will tag staff and staff will be able to tell it the winner. It can also be paused and un-paused, and questions can be removed. Note: all answers are case-insensitive but any extra or missing characters will be considered incorrect.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new DiscordContests() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_activity/discord-contests.js, line 19 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message) - - -

- - - - -
- Stores a map which keeps the questions (strings) as keys and an array of possible answers (strings) as values. It iterates through each key in order and asks them in the Discord channel in which it was called at the given intervals. It also listens for emojis that tell it to pause, resume, or remove a specified question. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - - the message in which this command was called
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_activity/discord-contests.js, line 41 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/ERoomDirectory.html b/docs/ERoomDirectory.html deleted file mode 100644 index a6bbfaee..00000000 --- a/docs/ERoomDirectory.html +++ /dev/null @@ -1,563 +0,0 @@ - - - - - - - - Factotum Documentation ERoomDirectory - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

ERoomDirectory

-
- - - - - -
- -
- -

ERoomDirectory()

- -
Shows an embed with a link used for activities happening outside discord. Initial intent was to be used for sponsor booths. A specified role can open and close the rooms as they want. When rooms open, a specified role is notified.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new ERoomDirectory() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_boothing/e-room-directory.js, line 16 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(message, botGuild) - - -

- - - - -
- Sends an embed same channel with the sponsor's name and link to their Zoom boothing room. The embed has 2 states: Open and Closed. In the Closed state the embed will be red and say the booth is closed, which is the default, and the bot will react to the embed with a door emoji at the beginning. In the Open state the embed will be green and say the booth is open. Any time a staff or sponsor clicks on that emoji, the embed changes to the other state. When a booth goes from Closed to Open, it will also notify a role (specified by the user) that it is open. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - - messaged that called this command
botGuild - - -BotGuildModel - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_boothing/e-room-directory.js, line 41 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Feature.html b/docs/Feature.html deleted file mode 100644 index 5514da00..00000000 --- a/docs/Feature.html +++ /dev/null @@ -1,2682 +0,0 @@ - - - - - - - - Factotum Documentation Feature - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Feature

-
- - - - - -
- -
- -

Feature()

- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Feature() - - -

- - - - -
- A feature is an object with information to make an action from a console. The emojiName can be either a custom emoji ID or a unicode emoji name. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 22 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -FeatureCallback - - - - -

- # - - - callback - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 80 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - description - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 75 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - emojiName - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 70 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - name - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 65 - -

- -
- - - - - -
- -
- - - - -FeatureCallback - - - - -

- # - - - removeCallback - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 85 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - static - - - - - create(args) → {Feature} - - -

- - - - -
- Creates a feature object when you have a GuildEmoji or a ReactionEmoji. Used for when adding features programmatically! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
args - - -Object - - - - - - - - - -
name - - -String - - - - - - - - - -
description - - -String - - - - - - - - - -
emoji - - -GuildEmoji -| - -ReactionEmoji -| - -String - - - - - - - - - -
callback - - -FeatureCallback - - - - - - - - - -
removeCallback - - -FeatureCallback - - - - - - <optional>
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 41 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Feature - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - getFieldName(guildEmojiManager) → {String} - - -

- - - - -
- Returns a string with the emoji and the feature name: - Feature 1 -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
guildEmojiManager - - -GuildEmojiManager - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 95 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -String - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - getFieldValue() → {String} - - -

- - - - -
- Returns the feature's value string for when adding it to a embed field. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 105 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -String - - -
- -
- - -
-
- - - - -
- -
-
- - - - - -
- -
- - - - - - - -
- -
- -

Feature(args)

- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Feature(args) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
args - - -Object - - - - - - - - - - arguments
name - - -String - - - - - - - - - - the name of the feature
emojiName - - -String - - - - - - - - - - the name of the emoji
description - - -String - - - - - - - - - - the description of the feature
callback - - -FeatureCallback - - - - - - - - - - the callback for when the feature is activated
removeCallback - - -FeatureCallback - - - - - - <optional>
- - - - - -
the callback for when the feature is deactivated
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 60 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -FeatureCallback - - - - -

- # - - - callback - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 80 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - description - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 75 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - emojiName - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 70 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - name - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 65 - -

- -
- - - - - -
- -
- - - - -FeatureCallback - - - - -

- # - - - removeCallback - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 85 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - static - - - - - create(args) → {Feature} - - -

- - - - -
- Creates a feature object when you have a GuildEmoji or a ReactionEmoji. Used for when adding features programmatically! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
args - - -Object - - - - - - - - - -
name - - -String - - - - - - - - - -
description - - -String - - - - - - - - - -
emoji - - -GuildEmoji -| - -ReactionEmoji -| - -String - - - - - - - - - -
callback - - -FeatureCallback - - - - - - - - - -
removeCallback - - -FeatureCallback - - - - - - <optional>
- - - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 41 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Feature - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - getFieldName(guildEmojiManager) → {String} - - -

- - - - -
- Returns a string with the emoji and the feature name: - Feature 1 -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
guildEmojiManager - - -GuildEmojiManager - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 95 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -String - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - getFieldValue() → {String} - - -

- - - - -
- Returns the feature's value string for when adding it to a embed field. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 105 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -String - - -
- -
- - -
-
- - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Help.html b/docs/Help.html deleted file mode 100644 index 03b75d2f..00000000 --- a/docs/Help.html +++ /dev/null @@ -1,436 +0,0 @@ - - - - - - - - Factotum Documentation Help - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Help

-
- - - - - -
- -
- -

Help()

- -
The help command shows all the available commands for the user via DM message.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Help() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/essentials/help.js, line 13 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - -
    -
  • Command
  • -
- - - - - - - - - - - - - - - - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - run(message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/essentials/help.js, line 27 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/InitBot.html b/docs/InitBot.html deleted file mode 100644 index cd3c88a7..00000000 --- a/docs/InitBot.html +++ /dev/null @@ -1,911 +0,0 @@ - - - - - - - - Factotum Documentation InitBot - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

InitBot

-
- - - - - -
- -
- -

InitBot()

- -
The InitBot command initializes the bot on the guild. It will prompt the user for information needed to set up the bot. It is only usable by server administrators. It can only be run once.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new InitBot() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/essentials/init-bot.js, line 16 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - -
    -
  • Command
  • -
- - - - - - - - - - - - - - - - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - askOrCreate(roleName, channel, userId, guild, color) → {Promise.<Role>} - - -

- - - - -
- Will ask the user if a role has been created, if so, then prompt it, else then create it. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
roleName - - -String - - - - the role name
channel - - -TextChannel - - - - the text channel were to prompt
userId - - -Snowflake - - - - the user id to prompt to
guild - - -Guild - - - - the current guild
color - - -ColorResolvable - - - - the role color
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/essentials/init-bot.js, line 301 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Role> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - getVerificationTypes(channel, userId) → {Promise.<Array.<TypeInfo>>} - - -

- - - - -
- Prompts the user for a verification type and if they want to add more. Will call itself if true for a recursive call. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - -
userId - - -String - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/essentials/init-bot.js, line 271 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Array.<TypeInfo>> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - run(message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/essentials/init-bot.js, line 31 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/ManualVerify.html b/docs/ManualVerify.html deleted file mode 100644 index 273a66d3..00000000 --- a/docs/ManualVerify.html +++ /dev/null @@ -1,559 +0,0 @@ - - - - - - - - Factotum Documentation ManualVerify - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

ManualVerify

-
- - - - - -
- -
- -

ManualVerify()

- -
Will manually verify a user to the server and the database. Asks the user for a user ID, email, and type(s) to add with.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new ManualVerify() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/verification/manual-verify.js, line 18 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/verification/manual-verify.js, line 39 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/NewActivity.html b/docs/NewActivity.html deleted file mode 100644 index 8faed22f..00000000 --- a/docs/NewActivity.html +++ /dev/null @@ -1,610 +0,0 @@ - - - - - - - - Factotum Documentation NewActivity - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

NewActivity

-
- - - - - -
- -
- -

NewActivity()

- -
Creates a new activity and prompts the user for any information. Look at the activity class to learn what an activity is.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new NewActivity() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_activity/new-activity.js, line 13 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message, args) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - - the message in which the command was run
args - - -Object - - - -
activityName - - -String - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_activity/new-activity.js, line 43 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/NewCoffeeChats.html b/docs/NewCoffeeChats.html deleted file mode 100644 index bdc2baeb..00000000 --- a/docs/NewCoffeeChats.html +++ /dev/null @@ -1,637 +0,0 @@ - - - - - - - - Factotum Documentation NewCoffeeChats - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

NewCoffeeChats

-
- - - - - -
- -
- -

NewCoffeeChats()

- -
Creates a new coffee chats activity. See the coffee chats class to learn what a coffee chats activity is.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new NewCoffeeChats() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_activity/new-coffee-chats.js, line 15 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(message, activity, args) - - -

- - - - -
- Required class by children, should contain the command code. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - - the message that has the command
activity - - -Activity - - - - the activity for this activity command
args - - -Object - - - -
activityName - - -String - - - -
numOfGroups - - -Number - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_activity/new-coffee-chats.js, line 52 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/NewWorkshop.html b/docs/NewWorkshop.html deleted file mode 100644 index 96b9c877..00000000 --- a/docs/NewWorkshop.html +++ /dev/null @@ -1,614 +0,0 @@ - - - - - - - - Factotum Documentation NewWorkshop - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

NewWorkshop

-
- - - - - -
- -
- -

NewWorkshop()

- -
Creates a new Workshop and prompts the user for any information. Take a look at the workshop class to learn what a workshop activity is.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new NewWorkshop() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_activity/new-workshop.js, line 17 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message, args) - - -

- - - - -
- Required class by children, should contain the command code. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - - the message that has the command
args - - -Object - - - -
activityName - - -String - - - - the activity for this activity command
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_activity/new-workshop.js, line 48 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/PasswordStamp.html b/docs/PasswordStamp.html deleted file mode 100644 index d0e317c5..00000000 --- a/docs/PasswordStamp.html +++ /dev/null @@ -1,656 +0,0 @@ - - - - - - - - Factotum Documentation PasswordStamp - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

PasswordStamp

-
- - - - - -
- -
- -

PasswordStamp()

- -
Sends a reaction collector for users to react, send a password and receive a stamp. Used to give out stamps for activities that don't have an activity instance. The user who starts the password stamp must give the activity name, password, and stop time defaults to 120 seconds. Users have 3 attempts to get the password right within the stop time.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new PasswordStamp() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/stamps/password-stamp.js, line 16 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message, args) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - -
args - - -Object - - - -
activityName - - -String - - - -
password - - -String - - - -
stopTime - - -Number - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/stamps/password-stamp.js, line 59 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/PermissionCommand.html b/docs/PermissionCommand.html deleted file mode 100644 index d17b8b39..00000000 --- a/docs/PermissionCommand.html +++ /dev/null @@ -1,914 +0,0 @@ - - - - - - - - Factotum Documentation PermissionCommand - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

PermissionCommand

-
- - - - - -
- -
- -

PermissionCommand(client, info, permissionInfo)

- -
The PermissionCommand is a custom command that extends the discord js commando Command class. This Command subclass adds role and channel permission checks before the command is run. It also removes the message used to call the command.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new PermissionCommand(client, info, permissionInfo) - - -

- - - - -
- Constructor for our custom command, calls the parent constructor. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
client - - -CommandoClientOptions - - - - the client the command is for
info - - -CommandInfo - - - - the information for this commando command
permissionInfo - - -CommandPermissionInfo - - - - the custom information for this command
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 14 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - -
    -
  • Command
  • -
- - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -String - - - - -

- # - - - static - - - - FLAGS - - -

- - - - -
- String permission flags used for command permissions. * ADMIN_ROLE : only admins can use this command * STAFF_ROLE : staff and admin can use this command * ADMIN_CONSOLE : can only be used in the admin console -
- - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
ADMIN_ROLE - - -String - - - -
STAFF_ROLE - - -String - - - -
ADMIN_CONSOLE - - -String - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 140 - -

- -
- - - - - -
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - abstract - - protected - - - - - runCommand(botGuild, message, args, fromPattern, result) - - -

- - - - -
- Required class by children, will throw error if not implemented! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -CommandoMessage - - - -
args - - -Object - - - -
fromPattern - - -Boolean - - - -
result - - -Promise.<*> - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 128 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Pronouns.html b/docs/Pronouns.html deleted file mode 100644 index 4df2b964..00000000 --- a/docs/Pronouns.html +++ /dev/null @@ -1,539 +0,0 @@ - - - - - - - - Factotum Documentation Pronouns - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Pronouns

-
- - - - - -
- -
- -

Pronouns()

- -
The pronouns command sends a role reaction console for users to select a pronoun role out of 4 options: -* she/her -* he/him -* they/them -* other pronouns -The roles must be already created on the server for this to work.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Pronouns() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_utility/pronouns.js, line 17 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - - the command message
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_utility/pronouns.js, line 36 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Raffle.html b/docs/Raffle.html deleted file mode 100644 index a6b751c2..00000000 --- a/docs/Raffle.html +++ /dev/null @@ -1,614 +0,0 @@ - - - - - - - - Factotum Documentation Raffle - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Raffle

-
- - - - - -
- -
- -

Raffle()

- -
Picks x amount of winners from the stamp contest. The more stamps a user has, the more chances they have of winning.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Raffle() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/stamps/raffle.js, line 11 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message, args) - - -

- - - - -
- Main function which looks at every member's roles, identifies all that end in a number, and adds the member's id that many times into an array. Then it chooses random numbers and picks the id corresponding to that index until it has numberOfWinners unique winners. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - - message used to call the command
args - - -Object - - - -
numberOfWinners - - -integer - - - - number of winners to be drawn
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/stamps/raffle.js, line 45 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Report.html b/docs/Report.html deleted file mode 100644 index 482ae326..00000000 --- a/docs/Report.html +++ /dev/null @@ -1,436 +0,0 @@ - - - - - - - - Factotum Documentation Report - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Report

-
- - - - - -
- -
- -

Report()

- -
The report command allows users to report incidents from the server to the admins. Reports are made via the bot's DMs and are 100% anonymous.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Report() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/hacker_utility/report.js, line 13 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - -
    -
  • Command
  • -
- - - - - - - - - - - - - - - - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - run(message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/hacker_utility/report.js, line 28 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/RoleSelector.html b/docs/RoleSelector.html deleted file mode 100644 index e5eddce2..00000000 --- a/docs/RoleSelector.html +++ /dev/null @@ -1,559 +0,0 @@ - - - - - - - - Factotum Documentation RoleSelector - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

RoleSelector

-
- - - - - -
- -
- -

RoleSelector()

- -
Make a message embed (console) available on the channel for users to react and un-react for roles. Staff can dynamically add roles to the console. Users can react to get the role, then un-react to loose the role.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new RoleSelector() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_utility/role-selector.js, line 15 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - - the command message
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_utility/role-selector.js, line 35 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Room.html b/docs/Room.html deleted file mode 100644 index f5fbc4cd..00000000 --- a/docs/Room.html +++ /dev/null @@ -1,2911 +0,0 @@ - - - - - - - - Factotum Documentation Room - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Room

-
- - - - - -
- -
- -

Room(guild, botGuild, name, rolesAllowedopt, usersAllowedopt)

- -
The room class represents a room where things can occur, a room consists of a category with voice and text channels. As well as roles or users allowed to see the room.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Room(guild, botGuild, name, rolesAllowedopt, usersAllowedopt) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
guild - - -Guild - - - - - - - - - - - - the guild in which the room lives
botGuild - - -BotGuildModel - - - - - - - - - - - - the botGuild
name - - -String - - - - - - - - - - - - name of the room
rolesAllowed - - -Collection.<String, Role> - - - - - - <optional>
- - - - - -
- - Collection() - - the participants able to view this room
usersAllowed - - -Collection.<String, User> - - - - - - <optional>
- - - - - -
- - Collection() - - the individual users allowed to see the room
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 29 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -BotGuildModel - - - - -

- # - - - botGuild - - -

- - - - -
- The mongoose BotGuildModel Object -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 84 - -

- -
- - - - - -
- -
- - - - -RoomChannels - - - - -

- # - - - channels - - -

- - - - -
- All the channels this room has! -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 70 - -

- -
- - - - - -
- -
- - - - -Guild - - - - -

- # - - - guild - - -

- - - - -
- The guild this activity is in. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 52 - -

- -
- - - - - -
- -
- - - - -Boolean - - - - -

- # - - - locked - - -

- - - - -
- True if the room is locked, false otherwise. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 90 - -

- -
- - - - - -
- -
- - - - -string - - - - -

- # - - - name - - -

- - - - -
- The name of this room. Will remove all leading and trailing whitespace and switch spaces for '-'. Will also replace all character except for numbers, letters and '-' and make it lowercase. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 46 - -

- -
- - - - - -
- -
- - - - -Collection.<String, Role> - - - - -

- # - - - rolesAllowed - - -

- - - - -
- Roles allowed to view the room. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 58 - -

- -
- - - - - -
- -
- - - - -Number - - - - -

- # - - - timeCreated - - -

- - - - -
- The time this room was created. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 96 - -

- -
- - - - - -
- -
- - - - -Collection.<String, User> - - - - -

- # - - - usersAllowed - - -

- - - - -
- Users allowed to view the room. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 64 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - - addExcisingChannel(channel) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel -| - -VoiceChannel - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 313 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - addRoomChannel(args) - - -

- - - - -
- Adds a channels to the room. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
args - - -Object - - - - - - - - - - - -
name - - -String - - - - - - - - - - - - name of the channel to create
info - - -GuildCreateChannelOptions - - - - - - <optional>
- - - - - -
- - {} - - one of voice or text
permissions - - -Array.<RolePermission> - - - - - - <optional>
- - - - - -
- - [] - - the permissions per role to be added to this channel after creation.
isSafe - - -Boolean - - - - - - <optional>
- - - - - -
- - false - - true if the channel is safe and cant be removed
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 176 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - archive(archiveCategory) - - -

- - - - -
- Archive the activity. Move general text channel to archive category, remove all remaining channels and remove the category. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
archiveCategory - - -CategoryChannel - - - - the category where the general text channel will be moved to
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 236 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - delete() - - -

- - - - -
- Deletes the room. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 217 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - giveRoleAccess(role) - - -

- - - - -
- Gives access to the room to a role. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
role - - -Role - - - - role to give access to
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 282 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - giveUserAccess(user) - - -

- - - - -
- Gives access to a user -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
user - - -User - - - - user to give access to
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 296 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - init(argsopt) → {Promise.<Room>} - - -

- - - - -
- Initialize this activity by creating the channels, adding the features and sending the admin console. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
args - - -Object - - - - - - <optional>
- - - - - -
channels already created to add to this room
category - - -CategoryChannel - - - - - - - - - -
textChannel - - -TextChannel - - - - - - - - - -
voiceChannel - - -VoiceChannel - - - - - - - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 109 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Room> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - lockRoom() → {Promise.<TextChannel>} - - -

- - - - -
- Locks the room for all roles except for a text channel. To gain access users must be allowed access individually. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 261 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
- channel available to roles
- - -
- - -Promise.<TextChannel> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - removeRoomChannel(channelToRemove, isForcedopt) - - -

- - - - -
- Removes a channel from the room. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
channelToRemove - - -VoiceChannel -| - -TextChannel - - - - - - - - - - - -
isForced - - -Boolean - - - - - - <optional>
- - - - - -
- - false - - is the deletion forced?, if so then channel will be removed even if its safeChannels
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 201 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - removeUserAccess(user) - - -

- - - - -
- Removes access to a user to see this room. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
user - - -User - - - - the user to remove access to
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 305 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/SelfCareReminders.html b/docs/SelfCareReminders.html deleted file mode 100644 index 349d2d76..00000000 --- a/docs/SelfCareReminders.html +++ /dev/null @@ -1,559 +0,0 @@ - - - - - - - - Factotum Documentation SelfCareReminders - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

SelfCareReminders

-
- - - - - -
- -
- -

SelfCareReminders()

- -
The self care command will send pre made reminders from firebase to the command channel. These reminders are self care reminders. Will prompt a role to mention with each reminder. We recommend that be an opt-in role.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new SelfCareReminders() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_utility/self-care.js, line 15 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - - the message in which this command was called
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_utility/self-care.js, line 34 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/StampsManager.html b/docs/StampsManager.html deleted file mode 100644 index 5ef432c4..00000000 --- a/docs/StampsManager.html +++ /dev/null @@ -1,742 +0,0 @@ - - - - - - - - Factotum Documentation StampsManager - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

StampsManager

-
- - - - - -
- -
- -

StampsManager()

- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new StampsManager() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/stamps-manager.js, line 11 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - - - -
-

Methods

-
- -
- - - -

- # - - - async - - static - - - - - distributeStamp(activity, timeopt, botGuild) - - -

- - - - -
- Will let hackers get a stamp for attending the activity. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
activity - - -Activity - - - - - - - - - - - - activity to use
time - - -Number - - - - - - <optional>
- - - - - -
- - 60 - - time to wait till collector closes, in seconds
botGuild - - -BotGuildModel - - - - - - - - - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/stamps-manager.js, line 19 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - static - - - - - parseRole(member, activityName, botGuild) - - -

- - - - -
- Upgrade the stamp role of a member. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
member - - -GuildMember - - - - the member to add the new role to
activityName - - -String - - - - the name of the activity
botGuild - - -BotGuildModel - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/stamps-manager.js, line 63 - -

- -
- - - - - - - - - - - - - - - - -
-
-
- - -
- - -
Error if the botGuild has stamps disabled
- - -
- - -
-
- - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/StartAttend.html b/docs/StartAttend.html deleted file mode 100644 index 5e8a324e..00000000 --- a/docs/StartAttend.html +++ /dev/null @@ -1,563 +0,0 @@ - - - - - - - - Factotum Documentation StartAttend - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

StartAttend

-
- - - - - -
- -
- -

StartAttend()

- -
StartAttend makes a new channel called #attend, or uses an existing channel of the user's choice, as the channel where an embed is sent for users to react and get attend. Users don't need to send any information to attend.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new StartAttend() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/attendance/start-attend.js, line 16 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message) - - -

- - - - -
- If existsChannel is true, asks user to indicate the channel to use. Else asks user to indicate the category under which the channel should be created, and then creates it. In both cases it will send an embed containing the instructions for hackers to check in. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - - message containing command
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/attendance/start-attend.js, line 40 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/StartChannelCreation.html b/docs/StartChannelCreation.html deleted file mode 100644 index 68d892e7..00000000 --- a/docs/StartChannelCreation.html +++ /dev/null @@ -1,559 +0,0 @@ - - - - - - - - Factotum Documentation StartChannelCreation - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

StartChannelCreation

-
- - - - - -
- -
- -

StartChannelCreation()

- -
The start channel creation command lets users create private channels for them to use. Users can create voice or text channels, invite as many people as they want when created and name the channels whatever they want. A category and channel is created for this feature. The new channels are created inside this category. Users can delete the channel by reacting to a message in their DMs with the bot. THERE IS A LIMIT OF CHANNELS! Categories can only have up to 50 channels, if you expect more than 50 channels please DO NOT USE THIS FEATURE.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new StartChannelCreation() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-channel-creation.js, line 19 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - - the message in which the command was run
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-channel-creation.js, line 40 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/StartMentorCave.html b/docs/StartMentorCave.html deleted file mode 100644 index 2b553a23..00000000 --- a/docs/StartMentorCave.html +++ /dev/null @@ -1,559 +0,0 @@ - - - - - - - - Factotum Documentation StartMentorCave - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

StartMentorCave

-
- - - - - -
- -
- -

StartMentorCave()

- -
The start mentor cave command creates a cave for mentors. To know what a cave is look at cave class.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new StartMentorCave() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-mentor-cave.js, line 17 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - - the message in which the command was run
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-mentor-cave.js, line 38 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/StartTeamFormation.html b/docs/StartTeamFormation.html deleted file mode 100644 index dbca6e18..00000000 --- a/docs/StartTeamFormation.html +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - Factotum Documentation StartTeamFormation - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

StartTeamFormation

-
- - - - - -
- -
- -

StartTeamFormation()

- -
The team formation activity is the most basic team formation activity available. This activity works like a menu or directory of available teams and solo participants. To join, a participant reacts to a message. The bot then sends instructions via DM, including a set of questions the user must respond to and send back to the bot. The responses are then sent to either a looking-for-team channel or looking-for-members channel. Other parties can then browse these channels and create teams over DMs. Members cannot send messages to these channels. There is an option for users in the activity to be notified of new posts of interest. For example, a team leader will get notified of new solo participants looking for a team. When someone finds a team, they can go back to their DMs with the bot and react to a message to remove their post from the channels and stop receiving notifications of new posts.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new StartTeamFormation() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-formation.js, line 21 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - - the message in which the command was run
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-formation.js, line 42 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/StartTeamRoulette.html b/docs/StartTeamRoulette.html deleted file mode 100644 index 5200a5c3..00000000 --- a/docs/StartTeamRoulette.html +++ /dev/null @@ -1,2003 +0,0 @@ - - - - - - - - Factotum Documentation StartTeamRoulette - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

StartTeamRoulette

-
- - - - - -
- -
- -

StartTeamRoulette()

- -
The team roulette activity is a special type of team formation activity. Users can join the activity by reacting to a message embed (console). They can join as a solo or a group of up to 3 members (them included). The bot will then create teams of 4 as they become available. When a team is created, the new team members are invited to a text channel only available to them. Users can leave the team and the bot will add a new member from the list (if any available). Admins can check the list of users waiting on a team by reacting to a message embed (console) sent to the admin channel.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new StartTeamRoulette() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 18 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -Collection.<Snowflake, User> - - - - -

- # - - - participants - - -

- - - - -
- All the users that have participated in the activity. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 75 - -

- -
- - - - - -
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - soloEmoji - - -

- - - - -
- The solo join emoji. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 51 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - teamEmoji - - -

- - - - -
- The non solo join emoji. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 57 - -

- -
- - - - - -
- -
- - - - -Collection.<Number, Array.<Team>> - - - - -

- # - - - teamList - - -

- - - - -
- The team list from which to create teams. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 63 - -

- -
- - - - - -
- -
- - - - -Number - - - - -

- # - - - teamNumber - - -

- - - - -
- The current team number. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 69 - -

- -
- - - - - -
- -
- - - - -TextChannel - - - - -

- # - - - textChannel - - -

- - - - -
- Channel used to send information about team roulette. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 81 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - assignGroupOf2() → {Promise.<(Team|null)>} - - -

- - - - -
- Will assign a team of 2 with a team of 2 or two of 1 -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 438 - -

- -
- - - -
-
-
-
    -
  • module:this.groupList
  • -
-
-
- - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<(Team|null)> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - assignGroupOf3() → {Promise.<(Team|null)>} - - -

- - - - -
- Will assign a team of 3 with a team of 1. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 425 - -

- -
- - - -
-
-
-
    -
  • module:this.groupList
  • -
-
-
- - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<(Team|null)> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - assignGroupsOf1() → {Promise.<(Team|null)>} - - -

- - - - -
- Assigns 4 groups of 1 together. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 454 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<(Team|null)> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - getOrCreateChannel(promptChannel, promptId, guildChannelManager) → {Promise.<TextChannel>} - - -

- - - - -
- Ask user if new channels are needed, if so create them, else ask for current channels to use for TR. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
promptChannel - - -TextChannel - - - - channel to prompt on
promptId - - -Snowflake - - - - user's ID to prompt
guildChannelManager - - -GuildChannelManager - - - - manager to create channels
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 261 - -

- -
- - - - - - - - - - - - - - - - -
-
-
- - -
- - -
Throws an error if the user cancels either of the two Prompts, the command should quit!
- - -
- - -
-
- - - -
-
-
- - - -
- - -
- - -Promise.<TextChannel> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - removeMemberFromTeam(team, teamMember) → {Number} - - -

- - - - -
- Will remove the team member from the team, notify the user of success, and remove the team from the teamList -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
team - - -Team - - - - the team to remove user from
teamMember - - -User - - - - the user to remove from the team
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 330 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
- the new size of the team
- - -
- - -Number - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - - the message in which the command was run
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 43 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - runTeamCreator(channelManager) - - -

- - - - -
- Will try to create a team and set them up for success! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channelManager - - -GuildChannelManager - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_start_commands/start-team-roulette.js, line 349 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/StartVerification.html b/docs/StartVerification.html deleted file mode 100644 index dd2b6c9b..00000000 --- a/docs/StartVerification.html +++ /dev/null @@ -1,559 +0,0 @@ - - - - - - - - Factotum Documentation StartVerification - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

StartVerification

-
- - - - - -
- -
- -

StartVerification()

- -
Sends an embed with reaction collector for users to re-verify via DMs with the bot from inside the server.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new StartVerification() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/verification/start-verification.js, line 16 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/verification/start-verification.js, line 35 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Team.html b/docs/Team.html deleted file mode 100644 index aafbe3d0..00000000 --- a/docs/Team.html +++ /dev/null @@ -1,1766 +0,0 @@ - - - - - - - - Factotum Documentation Team - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Team

-
- - - - - -
- -
- -

Team()

- -
A Team represents a real life team with members. Teams can merge together, have channels and each team has a unique ID.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Team() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 9 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -Boolean - - - - -

- # - - - deleted - - -

- - - - -
- True if the team has been deleted, else false. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 46 - -

- -
- - - - - -
- -
- - - - -Boolean - - - - -

- # - - - hasBeenComplete - - -

- - - - -
- True if the team has been complete at least once. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 40 - -

- -
- - - - - -
- -
- - - - -Number - - - - -

- # - - - id - - -

- - - - -
- The team ID -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 16 - -

- -
- - - - - -
- -
- - - - -Discord.Snowflake - - - - -

- # - - - leader - - -

- - - - -
- The team leader. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 34 - -

- -
- - - - - -
- -
- - - - -Discord.Collection.<Discord.Snowflake, (Discord.User|Discord.GuildMember)> - - - - -

- # - - - members - - -

- - - - -
- All the team members -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 22 - -

- -
- - - - - -
- -
- - - - -Discord.TextChannel -| - -null - - - - -

- # - - - textChannel - - -

- - - - -
- The team's text channel if any -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 28 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - addTeamMember(user) - - -

- - - - -
- Add a new user to the team. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
user - - -Discord.User -| - -Discord.GuildMember - - - - the user to add to the team
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 116 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - createTextChannel(channelManager, category) → {Promise.<Discord.TextChannel>} - - -

- - - - -
- Create a text channel for this team and add all the team members. Will notify the members of the channel creation -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channelManager - - -Discord.ChannelManager - - - - the channel manager to create the text channel
category - - -Discord.CategoryChannel - - - - the category where to create the channel
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 57 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Discord.TextChannel> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - isComplete() - - -

- - - - -
- True if the team has 4 members, false otherwise. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 161 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - mergeTeam(team) - - -

- - - - -
- Merge two teams. Team with a text channel, if any will be kept. New members will be added to the text channel, if any. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
team - - -Team - - - - team to merge into this team
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 82 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - removeTeamMember(user) → {Number} - - -

- - - - -
- Removes a user from the team. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
user - - -Discord.User - - - - the user to remove from the team
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 135 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
- the new size of this team
- - -
- - -Number - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - size() → {Number} - - -

- - - - -
- Return the length of the members collection. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 154 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Number - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - toString() → {String} - - -

- - - - -
- Returns a string with the team id and all the team members. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team.js, line 169 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -String - - -
- -
- - -
-
- - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/TeamFormation.html b/docs/TeamFormation.html deleted file mode 100644 index 0cde7992..00000000 --- a/docs/TeamFormation.html +++ /dev/null @@ -1,1747 +0,0 @@ - - - - - - - - Factotum Documentation TeamFormation - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

TeamFormation

-
- - - - - -
- -
- -

TeamFormation(teamFormationInfo)

- -
TeamFormation The team formation class represents the team formation activity. It helps teams and prospects find each other by adding their respective information to a catalogue of sorts. Admins have the ability to customize the messages sent, emojis used, and if they want users to be notified of new posts in the catalogue.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new TeamFormation(teamFormationInfo) - - -

- - - - -
- Create a new team formation. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
teamFormationInfo - - -TeamFormationInfo - - - - the team formation information
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 19 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -TeamFormationChannels - - - - -

- # - - - channels - - -

- - - - -
- The channels that a team formation activity needs. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 138 - -

- -
- - - - - -
- -
- - - - -Boolean - - - - -

- # - - - isNotificationEnabled - - -

- - - - -
- True if the parties will be notified when the opposite party has a new post. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 144 - -

- -
- - - - - -
- -
- - - - -TeamFormationPartyInfo - - - - -

- # - - - prospectInfo - - -

- - - - -
- The prospect info, those solo users that want to join a team will use this info. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 127 - -

- -
- - - - - -
- -
- - - - -SignupEmbedCreator - - - - -

- # - - - signupEmbedCreator - - -

- - - - -
- A creator of the info embed in case you want it to be different. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 150 - -

- -
- - - - - -
- -
- - - - -TeamFormationPartyInfo - - - - -

- # - - - teamInfo - - -

- - - - -
- The team information, those teams willing to join will use this. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 116 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - static - - - - - createProspectRole(roleManager) → {Promise.<Role>} - - -

- - - - -
- Creates the prospect role and returns it. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
roleManager - - -RoleManager - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 50 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Role> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - static - - - - - createTeamRole(roleManager) → {Promise.<Role>} - - -

- - - - -
- Creates the team role and returns it. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
roleManager - - -RoleManager - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 33 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Role> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - createChannels() - - -

- - - - -
- Will create the TeamFormationChannels object with new channels to use with a new TeamFormation -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 177 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - gatherForm(user, isTeam) → {Promise.<Message>} - - -

- - - - -
- Will gather the form from a user to add to the catalogues and send it to the correct channel. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
user - - -User - - - - the user being prompted
isTeam - - -Boolean - - - - true if the user is a team looking for members
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 334 - -

- -
- - - - - - - - - - - - - - - - -
-
-
- - -
- - -
Error if user cancels or takes too long to respond to prompt
- - -
- - -
-
- - - -
-
-
- - - -
- -
- the catalogue message
- - -
- - -Promise.<Message> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - reachOutToUser(user, isTeam) - - -

- - - - -
- Will reach out to the user to ask for the form response to add to the catalogue. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
user - - -User - - - - the user joining the team formation activity
isTeam - - -Boolean - - - - true if the user represents a team, else false
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 253 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - start(signupEmbedCreatoropt) - - -

- - - - -
- Will start the activity! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
signupEmbedCreator - - -SignupEmbedCreator - - - - - - <optional>
- - - - - -
- - null - - embed creator for the sign in
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 215 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Ticket.html b/docs/Ticket.html deleted file mode 100644 index 9bebc089..00000000 --- a/docs/Ticket.html +++ /dev/null @@ -1,1910 +0,0 @@ - - - - - - - - Factotum Documentation Ticket - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Ticket

-
- - - - - -
- -
- -

Ticket(hackers, question, requesterRole, ticketNumber, ticketManager)

- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Ticket(hackers, question, requesterRole, ticketNumber, ticketManager) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
hackers - - -Collection.<String, User> - - - -
question - - -String - - - -
requesterRole - - -Role - - - -
ticketNumber - - -Number - - - -
ticketManager - - -TicketManager - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 33 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -String - - - - -

- # - - - static - - - - STATUS - - -

- - - - -
- The possible status of the ticket. -
- - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
new - - -String - - - - Ticket is open for someone to take.
closed - - -String - - - - Ticket has been dealt with and is closed.
taken - - -String - - - - Ticket is being handled by someone.
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 425 - -

- -
- - - - - -
- -
- - - - -TicketConsoles - - - - -

- # - - - consoles - - -

- - - - -
- All the consoles sent out. GroupLeader -> sent via DM to leader, they can cancel the ticket from there ticketManager -> sent to the helper channel ticketRoom -> sent to the ticket room once created for users to leave -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 78 - -

- -
- - - - - -
- -
- - - - -TicketGarbageInfo - - - - -

- # - - - garbageCollectorInfo - - -

- - - - -
- Garbage collector info. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 88 - -

- -
- - - - - -
- -
- - - - -Collection.<String, User> - - - - -

- # - - - group - - -

- - - - -
- All the group members, group leader should be the first one! -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 63 - -

- -
- - - - - -
- -
- - - - -Collection.<String, User> - - - - -

- # - - - helpers - - -

- - - - -
- Mentors who join the ticket -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 69 - -

- -
- - - - - -
- -
- - - - -Number - - - - -

- # - - - id - - -

- - - - -
- Ticket number -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 39 - -

- -
- - - - - -
- -
- - - - -String - - - - -

- # - - - question - - -

- - - - -
- Question from hacker -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 51 - -

- -
- - - - - -
- -
- - - - -Role - - - - -

- # - - - requestedRole - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 56 - -

- -
- - - - - -
- -
- - - - -Room - - - - -

- # - - - room - - -

- - - - -
- The room this ticket will be solved in. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 45 - -

- -
- - - - - -
- -
- - - - -Ticket.STATUS - - - - -

- # - - - status - - -

- - - - -
- The status of this ticket -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 98 - -

- -
- - - - - -
- -
- - - - -TicketManager - - - - -

- # - - - ticketManager - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 103 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - basicTakenStatusCallback(helper) - - -

- - - - -
- Callback for status change to taken when ticket manager is NOT in advanced mode. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
helper - - -User - - - - the user who is taking the ticket
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 219 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - includeExclude(exclude) - - -

- - - - -
- This function is called by the ticket's Cave class to change its status between include/exclude for automatic garbage collection. If a previously excluded ticket is re-included, the bot starts listening for inactivity as well. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
exclude - - -Boolean - - - - true if ticket is now excluded from garbage collection, false if not
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 111 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - setStatus(status, reasonopt, useropt) - - -

- - - - -
- Change the status of this ticket. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
status - - -String - - - - - - - - - - one of Ticket.STATUS
reason - - -String - - - - - - <optional>
- - - - - -
the reason for the change
user - - -User - - - - - - <optional>
- - - - - -
user involved with the status change
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 130 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/TicketManager.html b/docs/TicketManager.html deleted file mode 100644 index 13286d76..00000000 --- a/docs/TicketManager.html +++ /dev/null @@ -1,2328 +0,0 @@ - - - - - - - - Factotum Documentation TicketManager - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

TicketManager

-
- - - - - -
- -
- -

TicketManager(parent, args)

- -
Represents a real life ticket system that can be used in any setting. It is very versatile so it can be used with one or many helper types, can edit options, embeds, etc.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new TicketManager(parent, args) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
parent - - -Activity - - - -
args - - -Object - - - -
ticketCreatorInfo - - -TicketCreatorInfo - - - -
ticketDispatcherInfo - - -TicketDispatcherInfo - - - -
systemWideTicketInfo - - -SystemWideTicketInfo - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 17 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- -

- # - - - multiRoleInfo - - -

- - - - -
- Information about the system being multi role, if its the case, it needs a Multi Role Selector. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 137 - -

- -
- - - - - -
- -
- - - - -Activity - - - - -

- # - - - parent - - -

- - - - -
- The parent of this ticket-system. It must be paired with a cave or an activity. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 92 - -

- -
- - - - - -
- -
- - - - -SystemWideTicketInfo - - - - -

- # - - - systemWideTicketInfo - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 124 - -

- -
- - - - - -
- -
- -

- # - - - ticketCount - - -

- - - - -
- The number of tickets created. Must be separate as tickets.length since we use this to assign IDs to tickets. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 86 - -

- -
- - - - - -
- -
- - - - -TicketCreatorInfo - - - - -

- # - - - ticketCreatorInfo - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 97 - -

- -
- - - - - -
- -
- - - - -TicketDispatcherInfo - - - - -

- # - - - ticketDispatcherInfo - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 105 - -

- -
- - - - - -
- -
- - - - -Collection.<Number, Ticket> - - - - -

- # - - - tickets - - -

- - - - -
- The tickets in this ticket system. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 80 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - - addTicketType(role, typeName, emoji) - - -

- - - - -
- Adds a new type of ticket, usually a more focused field, there must be a role associated to this new type of ticket. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
role - - -Role - - - - role to add
typeName - - -String - - - -
emoji - - -GuildEmoji -| - -ReactionEmoji - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 187 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - getTicketCount() → {Number} - - -

- - - - -
- Return the number of tickets in this ticket system. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 274 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Number - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - removeAllTickets(excludeTicketIdsopt) - - -

- - - - -
- Removes all the tickets from this ticket manager. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
excludeTicketIds - - -Array.<Number> - - - - - - <optional>
- - - - - -
- - [] - - tickets to be excluded
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 282 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - removeTicket(ticketId) - - -

- - - - -
- Removes a ticket, deletes the ticket's channels too! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
ticketId - - -Number - - - - the ticket id to remove
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 326 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - - removeTicketsByAge(minAge) - - -

- - - - -
- Removes all tickets older than the given age. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
minAge - - -Number - - - - the minimum age in minutes
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 308 - -

- -
- - - - - - - - - - - - - - - - -
-
-
- - -
- - -
Error when used and advanced mode is turned off
- - -
- - -
-
- - - - - - -
- -
- - - -

- # - - - - removeTicketsById(ticketIds) - - -

- - - - -
- Removes tickets by their ids -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
ticketIds - - -Array.<Number> - - - - ticket ids to remove
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 297 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - sendTicketCreatorConsole(title, description, coloropt) - - -

- - - - -
- Sends the ticket creator console. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
title - - -String - - - - - - - - - - the ticket creator console title
description - - -String - - - - - - - - - - the ticket creator console description
color - - -String - - - - - - <optional>
- - - - - -
the ticket creator console color, hex
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 163 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - startTicketCreationProcess(user, role, channel) - - -

- - - - -
- Prompts a user for more information to create a new ticket for them. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
user - - -User - - - - the user creating a ticket
role - - -Role - - - -
channel - - -TextChannel -| - -DMChannel - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 207 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/UnknownCommand.html b/docs/UnknownCommand.html deleted file mode 100644 index 98da522a..00000000 --- a/docs/UnknownCommand.html +++ /dev/null @@ -1,436 +0,0 @@ - - - - - - - - Factotum Documentation UnknownCommand - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

UnknownCommand

-
- - - - - -
- -
- -

UnknownCommand()

- -
This unknown command is used by the bot when a unknown command is run.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new UnknownCommand() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/essentials/unknown-command.js, line 12 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - -
    -
  • Command
  • -
- - - - - - - - - - - - - - - - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - run(message) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/essentials/unknown-command.js, line 27 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Verification.html b/docs/Verification.html deleted file mode 100644 index 5ff3e77d..00000000 --- a/docs/Verification.html +++ /dev/null @@ -1,700 +0,0 @@ - - - - - - - - Factotum Documentation Verification - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Verification

-
- - - - - -
- -
- -

Verification()

- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Verification() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/verification.js, line 7 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - - - - - - - - - - - - - - - -
-

Methods

-
- -
- - - -

- # - - - async - - static - - - - - attend(member, botGuild) - - -

- - - - -
- Will attend the user and give it the attendee role. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
member - - -GuildMember - - - -
botGuild - - -BotGuildModel - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/verification.js, line 97 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - static - - - - - verify(member, email, guild, botGuild) - - -

- - - - -
- Verifies a guild member into a guild. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
member - - -GuildMember - - - - member to verify
email - - -String - - - - email to verify with
guild - - -Guild - - - -
botGuild - - -BotGuildModel - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/verification.js, line 22 - -

- -
- - - - - - - - - - - - - - - - -
-
-
- - -
- - -
Error if email is not valid!
- - -
- - -
-
- - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Verify.html b/docs/Verify.html deleted file mode 100644 index 34068643..00000000 --- a/docs/Verify.html +++ /dev/null @@ -1,633 +0,0 @@ - - - - - - - - Factotum Documentation Verify - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Verify

-
- - - - - -
- -
- -

Verify()

- -
Will verify the user running the command, needs the user's email and guild ID. Can only be run through DM.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Verify() - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/verification/verify.js, line 15 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -CommandPermissionInfo - - - - -

- # - - - private - - - - permissionInfo - - -

- - - - -
- The permission info -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 40 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - runCommand(botGuild, message, args) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
botGuild - - -BotGuildModel - - - -
message - - -Message - - - -
args - - -Object - - - -
email - - -String - - - -
guildId - - -String - - - -
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/verification/verify.js, line 48 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/Workshop.html b/docs/Workshop.html deleted file mode 100644 index 7a651266..00000000 --- a/docs/Workshop.html +++ /dev/null @@ -1,4109 +0,0 @@ - - - - - - - - Factotum Documentation Workshop - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Class

-

Workshop

-
- - - - - -
- -
- -

Workshop(isLowTechSolutionopt, TARolesopt)

- -
A workshop is an activity with a TA system to help users with questions. The TA system has two options, regular or advanced. Regular option involves TAs reaching out via DMs to users while advanced option involves users joining a voice channel to receive help. The advanced option is only recommended with knowledgeable discord users. It also has polls the TAs can send to learn basic knowledge from the audience.
- - -
- -
-
- - -
-
-
-
- Constructor -
- - - - -

- # - - - - new Workshop(isLowTechSolutionopt, TARolesopt) - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
- - -Activity.ActivityInfo - - - - - - - - - - - -
isLowTechSolution - - -Boolean - - - - - - <optional>
- - - - - -
- - true - -
TARoles - - -Collection.<String, Role> - - - - - - <optional>
- - - - - -
- - roles with TA permissions
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 27 - -

- -
- - - - - - - - - - - - - - - - - - - - - - -
-
-
- - -
- - -

Extends

- - - - - - - - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -Console - - - - -

- # - - - adminConsole - - -

- - - - -
- The admin console with activity features. -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 99 - -

- -
- - - - - -
- -
- - - - -TextChannel - - - - -

- # - - - assistanceChannel - - -

- - - - -
- The channel where hackers can ask questions. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 54 - -

- -
- - - - - -
- -
- - - - -BotGuildModel - - - - -

- # - - - botGuild - - -

- - - - -
- The mongoose BotGuildModel Object -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 110 - -

- -
- - - - - -
- -
- - - - -Guild - - - - -

- # - - - guild - - -

- - - - -
- The guild this activity is in. -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 87 - -

- -
- - - - - -
- -
- - - - -Boolean - - - - -

- # - - - isLowTechSolution - - -

- - - - -
- True if the assistance protocol is low tech. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 48 - -

- -
- - - - - -
- -
- - - - -string - - - - -

- # - - - name - - -

- - - - -
- The name of this activity. -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 81 - -

- -
- - - - - -
- -
- - - - -Collection.<String, PollInfo> - - - - -

- # - - - polls - - -

- - - - -
- The polls available. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 84 - -

- -
- - - - - -
- -
- - - - -Room - - - - -

- # - - - room - - -

- - - - -
- The room this activity lives in. -
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 93 - -

- -
- - - - - -
- -
- - - - -Collection.<String, (TextChannel|VoiceChannel)> - - - - -

- # - - - TAChannels - - -

- - - - -
- The channels only available to TAs -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 60 - -

- -
- - - - - -
- -
- - - - -TextChannel - - - - -

- # - - - TAConsole - - -

- - - - -
- TA Console where assistance calls are sent. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 66 - -

- -
- - - - - -
- -
- - - - -Collection.<String, Role> - - - - -

- # - - - TARoles - - -

- - - - -
- - roles with TA permissions -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 42 - -

- -
- - - - - -
- -
- - - - -TicketManager - - - - -

- # - - - ticketManager - - -

- - - - -
- The ticket manager. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 90 - -

- -
- - - - - -
- -
- - - - -Collection.<String, String> - - - - -

- # - - - waitlist - - -

- - - - -
- wait list Collection -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 78 - -

- -
- - - - - -
- -
- - - - -Message - - - - -

- # - - - waitListEmbedMsg - - -

- - - - -
- The message where we show the wait list live. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 72 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - - - - addChannel(channel, userId) - - -

- - - - -
- Adds a channel to the activity, ask if it will be for TAs or not. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user
userId - - -String - - - - user to prompt for channel info
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 293 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - protected - - - - - addDefaultFeatures() - - -

- - - - -
- Adds extra workshop features, plus the regular features. Also adds default polls. -
- - - - - - - - - - - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 166 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - protected - - - - - addDefaultPolls() - - -

- - - - -
- Adds the default polls to the polls list. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 189 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - addTAChannel(name, info) → {Promise.<(TextChannel|VoiceChannel)>} - - -

- - - - -
- Creates a channel only available to TAs. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
name - - -String - - - -
info - - -GuildCreateChannelOptions - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 315 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<(TextChannel|VoiceChannel)> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - - - - archive(archiveCategory) - - -

- - - - -
- Archive the activity. Move general text channel to archive category, remove all remaining channels and remove the category. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
archiveCategory - - -CategoryChannel - - - - the category where the general text channel will be moved to
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 267 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - delete() - - -

- - - - -
- Delete all the channels and the category. Remove the workshop from firebase. -
- - - - - - - - - - - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 279 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - distributeStamp(channel, userId) - - -

- - - - -
- Will let hackers get a stamp for attending the activity. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 369 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - protected - - - - - getTAChannelPermissions() → {Array.<Activity.RolePermission>} - - -

- - - - -
- Returns the perms for a TA Channel -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 327 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Array.<Activity.RolePermission> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - incomingTicketsHandler(message) - - -

- - - - -
- Creates and handles with the emoji reactions on the incoming ticket console embed -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 393 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - init() → {Promise.<Activity>} - - -

- - - - -
- Initializes the workshop and adds the ta console, ta banter and assistance channel. -
- - - - - - - - - - - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 98 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Activity> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - outgoingTicketHandler(message) - - -

- - - - -
- Creates and handles with the emoji reactions on the outgoing ticket console embed -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 461 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - removeChannel(channel, userId) - - -

- - - - -
- Removes a channel from the activity, the user will decide which. Wont delete channels in the safeChannel map. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 243 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - roleShuffle(channel, userId) - - -

- - - - -
- Shuffles users with a specific role throughout the activity's voice channels -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 354 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - ruleValidation(channel, userId) - - -

- - - - -
- Will lock the channels behind an emoji collector. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 424 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - sendConsoles() - - -

- - - - -
- Will send all the consoles the workshop needs to work. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 223 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - sendPoll(type) - - -

- - - - -
- Send a poll to the general text channel -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
type - - -String - - - - the type of poll to send
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 358 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - - - - voiceCallBack(channel, userId) - - -

- - - - -
- Move all users back to a specified voice channel from the activity's voice channels. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - channel to prompt user for specified voice channel
userId - - -String - - - - user to prompt for specified voice channel
-
- - - - - -
- - - - - - - - -
Overrides:
-
- - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 292 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/app.js.html b/docs/app.js.html deleted file mode 100644 index 0c60f70b..00000000 --- a/docs/app.js.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - Factotum Documentation app.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

app.js

-
- - - - - -
-
-
require('dotenv-flow').config();
-const mongoUtil = require('./db/mongo/mongoUtil');
-const Commando = require('discord.js-commando');
-const Discord = require('discord.js');
-const firebaseServices = require('./db/firebase/firebase-services');
-const winston = require('winston');
-const fs = require('fs');
-const discordServices = require('./discord-services');
-const BotGuild = require('./db/mongo/BotGuild');
-const BotGuildModel = require('./classes/bot-guild');
-const Verification = require('./classes/verification');
-const { StringPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * The Main App module houses the bot events, process events, and initializes
- * the bot. It also handles new members and greets them.
- * @module MainApp
- */
-
-
-/**
- * Returns the config settings depending on the command line args.
- * Read command line args to know if prod, dev, or test and what server
- * First arg is one of prod, dev or test
- * the second is the test server, but the first one must be test
- * @param {string[]} args 
- * @returns {Map} config settings
- */
-function getConfig(args) {
-    if (args.length >= 1) {
-        if (args[0] === 'dev') {
-            // Default dev
-            return JSON.parse(process.env.DEV);
-        } else if (args[0] === 'prod') {
-            // Production
-            if (args[1] === 'yes') {
-                return JSON.parse(process.env.PROD);
-            }
-        } else if (args[0] === 'test') {
-            // Test
-            const testConfig = JSON.parse(process.env.TEST);
-            let server = args[1] ?? 0;
-            if (server === '1') {
-                return testConfig['ONE'];
-            } else if (server === '2') {
-                return testConfig['TWO'];
-            } else if (server === '3') {
-                return testConfig['THREE'];
-            } else if (server === '4') {
-                return testConfig['FOUR'];
-            }
-        }
-    }
-    
-    // exit if no configs are loaded!
-    console.log('No configs were found for given args.');
-    process.exit(0);
-}
-
-const config = getConfig(process.argv.slice(2));
-
-const isLogToConsole = config['consoleLog'];
-
-const bot = new Commando.Client({
-    commandPrefix: '!',
-    owner: config.owner,
-});
-
-const customLoggerLevels = {
-    levels: {
-        error: 0,
-        warning: 1,
-        command: 2,
-        event: 3,
-        userStats: 4,
-        verbose: 5,
-        debug: 6,
-        silly: 7,
-    },
-    colors: {
-        error: 'red',
-        warning: 'yellow',
-        command: 'blue',
-        event: 'green',
-        userStats: 'magenta',
-        verbose: 'cyan',
-        debug: 'white',
-        silly: 'black',
-    }
-};
-
-// the main logger to use for general errors
-const mainLogger = createALogger('main', 'main', true, isLogToConsole);
-winston.addColors(customLoggerLevels.colors);
-
-
-/**
- * Register all the commands except for help and unknown since we have our own.
- */
-bot.registry
-    .registerDefaultTypes()
-    .registerGroup('a_boothing', 'boothing group for admins')
-    .registerGroup('a_activity', 'activity group for admins')
-    .registerGroup('a_start_commands', 'advanced admin commands')
-    .registerGroup('a_utility', 'utility commands for admins')
-    .registerGroup('hacker_utility', 'utility commands for users')
-    .registerGroup('verification', 'verification commands')
-    .registerGroup('attendance', 'attendance commands')
-    .registerGroup('stamps', 'stamp related commands')
-    .registerGroup('utility', 'utility commands')
-    .registerGroup('essentials', 'essential commands for any guild', true)
-    .registerDefaultGroups()
-    .registerDefaultCommands({
-        unknownCommand: false,
-        help: false,
-    })
-    .registerCommandsIn(__dirname + '/commands');
-
-/**
- * Runs when the bot finishes the set up and is ready to work.
- */
-bot.once('ready', async () => {
-    mainLogger.warning('The bot ' + bot.user.username + ' has started and is ready to hack!');
-    
-    bot.user.setActivity('Ready to hack!');
-
-    // initialize firebase
-    const adminSDK = JSON.parse(process.env.NWPLUSADMINSDK);
-    firebaseServices.initializeFirebaseAdmin('nwPlusBotAdmin', adminSDK, 'https://nwplus-bot.firebaseio.com');
-    mainLogger.warning('Connected to firebase admin sdk successfully!', { event: 'Ready Event' });
-
-    // set mongoose connection
-    await mongoUtil.mongooseConnect();
-    mainLogger.warning('Connected to mongoose successfully!', { event: 'Ready Event' });
-
-    // make sure all guilds have a botGuild, this is in case the bot goes offline and its added
-    // to a guild. If botGuild is found, make sure only the correct commands are enabled.
-    bot.guilds.cache.forEach(async (guild, key, guilds) => {
-        // create the logger for the guild
-        createALogger(guild.id, guild.name, false, isLogToConsole);
-
-        let botGuild = await BotGuild.findById(guild.id);
-        if (!botGuild) {
-            newGuild(guild);
-            mainLogger.verbose(`Created a new botGuild for the guild ${guild.id} - ${guild.name} on bot ready.`, { event: 'Ready Event' });
-        } else {
-            // set all non guarded commands to not enabled for the guild
-            bot.registry.groups.forEach((group, key, map) => {
-                if (!group.guarded) guild.setGroupEnabled(group, false);
-            });
-
-            await botGuild.setCommandStatus(bot);
-
-            guild.commandPrefix = botGuild.prefix;
-            
-            mainLogger.verbose(`Found a botGuild for ${guild.id} - ${guild.name} on bot ready.`, { event: 'Ready Event' });
-        }
-    });
-});
-
-/**
- * Runs when the bot is added to a guild.
- */
-bot.on('guildCreate', /** @param {Commando.CommandoGuild} guild */(guild) => {
-    mainLogger.warning(`The bot was added to a new guild: ${guild.id} - ${guild.name}.`, { event: 'Guild Create Event' });
-
-    newGuild(guild);
-
-    // create a logger for this guild
-    createALogger(guild.id, guild.name);
-});
-
-
-/**
- * Will set up a new guild.
- * @param {Commando.CommandoGuild} guild
- * @private
- */
-function newGuild(guild) {
-    // set all non guarded commands to not enabled for the new guild
-    bot.registry.groups.forEach((group, key, map) => {
-        if (!group.guarded) guild.setGroupEnabled(group, false);
-    });
-    // create a botGuild object for this new guild.
-    BotGuild.create({
-        _id: guild.id,
-    });
-}
-
-/**
- * Runs when the bot is removed from a server.
- */
-bot.on('guildDelete', async (guild) => {
-    mainLogger.warning(`The bot was removed from the guild: ${guild.id} - ${guild.name}`);
-
-    let botGuild = await BotGuild.findById(guild.id);
-    botGuild.remove();
-    mainLogger.verbose(`BotGuild with id: ${guild.id} has been removed!`);
-});
-
-/**
- * Runs when the bot runs into an error.
- */
-bot.on('error', (error) => {
-    mainLogger.error(`Bot Error: ${error.name} - ${error.message}.`, { event: 'Error', data: error});
-});
-
-/**
- * Runs when the bot runs into an error when running a command.
- */
-bot.on('commandError', (command, error, message) => {
-    winston.loggers.get(message.channel?.guild?.id || 'main').error(`Command Error: In command ${command.name} got uncaught rejection ${error.name} : ${error.message}`, { event: 'Error', data: error});
-});
-
-/**
- * Runs when a message is sent in any server the bot is running in.
- */
-bot.on('message', async message => {
-    if (message?.guild) {
-        let botGuild = await BotGuild.findById(message.guild.id);
-
-        // Deletes all messages to any channel in the black list with the specified timeout
-        // this is to make sure that if the message is for the bot, it is able to get it
-        // bot and staff messages are not deleted
-        if (botGuild.blackList.has(message.channel.id)) {
-            if (!message.author.bot && !discordServices.checkForRole(message.member, botGuild.roleIDs.staffRole)) {
-                winston.loggers.get(message.guild.id).verbose(`Deleting message from user ${message.author.id} due to being in the blacklisted channel ${message.channel.name}.`);
-                (new Promise(res => setTimeout(res, botGuild.blackList.get(message.channel.id)))).then(() => discordServices.deleteMessage(message));
-            }
-        }
-    }
-});
-
-/**
- * Runs when a new member joins a guild the bot is running in.
- */
-bot.on('guildMemberAdd', async member => {
-    let botGuild = await BotGuild.findById(member.guild.id);
-
-    // if the guild where the user joined is complete then greet and verify.
-    // also checks to make sure it does not greet bots
-    if (botGuild.isSetUpComplete && !member.user.bot) {
-        try {
-            winston.loggers.get(member.guild.id).userStats('A new user joined the guild and is getting greeted!');
-            await greetNewMember(member, botGuild);
-        } catch (error) {
-            await fixDMIssue(error, member, botGuild);
-        }
-    } else {
-        winston.loggers.get(member.guild.id).warning('A new user joined the guild but was not greeted because the bot is not set up!');
-    }
-});
-
-bot.on('commandRun', (command, promise, message, args) => {
-    winston.loggers.get(message?.guild?.id || 'main').command(`The command ${command.name} with args ${args} is being run from the channel ${message.channel} with id ${message.channel.id} 
-        triggered by the message with id ${message.id} by the user with id ${message.author.id}`);
-});
-
-/**
- * Runs when an unknown command is triggered.
- */
-bot.on('unknownCommand', (message) => winston.loggers.get(message?.guild?.id || 'main').command(`An unknown command has been triggered in the channel ${message.channel.name} with id ${message.channel.id}. The message had the content ${message.cleanContent}.`));
-
-/**
- * Logs in the bot 
- */
-bot.login(config.token).catch(console.error);
-
-/**
- * Runs when the node process has an uncaught exception.
- */
-process.on('uncaughtException', (error) => {
-    console.log(
-        'Uncaught Rejection, reason: ' + error.name +
-        '\nmessage: ' + error.message +
-        '\nfile: ' + error.fileName +
-        '\nline number: ' + error.lineNumber +
-        '\nstack: ' + error.stack
-    );
-});
-
-/**
- * Runs when the node process has an unhandled rejection.
- */
-process.on('unhandledRejection', (error, promise) => {
-    console.log('Unhandled Rejection at:', promise,
-        'Unhandled Rejection, reason: ' + error.name +
-        '\nmessage: ' + error.message +
-        '\nfile: ' + error.fileName +
-        '\nline number: ' + error.lineNumber +
-        '\nstack: ' + error.stack
-    );
-});
-
-/**
- * Runs when the node process is about to exit and quit.
- */
-process.on('exit', () => {
-    mainLogger.warning('Node is exiting!');
-});
-
-/**
- * Will create a default logger to use.
- * @param {String} loggerName
- * @param {String} [loggerLabel=''] - usually a more readable logger name
- * @param {Boolean} [handleRejectionsExceptions=false] - will handle rejections and exceptions if true
- * @param {Boolean} [LogToConsole=false] - will log all levels to console if true
- * @returns {winston.Logger}
- */
-function createALogger(loggerName, loggerLabel = '', handelRejectionsExceptions = false, logToConsole = false) {
-    // custom format
-    let format = winston.format.printf(info => `${info.timestamp} [${info.label}] ${info.level}${info?.event ? ' <' + info.event + '>' : ''} : ${info.message} ${info?.data ? 'DATA : ' + info.data : '' }`);
-
-    // create main logs directory if not present
-    if (!fs.existsSync('./logs')) fs.mkdirSync('./logs');
-
-    // create the directory if not present
-    if (!fs.existsSync(`./logs/${loggerName}`)) fs.mkdirSync(`./logs/${loggerName}`);
-    let logger = winston.loggers.add(loggerName, {
-        levels: customLoggerLevels.levels,
-        transports: [
-            new winston.transports.File({ filename: `./logs/${loggerName}/logs.log`, level: 'silly' }),
-            new winston.transports.File({ filename: `./logs/${loggerName}/debug.log`, level: 'debug' }),
-            new winston.transports.File({ filename: `./logs/${loggerName}/verbose.log`, level: 'verbose' }),
-            new winston.transports.File({ filename: `./logs/${loggerName}/userStats.log`, level: 'userStats' }),
-            new winston.transports.File({ filename: `./logs/${loggerName}/event.log`, level: 'event' }),
-            new winston.transports.File({ filename: `./logs/${loggerName}/command.log`, level: 'command' }),
-            new winston.transports.File({ filename: `./logs/${loggerName}/warning.log`, level: 'warning' }),
-            new winston.transports.File({ filename: `./logs/${loggerName}/error.log`, level: 'error', handleExceptions: handelRejectionsExceptions, handleRejections: handelRejectionsExceptions, }),
-            ...(logToConsole ? [new winston.transports.Console({ 
-                level: 'silly', 
-                format: winston.format.combine(
-                    winston.format.colorize({ level: true }),
-                    winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
-                    winston.format.splat(),
-                    winston.format.label({ label: loggerLabel}),
-                    format,
-                ),
-                handleExceptions: true,
-                handleRejections: true,
-            })] : []),
-        ],
-        exitOnError: false,
-        format: winston.format.combine(
-            winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
-            winston.format.splat(),
-            winston.format.label({ label: loggerLabel}),
-            format,
-        )
-    });
-    return logger;
-}
-
-/**
- * Greets a member!
- * @param {Discord.GuildMember} member - the member to greet
- * @param {BotGuildModel} botGuild
- * @throws Error if the user has server DMs off
- */
-async function greetNewMember(member, botGuild) {
-    let verifyEmoji = '🍀';
-
-    var embed = new Discord.MessageEmbed()
-        .setTitle(`Welcome to the ${member.guild.name} Server!`)
-        .setDescription('We are very excited to have you here!')
-        .addField('Have a question?', 'Visit the #welcome-support channel to talk with our staff!')
-        .addField('Want to learn more about what I can do?', 'Use the !help command anywhere and I will send you a message!')
-        .setColor(botGuild.colors.embedColor);
-
-    if (botGuild.verification.isEnabled) embed.addField('Gain more access by verifying yourself!', 'React to this message with ' + verifyEmoji + ' and follow my instructions!');
-    
-    let msg = await member.send(embed);
-
-    // if verification is on then give guest role and let user verify
-    if (botGuild.verification.isEnabled) {
-        discordServices.addRoleToMember(member, botGuild.verification.guestRoleID);
-
-        msg.react(verifyEmoji);
-        let verifyCollector = msg.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === verifyEmoji);
-
-        verifyCollector.on('collect', async (reaction, user) => {
-            try {
-                var email = await StringPrompt.single({prompt: 'Please send me your email associated to this event!', channel: member.user.dmChannel, userId: member.id, time: 30, cancelable: true});
-            } catch (error) {
-                discordServices.sendEmbedToMember(member, {
-                    title: 'Verification Error',
-                    description: 'Email was not provided, please try again!'
-                }, true);
-                return;
-            }
-
-            try {
-                await Verification.verify(member, email, member.guild, botGuild);
-            } catch (error) {
-                discordServices.sendEmbedToMember(member, {
-                    title: 'Verification Error',
-                    description: 'Email provided is not valid! Please try again.'
-                }, true);
-            }
-        });
-    }
-    // if verification is off, then just ive member role
-    else {
-        discordServices.addRoleToMember(member, botGuild.roleIDs.memberRole);
-    }
-}
-
-/**
- * Will let the member know how to fix their DM issue.
- * @param {Error} error - the error
- * @param {Discord.GuildMember} member - the member with the error
- * @param {BotGuildModel} botGuild
- * @throws Error if the given error is not a DM error
- */
-async function fixDMIssue(error, member, botGuild) {
-    if (error.code === 50007) {
-        let logger = winston.loggers.get(member.guild.id);
-        logger.warning(`A new user with id ${member.id} joined the guild but was not able to be greeted, we have asked him to fix the issues!`);
-        let channelID = botGuild.verification?.welcomeSupportChannelID || botGuild.channelIDs.botSupportChannel;
-
-        member.guild.channels.resolve(channelID).send('<@' + member.id + '> I couldn\'t reach you :(.' +
-            '\n* Please turn on server DMs, explained in this link: https://support.discord.com/hc/en-us/articles/217916488-Blocking-Privacy-Settings-' +
-            '\n* Once this is done, please react to this message with 🤖 to let me know!').then(msg => {
-            msg.react('🤖');
-            const collector = msg.createReactionCollector((reaction, user) => user.id === member.id && reaction.emoji.name === '🤖');
-
-            collector.on('collect', (reaction, user) => {
-                reaction.users.remove(user.id);
-                try {
-                    greetNewMember(member);
-                    collector.stop();
-                    msg.delete();
-                    logger.userStats(`A user with id ${member.id} was able to fix the DM issue and was greeted!`);
-                } catch (error) {
-                    member.guild.channels.resolve(channelID).send('<@' + member.id + '> Are you sure you made the changes? I couldn\'t reach you again 😕').then(msg => msg.delete({ timeout: 8000 }));
-                }
-            });
-        });
-    } else {
-        throw error;
-    }
-}
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_activities_activity.js.html b/docs/classes_activities_activity.js.html deleted file mode 100644 index 902226ba..00000000 --- a/docs/classes_activities_activity.js.html +++ /dev/null @@ -1,581 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/activities/activity.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/activities/activity.js

-
- - - - - -
-
-
const { Guild, Collection, Role, CategoryChannel, TextChannel, MessageEmbed, GuildMember, PermissionOverwriteOption } = require('discord.js');
-const winston = require('winston');
-const BotGuild = require('../../db/mongo/BotGuild');
-const BotGuildModel = require('../bot-guild');
-const { shuffleArray, sendMsgToChannel } = require('../../discord-services');
-const StampsManager = require('../stamps-manager');
-const Room = require('../room');
-const Console = require('../consoles/console');
-const { StringPrompt, RolePrompt, ListPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * @typedef ActivityInfo
- * @property {string} activityName - the name of this activity!
- * @property {Guild} guild - the guild where the new activity lives
- * @property {Collection<String, Role>} roleParticipants - roles allowed to view activity 
- * @property {BotGuildModel} botGuild
- */
-
-/**
- * An object with a role and its permissions
- * @typedef RolePermission
- * @property {String} id - the role snowflake
- * @property {PermissionOverwriteOption} permissions - the permissions to set to that role
- */
-
-/**
- * @typedef ActivityFeature
- * @property {String} emoji - the emoji as a string
- * @property {String} name
- * @property {String} description
- * @property {Function} callback
- */
-
-/**
- * An activity is a overarching class for any kind of activity. An activity consists of a 
- * category with voice and text channels.
- * Activities have features admins can run from the admin console by reacting to a message (console).
- * The activity can be private to specified roles or public to all users.
- * @class
- */
-class Activity {
-
-    /**
-     * Prompts a user for the roles that can have access to an activity.
-     * @param {TextChannel} channel - the channel to prompt in
-     * @param {String} userId - the user id to prompt
-     * @param {Boolean} [isStaffAuto=false] - true if staff are added automatically
-     * @returns {Promise<Collection<String, Role>>}
-     * @async
-     * @static
-     */
-    static async promptForRoleParticipants(channel, userId, isStaffAuto = false) {
-        let allowedRoles = new Collection();
-        
-        try {
-            allowedRoles = await RolePrompt.multi({ prompt: `What roles${isStaffAuto ? ', aside from Staff,' : ''} will be allowed to view this activity? (Type "cancel" if none)`,
-                channel, userId, cancelable: true });
-        } catch (error) {
-            // nothing given is an empty collection viewable to admins only
-        }
-
-        // add staff role
-        if (isStaffAuto) {
-            let staffRoleId = (await BotGuild.findById(channel.guild.id)).roleIDs.staffRole;
-            allowedRoles.set(staffRoleId, channel.guild.roles.resolve(staffRoleId));
-        } 
-
-        return allowedRoles;
-    }
-
-    /**
-     * Constructor for an activity, will create the category, voice and text channel.
-     * @constructor
-     * @param {ActivityInfo} ActivityInfo 
-     */
-    constructor({activityName, guild, roleParticipants, botGuild}) {
-        /**
-         * The name of this activity.
-         * @type {string}
-         */
-        this.name = activityName;
-
-        /**
-         * The guild this activity is in.
-         * @type {Guild}
-         */
-        this.guild = guild;
-
-        /**
-         * The room this activity lives in.
-         * @type {Room}
-         */
-        this.room = new Room(guild, botGuild, activityName, roleParticipants);
-
-        /**
-         * The admin console with activity features.
-         * @type {Console}
-         */
-        this.adminConsole = new Console({
-            title: `Activity ${activityName} Console`,
-            description: 'This activity\'s information can be found below, you can also find the features available.',
-            channel: guild.channels.resolve(botGuild.channelIDs.adminConsole),
-            guild: this.guild,
-        });
-
-        /**
-         * The mongoose BotGuildModel Object
-         * @type {BotGuildModel}
-         */
-        this.botGuild = botGuild;
-
-        winston.loggers.get(guild.id).event(`An activity named ${this.name} was created.`, {data: {permissions: roleParticipants}});
-    }
-
-
-    /**
-     * Initialize this activity by creating the channels, adding the features and sending the admin console.
-     * @async
-     * @returns {Promise<Activity>}
-     */
-    async init() {
-        await this.room.init();
-
-        this.addDefaultFeatures();
-
-        await this.adminConsole.sendConsole();
-
-        winston.loggers.get(this.guild.id).event(`The activity ${this.name} was initialized.`, {event: 'Activity'});
-        return this;
-    }
-
-
-    /**
-     * Adds the default features to the activity, these features are available to all activities.
-     * @protected
-     */
-    addDefaultFeatures() {
-        /** @type {Console.Feature[]} */
-        let localFeatures = [
-            {
-                name: 'Add Channel',
-                description: 'Add one channel to the activity.',
-                emojiName: '⏫',
-                callback: (user, reaction, stopInteracting, console) => this.addChannel(console.channel, user.id).then(() => stopInteracting()),
-            },
-            {
-                name: 'Remove Channel',
-                description: 'Remove a channel, decide from a list.',
-                emojiName: '⏬',
-                callback: (user, reaction, stopInteracting, console) => this.removeChannel(console.channel, user.id).then(() => stopInteracting()),
-            },
-            {
-                name: 'Delete', 
-                description: 'Delete this activity and its channels.',
-                emojiName: '⛔',
-                callback: (user, reaction, stopInteracting, console) => this.delete(),
-            },
-            {
-                name: 'Archive',
-                description: 'Archive the activity, text channels are saved.',
-                emojiName: '💼',
-                callback: (user, reaction, stopInteracting, console) => {
-                    let archiveCategory = this.guild.channels.resolve(this.botGuild.channelIDs.archiveCategory);
-                    this.archive(archiveCategory);
-                }
-            },
-            {
-                name: 'Callback',
-                description: 'Move all users in the activity\'s voice channels back to a specified voice channel.',
-                emojiName: '🔃',
-                callback: (user, reaction, stopInteracting, console) => this.voiceCallBack(console.channel, user.id).then(() => stopInteracting()),
-            },
-            {
-                name: 'Shuffle',
-                description: 'Shuffle all members from one channel to all others in the activity.',
-                emojiName: '🌬️',
-                callback: (user, reaction, stopInteracting, console) => this.shuffle(console.channel, user.id).then(() => stopInteracting()),
-            },
-            {
-                name: 'Role Shuffle',
-                description: 'Shuffle all the members with a specific role from one channel to all others in the activity.',
-                emojiName: '🦜',
-                callback: (user, reaction, stopInteracting, console) => this.roleShuffle(console.channel, user.id).then(() => stopInteracting()),
-            },
-            {
-                name: 'Distribute Stamp',
-                description: 'Send a emoji collector for users to get a stamp.',
-                emojiName: '🏕️',
-                callback: (user, reaction, stopInteracting, console) => this.distributeStamp(console.channel, user.id).then(() => stopInteracting()),
-            },
-            {
-                name: 'Rules Lock',
-                description: 'Lock the activity behind rules, users must agree to the rules to access the channels.',
-                emojiName: '🔒',
-                callback: (user, reaction, stopInteracting, console) => this.ruleValidation(console.channel, user.id).then(() => stopInteracting()),
-            }
-        ];
-
-        localFeatures.forEach(feature => this.adminConsole.addFeature(feature));
-        
-    }
-
-    /**
-     * FEATURES FROM THIS POINT DOWN.
-     */
-
-    /**
-     * Add a channel to the activity, prompts user for info and name.
-     * @param {TextChannel} channel - channel to prompt user for specified voice channel
-     * @param {String} userId - user to prompt for specified voice channel
-     * @async
-     */
-    async addChannel(channel, userId) {
-        // voice or text
-        let option = await ListPrompt.singleReactionPicker({
-            prompt: 'What type of channel do you want?',
-            channel,
-            userId,
-        }, [
-            {
-                name: 'voice',
-                description: 'A voice channel',
-                emojiName: '🔊'
-            },
-            {
-                name: 'text', 
-                description: 'A text channel',
-                emojiName: '✍️',
-            }
-        ]);
-        // channel name
-        let name = await StringPrompt.single({ prompt: 'What is the name of the channel?', channel, userId});
-
-        return await this.room.addRoomChannel({name, info: { type: option.name}});
-    }
-
-    /**
-     * Removes a channel from the activity, the user will decide which. Wont delete channels in the safeChannel map.
-     * @param {TextChannel} channel - channel to prompt user for specified voice channel
-     * @param {String} userId - user to prompt for specified voice channel
-     * @async
-     */
-    async removeChannel(channel, userId) {
-        /** @type {TextChannel} channel to remove */
-        let removeChannel = await ListPrompt.singleListChooser({
-            prompt: 'What channel should be removed?',
-            channel: channel,
-            userId: userId
-        }, this.room.channels.category.children.array());
-
-        try {
-            this.room.removeRoomChannel(removeChannel);
-        } catch (error) {
-            sendMsgToChannel(channel, userId, 'Can\'t remove that channel!', 10);
-            return;
-        }
-
-        winston.loggers.get(this.guild.id).event(`The activity ${this.name} lost a channel named ${removeChannel.name}`, { event: 'Activity' });
-    }
-
-    /**
-     * Archive the activity. Move general text channel to archive category, remove all remaining channels
-     * and remove the category.
-     * @param {CategoryChannel} archiveCategory - the category where the general text channel will be moved to
-     * @async
-     */
-    async archive(archiveCategory) {
-        await this.room.archive(archiveCategory);
-
-        this.adminConsole.delete();
-
-        winston.loggers.get(this.guild.id).event(`The activity ${this.name} was archived!`, {event: 'Activity'});
-    }
-
-    /**
-     * Delete all the channels and the category. Remove the workshop from firebase.
-     * @async
-     */
-    async delete() {
-        await this.room.delete();
-
-        this.adminConsole.delete();
-
-        winston.loggers.get(this.guild.id).event(`The activity ${this.name} was deleted!`, {event: 'Activity'});
-    }
-
-    /**
-     * Move all users back to a specified voice channel from the activity's voice channels.
-     * @param {TextChannel} channel - channel to prompt user for specified voice channel
-     * @param {String} userId - user to prompt for specified voice channel
-     */
-    async voiceCallBack(channel, userId) {
-        /** @type {VoiceChannel} */
-        let mainChannel = await ListPrompt.singleListChooser({
-            prompt: 'What channel should people be moved to?',
-            channel: channel,
-            userId: userId
-        }, this.room.channels.voiceChannels.array());
-
-        this.room.channels.voiceChannels.forEach(channel => {
-            channel.members.forEach(member => member.voice.setChannel(mainChannel));
-        });
-
-        winston.loggers.get(this.guild.id).event(`Activity named ${this.name} had its voice channels called backs to channel ${mainChannel.name}.`, {event: 'Activity'});
-    }
-
-    /**
-     * @callback ShuffleFilter
-     * @param {GuildMember} member
-     * @returns {Boolean} - true if filtered
-    /**
-     * Shuffle all the general voice members on all other voice channels
-     * @param {TextChannel} channel - channel to prompt user for specified voice channel
-     * @param {String} userId - user to prompt for specified voice channel
-     * @param {ShuffleFilter} [filter] - filter the users to shuffle
-     * @async
-     */
-    async shuffle(channel, userId, filter) {
-        /** @type {VoiceChannel} */
-        let mainChannel = await ListPrompt.singleListChooser({
-            prompt: 'What channel should I move people from?',
-            channel: channel,
-            userId: userId
-        }, this.room.channels.voiceChannels.array());
-
-        let members = mainChannel.members;
-        if (filter) members = members.filter(member => filter(member));
-        
-        let memberList = members.array();
-        shuffleArray(memberList);
-
-        let channels = this.room.channels.voiceChannels.filter(channel => channel.id != mainChannel.id).array();
-
-        let channelsLength = channels.length;
-        let channelIndex = 0;
-        memberList.forEach(member => {
-            try {
-                member.voice.setChannel(channels[channelIndex % channelsLength]);
-                channelIndex++;
-            } catch (error) {
-                winston.loggers.get(this.guild.id).warning(`Could not set a users voice channel when shuffling an activity by role. Error: ${error}`, { event: 'Activity' });
-            }
-        });
-
-        winston.loggers.get(this.guild.id).event(`Activity named ${this.name} had its voice channel members shuffled around!`, {event: 'Activity'});
-    }
-
-    /**
-     * Shuffles users with a specific role throughout the activity's voice channels
-     * @param {TextChannel} channel - channel to prompt user for specified voice channel
-     * @param {String} userId - user to prompt for specified voice channel
-     * @async
-     */
-    async roleShuffle(channel, userId) {
-        try {
-            var role = await RolePrompt.single({ prompt: 'What role would you like to shuffle?', channel, userId });
-        } catch (error) {
-            winston.loggers.get(this.guild.id).warning(`User canceled a request when asking for a role for role shuffle. Error: ${error}.`, { event: 'Activity' });
-        }
-
-        this.shuffle(channel, userId, (member) => member.roles.cache.has(role.id));
-    }
-
-    /**
-     * Will let hackers get a stamp for attending the activity.
-     * @param {TextChannel} channel - channel to prompt user for specified voice channel
-     * @param {String} userId - user to prompt for specified voice channel
-     */
-    async distributeStamp(channel, userId) {
-
-        if (!this.botGuild.stamps.isEnabled) {
-            sendMsgToChannel(channel, userId, 'The stamp system is not enabled in this server!', 10);
-            return;
-        }
-        
-        // The users already seen by this stamp distribution.
-        let seenUsers = new Collection();
-
-        const promptEmbed = new MessageEmbed()
-            .setColor(this.botGuild.colors.embedColor)
-            .setTitle('React within ' + this.botGuild.stamps.stampCollectionTime + ' seconds of the posting of this message to get a stamp for ' + this.name + '!');
-
-        // send embed to general text or prompt for channel
-        let promptMsg;
-        if ((await this.room.channels.generalText.fetch(true))) promptMsg = await this.room.channels.generalText.send(promptEmbed);
-        else {
-            let stampChannel = await ListPrompt.singleListChooser({
-                prompt: 'What channel should the stamp distribution go?',
-                channel: channel,
-                userId: userId
-            }, this.room.channels.textChannels.array());
-            promptMsg = await stampChannel.send(promptEmbed);
-        }
-        
-        promptMsg.react('👍');
-
-        // reaction collector, time is needed in milliseconds, we have it in seconds
-        const collector = promptMsg.createReactionCollector((reaction, user) => !user.bot, { time: (1000 * this.botGuild.stamps.stampCollectionTime) });
-
-        collector.on('collect', async (reaction, user) => {
-            // grab the member object of the reacted user
-            const member = this.guild.member(user);
-
-            if (!seenUsers.has(user.id)) {
-                StampsManager.parseRole(member, this.name, this.botGuild);
-                seenUsers.set(user.id, user.username);
-            }
-        });
-
-        // edit the message to closed when the collector ends
-        collector.on('end', () => {
-            winston.loggers.get(this.guild.id).event(`Activity named ${this.name} stamp distribution has stopped.`, {event: 'Activity'});
-            if (!promptMsg.deleted) {
-                promptMsg.edit(promptEmbed.setTitle('Time\'s up! No more responses are being collected. Thanks for participating in ' + this.name + '!'));
-            }
-        });
-    }
-
-    /**
-     * Will lock the channels behind an emoji collector.
-     * @param {TextChannel} channel - channel to prompt user for specified voice channel
-     * @param {String} userId - user to prompt for specified voice channel
-     */
-    async ruleValidation(channel, userId) {
-
-        let rulesChannel = await this.room.lockRoom();
-
-        let rules = await StringPrompt.single({ prompt: 'What are the activity rules?', channel, userId});
-
-        let joinEmoji = '🚗';
-
-        const embed = new MessageEmbed().setTitle('Activity Rules').setDescription(rules).addField('To join the activity:', `React to this message with ${joinEmoji}`).setColor(this.botGuild.colors.embedColor);
-
-        const embedMsg = await rulesChannel.send(embed);
-
-        embedMsg.react(joinEmoji);
-
-        const collector = embedMsg.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === joinEmoji);
-
-        collector.on('collect', (reaction, user) => {
-            this.room.giveUserAccess(user);
-            rulesChannel.updateOverwrite(user.id, { VIEW_CHANNEL: false});
-        });
-    }
-}
-
-module.exports = Activity;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_activities_cave.js.html b/docs/classes_activities_cave.js.html deleted file mode 100644 index 33fce5d6..00000000 --- a/docs/classes_activities_cave.js.html +++ /dev/null @@ -1,540 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/activities/cave.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/activities/cave.js

-
- - - - - -
-
-
const { Guild, Collection, Role, TextChannel, MessageEmbed, GuildEmoji, ReactionEmoji, CategoryChannel } = require('discord.js');
-const { sendMsgToChannel, addRoleToMember, removeRolToMember } = require('../../discord-services');
-const BotGuildModel = require('../bot-guild');
-const Room = require('../room');
-const Console = require('../consoles/console');
-const Feature = require('../consoles/feature');
-const TicketManager = require('../tickets/ticket-manager');
-const Activity = require('./activity');
-const { StringPrompt, NumberPrompt, SpecialPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * @typedef CaveOptions
- * @property {String} name - the name of the cave category
- * @property {String} preEmojis - any pre name emojis
- * @property {String} preRoleText - the text to add before every role name, not including '-'
- * @property {String} color - the role color to use for this cave
- * @property {Role} role - the role associated with this cave
- * @property {Emojis} emojis - object holding emojis to use in this cave
- * @property {Times} times - object holding times to use in this cave
- * @property {Collection<String, Role>} publicRoles - the roles that can request tickets
- */
-
-/**
- * @typedef Emojis
- * @property {GuildEmoji | ReactionEmoji} joinTicketEmoji - emoji for mentors to accept a ticket
- * @property {GuildEmoji | ReactionEmoji} giveHelpEmoji - emoji for mentors to join an ongoing ticket
- * @property {GuildEmoji | ReactionEmoji} requestTicketEmoji - emoji for hackers to request a ticket
- * @property {GuildEmoji | ReactionEmoji} addRoleEmoji - emoji for Admins to add a mentor role
- * @property {GuildEmoji | ReactionEmoji} deleteChannelsEmoji - emoji for Admins to force delete ticket channels
- * @property {GuildEmoji | ReactionEmoji} excludeFromAutoDeleteEmoji - emoji for Admins to opt tickets in/out of garbage collector
- */
-
-/**
- * @typedef Times
- * @property {Number} inactivePeriod - number of minutes a ticket channel will be inactive before bot starts to delete it
- * @property {Number} bufferTime - number of minutes the bot will wait for a response before deleting ticket
- * @property {Number} reminderTime - number of minutes the bot will wait before reminding mentors of unaccepted tickets
- */
-
-/**
- * @typedef SubRole
- * @property {String} name - the role name
- * @property {String} id - the role id (snowflake)
- * @property {Number} activeUsers - number of users with this role
- */
-
-/**
- * @typedef CaveChannels
- * @property {TextChannel} roleSelection
- */
-
-class Cave extends Activity {
-
-    /**
-     * @constructor
-     * @param {CaveOptions} caveOptions 
-     * @param {BotGuildModel} botGuild 
-     * @param {Guild} guild
-     */
-    constructor(caveOptions, botGuild, guild) {
-        super({
-            activityName: caveOptions.name,
-            guild: guild,
-            roleParticipants: new Collection([[caveOptions.role.id, caveOptions.role]]),
-            botGuild: botGuild,
-        });
-
-        /**
-         * @type {CaveOptions}
-         */
-        this.caveOptions;
-        this.validateCaveOptions(caveOptions);
-
-        /**
-         * The cave sub roles, keys are the emoji name, holds the subRole
-         * @type {Map<String, SubRole>} - <Emoji Name, SubRole>
-         */
-        this.subRoles = new Map();
-
-        /**
-         * @type {TicketManager}
-         */
-        this.ticketManager;
-
-        /**
-          * The channels needed for a cave.
-          * @type {CaveChannels}
-          */
-        this.channels = {};
-
-        /**
-          * The public room for this cave.
-          * @type {Room}
-          */
-        this.publicRoom = new Room(guild, botGuild, `👉🏽👈🏽${caveOptions.name} Help`, caveOptions.publicRoles);
-
-        /**
-         * The console where cave members can get sub roles.
-         * @type {Console}
-         */
-        this.subRoleConsole;
-    }
-
-    /**
-     * Validates and set the cave options.
-     * @param {CaveOptions} caveOptions - the cave options to validate
-     * @param {Discord.Guild} guild - the guild where this cave is happening
-     * @private
-     */
-    validateCaveOptions(caveOptions) {
-        if (typeof caveOptions.name != 'string' && caveOptions.name.length === 0) throw new Error('caveOptions.name must be a non empty string');
-        if (typeof caveOptions.preEmojis != 'string') throw new Error('The caveOptions.preEmojis must be a string of emojis!');
-        if (typeof caveOptions.preRoleText != 'string' && caveOptions.preRoleText.length === 0) throw new Error('The caveOptions.preRoleText must be a non empty string!');
-        if (typeof caveOptions.color != 'string' && caveOptions.color.length === 0) throw new Error('The caveOptions.color must be a non empty string!');
-        if (!(caveOptions.role instanceof Role)) throw new Error('The caveOptions.role must be Role object!');
-        for (const emoji in caveOptions.emojis) {
-            if (!(caveOptions.emojis[emoji] instanceof GuildEmoji) && !(caveOptions.emojis[emoji] instanceof ReactionEmoji)) throw new Error('The ' + emoji + 'must be a GuildEmoji or ReactionEmoji!');
-        }
-        this.caveOptions = caveOptions;
-    }
-
-    async init() {
-        await super.init();
-
-        this.channels.roleSelection = await this.room.addRoomChannel({
-            name: `📝${this.name}-role-selector`,
-            info: {
-                topic: 'Sign yourself up for specific roles! New roles will be added as requested, only add yourself to one if you feel comfortable responding to questions about the topic.',
-            },
-            isSafe: true,
-        });
-        this.subRoleConsole = new Console({
-            title: 'Choose your sub roles!',
-            description: 'Choose sub roles you are comfortable answering questions for! Remove your reaction to loose the sub role.',
-            channel: this.channels.roleSelection,
-            guild: this.guild,
-        });
-        this.subRoleConsole.sendConsole();
-
-        for (var i = 0; i < 3; i++) {
-            this.room.addRoomChannel({
-                name: `🗣️ Room ${i}`,
-                info: { type: 'voice' },
-            });
-        }
-
-        await this.publicRoom.init();
-
-        this.ticketManager = new TicketManager(this, {
-            ticketCreatorInfo: {
-                channel: await this.publicRoom.addRoomChannel({
-                    name: '🎫request-ticket',
-                    isSafe: true,
-                }),
-            },
-            ticketDispatcherInfo: {
-                channel: await this.room.addRoomChannel({
-                    name: '📨incoming-tickets',
-                    isSafe: true,
-                }),
-                takeTicketEmoji: this.caveOptions.emojis.giveHelpEmoji,
-                joinTicketEmoji: this.caveOptions.emojis.joinTicketEmoji,
-                reminderInfo: {
-                    isEnabled: true,
-                    time: this.caveOptions.times.reminderTime,
-                },
-                mainHelperInfo: {
-                    role: this.caveOptions.role,
-                    emoji: this.caveOptions.emojis.requestTicketEmoji,
-                },
-                embedCreator: (ticket) => new MessageEmbed()
-                    .setTitle(`New Ticket - ${ticket.id}`)
-                    .setDescription(`<@${ticket.group.first().id}> has a question: ${ticket.question}`)
-                    .addField('They are requesting:', `<@&${ticket.requestedRole.id}>`)
-                    .setTimestamp(),
-            },
-            systemWideTicketInfo: {
-                garbageCollectorInfo: {
-                    isEnabled: true,
-                    inactivePeriod: this.caveOptions.times.inactivePeriod,
-                    bufferTime: this.caveOptions.times.bufferTime
-                },
-                isAdvancedMode: true,
-            }
-        });
-
-        await this.ticketManager.sendTicketCreatorConsole('Get some help from our mentors!', 
-            'To submit a ticket to the mentors please react to this message with the appropriate emoji. **If you are unsure, select a general ticket!**');
-    }
-
-    addDefaultFeatures() {
-        /** @type {Console.Feature[]} */
-        let localFeatures = [
-            Feature.create({
-                name: 'Add Sub-Role',
-                description: 'Add a new sub-role cave members can select and users can use to ask specific tickets.',
-                emoji: this.caveOptions.emojis.addRoleEmoji,
-                callback: (user, reaction, stopInteracting, console) => this.addSubRoleCallback(console.channel, user.id).then(() => stopInteracting()),
-            }),
-            Feature.create({
-                name: 'Delete Ticket Channels',
-                description: 'Get the ticket manager to delete ticket rooms to clear up the server.',
-                emoji: this.caveOptions.emojis.deleteChannelsEmoji,
-                callback: (user, reaction, stopInteracting, console) => this.deleteTicketChannelsCallback(console.channel, user.id).then(() => stopInteracting()),
-            }),
-            Feature.create({
-                name: 'Include/Exclude Tickets',
-                description: 'Include or exclude tickets from the automatic garbage collector.',
-                emoji: this.caveOptions.emojis.excludeFromAutoDeleteEmoji,
-                callback: (user, reaction, stopInteracting, console) => this.includeExcludeCallback(console.channel, user.id).then(() => stopInteracting()),
-            }),
-        ];
-
-        localFeatures.forEach(feature => this.adminConsole.addFeature(feature));
-
-        super.addDefaultFeatures();
-    }
-
-    /**
-     * Prompts a user for information to create a new sub role for this cave.
-     * @param {TextChannel} channel 
-     * @param {String} userId 
-     * @returns {Promise<Role>}
-     * @async
-     */
-    async addSubRoleCallback(channel, userId) {
-        let roleNameMsg = await StringPrompt.single({prompt: 'What is the name of the new role?', channel, userId});
-
-        let roleName = roleNameMsg.content;
-
-        let emojis = new Map();
-        this.subRoles.forEach((subRole, emojiName, map) => {
-            emojis.set(emojiName, subRole.name);
-        });
-
-        let reaction = await SpecialPrompt.singleRestrictedReaction({ prompt: 'What emoji do you want to associate with this new role?', channel, userId }, emojis);
-        let emoji = reaction.emoji;
-
-        // search for possible existing role
-        let findRole = this.guild.roles.cache.find(role => role.name.toLowerCase() === `${this.caveOptions.preRoleText}-${roleName}`.toLowerCase());
-        let useOld;
-        if (findRole) useOld = await SpecialPrompt.boolean({ prompt: 'I have found a role with the same name! Would you like to use that one? If not I will create a new one.', channel, userId });
-
-        let role;
-        if (useOld) role = findRole; 
-        else role = await this.guild.roles.create({
-            data: {
-                name: `${this.caveOptions.preRoleText}-${roleName}`,
-                color: this.caveOptions.color,
-            }
-        });
-
-        this.addSubRole(role, emoji);
-
-        try {
-            let addPublic = await SpecialPrompt.boolean({ prompt: 'Do you want me to create a public text channel?', channel, userId });
-            if (addPublic) this.publicRoom.addRoomChannel({ name: roleName });
-        } catch {
-            // do nothing
-        }
-
-        return role;
-    }
-
-    /**
-     * Will prompt the user for more information to delete some, all, or a few tickets.
-     * @param {TextChannel} channel 
-     * @param {String} userId 
-     * @async
-     */
-    async deleteTicketChannelsCallback(channel, userId) {
-        let type = await StringPrompt.restricted({
-            prompt: 'Type "all" if you would like to delete all tickets before x amount of time or type "some" to specify which tickets to remove.', 
-            channel, 
-            userId,
-        }, ['all', 'some']);
-
-        switch (type) {
-            case 'all': {
-                let age = await NumberPrompt.single({prompt: 'Enter how old, in minutes, a ticket has to be to remove. Send 0 if you want to remove all of them. Careful - this cannot be undone!', channel, userId});
-                this.ticketManager.removeTicketsByAge(age);
-                sendMsgToChannel(channel, userId, `All tickets over ${age} have been deleted!`);
-                break;
-            }
-            case('some'): {
-                let subtype = await StringPrompt.restricted({
-                    prompt: 'Would you like to remove all tickets except for some tickets you specify later or would you like to remove just some tickets. Type all or some respectively.',
-                    channel,
-                    userId
-                }, ['all', 'some']);
-
-                switch (subtype) {
-                    case 'all': {
-                        let ticketMentions = await NumberPrompt.multi({
-                            prompt: 'In one message write the numbers of the tickets to not delete! (Separated by spaces, ex 1 2 13).',
-                            channel,
-                            userId
-                        });
-                        this.ticketManager.removeAllTickets(ticketMentions);
-                        break;
-                    }
-                    case 'some': {
-                        let ticketMentions = await NumberPrompt.multi({
-                            prompt: 'In one message type the ticket numbers you would like to remove! (Separated by spaces, ex. 1 23 3).',
-                            channel,
-                            userId,
-                        });
-                        this.ticketManager.removeTicketsById(ticketMentions);
-                        break;
-                    }
-                }
-            }  
-        }
-    }
-
-    /**
-     * Will prompt the user for channel numbers to include or exclude from the garbage collector.
-     * @param {TextChannel} channel 
-     * @param {String} userId 
-     */
-    async includeExcludeCallback(channel, userId) {
-        let type = await StringPrompt.restricted({
-            prompt: 'Would you like to include tickets on the automatic garbage collector or exclude tickets? Respond with include or exclude respectively.',
-            channel,
-            userId,
-        }, ['include', 'exclude']);
-
-        let tickets = await NumberPrompt.multi({
-            prompt: `Type the ticket numbers you would like to ${type} separated by spaces.`,
-            channel, 
-            userId,
-        });
-
-        tickets.forEach((ticketNumber) => {
-            let ticket = this.ticketManager.tickets.get(ticketNumber);
-            ticket?.includeExclude(type === 'exclude' ? true : false);
-        });
-    }
-
-    /**
-     * Adds a subRole.
-     * @param {Role} role - the role to add
-     * @param {GuildEmoji} emoji - the emoji associated to this role
-     * @param {Number} [currentActiveUsers=0] - number of active users with this role
-     * @private
-     */
-    addSubRole(role, emoji, currentActiveUsers = 0) {
-        /** @type {SubRole} */
-        let subRoleName = role.name.substring(this.caveOptions.preRoleText.length + 1);
-        let subRole = {
-            name: subRoleName,
-            id: role.id,
-            activeUsers: currentActiveUsers,
-        };
-
-        // add to list of emojis being used
-        this.subRoles.set(emoji.name, subRole);
-
-        // add to subRole selector console
-        this.subRoleConsole.addFeature(
-            Feature.create({
-                name: `-> If you know ${subRoleName}`,
-                description: '---------------------------------',
-                emoji: emoji,
-                callback: (user, reaction, stopInteracting, console) => {
-                    let member = this.guild.member(user);
-                    addRoleToMember(member, role);
-                    sendMsgToChannel(console.channel, user.id, `You have received the ${subRoleName} role!`, 10);
-                    stopInteracting();
-                },
-                removeCallback: (user, reaction, stopInteracting, console) => {
-                    let member = this.guild.member(user);
-                    removeRolToMember(member, role);
-                    sendMsgToChannel(console.channel, user.id, `You have lost the ${subRoleName} role!`, 10);
-                    stopInteracting();
-                },
-            })
-        );
-
-        this.ticketManager.addTicketType(role, subRole.name, emoji);
-    }
-
-    /**
-     * Deletes all the tickets rooms, public channels and private channels.
-     * @override
-     */
-    delete() {
-        this.publicRoom.delete();
-        this.ticketManager.removeAllTickets();
-        super.delete();
-    }
-
-    /**
-     * Removes private channels and archives the public channels.
-     * It also deletes the ticket rooms.
-     * @override
-     * @param {CategoryChannel} archiveCategory 
-     */
-    archive(archiveCategory) {
-        this.room.delete();
-        this.publicRoom.archive(archiveCategory);
-        this.ticketManager.removeAllTickets();
-        super.archive();
-    }
-}
-module.exports = Cave;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_activities_coffee-chats.js.html b/docs/classes_activities_coffee-chats.js.html deleted file mode 100644 index 7590c47b..00000000 --- a/docs/classes_activities_coffee-chats.js.html +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/activities/coffee-chats.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/activities/coffee-chats.js

-
- - - - - -
-
-
const Activity = require('./activity');
-const { TextChannel, GuildMember, Collection, VoiceChannel } = require('discord.js');
-const winston = require('winston');
-const { sendMsgToChannel } = require('../../discord-services');
-const Console = require('../consoles/console');
-const { MemberPrompt, ListPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * A CoffeeChat is a special activity where teams get shuffled around voice channels to talk with other teams or members like mentors. 
- * Users can join the activity by reacting to a message. Groups are of unlimited size but must be pre-made. The activity will not create groups.
- * Admins get additional features to run this activity. These features let shuffle the groups around the available voice channels.
- * @extends Activity
- */
-class CoffeeChats extends Activity {
-
-    /**
-     * Basic constructor for a coffee chats.
-     * @param {Activity.ActivityInfo} activityInfo 
-     * @param {Number} numOfTeams
-     */
-    constructor(activityInfo, numOfTeams) {
-        super(activityInfo);
-
-        /**
-         * A collection of the groups that will attend this coffee chat.
-         * @type {Collection<Number, GuildMember[]>} - <group number, group members as array>
-         */
-        this.teams = new Collection();
-
-        /**
-         * The number of groups available in this coffee chat
-         * @type {Number}
-         */
-        this.numOfTeams = numOfTeams || 0;
-
-        /**
-         * The channel where users join the activity.
-         * @type {TextChannel}
-         */
-        this.joinActivityChannel;
-
-        /**
-         * The main voice channel where everyone starts.
-         * @type {VoiceChannel}
-         */
-        this.mainVoiceChannel;
-
-        winston.loggers.get(this.guild.id).event(`The activity ${this.name} was created as a coffee chats.`, {event: 'Activity'});
-    }
-
-    /**
-     * Initializes the activity by creating the necessary channels.
-     * @returns {Promise<CoffeeChats>}
-     * @param {TextChannel} channel
-     * @param {String} userId
-     * @override
-     */
-    async init(channel, userId) {
-        await super.init();
-
-        /** @type {VoiceChannel} */
-        this.mainVoiceChannel = await ListPrompt.singleListChooser({
-            prompt: 'What channel will the teams join first before being shuffled?',
-            channel: channel,
-            userId: userId
-        }, this.room.channels.voiceChannels.array());
-        this.room.channels.safeChannels.set(this.mainVoiceChannel.id, this.mainVoiceChannel);
-
-        for (var i = 0; i < this.numOfTeams; i++) {
-            this.room.addRoomChannel({name: `voice-${i}`, info: {type: 'voice'}});
-        }
-
-        this.joinActivityChannel = await this.room.addRoomChannel({
-            name: '☕' + 'join-activity', 
-            info: {
-                type: 'text',
-                topic: 'This channel is only intended to add your team to the activity list! Please do not use it for anything else!',
-            }, 
-            isSafe: true
-        });
-
-        this.sendJoinActivityConsole();
-
-        return this;
-    }
-
-
-    /**
-     * @override
-     */
-    addDefaultFeatures() {
-        /** @type {Console.Feature[]} */
-        let localFeatures = [
-            {
-                name: 'Team Shuffle',
-                description: 'Shuffle all the teams from the main voice channel to the other channels.',
-                emojiName: '👨‍👩‍👧‍👦',
-                callback: (user, reaction, stopInteracting, console) => {
-                    this.groupShuffle();
-                    sendMsgToChannel(console.channel, user.id, 'The teams have been shuffled!');
-                    stopInteracting();
-                },
-            },
-            {
-                name: 'Reset Teams',
-                description: 'Remove all the signed up teams.',
-                emojiName: '🗜️',
-                callback: (user, reaction, stopInteracting, console) => {
-                    this.resetTeams();
-                    sendMsgToChannel(console.channel, user.id, 'The teams have been deleted so new teams can join!');
-                    stopInteracting();
-                },
-            },
-            {
-                name: 'Add Team Slot',
-                description: 'Adds a team slot and a voice channel for them.',
-                emojiName: '☝️',
-                callback: (user, reaction, stopInteracting, console) => {
-                    this.addTeamSlot();
-                    sendMsgToChannel(console.channel, user.id, 'A new team slot has been added!');
-                    stopInteracting();
-                },
-            }
-        ];
-
-        localFeatures.forEach(feature => this.adminConsole.addFeature(feature));
-
-        super.addDefaultFeatures();
-    }
-
-
-    /**
-     * Will send the console for users to join the activity as a group.
-     * @private
-     * @async
-     */
-    async sendJoinActivityConsole() {
-        // reaction to use
-        var emoji = '⛷️';
-
-        let joinActivityConsole = new Console({
-            title: `${this.name}'s Join Console!`,
-            description: 'To join this activity read below! This activity is first come, first serve so get in quick!',
-            channel: this.joinActivityChannel,
-            guild: this.guild,
-        });
-
-        joinActivityConsole.addFeature({
-            name: 'Join the activity!',
-            description: `React to this message with ${emoji} and follow my instructions!`,
-            emojiName: emoji,
-            callback: async (user, reaction, stopInteracting, console) => {
-                // check to make sure there are spots left
-                if (this.teams.size > this.numOfTeams) {
-                    sendMsgToChannel(this.joinActivityChannel, user.id, 'Sorry, but the activity is full! Check back again later for a new cycle!', 10);
-                    return;
-                }
-                let members;
-                try {
-                    members = await MemberPrompt.multi({prompt: 'Who are you team members? Let me know in ONE message! Type cancel if you are joining solo.', channel: this.joinActivityChannel, userId: user.id});
-                } catch (error) {   
-                    members = new Collection();
-                }
-
-                // add team captain to members list
-                members.set(user.id, this.guild.member(user));
-
-                // add the team to the team list
-                this.teams.set(this.teams.size, members.array());
-
-                this.joinActivityChannel.send('<@' + user.id + '> Your team has been added to the activity! Make sure you follow the instructions in the main channel.').then(msg => {
-                    msg.delete({ timeout: 5000 });
-                });
-
-                stopInteracting();
-            }
-        });
-
-        joinActivityConsole.sendConsole();
-    }
-
-    /**
-     * FEATURES FROM THIS POINT DOWN.
-     */
-
-
-    /**
-     * Shuffle users from general voice to all other voice channel. Groups will stay on the same voice channel.
-     */
-    groupShuffle() {
-        let channels = this.room.channels.voiceChannels;
-        let voiceChannels = channels.filter(voiceChannel => voiceChannel.id != this.mainVoiceChannel.id).array();
-
-        // loop over the groups and channels at the same time using an index, add users for each group in a single voice channel
-        for (var index = 0; index < this.teams.size; index++) {
-            this.teams.get(index).forEach(member => {
-                try {
-                    if (member.voice.channel)
-                        member.voice.setChannel(voiceChannels[index % voiceChannels.length]);
-                } catch (error) {
-                    // do nothing, sad!
-                    winston.loggers.get(this.guild.id).warning(`For activity named ${this.name} I could not pull in user ${member.id} into the voice channel ${voiceChannels[index].name}.`, { event: 'Coffee Chats' });
-                }
-            });
-        }
-
-        winston.loggers.get(this.guild.id).event(`Activity named ${this.name} had its groups shuffled.`, { event: 'Coffee Chats' });
-    }
-
-
-    /**
-     * Resets the teams to have no teams.
-     */
-    resetTeams() {
-        this.teams = new Collection();
-    }
-    
-
-    /**
-     * Add a team slot to the activity and adds a voice channel for them.
-     */
-    addTeamSlot() {
-        this.numOfTeams += 1;
-        this.room.addRoomChannel({name: `voice-${this.numOfTeams - 1}`, info: { type: 'voice' }}); // -1 because we start from 0
-    }
-
-}
-
-module.exports = CoffeeChats;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_activities_workshop.js.html b/docs/classes_activities_workshop.js.html deleted file mode 100644 index 287aea2d..00000000 --- a/docs/classes_activities_workshop.js.html +++ /dev/null @@ -1,637 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/activities/workshop.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/activities/workshop.js

-
- - - - - -
-
-
const { Role, Collection, TextChannel, VoiceChannel, GuildCreateChannelOptions, MessageEmbed, Message } = require('discord.js');
-const winston = require('winston');
-const { randomColor, sendMessageToMember, sendMsgToChannel } = require('../../discord-services');
-const Console = require('../consoles/console');
-const Room = require('../room');
-const TicketManager = require('../tickets/ticket-manager');
-const Activity = require('./activity');
-const { StringPrompt, SpecialPrompt, ListPrompt } = require('advanced-discord.js-prompts');
-
-
-/**
- * @typedef PollInfo
- * @property {String} type
- * @property {String} title
- * @property {String} question
- * @property {String} emojiName - must be unicode emoji!
- * @property {Collection<String, String>} responses - <Emoji String, Description>
- */
-
-/**
- * A workshop is an activity with a TA system to help users with questions.
- * The TA system has two options, regular or advanced. Regular option involves TAs reaching out via DMs to users while advanced option 
- * involves users joining a voice channel to receive help. The advanced option is only recommended with knowledgeable discord users.
- * It also has polls the TAs can send to learn basic knowledge from the audience.
- * @extends Activity
- */
-class Workshop extends Activity {
-
-    /**
-     * 
-     * @constructor
-     * @param {Activity.ActivityInfo} 
-     * @param {Boolean} [isLowTechSolution=true]
-     * @param {Collection<String, Role>} [TARoles] - roles with TA permissions
-     */
-    constructor({activityName, guild, roleParticipants, botGuild}, isLowTechSolution = true, TARoles) {
-        super({activityName, guild, roleParticipants, botGuild});
-
-        /**
-         * @type {Collection<String, Role>} - roles with TA permissions
-         */
-        this.TARoles = TARoles || new Collection();
-
-        /**
-         * True if the assistance protocol is low tech.
-         * @type {Boolean}
-         */
-        this.isLowTechSolution = isLowTechSolution;
-
-        /**
-         * The channel where hackers can ask questions.
-         * @type {TextChannel}
-         */
-        this.assistanceChannel;
-
-        /**
-         * The channels only available to TAs
-         * @type {Collection<String, TextChannel | VoiceChannel>} - <Channel Name, channel>
-         */
-        this.TAChannels = new Collection();
-
-        /**
-         * TA Console where assistance calls are sent.
-         * @type {TextChannel}
-         */
-        this.TAConsole;
-
-        /**
-         * The message where we show the wait list live.
-         * @type {Message}
-         */
-        this.waitListEmbedMsg;
-
-        /**
-         * wait list Collection
-         * @type {Collection<String, String>} - <User Id, Username>
-         */
-        this.waitlist = new Collection();
-
-        /**
-         * The polls available.
-         * @type {Collection<String, PollInfo>} - <Poll type, PollInfo>
-         */
-        this.polls = new Collection;
-
-        /**
-         * The ticket manager.
-         * @type {TicketManager}
-         */
-        this.ticketManager;
-    }
-
-
-    /**
-     * Initializes the workshop and adds the ta console, ta banter and assistance channel.
-     * @override
-     */
-    async init() {
-        await super.init();
-
-        this.TAConsole = await this.addTAChannel('_🧑🏽‍🏫ta-console', {
-            type: 'text',
-            topic: 'The TA console, here TAs can chat, communicate with the workshop lead, look at the wait list, and send polls!',
-        }, [], true);
-
-        this.addTAChannel('_ta-banter', {
-            topic: 'For TAs to talk without cluttering the console.',
-        });
-
-        this.assistanceChannel = await this.room.addRoomChannel({
-            name: '🙋🏽assistance', 
-            info: {
-                type: 'text',
-                topic: 'For hackers to request help from TAs for this workshop, please don\'t send any other messages!'
-            },
-            isSafe: true,
-        });
-
-        this.botGuild.blackList.set(this.assistanceChannel.id, 3000);
-        this.botGuild.save();
-
-        if (this.isLowTechSolution) {
-            this.ticketManager = new TicketManager(this, {
-                ticketCreatorInfo: {
-                    channel: this.assistanceChannel,
-                },
-                ticketDispatcherInfo: {
-                    channel: await this.room.addRoomChannel({
-                        name: '_Incoming Tickets',
-                        isSafe: true,
-                    }),
-                    takeTicketEmoji: '👍',
-                    joinTicketEmoji: '☝️',
-                    reminderInfo: {
-                        isEnabled: true,
-                        time: 5
-                    },
-                    mainHelperInfo: {
-                        role: this.TARoles.first(),
-                        emoji: '✋',
-                    },
-                    embedCreator: (ticket) => new MessageEmbed()
-                        .setTitle(`New Ticket - ${ticket.id}`)
-                        .setDescription(`<@${ticket.group.first().id}> has a question: ${ticket.question}`)
-                        .setTimestamp(),
-                },
-                systemWideTicketInfo: {
-                    garbageCollectorInfo: {
-                        isEnabled: false,
-                    },
-                    isAdvancedMode: false,
-                }
-            }, this.guild, this.botGuild);
-        }
-
-        winston.loggers.get(this.guild.id).event(`The activity ${this.name} was transformed to a workshop.`, {event: 'Activity'});
-
-        return this;
-    }
-
-
-    /**
-     * Adds extra workshop features, plus the regular features. Also adds default polls.
-     * @override
-     */
-    addDefaultFeatures() {
-        this.addDefaultPolls();
-
-        /** @type {Console.Feature[]} */
-        let localFeatures = [];
-
-        this.polls.forEach((pollInfo) => localFeatures.push({
-            name: pollInfo.title,
-            description: `Asks the question: ${pollInfo.title} - ${pollInfo.question}`,
-            emojiName: pollInfo.emojiName,
-            callback: (user, reaction, stopInteracting, console) => this.sendPoll(pollInfo.type).then(() => stopInteracting()),
-        }));
-
-        localFeatures.forEach(feature => this.adminConsole.addFeature(feature));
-
-        super.addDefaultFeatures();
-    }
-
-
-    /**
-     * Adds the default polls to the polls list.
-     * @protected
-     */
-    addDefaultPolls() {
-        /** @type {PollInfo[]} */
-        let localPolls = [
-            {
-                title: 'Speed Poll!',
-                type: 'Speed Poll',
-                emojiName: '🏎️',
-                question: 'Please react to this poll!',
-                responses: new Collection([['🐢', 'Too Slow?'], ['🐶', 'Just Right?'], ['🐇', 'Too Fast?']]),
-            },
-            {
-                title: 'Difficulty Poll!',
-                type: 'Difficulty Poll',
-                emojiName: '✍️',
-                question: 'Please react to this poll! If you need help, go to the assistance channel!',
-                responses: new Collection([['🐢', 'Too Hard?'], ['🐶', 'Just Right?'], ['🐇', 'Too Easy?']]),
-            },
-            {
-                title: 'Explanation Poll!',
-                type: 'Explanation Poll',
-                emojiName: '🧑‍🏫',
-                question: 'Please react to this poll!',
-                responses: new Collection([['🐢', 'Hard to understand?'], ['🐶', 'Meh explanations?'], ['🐇', 'Easy to understand?']]),
-            }
-        ];
-
-        localPolls.forEach(pollInfo => this.polls.set(pollInfo.type, pollInfo));
-    }
-    
-
-    /**
-     * Will send all the consoles the workshop needs to work.
-     * @async
-     */
-    async sendConsoles() {
-        let mentorColor = randomColor();
-
-        const TAInfoEmbed = new MessageEmbed()
-            .setTitle('TA Information')
-            .setDescription('Please read this before the workshop starts!')
-            .setColor(mentorColor);
-        this.isLowTechSolution ? TAInfoEmbed.addField('Ticketing System is turned on!', `* Tickets will be sent to <#${this.ticketManager.ticketDispatcherInfo.channel.id}>
-            \n* React to the ticket message and send the user a DM by clicking on their name`) :
-            TAInfoEmbed.addField('Advanced Voice Channel System is turned on!', `* Users who need help will be listed in a message on channel <#${this.TAConsole}>
-                \n* Users must be on the general voice channel to receive assistance
-                \n* You must be on a private voice channel to give assistance
-                \n* When you react to the message, the user will be moved to your voice channel so you can give assistance
-                \n* Once you are done, move the user back to the general voice channel`);
-        this.TAConsole.send(TAInfoEmbed);
-
-        // Console for TAs to send polls and stamp distribution
-        let TAPollingConsole = new Console({
-            title: 'Polling and Stamp Console',
-            description: 'Here are some common polls you might want to use!',
-            channel: this.TAConsole,
-            guild: this.guild,
-        });
-        this.polls.forEach((pollInfo) => TAPollingConsole.addFeature({
-            name: pollInfo.title,
-            description: `Asks the question: ${pollInfo.title} - ${pollInfo.question}`,
-            emojiName: pollInfo.emojiName,
-            callback: (user, reaction, stopInteracting, console) => this.sendPoll(pollInfo.type).then(() => stopInteracting()),
-        }));
-        TAPollingConsole.addFeature({
-            name: 'Stamp Distribution',
-            description: 'Activate a stamp distribution on the activity\'s text channel',
-            emojiName: '📇',
-            callback: (user, reaction, stopInteracting, console) => {
-                this.distributeStamp(this.room.channels.generalText);
-                stopInteracting();
-            }
-        });
-        TAPollingConsole.sendConsole();
-
-        if (this.isLowTechSolution) {
-            await this.ticketManager.sendTicketCreatorConsole('Get some help from the Workshop TAs!', 
-                'React to this message with the emoji and write a quick description of your question. A TA will reach out via DM soon.');
-            this.ticketManager.ticketCreatorInfo.console.addField('Simple or Theoretical Questions', 'If you have simple or theory questions, ask them in the main banter channel!');
-        } else {
-            // embed message for TA console
-            const incomingTicketsEmbed = new MessageEmbed()
-                .setColor(mentorColor)
-                .setTitle('Hackers in need of help waitlist')
-                .setDescription('* Make sure you are on a private voice channel not the general voice channel \n* To get the next hacker that needs help click 🤝');
-            this.TAConsole.send(incomingTicketsEmbed).then(message => this.incomingTicketsHandler(message));
-
-            // where users can request assistance
-            const outgoingTicketEmbed = new MessageEmbed()
-                .setColor(this.botGuild.colors.embedColor)
-                .setTitle(this.name + ' Help Desk')
-                .setDescription('Welcome to the ' + this.name + ' help desk. There are two ways to get help explained below:')
-                .addField('Simple or Theoretical Questions', 'If you have simple or theory questions, ask them in the main banter channel!')
-                .addField('Advanced Question or Code Assistance', 'If you have a more advanced question, or need code assistance, click the 🧑🏽‍🏫 emoji for live TA assistance! Join the ' +  this.room.channels.generalVoice.name || Room.voiceChannelName + ' voice channel if not already there!');
-            this.assistanceChannel.send(outgoingTicketEmbed).then(message => this.outgoingTicketHandler(message));
-        }
-    }
-
-
-    /**
-     * Adds a channel to the activity, ask if it will be for TAs or not.
-     * @param {TextChannel} channel - channel to prompt user
-     * @param {String} userId - user to prompt for channel info
-     * @override
-     */
-    async addChannel(channel, userId) {
-        // ask if it will be for TA
-        let isTa = await SpecialPrompt.boolean({ prompt: 'Is this channel for TAs?', channel, userId });
-
-        if (isTa) {
-            /** @type {TextChannel} */
-            let newChannel = await super.addChannel(channel, userId);
-            this.getTAChannelPermissions().forEach(rolePermission => newChannel.updateOverwrite(rolePermission.id, rolePermission.permissions));
-            this.TAChannels.set(newChannel.name, newChannel);
-        } else {
-            super.addChannel(channel, userId);
-        }
-    }
-
-
-    /**
-     * Creates a channel only available to TAs.
-     * @param {String} name 
-     * @param {GuildCreateChannelOptions} info
-     * @returns {Promise<TextChannel | VoiceChannel>}
-     * @async 
-     */
-    async addTAChannel(name, info) {
-        let channel = await this.room.addRoomChannel({name, info, permissions: this.getTAChannelPermissions()});
-        this.TAChannels.set(channel.name, channel);
-        return channel;
-    }
-
-
-    /**
-     * Returns the perms for a TA Channel
-     * @protected
-     * @returns {Activity.RolePermission[]}
-     */
-    getTAChannelPermissions() {
-        /** The permissions for the TA channels */
-        let TAChannelPermissions = [
-            { id: this.botGuild.roleIDs.everyoneRole, permissions: { VIEW_CHANNEL: false } },
-        ];
-
-        // add regular activity members to the TA perms list as non tas, so they cant see that channel
-        this.room.rolesAllowed.forEach(role => {
-            TAChannelPermissions.push({id: role.id, permissions: {VIEW_CHANNEL: false}});
-
-        });
-
-        // Loop over ta roles, give them voice channel perms and add them to the TA permissions list
-        this.TARoles.forEach(role => {
-            TAChannelPermissions.push({id: role.id, permissions: {VIEW_CHANNEL: true}});
-        });
-
-        return TAChannelPermissions;
-    }
-
-
-    /**
-     * FEATURES:
-     */
-
-
-    /**
-     * Send a poll to the general text channel
-     * @param {String} type - the type of poll to send
-     * @async
-     */
-    async sendPoll(type, channel, userId) {
-        let poll = this.polls.get(type);
-        if (!poll) throw new Error('No poll was found of that type!');
-        
-        // create poll
-        let description = poll.question + '\n\n';
-        for (const key of poll.responses.keys()) {
-            description += '**' + poll.responses.get(key) + '->** ' + key + '\n\n';
-        }
-
-        let qEmbed = new MessageEmbed()
-            .setColor(this.botGuild.colors.embedColor)
-            .setTitle(poll.title)
-            .setDescription(description);
-
-        // send poll to general text or prompt for channel
-        let pollChannel;
-        if ((await this.room.channels.generalText.fetch(true))) pollChannel = this.room.channels.generalText;
-        else pollChannel = ListPrompt.singleListChooser({
-            prompt: 'What channel should the poll go to?',
-            channel: channel,
-            userId: userId
-        }, this.room.channels.textChannels.array());
-
-        pollChannel.send(qEmbed).then(msg => {
-            poll.responses.forEach((value, key) => msg.react(key));
-        });
-
-        winston.loggers.get(this.guild.id).event(`Activity named ${this.name} sent a poll with title: ${poll.title} and question ${poll.question}.`, { event: 'Workshop' });
-    }
-
-    /**
-     * Creates and handles with the emoji reactions on the incoming ticket console embed
-     * @param {Message} message 
-     */
-    incomingTicketsHandler(message) {
-        message.pin();
-        message.react('🤝');
-
-        this.waitListEmbedMsg = message;
-
-        // add reaction to get next in this message!
-        const getNextCollector = message.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === '🤝');
-
-        getNextCollector.on('collect', async (reaction, user) => {
-            // remove the reaction
-            reaction.users.remove(user.id);
-
-            // check that there is someone to help
-            if (this.waitlist.size === 0) {
-                this.TAConsole.send('<@' + user.id + '> No one to help right now!').then(msg => msg.delete({ timeout: 5000 }));
-                return;
-            }
-
-            // if pullInFunctionality is turned off then then just remove from list
-            if (this.isLowTechSolution) {
-                // remove hacker from wait list
-                let hackerKey = this.waitlist.firstKey();
-                this.waitlist.delete(hackerKey);
-
-            } else {
-                // grab the ta and their voice channel
-                var ta = message.guild.member(user.id);
-                var taVoice = ta.voice.channel;
-
-                // check that the ta is in a voice channel
-                if (taVoice === null || taVoice === undefined) {
-                    this.TAConsole.send('<@' + user.id + '> Please join a voice channel to assist hackers.').then(msg => msg.delete({ timeout: 5000 }));
-                    return;
-                }
-
-                // get next user
-                let hackerKey = this.waitlist.firstKey();
-                this.waitlist.delete(hackerKey);
-                var hacker = message.guild.member(hackerKey);
-
-                // if status mentor in use there are no hackers in list
-                if (hacker === undefined) {
-                    this.TAConsole.send('<@' + user.id + '> There are no hackers in need of help!').then(msg => msg.delete({ timeout: 5000 }));
-                    return;
-                }
-
-                try {
-                    await hacker.voice.setChannel(taVoice);
-                    sendMessageToMember(hacker, 'TA is ready to help you! You are with them now!', true);
-                    this.TAConsole.send('<@' + user.id + '> A hacker was moved to your voice channel! Thanks for your help!!!').then(msg => msg.delete({ timeout: 5000 }));
-                } catch (err) {
-                    sendMessageToMember(hacker, 'A TA was ready to talk to you, but we were not able to pull you to their voice ' +
-                        'voice channel. Try again and make sure you are in the general voice channel!');
-                    this.TAConsole.send('<@' + user.id + '> We had someone that needed help, but we were unable to move them to your voice channel. ' +
-                        'They have been notified and skipped. Please help someone else!').then(msg => msg.delete({ timeout: 8000 }));
-                }
-            }
-
-            // remove hacker from the embed list
-            this.waitListEmbedMsg.edit(this.waitListEmbedMsg.embeds[0].spliceFields(0, 1));
-        });
-    }
-
-    /**
-     * Creates and handles with the emoji reactions on the outgoing ticket console embed
-     * @param {Message} message 
-     */
-    outgoingTicketHandler(message) {
-        message.pin();
-        message.react('🧑🏽‍🏫');
-
-        // filter collector and event handler for help emoji from hackers
-        const helpCollector = message.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === '🧑🏽‍🏫');
-
-        helpCollector.on('collect', async (reaction, user) => {
-            // remove the emoji
-            reaction.users.remove(user.id);
-
-            // check that the user is not already on the wait list
-            if (this.waitlist.has(user.id)) {
-                sendMessageToMember(user, 'You are already on the TA wait list! A TA will get to you soon!', true);
-                return;
-            } else {
-                var position = this.waitlist.size;
-                // add user to wait list
-                this.waitlist.set(user.id, user.username);
-            }
-
-            let oneLiner = await StringPrompt.single({prompt: 'Please send to this channel a one-liner of your problem or question. You have 20 seconds to respond', channel: this.assistanceChannel, userId: user.id });
-
-            const hackerEmbed = new MessageEmbed()
-                .setColor(this.botGuild.colors.embedColor)
-                .setTitle('Hey there! We got you signed up to talk to a TA!')
-                .setDescription('You are number: ' + position + ' in the wait list.')
-                .addField(!this.isLowTechSolution ? 'JOIN THE VOICE CHANNEL!' : 'KEEP AN EYE ON YOUR DMs', 
-                    !this.isLowTechSolution ? 'Sit tight in the voice channel. If you are not in the voice channel when its your turn you will be skipped, and we do not want that to happen!' :
-                        'A TA will reach out to you soon via DM! Have your question ready and try to keep up with the workshop until then!');
-
-            sendMessageToMember(user, hackerEmbed);
-
-            // update message embed with new user in list
-            this.waitListEmbedMsg.edit(this.waitListEmbedMsg.embeds[0].addField(user.username, '<@' + user.id + '> has the question: ' +  oneLiner));
-            
-            // send a quick message to let ta know a new user is on the wait list
-            this.TAConsole.send('A new hacker needs help!').then(msg => msg.delete({timeout: 3000}));
-        });
-    }
-}
-
-module.exports = Workshop;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_bot-guild.js.html b/docs/classes_bot-guild.js.html deleted file mode 100644 index 81b8fcc5..00000000 --- a/docs/classes_bot-guild.js.html +++ /dev/null @@ -1,635 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/bot-guild.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/bot-guild.js

-
- - - - - -
-
-
const { Collection, TextChannel, Role, MessageEmbed, CategoryChannel, Guild } = require('discord.js');
-const { CommandoClient, CommandoGuild } = require('discord.js-commando');
-const winston = require('winston');
-const discordServices = require('../discord-services');
-
-/**
- * @class
- */
-class BotGuild {
-
-    
-    /**
-     * Staff role permissions.
-     * @type {String[]}
-     */
-    static staffPermissions = ['VIEW_CHANNEL', 'MANAGE_EMOJIS', 'CHANGE_NICKNAME', 'MANAGE_NICKNAMES', 
-    'KICK_MEMBERS', 'BAN_MEMBERS', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES', 'ADD_REACTIONS', 'USE_EXTERNAL_EMOJIS', 'MANAGE_MESSAGES', 
-    'READ_MESSAGE_HISTORY', 'CONNECT', 'STREAM', 'SPEAK', 'PRIORITY_SPEAKER', 'USE_VAD', 'MUTE_MEMBERS', 'DEAFEN_MEMBERS', 'MOVE_MEMBERS'];
-
-    /**
-     * Admin role permissions.
-     * @type {String[]}
-     */
-    static adminPermissions = ['ADMINISTRATOR'];
-
-    /**
-     * The regular member perms.
-     * @type {String[]}
-     */
-    static memberPermissions = ['VIEW_CHANNEL', 'CHANGE_NICKNAME', 'SEND_MESSAGES', 'ADD_REACTIONS', 'READ_MESSAGE_HISTORY',
-    'CONNECT', 'SPEAK', 'STREAM', 'USE_VAD'];
-
-    /**
-     * @typedef RoleIDs
-     * @property {String} memberRole - regular guild member role ID
-     * @property {String} staffRole - the staff role ID
-     * @property {String} adminRole - the admin role ID
-     * @property {String} everyoneRole - the everyone role ID
-     */
-
-     /**
-      * @typedef ChannelIDs
-      * @property {String} adminConsole - the admin console channel ID
-      * @property {String} adminLog - the admin log channel ID
-      * @property {String} botSupportChannel - the bot support channel ID
-      */
-
-    /**
-     * @typedef VerificationInfo
-     * @property {Boolean} isEnabled - true if verification is enabled
-     * @property {String} isVerifiedRoleID - the verified role ID that holds basic permissions
-     * @property {String[]} isVerifiedRolePermissions - the permissions for the isVerified role
-     * @property {String} guestRoleID - the guest role ID used for verification
-     * @property {String} welcomeChannelID -  the welcome channel where users learn to verify
-     * @property {String} welcomeSupportChannelID - the support channel where the bot can contact users
-     */
-
-    /**
-     * @typedef AttendanceInfo
-     * @property {Boolean} isEnabled - true if attendance is enabled in this guild
-     * @property {String} attendeeRoleID - the attendee role ID used for attendance
-     */
-
-    /**
-     * @typedef StampInfo
-     * @property {Boolean} isEnabled - true if stamps are enabled
-     * @property {Collection<Number, String>} stampRoleIDs - <StampNumber, roleID>
-     * @property {Number} stampCollectionTime - time given to users to collect password stamps
-     */
-
-    /**
-     * @typedef ReportInfo
-     * @property {Boolean} isEnabled - true if the report functionality is enabled
-     * @property {String} incomingReportChannelID - channel where reports are sent
-     */
-
-    /**
-     * @typedef AnnouncementInfo
-     * @property {Boolean} isEnabled
-     * @property {String} announcementChannelID
-     */
-
-    /**
-     * @typedef BotGuildInfo
-     * @property {RoleIDs} roleIDs
-     * @property {ChannelIDs} channelIDs
-     */
-
-    /**
-     * Validate the information.
-     * @param {BotGuildInfo} botGuildInfo - the information to validate
-     * @throws Error if the botGuildInfo is incomplete
-     */
-    validateBotGuildInfo(botGuildInfo) {
-        if (typeof botGuildInfo != 'object') throw new Error('The bot guild information is required!');
-        if (!botGuildInfo?.roleIDs || !botGuildInfo?.roleIDs?.adminRole || !botGuildInfo?.roleIDs?.everyoneRole
-            || !botGuildInfo?.roleIDs?.memberRole || !botGuildInfo?.roleIDs?.staffRole) throw new Error('All the role IDs are required!');
-        if (!botGuildInfo?.channelIDs || !botGuildInfo?.channelIDs?.adminConsole || !botGuildInfo?.channelIDs?.adminLog
-            || !botGuildInfo?.channelIDs?.botSupportChannel) throw new Error('All the channel IDs are required!');
-    }
-
-    /**
-     * Will set the minimum required information for the bot to work on this guild.
-     * @param {BotGuildInfo} botGuildInfo 
-     * @param {CommandoClient} client
-     * @returns {Promise<BotGuild>}
-     * @async
-     */
-    async readyUp(client, botGuildInfo) {
-        this.validateBotGuildInfo(botGuildInfo);
-
-        this.roleIDs = botGuildInfo.roleIDs;
-        this.channelIDs = botGuildInfo.channelIDs;
-
-        let guild = await client.guilds.fetch(this._id);
-
-        let adminRole = await guild.roles.fetch(this.roleIDs.adminRole);
-        // try giving the admins administrator perms
-        try {
-            if (!adminRole.permissions.has('ADMINISTRATOR')) 
-            {
-                adminRole.setPermissions(adminRole.permissions.add(['ADMINISTRATOR']));
-                await adminRole.setMentionable(true);
-            }
-        } catch {
-            discordServices.discordLog(guild, 'Was not able to give administrator privileges to the role <@&' + adminRole.id + '>. Please help me!')
-        }
-
-        // staff role set up
-        let staffRole = await guild.roles.fetch(this.roleIDs.staffRole);
-        staffRole.setMentionable(true);
-        staffRole.setHoist(true);
-        staffRole.setPermissions(staffRole.permissions.add(BotGuild.staffPermissions));
-
-        // regular member role setup
-        let memberRole = await guild.roles.fetch(this.roleIDs.memberRole);
-        memberRole.setMentionable(false);
-        memberRole.setPermissions(memberRole.permissions.add(BotGuild.memberPermissions));
-
-        // change the everyone role permissions
-        guild.roles.everyone.setPermissions(0); // no permissions for anything like the guest role
-
-        // make sure admin channels are only for admins
-        let adminCategory = guild.channels.resolve(this.channelIDs.adminConsole).parent
-        adminCategory.overwritePermissions([
-            {
-                id: adminRole.id,
-                allow: 'VIEW_CHANNEL'
-            },
-            {
-                id: this.roleIDs.everyoneRole,
-                deny: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'CONNECT']
-            }
-        ]);
-        adminCategory.children.forEach(channel => channel.lockPermissions());
-
-        // create the archive category
-        this.channelIDs.archiveCategory = (await this.createArchiveCategory(guild)).id;
-
-        this.isSetUpComplete = true;
-
-        winston.loggers.get(this._id).event(`The botGuild has run the ready up function.`, {event: "Bot Guild"});
-
-        return this;
-    }
-
-    /**
-     * Creates the archive category.
-     * @returns {Promise<CategoryChannel>}
-     * @param {Guild} guild
-     * @private
-     * @async
-     */
-    async createArchiveCategory(guild) {
-        let overwrites = [
-            {
-                id: this.roleIDs.everyoneRole,
-                deny: ['VIEW_CHANNEL']
-            },
-            {
-                id: this.roleIDs.memberRole,
-                allow: ['VIEW_CHANNEL'],
-            },
-            {
-                id: this.roleIDs.staffRole,
-                allow: ['VIEW_CHANNEL'],
-            }
-        ];
-
-        // position is used to create archive at the very bottom!
-        var position = (guild.channels.cache.filter(channel => channel.type === 'category')).size;
-        return await guild.channels.create('💼archive', {
-            type: 'category', 
-            position: position + 1,
-            permissionOverwrites: overwrites,
-        });
-    }
-
-    /**
-     * Will create the admin channels with the correct roles.
-     * @param {Guild} guild 
-     * @param {Role} adminRole 
-     * @param {Role} everyoneRole 
-     * @returns {Promise<{TextChannel, TextChannel}>} - {Admin Console, Admin Log Channel}
-     * @static
-     * @async
-     */
-    static async createAdminChannels(guild, adminRole, everyoneRole) {
-        let adminCategory = await guild.channels.create('Admins', {
-            type: 'category',
-            permissionOverwrites: [
-                {
-                    id: adminRole.id,
-                    allow: 'VIEW_CHANNEL'
-                },
-                {
-                    id: everyoneRole.id,
-                    deny: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'CONNECT']
-                }
-            ]
-        });
-
-        let adminConsoleChannel = await guild.channels.create('console', {
-            type: 'text',
-            parent: adminCategory,
-        });
-
-        let adminLogChannel = await guild.channels.create('logs', {
-            type: 'text',
-            parent: adminCategory,
-        });
-
-        winston.loggers.get(guild.id).event(`The botGuild has run the create admin channels function.`, {event: "Bot Guild"});
-
-        return {adminConsoleChannel: adminConsoleChannel, adminLog: adminLogChannel};
-    }
-
-    /**
-     * @typedef VerificationChannels
-     * @property {String} welcomeChannelID
-     * @property {String} welcomeChannelSupportID
-     */
-
-    /**
-     * @typedef TypeInfo
-     * @property {String} type
-     * @property {String} roleId
-     */
-
-    /**
-     * Will set up the verification process.
-     * @param {CommandoClient} client 
-     * @param {String} guestRoleId
-     * @param {TypeInfo[]} types
-     * @param {VerificationChannels} [verificationChannels]
-     * @return {Promise<BotGuild>}
-     * @async
-     */
-    async setUpVerification(client, guestRoleId, types, verificationChannels = null) {
-        /** @type {CommandoGuild} */
-        let guild = await client.guilds.fetch(this._id);
-
-        try {
-            var guestRole = await guild.roles.fetch(guestRoleId);
-        } catch (error) {
-            throw new Error('The given guest role ID is not valid for this guild!');
-        }
-        guestRole.setMentionable(false);
-        guestRole.setPermissions(0);
-        this.verification.guestRoleID = guestRoleId;
-
-        if (verificationChannels) {
-            this.verification.welcomeChannelID = verificationChannels.welcomeChannelID;
-            this.verification.welcomeSupportChannelID = verificationChannels.welcomeChannelSupportID;
-
-            /** @type {TextChannel} */
-            var welcomeChannel = guild.channels.resolve(this.verification.welcomeChannelID);
-            await welcomeChannel.bulkDelete(100, true);
-        } else {
-            let welcomeCategory = await guild.channels.create('Welcome', {
-                type: 'category',
-                permissionOverwrites: [
-                    {
-                        id: this.roleIDs.everyoneRole,
-                        allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'READ_MESSAGE_HISTORY']
-                    },
-                    {
-                        id: this.roleIDs.memberRole,
-                        deny: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'READ_MESSAGE_HISTORY']
-                    },
-                ],
-            });
-    
-            var welcomeChannel = await guild.channels.create('welcome', {
-                type: 'text',
-                parent: welcomeCategory,
-            });
-    
-            // update welcome channel to not send messages
-            welcomeChannel.updateOverwrite(this.roleIDs.everyoneRole, {
-                SEND_MESSAGES: false,
-            });
-    
-            let welcomeChannelSupport = await guild.channels.create('welcome-support', {
-                type: 'text',
-                parent: welcomeCategory,
-            });
-
-            this.verification.welcomeChannelID = welcomeChannel.id;
-            this.verification.welcomeSupportChannelID = welcomeChannelSupport.id;
-        }
-
-        // add the types to the type map.
-        types.forEach((type, index, list) => {
-            this.verification.verificationRoles.set(type.type.toLowerCase(), type.roleId);
-        });
-
-        const embed = new MessageEmbed().setTitle('Welcome to the ' + guild.name + ' Discord server!')
-            .setDescription('In order to verify that you have registered for ' + guild.name + ', please respond to the bot (me) via DM!')
-            .addField('Do you need assistance?', 'Head over to the welcome-support channel and ping the admins!')
-            .setColor(this.colors.embedColor);
-        welcomeChannel.send(embed).then(msg => msg.pin());
-
-        
-        this.verification.isEnabled = true;
-        guild.setCommandEnabled('verify', true);
-
-        winston.loggers.get(this._id).event(`The botGuild has set up the verification system. Verification channels ${verificationChannels === null ? 'were created' : 'were given'}. 
-            Guest role id: ${guestRoleId}. The types used are: ${types.join()}`, {event: "Bot Guild"});
-        return this;
-    }
-
-    /**
-     * Sets up the attendance functionality.
-     * @param {CommandoClient} client 
-     * @param {String} attendeeRoleID
-     * @returns {Promise<BotGuild>}
-     * @async
-     */
-    async setUpAttendance(client, attendeeRoleID) {
-        this.attendance.attendeeRoleID = attendeeRoleID;
-        this.attendance.isEnabled = true;
-        /** @type {CommandoGuild} */
-        let guild = await client.guilds.fetch(this._id);
-        guild.setCommandEnabled('start-attend', true);
-        guild.setCommandEnabled('attend', true);
-
-        winston.loggers.get(this._id).event(`The botGuild has set up the attendance functionality. Attendee role id is ${attendeeRoleID}`, {event: "Bot Guild"});
-        return this;
-    }
-
-    /**
-     * Will set up the firebase announcements.
-     * @param {CommandoClient} client 
-     * @param {String} announcementChannelID
-     * @async
-     */
-    async setUpAnnouncements(client, announcementChannelID) {
-        /** @type {CommandoGuild} */
-        let guild = await client.guilds.fetch(this._id);
-        
-        let announcementChannel = guild.channels.resolve(announcementChannelID);
-        if (!announcementChannel) throw new Error('The announcement channel ID is not valid for this guild!');
-
-        this.announcement.isEnabled = true;
-        this.announcement.announcementChannelID = announcementChannelID;
-
-        winston.loggers.get(this._id).event(`The botGuild has set up the announcement functionality with the channel ID ${announcementChannelID}`, {event: "Bot Guild"});
-
-        // TODO Firebase setup 
-        // start query listener for announcements
-        // nwFirebase.firestore().collection('Hackathons').doc('nwHacks2021').collection('Announcements').onSnapshot(querySnapshot => {
-        //     // exit if we are at the initial state
-        //     if (isInitState) {
-        //         isInitState = false;
-        //         return;
-        //     }
-
-        //     querySnapshot.docChanges().forEach(change => {
-        //         if (change.type === 'added') {
-        //             const embed = new Discord.MessageEmbed()
-        //                 .setColor(botGuild.colors.announcementEmbedColor)
-        //                 .setTitle('Announcement')
-        //                 .setDescription(change.doc.data()['content']);
-
-        //             announcementChannel.send('<@&' + discordServices.roleIDs.attendeeRole + '>', { embed: embed });
-        //         }
-        //     });
-        // });
-    }
-
-    /**
-     * Creates the stamps roles and adds them to this BotGuild. If stamps roles are given 
-     * then no roles are created!
-     * @param {CommandoClient} client 
-     * @param {Number} [stampAmount] - amount of stamps to create
-     * @param {Number} [stampCollectionTime] - time given to users to send password to get stamp
-     * @param {String[]} [stampRoleIDs] - current stamp roles to use
-     * @returns {Promise<BotGuild>}
-     * @async
-     */
-    async setUpStamps(client, stampAmount = 0, stampCollectionTime = 60, stampRoleIDs = []) {
-        let guild = await client.guilds.fetch(this._id);
-
-        if (stampRoleIDs.length > 0) {
-            stampRoleIDs.forEach((ID, index, array) => {
-                this.addStamp(ID, index);
-            });
-            winston.loggers.get(this._id).event(`The botGuild has set up the stamp functionality. The stamp roles were given. Stamp collection time is set at ${stampCollectionTime}.` [{stampIds: stampRoleIDs}]);
-        } else {
-            for (let i = 0; i < stampAmount; i++) {
-                let role = await guild.roles.create({
-                    data: {
-                        name: 'Stamp Role #' + i,
-                        hoist: true,
-                        color: discordServices.randomColor(),
-                    }
-                });
-
-                this.addStamp(role.id, i);
-            }
-            winston.loggers.get(this._id).event(`The botGuild has set up the stamp functionality. Stamps were created by me, I created ${stampAmount} stamps. Stamp collection time is set at ${stampCollectionTime}.`, {event: "Bot Guild"});
-        }
-
-        this.stamps.stampCollectionTime = stampCollectionTime;
-        this.stamps.isEnabled = true;
-
-        this.setCommandStatus(client);
-
-        return this;
-    }
-
-    /**
-     * Adds a stamp to the stamp collection. Does not save the mongoose document!
-     * @param {String} roleId 
-     * @param {Number} stampNumber 
-     */
-    addStamp(roleId, stampNumber) {
-        if (stampNumber === 0) this.stamps.stamp0thRoleId = roleId;
-        this.stamps.stampRoleIDs.set(roleId, stampNumber);
-        winston.loggers.get(this._id).event(`The botGuild has added a stamp with number ${stampNumber} linked to role id ${roleId}`, {event: "Bot Guild"});
-    }
-
-    /**
-     * Enables the report commands and sends the reports to the given channel.
-     * @param {CommandoClient} client 
-     * @param {String} incomingReportChannelID 
-     * @returns {Promise<BotGuild>}
-     * @async
-     */
-    async setUpReport(client, incomingReportChannelID) {
-        /** @type {CommandoGuild} */
-        let guild = await client.guilds.fetch(this._id);
-
-        this.report.isEnabled = true;
-        this.report.incomingReportChannelID = incomingReportChannelID;
-
-        guild.setCommandEnabled('report', true);
-
-        winston.loggers.get(this._id).event(`The botGuild has set up the report functionality. It will send reports to the channel id ${incomingReportChannelID}`, {event: "Bot Guild"});
-        return this;
-    }
-
-    /**
-     * Will enable the ask command.
-     * @param {CommandoClient} client 
-     */
-    async setUpAsk(client) {
-        /** @type {CommandoGuild} */
-        let guild = await client.guilds.fetch(this._id);
-        this.ask.isEnabled = true;
-        guild.setCommandEnabled('ask', true);
-        winston.loggers.get(this._id).event(`The botGuild has enabled the ask command!`, {event: "Bot Guild"});
-    }
-
-    /**
-     * Will enable and disable the appropriate commands by looking at what is enabled in the botGuild.
-     * @param {CommandoClient} client
-     * @async 
-     */
-    async setCommandStatus(client) {
-        /** @type {CommandoGuild} */
-        let guild = await client.guilds.fetch(this._id);
-
-        guild.setGroupEnabled('verification', this.verification.isEnabled);
-        guild.setGroupEnabled('attendance', this.attendance.isEnabled);
-
-        guild.setGroupEnabled('stamps', this.stamps.isEnabled);
-
-        guild.setCommandEnabled('report', this.report.isEnabled);
-        guild.setCommandEnabled('ask', this.ask.isEnabled);
-        guild.setGroupEnabled('hacker_utility', this.ask.isEnabled || this.report.isEnabled);
-        
-        client.registry.groups.forEach((group, key, map) => {
-            if (group.id.startsWith('a_')) guild.setGroupEnabled(group, this.isSetUpComplete);
-        });
-
-        winston.loggers.get(guild.id).verbose(`Set the command status of guild ${guild.name} with id ${guild.id}`);
-    }
-}
-module.exports = BotGuild;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_consoles_console.js.html b/docs/classes_consoles_console.js.html deleted file mode 100644 index 7fb47696..00000000 --- a/docs/classes_consoles_console.js.html +++ /dev/null @@ -1,414 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/consoles/console.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/consoles/console.js

-
- - - - - -
-
-
const { Collection, Message, TextChannel, MessageEmbed, DMChannel, MessageReaction, User, ReactionCollectorOptions, ReactionCollector, Guild, GuildEmoji, ReactionEmoji } = require('discord.js');
-const { randomColor } = require('../../discord-services');
-const getEmoji = require('get-random-emoji');
-const Feature = require('./feature');
-
-/**
- * @typedef ConsoleInfo
- * @property {String} title - the console title
- * @property {String} description - the description of the console
- * @property {TextChannel | DMChannel} channel - the channel this console lives in
- * @property {Collection<String, Feature>} [features] - the collection of features mapped by emoji name
- * @property {Collection<String, String>} [fields] - a collection of fields
- * @property {String} [color] - console color in hex
- * @property {ReactionCollectorOptions} [options={}] collector options
- */
-
-/**
- * The console class represents a Discord UI console. A console is an embed with options users 
- * can interact with by reacting with emojis.
- * @class
- */
-class Console {
-
-    /**
-     * @constructor
-     * @param {ConsoleInfo} args
-     */
-    constructor({title, description, channel, features = new Collection(), fields = new Collection(), color = randomColor(), options = {}}) {
-
-        /**
-         * @type {String}
-         */
-        this.title = title;
-
-        /**
-         * @type {String}
-         */
-        this.description = description;
-
-        /**
-         * @type {Collection<String, Feature>} - <Emoji Name, Button Info>
-         */
-        this.features = new Collection();
-
-        /**
-         * The fields this console has, not including feature fields.
-         * <Field Name, Field Description>
-         * @type {Collection<String, String>}
-         */
-        this.fields = new Collection();
-
-        /**
-         * @type {String} - hex color
-         */
-        this.color = color;
-
-        /**
-         * The collector options.
-         * @type {ReactionCollectorOptions}
-         */
-        this.collectorOptions = options;
-        this.collectorOptions.dispose = true;
-
-        /**
-         * The message holding the console.
-         * @type {Message}
-         */
-        this.message;
-
-        /**
-         * Users the console is interacting with;
-         * @type {Collection<String, User>} - <User.id, User>
-         */
-        this.interacting = new Collection();
-
-        /**
-         * @type {ReactionCollector}
-         */
-        this.collector;
-
-        /**
-         * The channel this console lives in.
-         * @type {TextChannel | DMChannel}
-         */
-        this.channel = channel;
-
-        features.forEach(feature => this.addFeature(feature));
-        fields.forEach((name, description) => this.addField(name, description));
-    }
-
-    /**
-     * Sends the console to a channel
-     * @param {String} [messageText] - text to add to the message used to send the embed
-     * @async
-     */
-    async sendConsole(messageText = '') {
-        let embed = new MessageEmbed().setColor(this.color)
-            .setTimestamp()
-            .setTitle(this.title)
-            .setDescription(this.description);
-        
-        this.features.forEach(feature => embed.addField(feature.getFieldName(), feature.getFieldValue()));
-        this.fields.forEach((description, name) => embed.addField(name, description));
-
-        this.message = await this.channel.send(messageText ,embed);
-
-        this.features.forEach(feature => {
-            this.message.react(feature.emojiName).catch(reason => {
-                // the emoji is probably custom we need to find it!
-                let emoji = this.message.guild.emojis.cache.find(guildEmoji => guildEmoji.name === feature.emojiName);
-                this.message.react(emoji);
-            });
-        });
-
-        this.createReactionCollector(this.message);
-    }
-
-    /**
-     * Creates the reaction collector in the message.
-     * @param {Message} message
-     */
-    createReactionCollector(message) {
-        // make sure we don't have two collectors going!
-        if (this.collector) this.stopConsole();
-
-        this.collector = message.createReactionCollector((reaction, user) => !user.bot &&
-            this.features.has(reaction.emoji.id || reaction.emoji.name) &&
-            !this.interacting.has(user.id),
-        this.collectorOptions);
-
-        this.collector.on('collect', (reaction, user) => {
-            this.interacting.set(user.id, user);
-            let feature = this.features.get(reaction.emoji.id || reaction.emoji.name);
-            feature?.callback(user, reaction, () => this.stopInteracting(user), this);
-            if (this.channel.type != 'dm' && !feature?.removeCallback)
-                reaction.users.remove(user);
-        });
-
-        this.collector.on('remove', (reaction, user) => {
-            let feature = this.features.get(reaction.emoji.id || reaction.emoji.name);
-            if (feature && feature?.removeCallback) {
-                this.interacting.set(user.id, user);
-                feature?.removeCallback(user, reaction, () => this.stopInteracting(user), this);
-            }
-        });
-    }
-
-    /**
-     * Adds a feature to this console.
-     * @param {Feature} feature - the feature to add
-     * @async
-     */
-    async addFeature(feature) {
-        // if the channel is a DM channel, we can't use custom emojis, so if the emoji is a custom emoji, its an ID,
-        // we will grab a random emoji and use that instead
-        if (this.channel.type === 'dm' && !isNaN(parseInt(feature.emojiName))) {
-            feature.emojiName = getEmoji();
-        }
-
-        this.features.set(feature.emojiName, feature);
-
-        if (this.message) {
-            await this.message.edit(this.message.embeds[0].addField(feature.getFieldName(), feature.getFieldValue()));
-            this.message.react(feature.emojiName).catch(reason => {
-                // the emoji is probably custom we need to find it!
-                let emoji = this.message.guild.emojis.cache.find(guildEmoji => guildEmoji.name === feature.emojiName);
-                this.message.react(emoji);
-            });
-        }
-    }
-    
-    /**
-     * Removes a feature from this console. TODO remove from embed too!
-     * @param {String | Feature} identifier - feature name, feature emojiName or feature
-     */
-    removeFeature(identifier) {
-        if (typeof identifier === 'string') {
-            let isDone = this.features.delete(identifier);
-            if (!isDone) {
-                let feature = this.features.find(feature => feature.name === identifier);
-                this.features.delete(feature.emojiName);
-            }
-        } else if (typeof identifier === 'object') {
-            this.features.delete(identifier?.emojiName);
-        } else {
-            throw Error(`Was not given an identifier to work with when deleting a feature from this console ${this.title}`);
-        }
-    }
-
-    /**
-     * Adds a field to this console without adding a feature.
-     * @param {String} name - the new field name
-     * @param {String} value - the description on this field
-     * @param {Boolean} [inline] 
-     * @async
-     */
-    async addField(name, value, inline) {
-        this.fields.set(name, value);
-        if(this.message) await this.message.edit(this.message.embeds[0].addField(name, value, inline));
-    }
-
-    /**
-     * Changes the console's color.
-     * @param {String} color - the new color in hex
-     * @async
-     */
-    async changeColor(color) {
-        await this.message.edit(this.message.embeds[0].setColor(color));
-    }
-
-    /**
-     * Stop the console from interacting with any users.
-     */
-    stopConsole() {
-        this.collector?.stop();
-    }
-
-    /**
-     * Deletes this console from discord.
-     */
-    delete() {
-        this.stopConsole();
-        this.message?.delete();
-    }
-
-    /**
-     * Callback for users to call when the user interacting with the console is done.
-     * @param {User} user - the user that stopped interacting with this console.
-     * @private
-     */
-    stopInteracting(user) {
-        this.interacting.delete(user.id);
-    }
-    
-    /**
-     * Creates a JSON representation of this object
-     * @returns {JSON} representation of this object as JSON
-     */
-    toJSON() {
-        return {
-            'title': this.title,
-            'description': this.description,
-            'features': [...this.features],
-            'fields': [...this.fields],
-            'color': this.color,
-            'collectorOptions': JSON.stringify(this.collectorOptions),
-            'messageId': this.message?.id,
-            'interacting': [...this.interacting],
-            'channelId': this.channel.id,
-        };
-    }
-
-    /**
-     * Creates a Console from JSON data.
-     * @param {JSON} json the json data
-     * @param {Guild} guild the guild where this console lives
-     * @returns {Console}
-     */
-    static async fromJSON(json, guild) {
-        let console = new Console(
-            {
-                title: json['title'],
-                description: json['description'],
-                channel: guild.channels.resolve(json['channelId']),
-                features: new Collection(json['features']),
-                color: json['color'],
-                options: JSON.parse(json['collectorOptions']),
-                fields: new Collection(json['fields']),
-            }
-        );
-        
-        if (json['messageId'] != undefined) {
-            let message = await console.channel.messages.fetch(json['messageId']);
-            console.message = message;
-            console.createReactionCollector(console.message);
-        }
-    }
-
-}
-module.exports = Console;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_consoles_feature.js.html b/docs/classes_consoles_feature.js.html deleted file mode 100644 index 157487e6..00000000 --- a/docs/classes_consoles_feature.js.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/consoles/feature.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/consoles/feature.js

-
- - - - - -
-
-
const { GuildEmojiManager, User, MessageReaction } = require('discord.js');
-const Console = require('./console');
-
-
-/**
- * The function to be called when a feature is activated.
- * @callback FeatureCallback
- * @param {User} user - the user that reacted
- * @param {MessageReaction} reaction - the reaction
- * @param {StopInteractingCallback} stopInteracting - callback to let the console know the user has
- * stopped interacting.
- * @param {Console} console - the console this feature is working on
- * @async
- */
-
-/**
- * The function used to signal the console the user interacting has finished.
- * @callback StopInteractingCallback
- * @param {User} user
- */
-
-/**
- * A feature is an object with information to make an action from a console.
- * The emojiName can be either a custom emoji ID or a unicode emoji name.
- * @class Feature
- */
-class Feature {
-
-
-    /**
-     * Creates a feature object when you have a GuildEmoji or a ReactionEmoji.
-     * Used for when adding features programmatically!
-     * @param {Object} args
-     * @param {String} args.name
-     * @param {String} args.description
-     * @param {GuildEmoji | ReactionEmoji | String} args.emoji
-     * @param {FeatureCallback} args.callback
-     * @param {FeatureCallback} [args.removeCallback]
-     * @returns {Feature}
-     */
-    static create({name, description, emoji, callback, removeCallback}) {
-        return {
-            name,
-            description,
-            emojiName: typeof emoji === 'string' ? emoji : emoji.id || emoji.name,
-            callback,
-            removeCallback,
-        };
-    }
-
-    /**
-     * 
-     * @param {Object} args arguments
-     * @param {String} args.name the name of the feature
-     * @param {String} args.emojiName the name of the emoji
-     * @param {String} args.description the description of the feature
-     * @param {FeatureCallback} args.callback the callback for when the feature is activated
-     * @param {FeatureCallback} [args.removeCallback=undefined] the callback for when the feature is deactivated
-     */
-    constructor({name, emojiName, description, callback, removeCallback = undefined}) {
-
-        /**
-         * @type {String}
-         */
-        this.name = name;
-
-        /**
-         * @type {String}
-         */
-        this.emojiName = emojiName;
-
-        /**
-         * @type {String}
-         */
-        this.description = description;
-
-        /**
-         * @type {FeatureCallback}
-         */
-        this.callback = callback;
-
-        /**
-         * @type {FeatureCallback}
-         */
-        this.removeCallback = removeCallback;
-
-    }
-
-    /**
-     * Returns a string with the emoji and the feature name:
-     * <emoji> - Feature 1
-     * @param {GuildEmojiManager} guildEmojiManager
-     * @returns {String} 
-     */
-    getFieldName(guildEmojiManager) {
-        let emoji = guildEmojiManager.resolve(this.emojiName);
-
-        return `${emoji ? emoji.toString() : this.emojiName} - ${this.name}`;
-    }
-
-    /**
-     * Returns the feature's value string for when adding it to a embed field.
-     * @returns {String}
-     */
-    getFieldValue() {
-        return this.description;
-    }
-
-}
-module.exports = Feature;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_permission-command.js.html b/docs/classes_permission-command.js.html deleted file mode 100644 index a181872e..00000000 --- a/docs/classes_permission-command.js.html +++ /dev/null @@ -1,280 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/permission-command.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/permission-command.js

-
- - - - - -
-
-
const Discord = require('discord.js');
-const { Command, CommandoMessage, CommandoClientOptions, CommandInfo } = require('discord.js-commando');
-const BotGuild = require('../db/mongo/BotGuild');
-const BotGuildModel = require('./bot-guild');
-const discordServices = require('../discord-services');
-const winston = require('winston');
-
-/**
- * The PermissionCommand is a custom command that extends the discord js commando Command class.
- * This Command subclass adds role and channel permission checks before the command is run. It also
- * removes the message used to call the command.
- * @extends Command
- */
-class PermissionCommand extends Command {
-    
-    /**
-     * Our custom command information for validation
-     * @typedef {Object} CommandPermissionInfo
-     * @property {string} role - the role this command can be run by
-     * @property {string} channel - the channel ID where this command can be run
-     * @property {string} roleMessage - the message to be sent for an incorrect role
-     * @property {string} channelMessage - the message to be sent for an incorrect channel
-     * @property {Boolean} dmOnly - true if this command can only be used on a DM
-     */
-
-    /**
-     * Constructor for our custom command, calls the parent constructor.
-     * @param {CommandoClientOptions} client - the client the command is for 
-     * @param {CommandInfo} info - the information for this commando command 
-     * @param {CommandPermissionInfo} permissionInfo - the custom information for this command 
-     */
-    constructor(client, info, permissionInfo) {
-        super(client, info);
-
-        /**
-         * The permission info
-         * @type {CommandPermissionInfo}
-         * @private
-         */
-        this.permissionInfo = this.validateInfo(permissionInfo);
-    }
-
-    /**
-     * Adds default values if not found on the object.
-     * @param {CommandPermissionInfo} permissionInfo 
-     * @returns {CommandPermissionInfo}
-     * @private
-     */
-    validateInfo(permissionInfo) {
-        // Make sure permissionInfo is an object, if not given then create empty object
-        if (typeof permissionInfo != 'object') permissionInfo = {};
-        if (!permissionInfo?.channelMessage) permissionInfo.channelMessage = 'Hi, the command you just used is not available on that channel!';
-        if (!permissionInfo?.roleMessage) permissionInfo.roleMessage = 'Hi, the command you just used is not available to your current role!';
-        permissionInfo.dmOnly = permissionInfo?.dmOnly ?? false;
-        return permissionInfo;
-    }
-
-
-    /**
-     * Run command used by Command class. Has the permission checks and runs the child runCommand method.
-     * @param {Discord.Message} message 
-     * @param {Object|string|string[]} args 
-     * @param {boolean} fromPattern 
-     * @param {Promise<?Message|?Array<Message>>} result
-     * @override
-     * @private
-     */
-    async run(message, args, fromPattern, result){
-
-        // delete the message
-        discordServices.deleteMessage(message);
-
-        /** @type {BotGuildModel} */
-        let botGuild;
-        if (message?.guild) botGuild = await BotGuild.findById(message.guild.id);
-        else botGuild = null;
-
-        // check for DM only, when true, all other checks should not happen!
-        if (this.permissionInfo.dmOnly) {
-            if (message.channel.type != 'dm') {
-                discordServices.sendEmbedToMember(message.member, {
-                    title: 'Error',
-                    description: 'The command you just tried to use is only usable via DM!',
-                });
-                winston.loggers.get(botGuild?._id || 'main').warning(`User ${message.author.id} tried to run a permission command ${this.name} that is only available in DMs in the channel ${message.channel.name}.`);
-                return;
-            }
-        } else {
-            // Make sure it is only used in the permitted channel
-            if (this.permissionInfo?.channel) {
-                let channelID = botGuild.channelIDs[this.permissionInfo.channel];
-
-                if (channelID && message.channel.id != channelID) {
-                    discordServices.sendMessageToMember(message.member, this.permissionInfo.channelMessage, true);
-                    winston.loggers.get(botGuild?._id || 'main').warning(`User ${message.author.id} tried to run a permission command ${this.name} that is only available in the channel ${this.permissionInfo.channel}, in the channel ${message.channel.name}.`);
-                    return;
-                }
-            }
-            // Make sure only the permitted role can call it
-            else if (this.permissionInfo?.role) {
-
-                let roleID = botGuild.roleIDs[this.permissionInfo.role];
-
-                // if staff role then check for staff and admin, else check the given role
-                if (roleID && (roleID === botGuild.roleIDs.staffRole && 
-                    (!discordServices.checkForRole(message.member, roleID) && !discordServices.checkForRole(message.member, botGuild.roleIDs.adminRole))) || 
-                    (roleID != botGuild.roleIDs.staffRole && !discordServices.checkForRole(message.member, roleID))) {
-                    discordServices.sendMessageToMember(message.member, this.permissionInfo.roleMessage, true);
-                    winston.loggers.get(botGuild?._id || 'main').warning(`User ${message.author.id} tried to run a permission command ${this.name} that is only available for members with role ${this.permissionInfo.role}, but he has roles: ${message.member.roles.cache.array().map((role) => role.name)}`);
-                    return;
-                }
-            }
-        }
-        this.runCommand(botGuild, message, args, fromPattern, result);
-    }
-
-
-    /**
-     * Required class by children, will throw error if not implemented!
-     * @param {BotGuildModel} botGuild
-     * @param {CommandoMessage} message
-     * @param {Object} args
-     * @param {Boolean} fromPattern
-     * @param {Promise<*>} result
-     * @abstract
-     * @protected
-     */
-    runCommand(botGuild, message, args, fromPattern, result) {
-        throw new Error('You need to implement the runCommand method!');
-    }
-}
-
-/**
- * String permission flags used for command permissions.
- * * ADMIN_ROLE : only admins can use this command
- * * STAFF_ROLE : staff and admin can use this command
- * * ADMIN_CONSOLE : can only be used in the admin console
- * @enum {String}
- */
-PermissionCommand.FLAGS = {
-    ADMIN_ROLE: 'adminRole',
-    STAFF_ROLE: 'staffRole',
-    ADMIN_CONSOLE: 'adminConsole',
-};
-
-module.exports = PermissionCommand;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_room.js.html b/docs/classes_room.js.html deleted file mode 100644 index 3d3674a1..00000000 --- a/docs/classes_room.js.html +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/room.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/room.js

-
- - - - - -
-
-
const winston = require('winston');
-const { GuildCreateChannelOptions, User, Guild, Collection, Role, CategoryChannel, VoiceChannel, TextChannel, OverwriteResolvable, PermissionOverwriteOption } = require('discord.js');
-const BotGuildModel = require('./bot-guild');
-const { deleteChannel } = require('../discord-services');
-
-/**
- * @typedef RoomChannels
- * @property {CategoryChannel} category
- * @property {TextChannel} generalText
- * @property {VoiceChannel} generalVoice
- * @property {TextChannel} nonLockedChannel
- * @property {Collection<String, VoiceChannel>} voiceChannels - <Channel ID, channel>
- * @property {Collection<String, TextChannel>} textChannels - <Channel ID, channel>
- * @property {Collection<String, TextChannel | VoiceChannel>} safeChannels - <Channel ID, channel>, channels that can not be removed
- */
-
-/**
- * An object with a role and its permissions
- * @typedef RolePermission
- * @property {String} id - the role snowflake
- * @property {PermissionOverwriteOption} permissions - the permissions to set to that role
- */
-
-/**
- * The room class represents a room where things can occur, a room 
- * consists of a category with voice and text channels. As well as roles 
- * or users allowed to see the room.
- */
-class Room {
-
-    /**
-     * @param {Guild} guild - the guild in which the room lives
-     * @param {BotGuildModel} botGuild - the botGuild
-     * @param {String} name - name of the room 
-     * @param {Collection<String, Role >} [rolesAllowed=Collection()] - the participants able to view this room
-     * @param {Collection<String, User>} [usersAllowed=Collection()] - the individual users allowed to see the room
-     */
-    constructor(guild, botGuild, name, rolesAllowed = new Collection(), usersAllowed = new Collection()) {
-
-        /**
-         * The name of this room. Will remove all leading and trailing whitespace and
-         * switch spaces for '-'. Will also replace all character except for numbers, letters and '-' 
-         * and make it lowercase.
-         * @type {string}
-         */
-        this.name = name.split(' ').join('-').trim().replace(/[^0-9a-zA-Z-]/g, '').toLowerCase();
-
-        /**
-         * The guild this activity is in.
-         * @type {Guild}
-         */
-        this.guild = guild;
-
-        /**
-         * Roles allowed to view the room.
-         * @type {Collection<String, Role>}
-         */
-        this.rolesAllowed = rolesAllowed;
-
-        /**
-         * Users allowed to view the room.
-         * @type {Collection<String, User>}
-         */
-        this.usersAllowed = usersAllowed;
-
-        /**
-         * All the channels this room has!
-         * @type {RoomChannels}
-         */
-        this.channels = {
-            category: null,
-            generalVoice: null,
-            generalText: null,
-            nonLockedChannel: null,
-            voiceChannels: new Collection(),
-            textChannels: new Collection(),
-            safeChannels: new Collection(),
-        };
-
-        /**
-         * The mongoose BotGuildModel Object
-         * @type {BotGuildModel}
-         */
-        this.botGuild = botGuild;
-
-        /**
-         * True if the room is locked, false otherwise.
-         * @type {Boolean}
-         */
-        this.locked = false;
-
-        /**
-         * The time this room was created.
-         * @type {Number}
-         */
-        this.timeCreated = Date.now();
-
-    }
-
-    /**
-     * Initialize this activity by creating the channels, adding the features and sending the admin console.
-     * @param {Object} [args] - channels already created to add to this room
-     * @param {CategoryChannel} args.category
-     * @param {TextChannel} args.textChannel
-     * @param {VoiceChannel} args.voiceChannel
-     * @async
-     * @returns {Promise<Room>}
-     */
-    async init(args) {
-        if (args?.category) this.channels.category = args.category;
-        else {
-            let position = this.guild.channels.cache.filter(channel => channel.type === 'category').size;
-            this.channels.category = await this.createCategory(position);
-        }
-
-        if (args?.textChannel) {
-            this.channels.generalText = args.textChannel;
-            this.addExcisingChannel(args.textChannel);
-        }
-        else this.channels.generalText = await this.addRoomChannel({
-            name: this.name.length < 12 ? `${this.name}-banter` : Room.mainTextChannelName, 
-            info: {
-                parent: this.channels.category,
-                type: 'text',
-                topic: 'A general banter channel to be used to communicate with other members, mentors, or staff. The !ask command is available for questions.',
-            }
-        });
-
-        if (args?.voiceChannel) {
-            this.channels.generalVoice = args.voiceChannel;
-            this.addExcisingChannel(args.voiceChannel);
-        }
-        else this.channels.generalVoice = await this.addRoomChannel({
-            name: this.name.length < 12 ? `${this.name}-room` : Room.mainVoiceChannelName, 
-            info: {
-                parent: this.channels.category,
-                type: 'voice',
-            }
-        });
-
-        winston.loggers.get(this.guild.id).event(`The room ${this.name} was initialized.`, {event: 'Room'});
-        return this;
-    }
-    /**
-     * Helper function to create the category 
-     * @param {Number} position - the position of this category on the server
-     * @returns {Promise<CategoryChannel>} - a category with the activity name
-     * @async
-     * @private
-     */
-    async createCategory(position) {
-        /** @type {OverwriteResolvable[]} */
-        let overwrites = [
-            {
-                id: this.botGuild.roleIDs.everyoneRole,
-                deny: ['VIEW_CHANNEL'],
-            }];
-        this.rolesAllowed.each(role => overwrites.push({ id: role.id, allow: ['VIEW_CHANNEL'] }));
-        this.usersAllowed.each(user => overwrites.push({ id: user.id, allow: ['VIEW_CHANNEL'] }));
-        return this.guild.channels.create(this.name, {
-            type: 'category',
-            position: position >= 0 ? position : 0,
-            permissionOverwrites: overwrites
-        });
-    }
-
-    /**
-     * Adds a channels to the room.
-     * @param {Object} args
-     * @param {String} args.name - name of the channel to create
-     * @param {GuildCreateChannelOptions} [args.info={}] - one of voice or text
-     * @param {RolePermission[]} [args.permissions=[]] - the permissions per role to be added to this channel after creation.
-     * @param {Boolean} [args.isSafe=false] - true if the channel is safe and cant be removed
-     * @async
-     */
-    async addRoomChannel({name, info = {}, permissions = [], isSafe = false}) {
-        info.parent = info?.parent || this.channels.category;
-        info.type = info?.type || 'text';
-
-        let channel = await this.guild.channels.create(name, info);
-
-        permissions.forEach(rolePermission => channel.updateOverwrite(rolePermission.id, rolePermission.permissions));
-
-        // add channel to correct list
-        if (info.type == 'text') this.channels.textChannels.set(channel.id, channel);
-        else this.channels.voiceChannels.set(channel.id, channel);
-
-        if (isSafe) this.channels.safeChannels.set(channel.id, channel);
-
-        winston.loggers.get(this.guild.id).event(`The activity ${this.name} had a channel named ${name} added to it of type ${info?.type || 'text'}.`, {event: 'Activity'});
-
-        return channel;
-    }
-
-    /**
-     * Removes a channel from the room.
-     * @param {VoiceChannel | TextChannel} channelToRemove 
-     * @param {Boolean} [isForced=false] - is the deletion forced?, if so then channel will be removed even if its safeChannels
-     * @async
-     */
-    async removeRoomChannel(channelToRemove, isForced = false) {
-        if (isForced && this.channels.safeChannels.has(channelToRemove.id)) throw Error('Can\'t remove that channel.');
-
-        if (channelToRemove.type === 'text') this.channels.textChannels.delete(channelToRemove.id);
-        else this.channels.voiceChannels.delete(channelToRemove.id);
-
-        this.channels.safeChannels.delete(channelToRemove.id);
-
-        deleteChannel(channelToRemove);
-        winston.loggers.get(this.guild.id).event(`The room ${this.name} lost a channel named ${channelToRemove.name}`, { event: 'Room' });
-    }
-
-    /**
-     * Deletes the room.
-     * @async
-     */
-    async delete() {
-        // only delete channels if they were created!
-        if (this.channels?.category) {
-            var listOfChannels = this.channels.category.children.array();
-            for (var i = 0; i < listOfChannels.length; i++) {
-                await deleteChannel(listOfChannels[i]);
-            }
-
-            await deleteChannel(this.channels.category);
-        }
-        winston.loggers.get(this.guild.id).event(`The room ${this.name} was deleted!`, {event: "Room"});
-    }
-
-    /**
-     * Archive the activity. Move general text channel to archive category, remove all remaining channels
-     * and remove the category.
-     * @param {CategoryChannel} archiveCategory - the category where the general text channel will be moved to
-     * @async
-     */
-    async archive(archiveCategory) {
-        // move all text channels to the archive and rename with activity name
-        // remove all voice channels in the category one at a time to not get a UI glitch
-
-        this.channels.category.children.forEach(async (channel, key) => {
-            this.botGuild.blackList.delete(channel.id);
-            if (channel.type === 'text') {
-                let channelName = channel.name;
-                await channel.setName(`${this.name}-${channelName}`);
-                await channel.setParent(archiveCategory);
-            } else deleteChannel(channel);
-        });
-
-        await deleteChannel(this.channels.category);
-
-        this.botGuild.save();
-
-        winston.loggers.get(this.guild.id).event(`The activity ${this.name} was archived!`, {event: 'Activity'});
-    }
-
-    /**
-     * Locks the room for all roles except for a text channel. To gain access users must be allowed access
-     * individually.
-     * @returns {Promise<TextChannel>} - channel available to roles
-     */
-    async lockRoom() {
-        // set category private
-        this.rolesAllowed.forEach((role, key) => this.channels.category.updateOverwrite(role, { VIEW_CHANNEL: false }));
-
-        /** @type {TextChannel} */
-        this.channels.nonLockedChannel = await this.addRoomChannel({
-            name: 'Activity Rules START HERE', 
-            info: { type: 'text' }, 
-            permissions: this.rolesAllowed.map((role, key) => ({ id: role.id, permissions: { VIEW_CHANNEL: true, SEND_MESSAGES: false, }})), 
-            isSafe: true});
-        this.channels.safeChannels.set(this.channels.nonLockedChannel.id, this.channels.nonLockedChannel);
-
-        this.locked = true;
-
-        return this.channels.nonLockedChannel;
-    }
-
-    /**
-     * Gives access to the room to a role.
-     * @param {Role} role - role to give access to
-     */
-    giveRoleAccess(role) {
-        this.rolesAllowed.set(role.id, role);
-
-        if (this.locked) {
-            this.channels.nonLockedChannel.updateOverwrite(role.id, { VIEW_CHANNEL: true, SEND_MESSAGES: false });
-        } else {
-            this.channels.category.updateOverwrite(role.id, { VIEW_CHANNEL: true });
-        }
-    }
-
-    /**
-     * Gives access to a user
-     * @param {User} user - user to give access to
-     */
-    giveUserAccess(user) {
-        this.usersAllowed.set(user.id, user);
-        this.channels.category.updateOverwrite(user.id, { VIEW_CHANNEL: true, SEND_MESSAGES: true });
-    }
-
-    /**
-     * Removes access to a user to see this room.
-     * @param {User} user - the user to remove access to
-     */
-    removeUserAccess(user) {
-        this.usersAllowed.delete(user.id);
-        this.channels.category.updateOverwrite(user.id, { VIEW_CHANNEL: false });
-    }
-
-    /**
-     * @param {TextChannel | VoiceChannel} channel
-     */
-    addExcisingChannel(channel) {
-        if (channel.type === 'text') this.channels.textChannels.set(channel.id, channel);
-        else if (channel.type === 'voice') this.channels.voiceChannels.set(channel.id, channel);
-    }
-}
-
-Room.voiceChannelName = '🔊Room-';
-Room.mainTextChannelName = '🖌️activity-banter';
-Room.mainVoiceChannelName = '🗣️activity-room';
-
-module.exports = Room;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_stamps-manager.js.html b/docs/classes_stamps-manager.js.html deleted file mode 100644 index e87e6cbf..00000000 --- a/docs/classes_stamps-manager.js.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/stamps-manager.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/stamps-manager.js

-
- - - - - -
-
-
const { Collection, MessageEmbed, GuildMember } = require('discord.js');
-const winston = require('winston');
-const { addRoleToMember, sendEmbedToMember, replaceRoleToMember, sendMessageToMember } = require('../discord-services');
-const BotGuildModel = require('./bot-guild');
-const Activity = require('./activities/activity');
-
-/**
- * @class
- * 
- */
-class StampsManager {
-    /**
-     * Will let hackers get a stamp for attending the activity.
-     * @param {Activity} activity - activity to use
-     * @param {Number} [time] - time to wait till collector closes, in seconds
-     * @param {BotGuildModel} botGuild
-     * @async
-     */
-    static async distributeStamp(activity, botGuild, time = 60) {
-
-        winston.loggers.get(activity.guild.id).event(`Activity named ${activity.name} is distributing stamps.`, {event: 'Activity Manager'});
-        
-        // The users already seen by this stamp distribution.
-        let seenUsers = new Collection();
-
-        const promptEmbed = new MessageEmbed()
-            .setColor(botGuild.colors.embedColor)
-            .setTitle('React within ' + time + ' seconds of the posting of this message to get a stamp for ' + activity.name + '!');
-
-        let promptMsg = await activity.generalText.send(promptEmbed);
-        promptMsg.react('👍');
-
-        // reaction collector, time is needed in milliseconds, we have it in seconds
-        const collector = promptMsg.createReactionCollector((reaction, user) => !user.bot, { time: (1000 * time) });
-
-        collector.on('collect', async (reaction, user) => {
-            // grab the member object of the reacted user
-            const member = activity.generalText.guild.member(user);
-
-            if (!seenUsers.has(user.id)) {
-                this.parseRole(member, activity.name, botGuild);
-                seenUsers.set(user.id, user.username);
-            }
-        });
-
-        // edit the message to closed when the collector ends
-        collector.on('end', collected => {
-            winston.loggers.get(activity.guild.id).event(`Activity named ${activity.name} stamp distribution has stopped.`, {event: 'Activity Manager'});
-            if (!promptMsg.deleted) {
-                promptMsg.edit(promptEmbed.setTitle('Time\'s up! No more responses are being collected. Thanks for participating in ' + activity.name + '!'));
-            }
-        });
-    }
-
-
-    /**
-     * Upgrade the stamp role of a member.
-     * @param {GuildMember} member - the member to add the new role to
-     * @param {String} activityName - the name of the activity
-     * @param {BotGuildModel} botGuild
-     * @throws Error if the botGuild has stamps disabled
-     */
-    static parseRole(member, activityName, botGuild) {
-        if (!botGuild.stamps.isEnabled) {
-            winston.loggers.get(botGuild._id).error(`Stamp system is turned off for guild ${botGuild._id} but I was asked to parse a role for member ${member.id} for activity ${activityName}.`, { event: 'Activity Manager' });
-            throw Error(`Stamp system is turned of for guild ${botGuild._id} but I was asked to parse a role for member ${member.id} for activity ${activityName}.`);
-        }
-
-        let role = member.roles.cache.find(role => botGuild.stamps.stampRoleIDs.has(role.id));
-
-        if (role === undefined) {
-            addRoleToMember(member, botGuild.stamps.stamp0thRoleId);
-            sendEmbedToMember(member, 'I did not find an existing stamp role for you so I gave you one for attending '
-                + activityName + '. Please contact an admin if there was a problem.', true);
-            winston.loggers.get(botGuild._id).userStats(`Activity named ${activityName} tried to give a stamp to the user with id ${member.id} but he has no stamp, I gave them the first stamp!`, {event: 'Activity Manager'});
-            return;
-        }
-
-        let stampNumber = botGuild.stamps.stampRoleIDs.get(role.id);
-        if (stampNumber === botGuild.stamps.stampRoleIDs.size - 1) {
-            sendMessageToMember(member, 'You already have the maximum allowed number of stamps!', true);
-            winston.loggers.get(botGuild._id).userStats(`Activity named ${activityName} tried to give a stamp to the user with id ${member.id} but he is already in the max stamp ${stampNumber}`, {event: 'Activity Manager'});
-            return;
-        }
-        let newRoleID;
-
-        botGuild.stamps.stampRoleIDs.forEach((num, key, map) => {
-            if (num === stampNumber + 1) newRoleID = key;
-        });
-
-        if (newRoleID != undefined) {
-            replaceRoleToMember(member, role.id, newRoleID);
-            sendMessageToMember(member, 'You have received a higher stamp for attending ' + activityName + '!', true);
-            winston.loggers.get(botGuild._id).userStats(`Activity named ${activityName} gave a stamp to the user with id ${member.id} going from stamp number ${stampNumber} to ${stampNumber + 1}`, {event: 'Activity Manager'});
-        }
-    }
-}
-module.exports = StampsManager;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_team-formation.js.html b/docs/classes_team-formation.js.html deleted file mode 100644 index e3058288..00000000 --- a/docs/classes_team-formation.js.html +++ /dev/null @@ -1,492 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/team-formation.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/team-formation.js

-
- - - - - -
-
-
const { GuildEmoji, ReactionEmoji, Role, TextChannel, MessageEmbed, Guild, Collection, User, Message, RoleManager } = require('discord.js');
-const { sendEmbedToMember, addRoleToMember, deleteMessage, sendMessageToMember, removeRolToMember } = require('../discord-services');
-const BotGuild = require('../db/mongo/BotGuild');
-const winston = require('winston');
-const Activity = require('./activities/activity');
-const BotGuildModel = require('./bot-guild');
-const Console = require('./consoles/console');
-const { StringPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * @class TeamFormation
- * 
- * The team formation class represents the team formation activity. It helps teams and prospects 
- * find each other by adding their respective information to a catalogue of sorts. Admins have the 
- * ability to customize the messages sent, emojis used, and if they want users to be notified of new
- * posts in the catalogue.
- * 
- */
-class TeamFormation extends Activity {
-    
-    static defaultTeamForm = 'Team Member(s): \nTeam Background: \nObjective: \nFun Fact About Team: \nLooking For: ';
-    static defaultProspectForm = 'Name: \nSchool: \nPlace of Origin: \nSkills: \nFun Fact: \nDeveloper or Designer?:';
-    static defaultTeamColor = '#60c2e6';
-    static defaultProspectColor = '#d470cd';
-
-    /**
-     * Creates the team role and returns it.
-     * @param {RoleManager} roleManager
-     * @returns {Promise<Role>}
-     * @static
-     * @async
-     */
-     static async createTeamRole(roleManager) {
-        winston.loggers.get(roleManager.guild.id).verbose(`Team formation team role has been created!`, { event: "Team Formation" });
-        return await roleManager.create({
-            data: {
-                name: 'tf-team-leader',
-                color: TeamFormation.defaultTeamColor,
-            }
-        });
-    }
-
-    /**
-     * Creates the prospect role and returns it.
-     * @param {RoleManager} roleManager
-     * @returns {Promise<Role>}
-     * @static
-     * @async
-     */
-    static async createProspectRole(roleManager) {
-        winston.loggers.get(roleManager.guild.id).verbose(`Team formation prospect role has been created!`, { event: "Team Formation" });
-        return await roleManager.create({
-            data: {
-                name: 'tf-prospect',
-                color: TeamFormation.defaultProspectColor,
-            }
-        });
-    }
-
-    /**
-     * @typedef TeamFormationPartyInfo
-     * @property {GuildEmoji | ReactionEmoji} emoji - the emoji used to add this party to the team formation
-     * @property {Role} role - the role given to the users of this party
-     * @property {String} [form] - the form added to the signup embed for users to respond to. Will not be added if signupEmbed given!
-     * @property {MessageEmbed} [signupEmbed] - the embed sent to users when they sign up, must include the form!
-     */
-
-    /**
-     * @typedef TeamFormationChannels
-     * @property {TextChannel} info - the info channel where users read about this activity
-     * @property {TextChannel} teamCatalogue - the channel where team info is posted
-     * @property {TextChannel} prospectCatalogue - the channel where prospect info is posted
-     */
-
-    /**
-     * @callback SignupEmbedCreator
-     * @param {String} teamEmoji - the emoji used by teams to sign up
-     * @param {String} prospectEmoji - the emoji used by prospects to sign up
-     * @param {Boolean} isNotificationEnabled - true if parties will be notified when the other party has a new post
-     * @return {MessageEmbed}
-     */
-
-    /**
-     * @typedef TeamFormationInfo
-     * @property {TeamFormationPartyInfo} teamInfo
-     * @property {TeamFormationPartyInfo} prospectInfo
-     * @property {Guild} guild
-     * @property {BotGuildModel} botGuild
-     * @property {Collection<string, Role>} activityRoles
-     * @property {Boolean} [isNotificationsEnabled]
-     * @property {SignupEmbedCreator} [signupEmbedCreator]
-     */
-
-     /**
-      * Create a new team formation.
-      * @param {TeamFormationInfo} teamFormationInfo - the team formation information
-      */
-    constructor(teamFormationInfo) {
-
-        super({
-            activityName: 'Team Formation',
-            guild: teamFormationInfo.guild,
-            roleParticipants: teamFormationInfo.activityRoles,
-            botGuild: teamFormationInfo.botGuild
-        });
-
-        if (!teamFormationInfo?.teamInfo || !teamFormationInfo?.prospectInfo) throw new Error('Team and prospect info must be given!');
-        this.validatePartyInfo(teamFormationInfo.teamInfo);
-        this.validatePartyInfo(teamFormationInfo.prospectInfo);
-        if (!teamFormationInfo?.guild) throw new Error('A guild is required for a team formation!');
-
-        /**
-         * The team information, those teams willing to join will use this.
-         * @type {TeamFormationPartyInfo}
-         */
-        this.teamInfo = {
-            emoji : teamFormationInfo.teamInfo.emoji,
-            role : teamFormationInfo.teamInfo.role,
-            form: teamFormationInfo.teamInfo?.form || TeamFormation.defaultTeamForm,
-            signupEmbed : teamFormationInfo.teamInfo?.signupEmbed,
-        }
-
-        /**
-         * The prospect info, those solo users that want to join a team will use this info.
-         * @type {TeamFormationPartyInfo}
-         */
-        this.prospectInfo = {
-            emoji: teamFormationInfo.prospectInfo.emoji,
-            role: teamFormationInfo.prospectInfo.role,
-            form: teamFormationInfo.prospectInfo?.form || TeamFormation.defaultProspectForm,
-            signupEmbed : teamFormationInfo.prospectInfo?.signupEmbed,
-        }
-
-        /**
-         * The channels that a team formation activity needs.
-         * @type {TeamFormationChannels}
-         */
-        this.channels = {};
-
-        /**
-         * True if the parties will be notified when the opposite party has a new post.
-         * @type {Boolean}
-         */
-        this.isNotificationEnabled = teamFormationInfo?.isNotificationsEnabled || false;
-
-        /**
-         * A creator of the info embed in case you want it to be different.
-         * @type {SignupEmbedCreator}
-         */
-        this.signupEmbedCreator = teamFormationInfo?.signupEmbedCreator || null;
-
-        winston.loggers.get(this.guild.id).event(`A Team formation has been created!`, { event: "Team Formation"});
-        winston.loggers.get(this.guild.id).verbose(`A Team formation has been created!`, { event: "Team Formation", data: {teamFormationInfo: teamFormationInfo}});
-    }
-
-    /**
-     * Validates a TeamFormationPartyInfo object
-     * @param {TeamFormationPartyInfo} partyInfo - the party info to validate
-     * @private
-     */
-    validatePartyInfo(partyInfo) {
-        if (!partyInfo?.emoji && typeof partyInfo.emoji != (GuildEmoji || ReactionEmoji)) throw new Error('A Discord emoji is required for a TeamFormationPartyInfo');
-        if (!partyInfo?.role && typeof partyInfo.role != Role) throw new Error ('A Discord Role is required in a TeamFormationPartyInfo');
-        if (partyInfo.signupEmbed && typeof partyInfo.signupEmbed != MessageEmbed) throw new Error('The message embed must be a Discord Message Embed');
-        if (partyInfo.form && typeof partyInfo.form != 'string') throw new Error('The form must be a string!');
-    }
-
-    async init() {
-        await super.init();
-        await this.createChannels();
-    }
-
-    /**
-     * Will create the TeamFormationChannels object with new channels to use with a new TeamFormation
-     * @async
-     */
-    async createChannels() {
-
-        this.room.channels.category.setName('🏅Team Formation');
-        this.room.channels.generalText.delete();
-        this.room.channels.generalVoice.delete();
-
-        this.channels.info = await this.room.addRoomChannel({
-            name: '👀team-formation',
-            permissions: [{ id: this.botGuild.roleIDs.everyoneRole, permissions: { SEND_MESSAGES: false }}],
-            isSafe: true,
-        });
-
-        this.channels.prospectCatalogue = await this.room.addRoomChannel({
-            name: '🙋🏽prospect-catalogue',
-            info: {
-                topic: 'Information about users looking to join teams can be found here. Happy hunting!!!',
-            },
-            permissions: [{ id: this.botGuild.roleIDs.everyoneRole, permissions: { SEND_MESSAGES: false }}],
-            isSafe: true,
-        });
-
-        this.channels.teamCatalogue = await this.room.addRoomChannel({
-            name: '💼team-catalogue',
-            info: {
-                topic: 'Channel for teams to post about themselves and who they are looking for! Expect people to send you private messages.',
-            },
-            permissions: [{ id: this.botGuild.roleIDs.everyoneRole, permissions: { SEND_MESSAGES: false }}],
-            isSafe: true,
-        });
-
-        winston.loggers.get(this.guild.id).verbose(`Team formation channels have been created!`, { event: "Team Formation" });
-    }
-
-    /**
-     * Will start the activity!
-     * @param {SignupEmbedCreator} [signupEmbedCreator] - embed creator for the sign in
-     * @async
-     */
-    async start(signupEmbedCreator = null) {
-        let embed;
-
-        if (signupEmbedCreator) {
-            embed = signupEmbedCreator(this.teamInfo.emoji, this.prospectInfo.emoji, this.isNotificationEnabled);
-        } else {
-            embed = new MessageEmbed()
-            .setColor((await (BotGuild.findById(this.guild.id))).colors.embedColor)
-            .setTitle('Team Formation Information')
-            .setDescription('Welcome to the team formation section! If you are looking for a team or need a few more members to complete your ultimate group, you are in the right place!')
-            .addField('How does this work?', '* Once you react to this message, I will send you a template you need to fill out and send back to me via DM. \n* Then I will post your information in the channels below. \n* Then, other members, teams, or yourself can browse these channels and reach out via DM!')
-            .addField('Disclaimer!!', 'By participating in this activity, you consent to other server members sending you a DM.')
-            .addField('Teams looking for new members', 'React with ' + this.teamInfo.emoji.toString() + ' and the bot will send you instructions.')
-            .addField('Prospects looking for a team', 'React with ' + this.prospectInfo.emoji.toString() + ' and the bot will send you instructions.');
-        }
-
-        let signupMsg = await this.channels.info.send(embed);
-        signupMsg.react(this.teamInfo.emoji);
-        signupMsg.react(this.prospectInfo.emoji);
-
-        winston.loggers.get(this.guild.id).event(`The team formation has started. ${signupEmbedCreator ? 'A custom embed creator was used' : 'The default embed was used.'}`, { event: "Team Formation"});
-        winston.loggers.get(this.guild.id).verbose(`The team formation has started. ${signupEmbedCreator ? 'A custom embed creator was used' : 'The default embed was used.'}`, { event: "Team Formation", data: { embed: embed }});
-
-        const signupCollector = signupMsg.createReactionCollector((reaction, user) => !user.bot && (reaction.emoji.name === this.teamInfo.emoji.name || reaction.emoji.name === this.prospectInfo.emoji.name));
-
-        signupCollector.on('collect', (reaction, user) => {
-            let isTeam = reaction.emoji.name === this.teamInfo.emoji.name;
-            winston.loggers.get(this.guild.id).userStats(`The user ${user.id} is signing up to the team formation as a ${isTeam ? "team" : "prospect"}.`, { event: "Team Formation" })
-            this.reachOutToUser(user, isTeam);
-        });
-    }
-
-    /**
-     * Will reach out to the user to ask for the form response to add to the catalogue.
-     * @param {User} user - the user joining the team formation activity
-     * @param {Boolean} isTeam - true if the user represents a team, else false
-     * @async
-     */
-    async reachOutToUser(user, isTeam) {
-        let logger = winston.loggers.get(this.guild.id);
-
-        let console = new Console({
-            title: `Team Formation - ${isTeam ? 'Team Format' : 'Prospect Format'}`,
-            description: 'We are very excited for you to find your perfect ' + (isTeam ? 'team members.' : 'team.') + '\n* Please **copy and paste** the following format in your next message. ' +
-            '\n* Try to respond to all the sections! \n* Once you are ready to submit, react to this message with 🇩 and then send me your information!\n' +
-            '* Once you fill your team, please come back and click the ⛔ emoji.',
-            channel: await user.createDM(),
-            guild: this.guild,
-        });
-
-        if (this.isNotificationEnabled) console.addField('READ THIS!', 'As soon as you submit your form, you will be notified of every new ' + (isTeam ? 'available prospect.' : 'available team.') + 
-        ' Once you close your form, you will stop receiving notifications!');
-
-        await console.addField('Format:', isTeam ? this.teamInfo.form || TeamFormation.defaultTeamForm : this.prospectInfo.form || TeamFormation.defaultProspectForm);
-
-        await console.addFeature({
-            name: 'Send completed form',
-            description: 'React to this emoji, wait for my prompt, and send the finished form.',
-            emojiName: '🇩',
-            callback: async (user, reaction, stopInteracting, console) => {
-                // gather and send the form from the user
-                try {
-                    var catalogueMsg = await this.gatherForm(user, isTeam);
-                    logger.verbose(`I was able to get the user's team formation response: ${catalogueMsg.cleanContent}`, { event: "Team Formation" });
-                } catch (error) {
-                    logger.warning(`While waiting for a user's team formation response I found an error: ${error}`, { event: "Team Formation" });
-                    user.dmChannel.send('You have canceled the prompt. You can try again at any time!').then(msg => msg.delete({timeout: 10000}));
-                    stopInteracting();
-                    return;
-                }
-        
-                // confirm the post has been received
-                sendEmbedToMember(user, {
-                    title: 'Team Formation',
-                    description: 'Thanks for sending me your information, you should see it pop up in the respective channel under the team formation category.' +
-                    `Once you find your ${isTeam ? 'members' : 'ideal team'} please react to my original message with ⛔ so I can remove your post. Good luck!!!`,
-                }, 15);
-                logger.event(`The user ${user.id} has successfully sent their information to the team formation feature.`, { event: "Team Formation" });
-
-                // add role to the user
-                addRoleToMember(this.guild.member(user), isTeam ? this.teamInfo.role : this.prospectInfo.role);
-
-                // add remove post feature
-                await console.addFeature({
-                    name: 'Done with team formation!',
-                    description: 'React with this emoji if you are done with team formation.',
-                    emojiName: '⛔',
-                    callback: (user, reaction, stopInteracting, console) => {
-                        // remove message sent to channel
-                        deleteMessage(catalogueMsg);
-            
-                        // confirm deletion
-                        sendMessageToMember(user, 'This is great! You are all set! Have fun with your new team! Your message has been deleted.', true);
-            
-                        removeRolToMember(this.guild.member(user), isTeam ? this.teamInfo.role : this.prospectInfo.role);
-
-                        logger.event(`The user ${user.id} has found a team and has been removed from the team formation feature.`, { event: "Team Formation" });
-
-                        console.delete();
-                    }
-                });
-
-                console.removeFeature('🇩');
-
-                stopInteracting();
-            }
-        });
-
-        console.sendConsole();
-    }
-
-    /**
-     * Will gather the form from a user to add to the catalogues and send it to the correct channel.
-     * @param {User} user - the user being prompted
-     * @param {Boolean} isTeam - true if the user is a team looking for members
-     * @returns {Promise<Message>} - the catalogue message
-     * @async
-     * @throws Error if user cancels or takes too long to respond to prompt
-     */
-    async gatherForm(user, isTeam) {
-        
-        var formMsg = await StringPrompt.single({
-            prompt: 'Please send me your completed form, if you do not follow the form your post will be deleted!', 
-            channel: user.dmChannel, userId: user.id, time: 30, cancelable: true,
-        });
-
-        const embed = new MessageEmbed()
-                .setTitle('Information about them can be found below:')
-                .setDescription(formMsg.content + '\nDM me to talk -> <@' + user.id + '>')
-                .setColor(isTeam ? this.teamInfo.role.hexColor : this.prospectInfo.role.hexColor);
-
-        let channel = isTeam ? this.channels.teamCatalogue : this.channels.prospectCatalogue;
-
-        let catalogueMsg = await channel.send(
-            (this.isNotificationEnabled ? '<@&' + (isTeam ? this.prospectInfo.role.id : this.teamInfo.role.id) + '>, ' : '') + 
-            '<@' + user.id + (isTeam ? '> and their team are looking for more team members!' : '>  is looking for a team to join!'), {embed: embed});
-
-        winston.loggers.get(channel.guild.id).verbose(`A message with the user's information has been sent to the channel ${channel.name} with id ${channel.id}.`, { event: "Team Formation", data: embed});
-
-        return catalogueMsg;
-    }
-
-}
-module.exports = TeamFormation;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_team.js.html b/docs/classes_team.js.html deleted file mode 100644 index a4b330b8..00000000 --- a/docs/classes_team.js.html +++ /dev/null @@ -1,314 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/team.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/team.js

-
- - - - - -
-
-
const Discord = require('discord.js');
-const winston = require('winston');
-const discordServices = require('../discord-services');
-
-
-/**
- * A Team represents a real life team with members. Teams can merge together, have channels and each team has a unique ID.
- */
-class Team {
-
-    constructor(teamNumber) {
-        /**
-         * The team ID
-         * @type {Number}
-         */
-        this.id = teamNumber;
-
-        /**
-         * All the team members
-         * @type {Discord.Collection<Discord.Snowflake, Discord.User | Discord.GuildMember>} - <user ID, User or Member>
-         */
-        this.members = new Discord.Collection();
-
-        /**
-         * The team's text channel if any
-         * @type {Discord.TextChannel | null}
-         */
-        this.textChannel;
-
-        /**
-         * The team leader.
-         * @type {Discord.Snowflake} - ID of the team leader
-         */
-        this.leader;
-
-        /**
-         * True if the team has been complete at least once.
-         * @type {Boolean}
-         */
-        this.hasBeenComplete = false;
-
-        /**
-         * True if the team has been deleted, else false.
-         * @type {Boolean}
-         */
-        this.deleted = false;        
-    }
-
-    /**
-     * Create a text channel for this team and add all the team members. 
-     * Will notify the members of the channel creation
-     * @param {Discord.ChannelManager} channelManager - the channel manager to create the text channel
-     * @param {Discord.CategoryChannel} category - the category where to create the channel
-     * @async
-     * @returns {Promise<Discord.TextChannel>}
-     */
-    async createTextChannel(channelManager, category) {
-        this.textChannel = await channelManager.create('Team-' + this.id, {
-            type: 'text',
-            topic: 'Welcome to your new team, good luck!',
-            parent: category,
-        });
-
-        let usersMentions = '';
-
-        this.members.forEach((user, id) => {
-            usersMentions += '<@' + id + '>, ';
-            this.addUserToTextChannel(user);
-        });
-
-        this.textChannel.send(usersMentions).then(msg => msg.delete({timeout: 5000}));
-
-        return this.textChannel;
-    }
-
-    /**
-     * Merge two teams. Team with a text channel, if any will be kept. New 
-     * members will be added to the text channel, if any.
-     * @param {Team} team - team to merge into this team
-     * @async
-     */
-    async mergeTeam(team) {
-        if (this?.textChannel || !team?.textChannel) {
-            team.members.forEach((user, id) => {
-                this.members.set(id, user);
-                this.addUserToTextChannel(user);
-            });
-            return this;
-        } else {
-            return await team.mergeTeam(this);
-        }
-    }
-
-    /**
-     * Add a user to the team's text channel by giving them permission. 
-     * Will also introduce them to the team.
-     * @param {Discord.User} user
-     * @private
-     * @async
-     */
-    async addUserToTextChannel(user) {
-        if (this?.textChannel) {
-            await this.textChannel.createOverwrite(user.id, {
-                'VIEW_CHANNEL' : true,
-                'SEND_MESSAGES' : true,
-            });
-            this.textChannel.send('Hello <@' + user.id + '>, welcome to the team!').then(msg => msg.delete({timeout: 30000}));
-        }
-    }
-
-    /**
-     * Add a new user to the team.
-     * @param {Discord.User | Discord.GuildMember} user - the user to add to the team
-     * @async
-     */
-    async addTeamMember(user) {
-        if (!this.members.has(user.id)) {
-            this.members.set(user.id, user);
-            await this.addUserToTextChannel(user);
-
-            if (this.members.size === 1) {
-                this.leader = user.id;
-                
-            }
-        }
-
-        if (this.size() === 4) this.hasBeenComplete = true;
-    }
-
-    /**
-     * Removes a user from the team.
-     * @param {Discord.User} user - the user to remove from the team
-     * @returns {Number} - the new size of this team
-     */
-    removeTeamMember(user) {
-        this.members.delete(user.id);
-        if (this?.textChannel) this.textChannel.createOverwrite(user.id, {
-            VIEW_CHANNEL: false,
-            SEND_MESSAGES: false,
-        });
-
-        // if user is the team leader appoint another team member
-        if (this.leader === user.id) {
-            this.leader = this.members.first().id;
-        }
-
-        return this.size();
-    }
-
-    /**
-     * Return the length of the members collection.
-     * @returns {Number}
-     */
-    size() {
-        return this.members.size;
-    }
-
-    /**
-     * True if the team has 4 members, false otherwise.
-     */
-    isComplete() {
-        return this.size() === 4;
-    }
-
-    /**
-     * Returns a string with the team id and all the team members.
-     * @returns {String}
-     */
-    toString() {
-        let teamMemberString = '';
-
-        this.members.forEach((user, key) => {
-            teamMemberString += user.username + ', ';
-        });
-
-        return 'Team ' + this.id + ': ' + teamMemberString;
-    }
-
-}
-module.exports = Team;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_tickets_ticket-manager.js.html b/docs/classes_tickets_ticket-manager.js.html deleted file mode 100644 index 5c21e2eb..00000000 --- a/docs/classes_tickets_ticket-manager.js.html +++ /dev/null @@ -1,469 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/tickets/ticket-manager.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/tickets/ticket-manager.js

-
- - - - - -
-
-
const { Collection, GuildEmoji, ReactionEmoji, TextChannel, Guild, Role, User } = require('discord.js');
-const Ticket = require('./ticket');
-const BotGuildModel = require('../bot-guild');
-const Console = require('../consoles/console');
-const Feature = require('../consoles/feature');
-const { sendMsgToChannel } = require('../../discord-services');
-const winston = require('winston');
-const { StringPrompt } = require('advanced-discord.js-prompts');
-const Activity = require('../activities/activity');
-
-
-/**
- * Represents a real life ticket system that can be used in any setting. It is very versatile so it can be 
- * used with one or many helper types, can edit options, embeds, etc.
- * @class
- */
-class TicketManager {
-
-    /**
-     * All the information needed for tickets in this ticket manager
-     * @typedef SystemWideTicketInfo
-     * @property {GarbageCollectorInfo} garbageCollectorInfo - the garbage collector information for each tickets
-     * @property {Boolean} isAdvancedMode Information about the system being advanced. Advanced mode will create a category with channels for 
-     * the users and the helpers. Regular will not create anything and expects the helper to DM the user or users.
-     */
-    /**
-     * @typedef GarbageCollectorInfo
-     * @property {Boolean} isEnabled - if the garbage collector is enabled for this ticket system
-     * @property {Number} inactivePeriod - number of minutes a ticket channel will be inactive before bot starts to delete it
-     * @property {Number} bufferTime - number of minutes the bot will wait for a response before deleting ticket
-     */
-
-    /**
-     * @typedef TicketCreatorInfo
-     * @property {TextChannel} channel - the channel where users can create a ticket
-     * @property {Console} console - the console used to let users create tickets
-     */
-
-    /**
-     * @typedef TicketDispatcherInfo
-     * @property {TextChannel} channel - the channel where tickets are dispatched to 
-     * @property {GuildEmoji | ReactionEmoji} takeTicketEmoji - emoji for mentors to accept/take a ticket, can be a unicode emoji string
-     * @property {GuildEmoji | ReactionEmoji} joinTicketEmoji - emoji for mentors to join a taken ticket, can be a unicode emoji string
-     * @property {NewTicketEmbedCreator} embedCreator - function to create a Discord MessageEmbed
-     * @property {ReminderInfo} reminderInfo - the reminder information
-     * @property {MainHelperInfo} mainHelperInfo
-     */
-    /**
-     * @typedef ReminderInfo
-     * @property {Boolean} isEnabled - is this feature enabled
-     * @property {Number} time - how long should I wait to remind helpers
-     * @property {Collection<Number, NodeJS.Timeout>} reminders - the timeout reminders mapped by the ticket ID
-     */
-    /**
-     * @callback NewTicketEmbedCreator
-     * @param {Ticket} ticket
-     * @returns {MessageEmbed}
-     */
-    /**
-     * @typedef MainHelperInfo
-     * @property {Role} role
-     * @property {GuildEmoji | ReactionEmoji} emoji - can be a unicode emoji string
-     */
-
-
-    /**
-     * @constructor
-     * @param {Activity} parent 
-     * @param {Object} args
-     * @param {TicketCreatorInfo} args.ticketCreatorInfo
-     * @param {TicketDispatcherInfo} args.ticketDispatcherInfo
-     * @param {SystemWideTicketInfo} args.systemWideTicketInfo
-     */
-    constructor(parent, { ticketCreatorInfo, ticketDispatcherInfo, systemWideTicketInfo }) {
-
-        /**
-         * The tickets in this ticket system.
-         * @type {Collection<Number, Ticket>} - <Ticket Number (ID), Ticket>
-         */
-        this.tickets = new Collection();
-
-        /**
-         * The number of tickets created.
-         * Must be separate as tickets.length since we use this to assign IDs to tickets.
-         */
-        this.ticketCount = 0;
-
-        /**
-         * The parent of this ticket-system. It must be paired with a cave or an activity.
-         * @type {Activity}
-         */
-        this.parent = parent;
-
-        /**
-         * @type {TicketCreatorInfo}
-         */
-        this.ticketCreatorInfo = {
-            channel: null,
-            console: null,
-        };
-
-        /**
-         * @type {TicketDispatcherInfo}
-         */
-        this.ticketDispatcherInfo = {
-            channel: null,
-            takeTicketEmoji: null,
-            joinTicketEmoji: null,
-            embedCreator: null, // function
-            reminderInfo: {
-                isEnabled: null,
-                time: null,
-                reminders: new Collection(),
-            },
-            mainHelperInfo: {
-                role: null,
-                emoji: null,
-            },
-        };
-
-        /**
-         * @type {SystemWideTicketInfo}
-         */
-        this.systemWideTicketInfo = {
-            garbageCollectorInfo: {
-                isEnabled : false,
-                inactivePeriod : null,
-                bufferTime : null,
-            },
-            isAdvancedMode: false,
-        };
-
-        /**
-         * Information about the system being multi role, if its the case, it needs a 
-         * Multi Role Selector.
-         */
-        this.multiRoleInfo = {
-            isEnabled : false,
-            multiRoleSelector : null,
-        };
-
-        this.validateTicketSystemInfo({ ticketCreatorInfo, ticketDispatcherInfo, systemWideTicketInfo });
-    }
-    /**
-     * 
-     * @param {Object} param0
-     * @private
-     */
-    validateTicketSystemInfo({ ticketCreatorInfo, ticketDispatcherInfo, systemWideTicketInfo }) {
-        this.ticketCreatorInfo = ticketCreatorInfo;
-        this.ticketDispatcherInfo = ticketDispatcherInfo;
-        this.systemWideTicketInfo = systemWideTicketInfo;
-        this.ticketDispatcherInfo.reminderInfo.reminders = new Collection();
-    }
-
-    /**
-     * Sends the ticket creator console.
-     * @param {String} title - the ticket creator console title
-     * @param {String} description - the ticket creator console description
-     * @param {String} [color] - the ticket creator console color, hex
-     * @async
-     */
-    async sendTicketCreatorConsole(title, description, color) {
-        /** @type {Console.Feature[]} */
-        let featureList = [
-            Feature.create({
-                name: 'General Ticket',
-                description: 'A general ticket aimed to all helpers.',
-                emoji: this.ticketDispatcherInfo.mainHelperInfo.emoji,
-                callback: (user, reaction, stopInteracting, console) => this.startTicketCreationProcess(user, this.ticketDispatcherInfo.mainHelperInfo.role, console.channel).then(() => stopInteracting()),
-            })
-        ];
-
-        let features = new Collection(featureList.map(feature => [feature.emojiName, feature]));
-
-        this.ticketCreatorInfo.console = new Console({ title, description, channel: this.ticketCreatorInfo.channel, features, color, guild: this.parent.guild });
-        await this.ticketCreatorInfo.console.sendConsole();
-    }
-
-    /**
-     * Adds a new type of ticket, usually a more focused field, there must be a role associated 
-     * to this new type of ticket.
-     * @param {Role} role - role to add
-     * @param {String} typeName
-     * @param {GuildEmoji | ReactionEmoji} emoji 
-     */
-    addTicketType(role, typeName, emoji) {
-        this.ticketCreatorInfo.console.addFeature(
-            Feature.create({
-                name: `Question about ${typeName}`,
-                description: '---------------------------------',
-                emoji: emoji,
-                callback: (user, reaction, stopInteracting, console) => {
-                    this.startTicketCreationProcess(user, role, console.channel).then(() => stopInteracting());
-                }
-            })
-        );
-    }
-
-    /**
-     * Prompts a user for more information to create a new ticket for them.
-     * @param {User} user - the user creating a ticket
-     * @param {Role} role 
-     * @param {TextChannel | DMChannel}
-     * @async
-     */
-    async startTicketCreationProcess(user, role, channel) {
-        // check if role has mentors in it
-        if (role.members.size <= 0) {
-            sendMsgToChannel(channel, user.id, 'There are no mentors available with that role. Please request another role or the general role!', 10);
-            winston.loggers.get(this.parent.botGuild._id).userStats(`The cave ${this.parent.name} received a ticket from user ${user.id} but was canceled due to no mentor having the role ${role.name}.`, { event: 'Ticket Manager' });
-            return;
-        }
-
-        try {
-            var promptMsg = await StringPrompt.single({prompt: 'Please send ONE message with: \n* A one liner of your problem ' + 
-                                '\n* Mention your team members using @friendName (example: @John).', channel, userId: user.id, cancelable: true, time: 45});
-        } catch (error) {
-            winston.loggers.get(this.parent.botGuild._id).warning(`New ticket was canceled due to error: ${error}`, { event: 'Ticket Manager' });
-            return;
-        }
-
-        let hackers = new Collection();
-        hackers.set(user.id, user);
-        if (promptMsg.mentions.users.size > 0) hackers = hackers.concat([promptMsg.mentions.users]);
-
-        this.newTicket(hackers, promptMsg.cleanContent, role);
-    }
-
-    /**
-     * Adds a new ticket.
-     * @param {Collection<String, User>} hackers
-     * @param {String} question
-     * @param {Role} roleRequested
-     * @private
-     */
-    newTicket(hackers, question, roleRequested) {
-        let ticket = new Ticket(hackers, question, roleRequested, this.ticketCount, this);
-        this.tickets.set(ticket.id, ticket);
-
-        this.setReminder(ticket);
-
-        this.ticketCount ++;
-
-        ticket.setStatus('new');
-    }
-
-    /**
-     * Sets a reminder to a ticket only if reminders are on.
-     * @param {Ticket} ticket 
-     * @private
-     */
-    setReminder(ticket) {
-        // if reminders are on, set a timeout to reminder the main role of this ticket if the ticket is still new
-        if (this.ticketDispatcherInfo.reminderInfo.isEnabled) {
-            let timeout = setTimeout(() => {
-                if (ticket.status === Ticket.STATUS.new) {
-                    ticket.consoles.ticketManager.changeColor('#ff5736');
-                    this.ticketDispatcherInfo.channel.send(`Hello <@&${this.ticketDispatcherInfo.mainHelperInfo.role.id}> ticket number ${ticket.id} still needs help!`)
-                        .then(msg => msg.delete({ timeout: (this.ticketDispatcherInfo.reminderInfo.time * 60 * 1000)/2 }));
-                    // sets another timeout
-                    this.setReminder(ticket);
-                }
-            }, this.ticketDispatcherInfo.reminderInfo.time * 60 * 1000);
-
-            this.ticketDispatcherInfo.reminderInfo.reminders.set(ticket.id, timeout);
-        }
-    }
-
-    /**
-     * Return the number of tickets in this ticket system.
-     * @returns {Number}
-     */
-    getTicketCount() {
-        return this.tickets.size;
-    }
-
-    /**
-     * Removes all the tickets from this ticket manager.
-     * @param {Number[]} [excludeTicketIds=[]] - tickets to be excluded
-     */
-    removeAllTickets(excludeTicketIds = []) {
-        // exclude the tickets
-        let ticketsToRemove;
-        if (excludeTicketIds.length > 0) ticketsToRemove = this.tickets.filter((ticket, ticketId) => excludeTicketIds.includes(ticketId));
-        else ticketsToRemove = this.tickets;
-
-        ticketsToRemove.forEach((ticket, ticketId) => {
-            this.removeTicket(ticketId);
-        });
-    }
-
-    /**
-     * Removes tickets by their ids
-     * @param {Number[]} ticketIds - ticket ids to remove
-     */
-    removeTicketsById(ticketIds) {
-        ticketIds.forEach(ticketId => {
-            this.removeTicket(ticketId);
-        });
-    }
-
-    /**
-     * Removes all tickets older than the given age.
-     * @param {Number} minAge - the minimum age in minutes
-     * @throws Error when used and advanced mode is turned off
-     */
-    removeTicketsByAge(minAge) {
-        // only usable when advanced mode is turned on
-        if (!this.systemWideTicketInfo.isAdvancedMode) throw new Error('Remove by age is only available when advanced mode is on!');
-        this.tickets.forEach((ticket, ticketId, tickets) => {
-            let now = Date.now();
-
-            let timeDif = now - ticket.room.timeCreated;
-
-            if (timeDif > minAge * 50 * 1000) {
-                this.removeTicket(ticketId);
-            }
-        });
-    }
-
-    /**
-     * Removes a ticket, deletes the ticket's channels too!
-     * @param {Number} ticketId - the ticket id to remove
-     */
-    removeTicket(ticketId) {
-        // remove the reminder for this ticket if reminders are on
-        if (this.ticketDispatcherInfo.reminderInfo.isEnabled && this.ticketDispatcherInfo.reminderInfo.reminders.has(ticketId)) {
-            clearTimeout(this.ticketDispatcherInfo.reminderInfo.reminders.get(ticketId));
-            this.ticketDispatcherInfo.reminderInfo.reminders.delete(ticketId);
-        }
-        this.tickets.get(ticketId).setStatus(Ticket.STATUS.closed, 'ticket manager closed the ticket');
-    }
-}
-module.exports = TicketManager;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_tickets_ticket.js.html b/docs/classes_tickets_ticket.js.html deleted file mode 100644 index 729f9be4..00000000 --- a/docs/classes_tickets_ticket.js.html +++ /dev/null @@ -1,569 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/tickets/ticket.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/tickets/ticket.js

-
- - - - - -
-
-
const { Collection, User, Role } = require('discord.js');
-const winston = require('winston');
-const discordServices = require('../../discord-services');
-const Console = require('../consoles/console');
-const Feature = require('../consoles/feature');
-const Room = require('../room');
-const TicketManager = require('./ticket-manager');
-
-class Ticket {
-
-    /**
-     * @typedef TicketConsoles
-     * @property {Console} groupLeader
-     * @property {Console} ticketManager - Message sent to incoming ticket channel for helpers to see.
-     * @property {Console} ticketRoom - The message with the information embed sent to the ticket channel once the ticket is open.
-     */
-
-    /**
-     * @typedef TicketGarbageInfo
-     * @property {Number} noHelperInterval - Interval ID for when there are no more helpers in the ticket
-     * @property {Boolean} mentorDeletionSequence - Flag to check if a deletion sequence has already been triggered by all mentors leaving the ticket; if so, there will not be
-     * another sequence started for inactivity
-     * @property {Boolean} exclude - Flag for whether this ticket is excluded from automatic garbage collection
-     */
-
-    /**
-     * @param {Collection<String, User>} hackers 
-     * @param {String} question 
-     * @param {Role} requesterRole
-     * @param {Number} ticketNumber
-     * @param {TicketManager} ticketManager 
-     */
-    constructor(hackers, question, requestedRole, ticketNumber, ticketManager) {
-
-        /**
-         * Ticket number
-         * @type {Number}
-         */
-        this.id = ticketNumber;
-
-        /**
-         * The room this ticket will be solved in.
-         * @type {Room}
-         */
-        this.room = ticketManager.systemWideTicketInfo.isAdvancedMode ? new Room(ticketManager.parent.guild, ticketManager.parent.botGuild, `Ticket-${ticketNumber}`, undefined, hackers.clone()) : null;
-
-        /**
-         * Question from hacker
-         * @type {String}
-         */
-        this.question = question;
-
-        /**
-         * @type {Role}
-         */
-        this.requestedRole = requestedRole;
-
-        /**
-         * All the group members, group leader should be the first one!
-         * @type {Collection<String, User>} - <ID, User>
-         * Must clone the Map since we edit it.
-         */
-        this.group = hackers.clone();
-
-        /**
-         * Mentors who join the ticket
-         * @type {Collection<String, User>} - <ID, User>
-         */
-        this.helpers = new Collection();
-
-        /**
-         * All the consoles sent out.
-         * GroupLeader -> sent via DM to leader, they can cancel the ticket from there
-         * ticketManager -> sent to the helper channel
-         * ticketRoom -> sent to the ticket room once created for users to leave
-         * @type {TicketConsoles}
-         */
-        this.consoles = {
-            groupLeader: null,
-            ticketManager: null,
-            ticketRoom: null,
-        };
-
-        /**
-         * Garbage collector info.
-         * @type {TicketGarbageInfo}
-         */
-        this.garbageCollectorInfo = {
-            noHelperInterval: null,
-            mentorDeletionSequence: false,
-            exclude: false,
-        };
-
-        /**
-         * The status of this ticket
-         * @type {Ticket.STATUS}
-         */
-        this.status = null;
-
-        /**
-         * @type {TicketManager}
-         */
-        this.ticketManager = ticketManager; 
-    }
-
-    /**
-     * This function is called by the ticket's Cave class to change its status between include/exclude for automatic garbage collection.
-     * If a previously excluded ticket is re-included, the bot starts listening for inactivity as well.
-     * @param {Boolean} exclude - true if ticket is now excluded from garbage collection, false if not
-     */
-    async includeExclude(exclude) {
-        // oldExclude saves the previous inclusion status of the ticket
-        var oldExclude = this.garbageCollectorInfo.exclude;
-        // set excluded variable to new status
-        this.garbageCollectorInfo.exclude = exclude;
-
-        // if this ticket was previously excluded and is now included, start the listener for inactivity
-        if (oldExclude && !exclude) {
-            this.startChannelActivityListener();
-        }
-    }
-
-    /**
-     * Change the status of this ticket.
-     * @param {String} status - one of Ticket.STATUS
-     * @param {String} [reason] - the reason for the change
-     * @param {User} [user] - user involved with the status change
-     * @async
-     */
-    async setStatus(status, reason = '', user) {
-        this.status = status;
-        
-        switch(status) {
-            case Ticket.STATUS.new:
-                // let user know that ticket was submitted and give option to remove ticket
-                await this.contactGroupLeader();
-
-                this.newStatusCallback();
-                break;
-
-            case Ticket.STATUS.taken:
-                if (this.ticketManager.systemWideTicketInfo.isAdvancedMode) await this.advancedTakenStatusCallback(user);
-                else await this.basicTakenStatusCallback(user);
-                break;
-            case Ticket.STATUS.closed:
-                this.delete(reason);
-                break;
-        }
-    }
-
-    /**
-     * The new ticket status callback creates the ticket manager helper console and sends it to the incoming tickets channel.
-     * @private
-     */
-    async newStatusCallback() {
-        const ticketManagerMsgEmbed = this.ticketManager.ticketDispatcherInfo.embedCreator(this);
-
-        this.consoles.ticketManager = new Console({
-            title: ticketManagerMsgEmbed.title,
-            description: ticketManagerMsgEmbed.description,
-            channel: this.ticketManager.ticketDispatcherInfo.channel,
-            guild: this.ticketManager.parent.guild,
-            color: '#fff536'
-        });
-
-        ticketManagerMsgEmbed.fields.forEach((embedField => {
-            this.consoles.ticketManager.addField(embedField.name, embedField.value, embedField.inline);
-        }));
-
-        let joinTicketFeature = Feature.create({
-            name: 'Can you help them?',
-            description: 'If so, react to this message with the emoji!',
-            emoji: this.ticketManager.ticketDispatcherInfo.takeTicketEmoji,
-            callback: (user, reaction, stopInteracting) => {
-                if (this.status === Ticket.STATUS.new) {
-                    this.setStatus(Ticket.STATUS.taken, 'helper has taken the ticket', user);
-                }
-                stopInteracting();
-            }
-        });
-
-        this.consoles.ticketManager.addFeature(joinTicketFeature);
-
-        this.consoles.ticketManager.sendConsole(`<@&${this.requestedRole.id}>`);
-    }
-
-    /**
-     * Contacts the group leader and sends a console with the ability to remove the ticket.
-     * @private
-     */
-    async contactGroupLeader() {
-        let removeTicketEmoji = '⚔️';
-        this.consoles.groupLeader = new Console({
-            title: 'Ticket was Successful!',
-            description: `Your ticket to the ${this.ticketManager.parent.name} group was successful! It is ticket number ${this.id}`,
-            channel: await this.group.first().createDM(),
-            guild: this.ticketManager.parent.guild,
-            features: new Collection([
-                [removeTicketEmoji, {
-                    name: 'Remove the ticket',
-                    description: 'React to this message if you don\'t need help any more!',
-                    emojiName: removeTicketEmoji,
-                    callback: (user, reaction, stopInteracting) => {
-                        // make sure user can only close the ticket if no one has taken the ticket
-                        if (this.status === Ticket.STATUS.new) this.setStatus(Ticket.STATUS.closed, 'group leader closed the ticket');
-                    },
-                }]
-            ]),
-            options: { max: 1 }
-        });
-
-        this.consoles.groupLeader.sendConsole();
-    }
-
-    /**
-     * Callback for status change to taken when ticket manager is NOT in advanced mode.
-     * @param {User} helper - the user who is taking the ticket
-     */
-    async basicTakenStatusCallback(helper) {
-        this.addHelper(helper);
-
-        // edit ticket manager helper console with mentor information
-        await this.consoles.ticketManager.addField('This ticket is being handled!', `<@${helper.id}> is helping this team!`);
-        await this.consoles.ticketManager.changeColor('#36c3ff');
-
-        // update dm with user to reflect that their ticket has been accepted
-        this.consoles.groupLeader.addField('Your ticket has been taken by a helper!', 'Expect a DM from a helper soon!');
-        this.consoles.groupLeader.stopConsole();
-    }
-
-    /**
-     * Callback for status change for when the ticket is taken by a helper.
-     * @param {User} helper - the helper user
-     * @private
-     */
-    async advancedTakenStatusCallback(helper) {
-        await this.room.init();
-
-        // add helper and clear the ticket reminder timeout
-        this.addHelper(helper);
-
-        // edit ticket manager helper console with mentor information
-        await this.consoles.ticketManager.addField('This ticket is being handled!', `<@${helper.id}> is helping this team!`);
-        await this.consoles.ticketManager.changeColor('#36c3ff');
-
-        let takeTicketFeature = Feature.create({
-            name: 'Still want to help?',
-            description: `Click the ${this.ticketManager.ticketDispatcherInfo.joinTicketEmoji.toString()} emoji to join the ticket!`,
-            emoji: this.ticketManager.ticketDispatcherInfo.joinTicketEmoji,
-            callback: (user, reaction, stopInteracting) => {
-                if (this.status === Ticket.STATUS.taken) this.helperJoinsTicket(user);
-                stopInteracting();
-            }
-        });
-        await this.consoles.ticketManager.addFeature(takeTicketFeature);
-
-        // update dm with user to reflect that their ticket has been accepted
-        this.consoles.groupLeader.addField('Your ticket has been taken by a helper!', 'Please go to the corresponding channel and read the instructions there.');
-        this.consoles.groupLeader.stopConsole();
-
-        // send message mentioning all the parties involved so they get a notification
-        let notificationMessage = '<@' + helper.id + '> ' + this.group.array().join(' ');
-        this.room.channels.generalText.send(notificationMessage).then(msg => msg.delete({ timeout: 15000 }));
-
-        let leaveTicketEmoji = '👋🏽';
-
-        this.consoles.ticketRoom = new Console({
-            title: 'Original Question',
-            description: `<@${this.group.first().id}> has the question: ${this.question}`,
-            channel: this.room.channels.generalText,
-            color: this.ticketManager.parent.botGuild.colors.embedColor,
-            guild: this.ticketManager.parent.guild,
-        });
-
-        this.consoles.ticketRoom.addField('Thank you for helping this team.', `<@${helper.id}> best of luck!`);
-        this.consoles.ticketRoom.addFeature({
-            name: 'When done:',
-            description: `React to this message with ${leaveTicketEmoji} to lose access to these channels!`,
-            emojiName: leaveTicketEmoji,
-            callback: (user, reaction, stopInteracting) => {
-                // delete the mentor or the group member that is leaving the ticket
-                this.helpers.delete(user.id);
-                this.group.delete(user.id);
-
-                this.room.removeUserAccess(user);
-
-                // if all hackers are gone, delete ticket channels
-                if (this.group.size === 0) {
-                    this.setStatus(Ticket.STATUS.closed, 'no users on the ticket remaining');
-                }
-
-                // tell hackers all mentors are gone and ask to delete the ticket if this has not been done already 
-                else if (this.helpers.size === 0 && !this.garbageCollectorInfo.mentorDeletionSequence && !this.garbageCollectorInfo.exclude) {
-                    this.garbageCollectorInfo.mentorDeletionSequence = true;
-                    this.askToDelete('mentor');
-                }
-
-                stopInteracting();
-            }
-        });
-
-        this.consoles.ticketRoom.sendConsole();
-
-        //create a listener for inactivity in the text channel
-        this.startChannelActivityListener();
-    }
-
-    /**
-     * Callback for collector for when a new helper joins the ticket.
-     * @param {User} helper - the new helper user
-     * @private
-     */
-    helperJoinsTicket(helper) {
-        this.addHelper(helper, this.garbageCollectorInfo.noHelperInterval);
-
-        discordServices.sendMsgToChannel(this.room.channels.generalText, helper.id, 'Has joined the ticket!', 10);
-
-        // update the ticket manager and ticket room embeds with the new mentor
-        this.consoles.ticketManager.addField('More hands on deck!', '<@' + helper.id + '> Joined the ticket!');
-        this.consoles.ticketRoom.addField('More hands on deck!', '<@' + helper.id + '> Joined the ticket!');
-    }
-
-    /**
-     * Adds a helper to the ticket.
-     * @param {User} user - the user to add to the ticket as a helper
-     * @param {NodeJS.Timeout} [timeoutId] - the timeout to clear due to this addition
-     * @private
-     */
-    addHelper(user, timeoutId) {
-        this.helpers.set(user.id, user);
-        if (this.room) this.room.giveUserAccess(user);
-        if (timeoutId) clearTimeout(timeoutId);
-    }
-
-    /**
-     * Main deletion sequence: mentions and asks hackers if ticket can be deleted, and deletes if there is no response or indicates that
-     * it will check in again later if someone does respond
-     * @param {String} reason - 'mentor' if this deletion sequence was initiated by the last mentor leaving, 'inactivity' if initiated by
-     * inactivity in the text channel
-     * @private
-     */
-    async askToDelete(reason) {
-        // assemble message to send to hackers to verify if they still need the ticket
-        let msgText = `${this.group.array().map(user => '<@' + user.id + '>').join(' ')} `;
-        if (reason === 'inactivity') {
-            msgText += `${this.helpers.array().map(user => '<@' + user.id + '>').join(' ')} Hello! I detected some inactivity on this channel and wanted to check in.\n`;
-        } else if (reason === 'mentor') {
-            msgText += 'Hello! Your mentor(s) has/have left the ticket.\n';
-        }
-
-        let warning = await this.room.channels.generalText.send(`${msgText} If the ticket has been solved, please click the 👋 emoji above 
-            to leave the channel. If you need to keep the channel, please click the emoji below, 
-            **otherwise this ticket will be deleted in ${this.ticketManager.systemWideTicketInfo.garbageCollectorInfo.bufferTime} minutes**.`);
-
-        await warning.react('🔄');
-
-        // reaction collector to listen for someone to react with the emoji for more time
-        const deletionCollector = warning.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === '🔄', { time: this.ticketManager.systemWideTicketInfo.garbageCollectorInfo.bufferTime * 60 * 1000, max: 1 });
-        
-        deletionCollector.on('end', async (collected) => {
-            // if a channel has already been deleted by another process, stop this deletion sequence
-            if (collected.size === 0 && !this.garbageCollectorInfo.exclude && this.status != Ticket.STATUS.closed) { // checks to see if no one has responded and this ticket is not exempt
-                this.setStatus(Ticket.STATUS.closed, 'inactivity');
-            } else if (collected.size > 0) {
-                await this.room.channels.generalText.send('You have indicated that you need more time. I\'ll check in with you later!');
-
-                // set an interval to ask again later
-                this.garbageCollectorInfo.noHelperInterval = setInterval(() => this.askToDelete(reason), this.ticketManager.systemWideTicketInfo.garbageCollectorInfo.inactivePeriod * 60 * 1000);
-            }
-        });
-    }
-
-    /**
-     * Uses a message collector to see if there is any activity in the room's text channel. When the collector ends, if it collected 
-     * no messages and there is no one on the voice channels then ask to delete and listen once again.
-     * @async
-     * @private
-     */
-    async startChannelActivityListener() {
-        // message collector that stops when there are no messages for inactivePeriod minutes
-        const activityListener = this.room.channels.generalText.createMessageCollector(m => !m.author.bot, { idle: this.ticketManager.systemWideTicketInfo.garbageCollectorInfo.inactivePeriod * 60 * 1000 });
-        activityListener.on('end', async collected => {
-            if (collected.size === 0 && this.room.channels.generalVoice.members.size === 0 && this.status === Ticket.STATUS.taken) {
-                await this.askToDelete('inactivity');
-                
-                // start listening again for inactivity in case they ask for more time
-                this.startChannelActivityListener(); 
-            }
-        });
-    }
-
-    /**
-     * Deletes the ticket, the room and the intervals.
-     * @param {String} reason - the reason to delete the ticket
-     * @private
-     */
-    delete(reason) {
-        // update ticketManager msg and let user know the ticket is closed
-        this.consoles.ticketManager.addField(
-            'Ticket Closed', 
-            `This ticket has been closed${reason ? ' due to ' + reason : '!! Good job!'}`
-        );
-        this.consoles.ticketManager.changeColor('#43e65e');
-        this.consoles.ticketManager.stopConsole();
-        
-        this.consoles.groupLeader.addField(
-            'Ticket Closed!', 
-            `Your ticket was closed due to ${reason}. If you need more help, please request another ticket!`
-        );
-        this.consoles.groupLeader.stopConsole();
-
-        // delete the room, clear intervals
-        if (this.room) this.room.delete();
-        clearInterval(this.garbageCollectorInfo.noHelperInterval);
-        
-        if (this.consoles?.ticketRoom) this.consoles.ticketRoom.stopConsole();
-    }
-}
-
-/**
- * The possible status of the ticket.
- * @enum {String}
- * @static
- */
-Ticket.STATUS = {
-    /** Ticket is open for someone to take. */
-    new: 'new',
-    /** Ticket has been dealt with and is closed. */
-    closed: 'closed',
-    /** Ticket is being handled by someone. */
-    taken: 'taken',
-};
-
-module.exports = Ticket;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/classes_verification.js.html b/docs/classes_verification.js.html deleted file mode 100644 index 3ed2b1ee..00000000 --- a/docs/classes_verification.js.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - - - Factotum Documentation classes/verification.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

classes/verification.js

-
- - - - - -
-
-
const { GuildMember, Guild } = require('discord.js');
-const discordServices = require('../discord-services');
-const firebaseServices = require('../db/firebase/firebase-services');
-const BotGuildModel = require('./bot-guild');
-const winston = require('winston');
-
-/**
- * @class Verification
- */
-class Verification {
-
-    /**
-     * Verifies a guild member into a guild.
-     * @param {GuildMember} member - member to verify
-     * @param {String} email - email to verify with
-     * @param {Guild} guild
-     * @param {BotGuildModel} botGuild
-     * @async
-     * @static
-     * @throws Error if email is not valid!
-     */
-    static async verify(member, email, guild, botGuild) {
-        if (!discordServices.validateEmail(email)) {
-            throw new Error('Email is not valid!!');
-        }
-
-        let logger = winston.loggers.get(guild.id);
-
-        // try to get member types, error will mean no email was found
-        try {
-            var types = await firebaseServices.verify(email, member.id, member.guild.id);
-        } catch (error) {
-            logger.warning(`The email provided (${email}) by user ${member.id} was not found, got an error: ${error}`, { event: 'Verification' });
-            discordServices.sendEmbedToMember(member, {
-                title: 'Verification Failure',
-                description: 'The email provided was not found! If you need assistance ask an admin for help!',
-            });
-            discordServices.discordLog(guild, `VERIFY FAILURE : <@${member.id}> Verified email: ${email} but was a failure, I could not find that email!`);
-            return;
-        }
-
-        // check for types, if no types it means they are already verified with those types
-        if (types.length === 0) {
-            logger.warning(`An email was found but the user ${member.id} is already set as verified. Email used: ${email}`, { event: 'Verification' });
-            discordServices.sendEmbedToMember(member, {
-                title: 'Verification Warning',
-                description: 'We found your email, but you are already verified! If this is not the case let an admin know!',
-                color: '#fc1403',
-            });
-            discordServices.discordLog(guild, `VERIFY WARNING : <@${member.id}> Verified email: ${email} but he was already verified for all types!`);
-            return;
-        }
-
-        let correctTypes = [];
-        
-        // check for correct types with botGuild verification info and give the roles
-        types.forEach((type, index, array) => {
-            if (botGuild.verification.verificationRoles.has(type)) {
-                let roleId = botGuild.verification.verificationRoles.get(type);
-                logger.verbose(`User ${member.id} has type ${type} in list index ${index} and it was found, he got the role ${roleId}`, { event: 'Verification' });
-                discordServices.addRoleToMember(member, roleId);
-                correctTypes.push(type);
-            } else {
-                logger.error(`User ${member.id} has type ${type} in list index ${index} and it was not found in the botGuild verification roles map.`, { event: 'Verification' });
-            }
-
-        });
-
-        // extra check to see if types were found, give stamp role if available and let user know of success
-        if (correctTypes.length > 0) {
-            discordServices.replaceRoleToMember(member, botGuild.verification.guestRoleID, botGuild.roleIDs.memberRole);
-            if (botGuild.stamps.isEnabled) discordServices.addRoleToMember(member, botGuild.stamps.stamp0thRoleId);
-            discordServices.sendEmbedToMember(member, {
-                title: `${guild.name} Verification Success`,
-                description: `You have been verified as a ${correctTypes.join()}, good luck and have fun!`,
-                color: botGuild.colors.specialDMEmbedColor,
-            });
-            discordServices.discordLog(guild, `VERIFY SUCCESS : <@${member.id}> Verified email: ${email} successfully as ${correctTypes.join()}.`);
-            logger.event(`User ${member.id} was verified with email ${email} successfully as ${correctTypes.join()}.`, { event: 'Verification' });
-        } else {
-            discordServices.sendEmbedToMember(member, {
-                title: 'Verification Error',
-                description: 'There has been an error, contact an admin ASAP!',
-                color: '#fc1403',
-            });
-            discordServices.discordLog(guild, `VERIFY ERROR : <@${member.id}> Verified email: ${email} had types available, but I could not find them on the botGuild!`);
-            logger.error(`User ${member.id} Verified email: ${email} had types available, but I could not find them on the botGuild!`, { event: 'Verification' });
-        }
-    }
-
-
-    /**
-     * Will attend the user and give it the attendee role.
-     * @param {GuildMember} member 
-     * @param {BotGuildModel} botGuild
-     */
-    static async attend(member, botGuild) {
-        try {
-            // wait for attend to end, then give role
-            await firebaseServices.attend(member.id, member.guild.id);
-            discordServices.addRoleToMember(member, botGuild.attendance.attendeeRoleID);
-            discordServices.sendEmbedToMember(member, {
-                title: 'Attendance Success',
-                description: 'You have been marked as attending, thank you and good luck!',
-                color: botGuild.colors.specialDMEmbedColor,
-            });
-            discordServices.discordLog(member.guild, `ATTEND SUCCESS : <@${member.id}> has been marked as attending!`);
-            winston.loggers.get(botGuild._id).event(`User ${member.id} was marked as attending!`, { event: 'Verification' });
-        } catch (error) {
-            // email was not found, let admins know!
-            discordServices.discordLog(member.guild, `ATTEND WARNING : <@${member.id}> tried to attend but I could not find his discord ID! He might be an impostor!!!`);
-            winston.loggers.get(botGuild._id).warning(`User ${member.id} could not be marked as attending, I could not find his discord ID, he could be an impostor! Got the error: ${error}`, { event: 'Verification' });
-        }
-    }
-
-}
-module.exports = Verification;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_activity_discord-contests.js.html b/docs/commands_a_activity_discord-contests.js.html deleted file mode 100644 index 0eccca80..00000000 --- a/docs/commands_a_activity_discord-contests.js.html +++ /dev/null @@ -1,354 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_activity/discord-contests.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_activity/discord-contests.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { discordLog, checkForRole } = require('../../discord-services');
-const { Message, MessageEmbed, Snowflake } = require('discord.js');
-const { getQuestion } = require('../../db/firebase/firebase-services');
-const BotGuildModel = require('../../classes/bot-guild');
-const { NumberPrompt, SpecialPrompt, RolePrompt, MemberPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * The DiscordContests class handles all functions related to Discord contests. It will ask questions in set intervals and pick winners
- * based on keywords for those questions that have correct answers. For other questions it will tag staff and staff will be able to tell
- * it the winner. It can also be paused and un-paused, and questions can be removed.
- * 
- * Note: all answers are case-insensitive but any extra or missing characters will be considered incorrect.
- * @category Commands
- * @subcategory Activity
- * @extends PermissionCommand
- * @guildonly
- */
-class DiscordContests extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'discord-contest',
-            group: 'a_utility',
-            memberName: 'handle discord contest',
-            description: 'Sends each Discord contest question once at designated times and determines winners.',
-            guildOnly: true,
-        },
-        {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'Hey there, the command !contests is only available to Staff!',
-        });
-    }
-
-    /**
-     * Stores a map which keeps the questions (strings) as keys and an array of possible answers (strings) as values. It iterates through
-     * each key in order and asks them in the Discord channel in which it was called at the given intervals. It also listens for emojis
-     * that tell it to pause, resume, or remove a specified question. 
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message - the message in which this command was called
-     */
-    async runCommand(botGuild, message) {
-        // helpful prompt vars
-        let channel = message.channel;
-        let userId = message.author.id;
-        this.botGuild = botGuild;
-
-        var interval;
-
-        //ask user for time interval between questions
-        var timeInterval;
-        try {
-            let num = await NumberPrompt.single({prompt: 'What is the time interval between questions in minutes (integer only)? ', channel, userId, cancelable: true});
-            timeInterval = 1000 * 60 * num;
-
-            // ask user whether to start asking questions now(true) or after 1 interval (false)
-            var startNow = await SpecialPrompt.boolean({prompt: 'Type "yes" to start first question now, "no" to start one time interval from now. ', channel, userId, cancelable: true});
-
-            // id of role to mention when new questions come out
-            var roleId = (await RolePrompt.single({prompt: 'What role should I notify with a new Discord contest is available?', channel, userId})).id;
-        } catch (error) {
-            channel.send('<@' + userId + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000}));
-            return;
-        }
-
-        //paused keeps track of whether it has been paused
-        var paused = false;        
-
-        /**
-         * array of winners' ids
-         * @type {Array<Snowflake>}
-         */
-        const winners = [];
-
-        var string;
-        if (startNow) {
-            string = 'Discord contests starting now! Answer for a chance to win a prize!';
-        } else {
-            const time = new Date();
-            //calculate time till next interval to display as the start time if startNow is false
-            const nextQTime = time.valueOf() + timeInterval;
-            let options = { weekday: 'long', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', timeZoneName: 'short'};
-            var nextTime = new Date(nextQTime).toLocaleString('en-US', options);
-            string = 'Discord contests starting at ' + nextTime + '! Answer for a chance to win a prize!';
-        }
-
-        const startEmbed = new MessageEmbed()
-            .setColor(this.botGuild.colors.embedColor)
-            .setTitle(string)
-            .setDescription('Note: Questions that have correct answers are non-case sensitive but any extra or missing symbols will be considered incorrect.\n' +
-                'For Staff only:\n' +
-                '⏸️ to pause\n' +
-                '⏯️ to resume\n');
-
-        message.channel.send('<@&' + roleId + '>', { embed: startEmbed }).then((msg) => {
-            msg.pin();
-            msg.react('⏸️');
-            msg.react('⏯️');
-
-            //filters so that it will only respond to Staff who reacted with one of the 3 emojis 
-            const emojiFilter = (reaction, user) => !user.bot && (reaction.emoji.name === '⏸️' || reaction.emoji.name === '⏯️') && message.guild.member(user).roles.cache.has(this.botGuild.roleIDs.staffRole);
-            const emojiCollector = msg.createReactionCollector(emojiFilter);
-            
-            emojiCollector.on('collect', (reaction, user) => {
-                reaction.users.remove(user.id);
-                if (reaction.emoji.name === '⏸️') {
-                    //if it isn't already paused, pause by clearing the interval
-                    if (interval != null && !paused) {
-                        clearInterval(interval);
-                        paused = true;
-                        message.channel.send('<@' + user.id + '> Discord contest has been paused!').then(msg => msg.delete({timeout: 10000}));
-                    }
-                } else if (reaction.emoji.name === '⏯️') {
-                    //if it is currently paused, restart the interval and send the next question immediately
-                    if (paused) {
-                        sendQuestion();
-                        interval = setInterval(sendQuestion, timeInterval);
-                        paused = false;
-                        message.channel.send('<@' + user.id + '> Discord contest has been un-paused!').then(msg => msg.delete({timeout: 10000}));
-                    }
-                } 
-            });
-        });
-
-        //starts the interval, and sends the first question immediately if startNow is true
-        if (startNow) {
-            sendQuestion();
-        }
-        interval = setInterval(sendQuestion, timeInterval);
-
-        /**
-         * sendQuestion is the function that picks and sends the next question, then picks the winner by matching participants' messages
-         * against the answer(s) or receives the winner from Staff. Once it reaches the end it will notify Staff in the Logs channel and
-         * list all the winners in order.
-         */
-        async function sendQuestion() {
-            //get question's parameters from db 
-            var data = await getQuestion(message.guild.id);
-            
-            //sends results to Staff after all questions have been asked and stops looping
-            if (data === null) {
-                discordLog(message.guild, '<@&' + this.botGuild.roleIDs.staffRole + '> Discord contests have ended! Winners are: <@' + winners.join('> <@') + '>');
-                clearInterval(interval);
-                return;
-            }
-
-            let question = data.question;
-            let answers = data.answers;
-            let needAllAnswers = data.needAllAnswers;
-
-            const qEmbed = new MessageEmbed()
-                .setColor(this.botGuild.colors.embedColor)
-                .setTitle('A new Discord Contest Question:')
-                .setDescription(question + '\n' + ((answers.length === 0) ? 'Staff: click the 👑 emoji to announce a winner!' : 
-                    'Exact answers only!'));
-
-
-            message.channel.send('<@&' + roleId + '>' + ((answers.length === 0) ? (' - <@&' + this.botGuild.roleIDs.staffRole + '> Need manual review!') : ''), { embed: qEmbed }).then((msg) => {
-                if (answers.length === 0) {
-                    msg.react('👑');
-
-                    const emojiFilter = (reaction, user) => !user.bot && (reaction.emoji.name === '👑') && checkForRole(message.guild.member(user), this.botGuild.roleIDs.staffRole);
-                    const emojiCollector = msg.createReactionCollector(emojiFilter);
-
-                    emojiCollector.on('collect', (reaction, user) => {
-                        //once someone from Staff hits the crown emoji, tell them to mention the winner in a message in the channel
-                        reaction.users.remove(user.id);
-
-                        MemberPrompt.single({prompt: 'Pick a winner for the previous question by mentioning them in your next message in this channel!', channel: message.channel, userId: user.id, cancelable: true})
-                            .then(member => {
-                                winners.push(member.id);
-                                message.channel.send('Congrats <@' + member.id + '> for the best answer to the previous question!');
-                                emojiCollector.stop();
-                            }).catch( () => {
-                                msg.channel.send('<@' + user.id + '> You have canceled the prompt, you can select a winner again at any time.').then(msg => msg.delete({timeout: 8000}));
-                            });
-                    });
-
-                    emojiCollector.on('end', () => {
-                        message.channel.send('Answers are no longer being accepted. Stay tuned for the next question!');
-                    });
-                } else {
-                    //automatically mark answers
-                    const filter = m => !m.author.bot;
-                    const collector = message.channel.createMessageCollector(filter, { time: timeInterval * 0.75 });
-
-                    collector.on('collect', m => {
-                        if (!needAllAnswers) {
-                            // for questions that have numbers as answers, the answer has to match at least one of the correct answers exactly
-                            if (!isNaN(answers[0])) {
-                                if (answers.some(correctAnswer => m.content === correctAnswer)) {
-                                    message.channel.send('Congrats <@' + m.author.id + '> for getting the correct answer! The answer key is ' + answers.join(' or ') + '.');
-                                    winners.push(m.author.id);
-                                    collector.stop();
-                                }
-                            } else if (answers.some(correctAnswer => m.content.toLowerCase().includes(correctAnswer.toLowerCase()))) {
-                                //for most questions, an answer that contains at least once item of the answer array is correct
-                                message.channel.send('Congrats <@' + m.author.id + '> for getting the correct answer! The answer key is ' + answers.join(' or ') + '.');
-                                winners.push(m.author.id);
-                                collector.stop();
-                            }
-                        } else {
-                            //check if all answers in answer array are in the message
-                            if (answers.every((answer) => m.content.toLowerCase().includes(answer.toLowerCase()))) {
-                                message.channel.send('Congrats <@' + m.author.id + '> for getting the correct answer! The answer key is ' + answers.join(', ') + '.');
-                                winners.push(m.author.id);
-                                collector.stop();
-                            }
-                        }
-                    });
-
-                    collector.on('end', () => {
-                        message.channel.send('Answers are no longer being accepted. Stay tuned for the next question!');
-                    });
-                }
-            });
-        }
-    }
-}
-module.exports = DiscordContests;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_activity_new-activity.js.html b/docs/commands_a_activity_new-activity.js.html deleted file mode 100644 index 2abf2774..00000000 --- a/docs/commands_a_activity_new-activity.js.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_activity/new-activity.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_activity/new-activity.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { Message } = require('discord.js');
-const Activity = require('../../classes/activities/activity');
-
-/**
- * Creates a new activity and prompts the user for any information.
- * Look at the [activity]{@link Activity} class to learn what an activity is.
- * @category Commands
- * @subcategory Activity
- * @extends PermissionCommand
- * @guildonly
- */
-class NewActivity extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'new-activity',
-            group: 'a_activity',
-            memberName: 'create a new activity',
-            description: 'Will create a new activity.',
-            guildOnly: true,
-            args: [
-                {
-                    key: 'activityName',
-                    prompt: 'the activity name, can use emojis!',
-                    type: 'string',
-                },
-            ],
-        },
-        {
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'This command can only be used in the admin console!',
-            role: PermissionCommand.FLAGS.ADMIN_ROLE,
-            roleMessage: 'You do not have permission for this command, only admins can use it!',
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message - the message in which the command was run
-     * @param {Object} args
-     * @param {String} args.activityName
-     */
-    async runCommand(botGuild, message, {activityName}) {
-
-        let allowedRoles = await Activity.promptForRoleParticipants(message.channel, message.author.id, true);
-        let activity = new Activity({ activityName, guild: message.guild, roleParticipants: allowedRoles, botGuild});
-        await activity.init();
-
-    }
-}
-module.exports = NewActivity;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_activity_new-coffee-chats.js.html b/docs/commands_a_activity_new-coffee-chats.js.html deleted file mode 100644 index 1cea3407..00000000 --- a/docs/commands_a_activity_new-coffee-chats.js.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_activity/new-coffee-chats.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_activity/new-coffee-chats.js

-
- - - - - -
-
-
const { replyAndDelete } = require('../../discord-services');
-const { Message } = require('discord.js');
-const Activity = require('../../classes/activities/activity');
-const PermissionCommand = require('../../classes/permission-command');
-const CoffeeChats = require('../../classes/activities/coffee-chats');
-
-/**
- * Creates a new coffee chats activity.
- * See the [coffee chats]{@link CoffeeChats} class to learn what a coffee chats activity is.
- * @category Commands
- * @subcategory Activity
- * @extends PermissionCommand
- * @guildonly
- */
-class NewCoffeeChats extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'new-coffee-chats',
-            group: 'a_activity',
-            memberName: 'initialize coffee chat functionality for activity',
-            description: 'Will initialize the coffee chat functionality for the given workshop.',
-            guildOnly: true,
-            args: [
-                {
-                    key: 'activityName',
-                    prompt: 'the activity name, can use emojis!',
-                    type: 'string',
-                },
-                {
-                    key: 'numOfGroups',
-                    prompt: 'number of groups to participate in coffee chat',
-                    type: 'integer'
-                },
-            ],
-        },
-        {
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'This command can only be used in the admin console!',
-            role: PermissionCommand.FLAGS.ADMIN_ROLE,
-            roleMessage: 'You do not have permission for this command, only admins can use it!',
-        });
-    }
-
-    /**
-     * Required class by children, should contain the command code.
-     * @param {Message} message - the message that has the command
-     * @param {Activity} activity - the activity for this activity command
-     * @param {Object} args
-     * @param {String} args.activityName
-     * @param {Number} args.numOfGroups
-     */
-    async runCommand(botGuild, message, { activityName, numOfGroups }) {
-
-        let roleParticipants = await Activity.promptForRoleParticipants(message.channel, message.author.id, true);
-
-        let coffeeChats = await new CoffeeChats({activityName: activityName, guild: message.guild, roleParticipants: roleParticipants, botGuild: botGuild}, numOfGroups).init(message.channel, message.author.id);
-
-        // report success of coffee chat creation
-        replyAndDelete(message,'Activity named: ' + activityName + ' now has coffee chat functionality.');
-    }
-}
-module.exports = NewCoffeeChats;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_activity_new-workshop.js.html b/docs/commands_a_activity_new-workshop.js.html deleted file mode 100644 index 2ca56562..00000000 --- a/docs/commands_a_activity_new-workshop.js.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_activity/new-workshop.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_activity/new-workshop.js

-
- - - - - -
-
-
const { replyAndDelete } = require('../../discord-services');
-const { Message, Collection } = require('discord.js');
-const BotGuildModel = require('../../classes/bot-guild');
-const Workshop = require('../../classes/activities/workshop');
-const PermissionCommand = require('../../classes/permission-command');
-const Activity = require('../../classes/activities/activity');
-const { SpecialPrompt, RolePrompt } = require('advanced-discord.js-prompts');
-
-/**
- * Creates a new Workshop and prompts the user for any information.
- * Take a look at the [workshop]{@link Workshop} class to learn what a workshop activity is.
- * @category Commands
- * @subcategory Activity
- * @extends PermissionCommand
- * @guildonly
- */
-class NewWorkshop extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'new-workshop',
-            group: 'a_activity',
-            memberName: 'initialize workshop functionality for activity',
-            description: 'Will initialize the workshop functionality for the given workshop. General voice channel will be muted for all hackers.',
-            guildOnly: true,
-            args: [
-                {
-                    key: 'activityName',
-                    prompt: 'the activity name, can use emojis!',
-                    type: 'string',
-                },
-            ],
-        },
-        {
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'This command can only be used in the admin console!',
-            role: PermissionCommand.FLAGS.ADMIN_ROLE,
-            roleMessage: 'You do not have permission for this command, only admins can use it!',
-        });
-    }
-
-    /**
-     * Required class by children, should contain the command code.
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message - the message that has the command
-     * @param {Object} args
-     * @param {String} args.activityName - the activity for this activity command
-     */
-    async runCommand(botGuild, message, {activityName}) {
-
-
-        // prompt user for roles that will be allowed to see this activity.
-        let roleParticipants = await Activity.promptForRoleParticipants(message.channel, message.author.id, true);
-
-
-        // prompt user for roles that will have access to the TA side of the workshop
-        var TARoles = new Collection();
-        try {
-            TARoles = await RolePrompt.multi({prompt: 'What roles will have access to the TA portion of this workshop?', channel: message.channel, userId: message.author.id});
-        } catch (error) {
-            // do nothing if canceled
-        }
-
-        let isLowTechSolution = await SpecialPrompt.boolean({ prompt: `Would you like to use the low tech solution? Else the high tech solution will be used. 
-            \n Low tech solution involves TAs reaching out to users via DM. 
-            \n High tech solution involves users and TAs being in voice channels and being moved to a common voice channel to give/receive assistance. 
-            \n We recommend the low tech solution!`, channel: message.channel, userId: message.author.id});
-
-
-        let workshop = await new Workshop({activityName, guild: message.guild, roleParticipants, botGuild}, isLowTechSolution,TARoles).init();
-
-        workshop.sendConsoles();
-
-        // report success of workshop creation
-        replyAndDelete(message, 'Activity named: ' + activityName + ' was created as a workshop!');
-    }
-}
-module.exports = NewWorkshop;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_boothing_e-room-directory.js.html b/docs/commands_a_boothing_e-room-directory.js.html deleted file mode 100644 index 1677bf49..00000000 --- a/docs/commands_a_boothing_e-room-directory.js.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_boothing/e-room-directory.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_boothing/e-room-directory.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { Message, MessageEmbed, Role, Collection} = require('discord.js');
-const { deleteMessage } = require('../../discord-services');
-const BotGuildModel = require('../../classes/bot-guild');
-const winston = require('winston');
-const { StringPrompt, RolePrompt, SpecialPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * Shows an embed with a link used for activities happening outside discord. Initial intent was to be used for 
- * sponsor booths. A specified role can open and close the rooms as they want. When rooms open, a specified role is notified.
- * @category Commands
- * @subcategory Boothing
- * @extends PermissionCommand
- * @guildonly
- */
-class ERoomDirectory extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'e-room-directory',
-            group: 'a_boothing',
-            memberName: 'keep track of booths',
-            description: 'Sends embeds to booth directory to notify hackers of booth statuses',
-            guildOnly: true,
-        },
-        {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'This command can only be ran by staff!',
-        });
-    }
-
-    /**
-     * Sends an embed same channel with the sponsor's name and link to their Zoom boothing room. The embed has 2 states: Open and Closed. 
-     * In the Closed state the embed will be red and say the booth is closed, which is the default, and the bot will react to the embed with 
-     * a door emoji at the beginning. In the Open state the embed will be green and say the booth is open. Any time a staff or sponsor clicks 
-     * on that emoji, the embed changes to the other state. When a booth goes from Closed to Open, it will also notify a role (specified by 
-     * the user) that it is open.
-     * 
-     * @param {Message} message - messaged that called this command
-     * @param {BotGuildModel} botGuild
-     */
-    async runCommand(botGuild, message) {
-
-        // helpful vars
-        let channel = message.channel;
-        let userId = message.author.id;
-
-        try {
-            var sponsorName = await StringPrompt.single({prompt: 'What is the room name?', channel, userId, cancelable: true});
-            sponsorName = sponsorName.content;
-
-            var link = await StringPrompt.single({prompt: 'What is the room link? We will add no words to it! (ex. <Room Name> is Currently Open).', channel, userId, cancelable: true});
-            link = link.content;
-
-            //ask user for role and save its id in the role variable
-            var role = (await RolePrompt.single({prompt: 'What role will get pinged when the rooms open?', channel, userId})).id;
-        } catch (error) {
-            channel.send('<@' + userId + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000}));
-            return;
-        }
-
-        /**
-         * prompt for roles that can open/close the room
-         * @type {Collection<String, Role>}
-         */
-        var roomRoles;
-        try {
-            roomRoles = await RolePrompt.multi({ prompt: 'What other roles can open/close the room? (Apart form staff).', channel, userId, cancelable: true });
-        } catch (error) {
-            // do nothing as this is fine
-            winston.loggers.get(message.guild.id).warning(`Got an error: ${error} but I let it go since we expected it from the prompt.`, { event: 'E-Room-Directory Command' });
-        }
-        // add staff role
-        roomRoles.set(botGuild.roleIDs.staffRole, message.guild.roles.resolve(botGuild.roleIDs.staffRole));
-
-        // prompt user for emoji to use
-        let emoji = await SpecialPrompt.singleEmoji({prompt: 'What emoji do you want to use to open/close the room?', channel, userId});
-    
-        //variable to keep track of state (Open vs Closed)
-        var closed = true;
-        //embed for closed state
-        const embed = new MessageEmbed()
-            .setColor('#FF0000')
-            .setTitle(sponsorName + ' is Currently Closed')
-            .setDescription('Room link: ' + link);
-        
-        //send closed embed at beginning (default is Closed)
-        channel.send(embed).then((msg) => {
-            msg.pin();
-            msg.react(emoji);
-
-            //only listen for the door react from users that have one of the roles in the room roles collection
-            const emojiFilter = (reaction, user) => {
-                let member = message.guild.member(user);
-                return !user.bot && reaction.emoji.name === emoji.name && roomRoles.some(role => member.roles.cache.has(role.id));
-            };
-            const emojiCollector = msg.createReactionCollector(emojiFilter);
-            
-            var announcementMsg;
-
-            emojiCollector.on('collect', async (reaction, user) => {
-                reaction.users.remove(user);
-                if (closed) {
-                    //embed for open state
-                    const openEmbed = new MessageEmbed()
-                        .setColor('#008000')
-                        .setTitle(sponsorName + ' \'s Booth is Currently Open')
-                        .setDescription('Please visit this Zoom link to join: ' + link);
-                    //change to open state embed if closed is true
-                    msg.edit(openEmbed);
-                    closed = false;
-                    //notify people of the given role that booth is open and delete notification after 5 mins
-                    announcementMsg = await channel.send('<@&' + role + '> ' + sponsorName + ' \'s booth has just opened!');
-                    announcementMsg.delete({timeout: 300 * 1000});
-                } else {
-                    //change to closed state embed if closed is false
-                    msg.edit(embed);
-                    closed = true;
-                    deleteMessage(announcementMsg);
-                }
-            });
-        });
-    }
-}
-module.exports = ERoomDirectory;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_start_commands_start-channel-creation.js.html b/docs/commands_a_start_commands_start-channel-creation.js.html deleted file mode 100644 index c4660bd8..00000000 --- a/docs/commands_a_start_commands_start-channel-creation.js.html +++ /dev/null @@ -1,264 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_start_commands/start-channel-creation.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_start_commands/start-channel-creation.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { sendEmbedToMember, replyAndDelete } = require('../../discord-services');
-const { Message, MessageEmbed } = require('discord.js');
-const BotGuildModel = require('../../classes/bot-guild');
-const { StringPrompt, MessagePrompt } = require('advanced-discord.js-prompts');
-const ChannelPrompt = require('advanced-discord.js-prompts/prompts/channel-prompt');
-
-/**
- * The start channel creation command lets users create private channels for them to use.
- * Users can create voice or text channels, invite as many people as they want when created and name the channels whatever they want.
- * A category and channel is created for this feature. The new channels are created inside this category.
- * Users can delete the channel by reacting to a message in their DMs with the bot.
- * THERE IS A LIMIT OF CHANNELS! Categories can only have up to 50 channels, if you expect more than 50 channels please DO NOT USE THIS FEATURE.
- * @category Commands
- * @subcategory Start-Commands
- * @extends PermissionCommand
- * @guildonly
- */
-class StartChannelCreation extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'start-channel-creation',
-            group: 'a_start_commands',
-            memberName: 'start channel creation',
-            description: 'Send a message with emoji collector, for each emoji bot will ask type and other friends invited and create the private channel.',
-            guildOnly: true,
-        },
-        {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'Hey there, the !start-channel-creation command is only for staff!',
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'Hey there, the !start-channel-creation command is only available in the admin console channel.',
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message - the message in which the command was run
-     */
-    async runCommand(botGuild, message) {
-
-        try {
-            // grab current channel
-            var channel = await ChannelPrompt.single({prompt: 'What channel do you want to use? The channel\'s category will be used to create the new channels.', channel: message.channel, userId: message.author.id});
-        } catch (error) {
-            message.channel.send('<@' + message.author.id + '> The command has been canceled due to the prompt cancel.').then(msg => msg.delete({timeout: 5000}));
-            return;
-        }
-
-        // grab channel creation category and update permissions
-        var category = channel.parent;
-        category.updateOverwrite(botGuild.roleIDs.everyoneRole, {
-            VIEW_CHANNEL: false,
-        });
-
-        channel.updateOverwrite(botGuild.roleIDs.everyoneRole, {
-            VIEW_CHANNEL: true,
-        });
-
-        
-        // create and send embed message to channel with emoji collector
-        const msgEmbed = new MessageEmbed()
-            .setColor(botGuild.colors.embedColor)
-            .setTitle('Private Channel Creation')
-            .setDescription('Do you need a private channel to work with your friends? Or a voice channel to get to know a mentor, here you can create private text or voice channels.' +
-                ' However do know that server admins will have access to these channels, and the bot will continue to monitor for bad language, so please follow the rules!')
-            .addField('Ready for a channel of your own?', 'Just react this message with any emoji and the bot will ask you a few simple questions.');
-        
-        var cardMessage = await channel.send(msgEmbed);
-        cardMessage.pin();
-
-        // main collector works with any emoji
-        var mainCollector = cardMessage.createReactionCollector(m => true);
-
-        mainCollector.on('collect', async (reaction, user) => {
-            // helpful vars
-            let userId = user.id;
-
-            try {
-                let channelType = await StringPrompt.restricted({prompt: 'Do you want a "voice" or "text" channel?', channel, userId, time: 20, cancelable: true}, ['voice', 'text']);
-
-                let guests = (await MessagePrompt.prompt({prompt: 'Please tag all the invited users to this private ' + channelType + ' channel. Type "none" if no guests are welcomed.', channel, userId, time: 60, cancelable: true})).mentions.members;
-
-                let channelName = await StringPrompt.single({prompt: 'What do you want to name the channel? If you don\'t care then send "default"!', channel, userId, time: 30});
-
-                // if channelName is default then use default
-                if (channelName === 'default') {
-                    channelName = user.username + '-private-channel';
-                }
-
-                // create channel
-                message.guild.channels.create(channelName, {
-                    type: channelType, 
-                    parent: category
-                }).then(async newChannel => {
-                    newChannel.updateOverwrite(user, {
-                        VIEW_CHANNEL : true,
-                    });
-
-                    // add guests
-                    guests.each(mem => newChannel.updateOverwrite(mem.user, {
-                        VIEW_CHANNEL : true,
-                    }));
-
-                    // DM to creator with emoji collector
-                    let dmMsg = await sendEmbedToMember(user, {
-                        title: 'Channel Creation',
-                        description: 'Your private channel ' + channelName +
-                            ' has been created, when you are done with it, please react to this message with 🚫 to delete the channel.',
-                    });
-                    dmMsg.react('🚫');
-
-                    const deleteFilter = (react, user) => !user.bot && react.emoji.name === '🚫';
-                    dmMsg.awaitReactions(deleteFilter, {max: 1}).then(reacts => {
-                        newChannel.delete();
-                        dmMsg.delete();
-                        sendEmbedToMember(user, {
-                            title: 'Channel Creation',
-                            description: 'Private channel has been deleted successfully!',
-                        }, true);
-                    });
-                });
-            } catch (error) {
-                channel.send('<@' + user.id + '> The channel creation was canceled due to a timeout or prompt cancel. Try again!').then(msg => msg.delete({timeout: 8000}));
-            }
-        });
-    }
-}
-module.exports = StartChannelCreation;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_start_commands_start-mentor-cave.js.html b/docs/commands_a_start_commands_start-mentor-cave.js.html deleted file mode 100644 index ac135765..00000000 --- a/docs/commands_a_start_commands_start-mentor-cave.js.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_start_commands/start-mentor-cave.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_start_commands/start-mentor-cave.js

-
- - - - - -
-
-
// Discord.js commando requirements
-const PermissionCommand = require('../../classes/permission-command');
-const { randomColor } = require('../../discord-services');
-const { Message, Collection } = require('discord.js');
-const Cave = require('../../classes/activities/cave');
-const winston = require('winston');
-const BotGuildModel = require('../../classes/bot-guild');
-const { NumberPrompt, SpecialPrompt, RolePrompt } = require('advanced-discord.js-prompts');
-
-/**
- * The start mentor cave command creates a cave for mentors. To know what a cave is look at [cave]{@link Cave} class.
- * @category Commands
- * @subcategory Start-Commands
- * @extends PermissionCommand
- * @guildonly
- */
-class StartMentorCave extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'start-mentor-cave',
-            group: 'a_start_commands',
-            memberName: 'start the mentor\'s experience',
-            description: 'Will create a private category for mentors with channels for them to use!',
-            guildOnly: true,
-        },
-        {
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'This command can only be used in the admin console!',
-            role: PermissionCommand.FLAGS.ADMIN_ROLE,
-            roleMessage: 'You do not have permission for this command, only admins can use it!',
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message - the message in which the command was run
-     */
-    async runCommand(botGuild, message) {
-        try {
-            // helpful prompt vars
-            let channel = message.channel;
-            let userId = message.author.id;
-
-
-            var emojis = new Collection(); //collection to keep the names of the emojis used so far, used to check for duplicates
-
-            //ask user for each emoji
-            let joinTicketEmoji = await checkForDuplicateEmojis('What is the join ticket emoji?');
-            let giveHelpEmoji = await checkForDuplicateEmojis('What is the give help emoji?');
-            let requestTicketEmoji = await checkForDuplicateEmojis('What is the request ticket emoji?');
-            let addRoleEmoji = await checkForDuplicateEmojis('What is the add mentor role emoji?');
-            let deleteChannelsEmoji = await checkForDuplicateEmojis('What is the delete ticket channels emoji?');
-            let excludeFromAutoDeleteEmoji = await checkForDuplicateEmojis('What is the emoji to opt tickets in/out for the garbage collector?');
-
-            var role;
-            if (await SpecialPrompt.boolean({prompt: 'Have you created the mentor role? If not it is okay, I can make it for you!', channel, userId})) {
-                role = await RolePrompt.single({prompt: 'Please mention the mentor role now!', channel, userId});
-            } else {
-                role = await message.guild.roles.create({
-                    data: {
-                        name: 'Mentor',
-                        color: randomColor(),
-                    }
-                });
-            }
-
-            let publicRoles = await RolePrompt.multi({ prompt: 'What roles can request tickets?', channel, userId });
-
-            /**
-             * @param {String} prompt - message to ask user to choose an emoji for a function
-             * 
-             * Gets user's reaction and adds them to the emoji collection.
-             */
-            // eslint-disable-next-line no-inner-declarations
-            async function checkForDuplicateEmojis(prompt) {
-                let reaction = await SpecialPrompt.singleRestrictedReaction({prompt, channel, userId}, emojis);
-                var emoji = reaction.emoji;
-                emojis.set(emoji.name, emoji);
-                return emoji;
-            }
-
-            let inactivePeriod = await NumberPrompt.single({prompt: 'How long, in minutes, does a ticket need to be inactive for before asking to delete it?',
-                channel, userId});
-            var bufferTime = inactivePeriod;
-            while (bufferTime >= inactivePeriod) {
-                bufferTime = await NumberPrompt.single({prompt: `How long, in minutes, will the bot wait for a response to its request to delete a ticket? Must be less than inactive period: ${inactivePeriod}.`,
-                    channel, userId});
-            }
-            let reminderTime = await NumberPrompt.single({prompt: 'How long, in minutes, shall a ticket go unaccepted before the bot sends a reminder to all mentors?',
-                channel, userId});
-
-            let cave = new Cave({
-                name: 'Mentor',
-                preEmojis: '🧑🏽🎓',
-                preRoleText: 'M',
-                color: 'ORANGE',
-                role: role,
-                emojis: {
-                    joinTicketEmoji: joinTicketEmoji,
-                    giveHelpEmoji: giveHelpEmoji,
-                    requestTicketEmoji: requestTicketEmoji,
-                    addRoleEmoji: addRoleEmoji,
-                    deleteChannelsEmoji: deleteChannelsEmoji,
-                    excludeFromAutoDeleteEmoji: excludeFromAutoDeleteEmoji,
-                },
-                times: {
-                    inactivePeriod,
-                    bufferTime,
-                    reminderTime,
-                },
-                publicRoles: publicRoles,
-            }, botGuild, message.guild);
-
-            await cave.init();
-          
-        } catch (error) {
-            message.channel.send('Due to a prompt cancel, the mentor cave creation was unsuccessful.').then(msg => msg.delete({timeout: 5000}));
-            winston.loggers.get(message.guild.id).warning(`An error was found but it was handled by not setting up the mentor cave. Error: ${error}`, { event: 'StartMentorCave Command' });
-        }
-    }
-}
-module.exports = StartMentorCave;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_start_commands_start-team-formation.js.html b/docs/commands_a_start_commands_start-team-formation.js.html deleted file mode 100644 index b261583d..00000000 --- a/docs/commands_a_start_commands_start-team-formation.js.html +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_start_commands/start-team-formation.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_start_commands/start-team-formation.js

-
- - - - - -
-
-
// Discord.js commando requirements
-const PermissionCommand = require('../../classes/permission-command');
-const { Message } = require('discord.js');
-const TeamFormation = require('../../classes/team-formation');
-const Activity = require('../../classes/activities/activity');
-const { sendMsgToChannel } = require('../../discord-services');
-const { StringPrompt, SpecialPrompt, RolePrompt } = require('advanced-discord.js-prompts');
-
-/**
- * The team formation activity is the most basic team formation activity available. This activity works like a menu or directory of available teams and solo participants.
- * To join, a participant reacts to a message. The bot then sends instructions via DM, including a set of questions the user must respond to and send back to the bot. The responses 
- * are then sent to either a looking-for-team channel or looking-for-members channel. Other parties can then browse these channels and create teams over DMs. Members cannot send 
- * messages to these channels. 
- * There is an option for users in the activity to be notified of new posts of interest. For example, a team leader will get notified of new solo participants looking for a team.
- * When someone finds a team, they can go back to their DMs with the bot and react to a message to remove their post from the channels and stop receiving notifications of new posts.
- * @category Commands
- * @subcategory Start-Commands
- * @extends PermissionCommand
- * @guildonly
- */
-class StartTeamFormation extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'start-team-formation',
-            group: 'a_start_commands',
-            memberName: 'start team formation',
-            description: 'Send a message with emoji collector, one emoji for recruiters, one emoji for team searchers. Instructions will be sent via DM.',
-            guildOnly: true,
-        },
-        {
-            role: PermissionCommand.FLAGS.ADMIN_ROLE,
-            roleMessage: 'Hey there, the !start-team-formation command is only for admins!',
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'Hey there, the !start-team-formation command is only available in the admin console.',
-        });
-    }
-
-    /**
-     * 
-     * @param {Message} message - the message in which the command was run
-     */
-    async runCommand(botGuild, message) {
-        // helpful prompt vars
-        let channel = message.channel;
-        let userId = message.author.id;
-
-        let activityRoles = await Activity.promptForRoleParticipants(channel, userId, true);
-
-        try {
-            
-            var teamFormation = new TeamFormation({
-                teamInfo: {
-                    emoji: await SpecialPrompt.singleEmoji({prompt: 'What emoji do you want to use for teams to sign up?', channel, userId}),
-                    role: (await SpecialPrompt.boolean({prompt: 'Have you created the role teams will get when they sign up? If not its okay, I will create it for you!', channel, userId})) ? 
-                        await RolePrompt.single({prompt: 'What role should team users get?', channel, userId}) :
-                        await TeamFormation.createTeamRole(message.guild.roles),
-                    form: (await SpecialPrompt.boolean({ prompt: `Would you like to use the default form?: ${TeamFormation.defaultTeamForm}\n else you create your own!`, channel, userId})) ? 
-                        TeamFormation.defaultTeamForm : await StringPrompt.single({ prompt: 'Please send your form for teams now:', channel, userId }),
-                },
-                prospectInfo: {
-                    emoji: await SpecialPrompt.singleEmoji({prompt: 'What emoji do you want to use for prospects to sign up?', channel, userId}),
-                    role: (await SpecialPrompt.boolean({prompt: 'Have you created the role prospects will get when they sign up? Worry not if you don\'t I can create it for you!', channel, userId})) ? 
-                        await RolePrompt.single({prompt: 'What role should prospects get?', channel, userId}) : 
-                        await TeamFormation.createProspectRole(message.guild.roles),
-                    form: (await SpecialPrompt.boolean({ prompt: `Would you like to use the default form?: ${TeamFormation.defaultProspectForm}\n else you create your own!`, channel, userId})) ? 
-                        TeamFormation.defaultProspectForm : await StringPrompt.single({ prompt: 'Please send your form for teams now:', channel, userId }),
-                },
-                guild: message.guild,
-                botGuild: botGuild,
-                activityRoles,
-                isNotificationsEnabled: await SpecialPrompt.boolean({prompt: 'Do you want to notify users when the opposite party has a new post?', channel, userId}),
-            });
-
-        } catch (error) {
-            console.log(error);
-            message.channel.send('<@' + message.author.id + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000}));
-            return;
-        }
-
-        await teamFormation.init();
-
-        sendMsgToChannel(message.channel, userId, `The team formation activity is ready to go! <#${teamFormation.channels.info.id}>`, 10);
-
-        await teamFormation.start();
-    }
-}
-module.exports = StartTeamFormation;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_start_commands_start-team-roulette.js.html b/docs/commands_a_start_commands_start-team-roulette.js.html deleted file mode 100644 index c93f91e9..00000000 --- a/docs/commands_a_start_commands_start-team-roulette.js.html +++ /dev/null @@ -1,609 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_start_commands/start-team-roulette.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_start_commands/start-team-roulette.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { sendEmbedToMember } = require('../../discord-services');
-const { TextChannel, Snowflake, Message, MessageEmbed, Collection, GuildChannelManager, User } = require('discord.js');
-const Team = require('../../classes/team');
-const BotGuildModel = require('../../classes/bot-guild');
-const { MemberPrompt, SpecialPrompt, ChannelPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * The team roulette activity is a special type of team formation activity. Users can join the activity by reacting to a message embed (console). They can join
- * as a solo or a group of up to 3 members (them included). The bot will then create teams of 4 as they become available.
- * When a team is created, the new team members are invited to a text channel only available to them. Users can leave the team and the bot will 
- * add a new member from the list (if any available).
- * Admins can check the list of users waiting on a team by reacting to a message embed (console) sent to the admin channel.
- * @category Commands
- * @subcategory Start-Commands
- * @extends PermissionCommand
- */
-class StartTeamRoulette extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'start-team-roulette',
-            group: 'a_start_commands',
-            memberName: 'start team roulette',
-            description: 'Send a message with emoji collector, solos, duos or triplets can join to get assigned a random team.',
-            guildOnly: true,
-        },
-        {
-            role: PermissionCommand.FLAGS.ADMIN_ROLE,
-            roleMessage: 'Hey there, the !start-team-roulette command is only for staff!',
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'Hey there, th !start-team-roulette command is only available on the admin console.',
-        });
-
-        // collection of reaction collectors listening for team leaders to delete teams; used for scope so collectors can be stopped
-        // when a team forms
-        this.destroyCollectors = new Collection();
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message - the message in which the command was run
-     */
-    async runCommand(botGuild, message) {
-
-        this.botGuild = botGuild;
-
-        /**
-         * The solo join emoji.
-         * @type {String} - an emoji string
-         */
-        this.soloEmoji = '🏃🏽';
-
-        /**
-         * The non solo join emoji.
-         * @type {String} - an emoji string
-         */
-        this.teamEmoji = '👯';
-
-        /**
-         * The team list from which to create teams.
-         * @type {Collection<Number, Array<Team>>} - <Team Size, List of Teams>
-         */
-        this.teamList = new Collection();
-
-        /**
-         * The current team number.
-         * @type {Number}
-         */
-        this.teamNumber = 0;
-
-        /**
-         * All the users that have participated in the activity.
-         * @type {Collection<Snowflake, User>}
-         */
-        this.participants = new Collection();
-
-        /**
-         * Channel used to send information about team roulette.
-         * @type {TextChannel}
-         */
-        this.textChannel;
-
-        this.initList();
-
-        try {
-            // ask for channel to use, this will also give us the category to use
-            this.textChannel = await this.getOrCreateChannel(message.channel, message.author.id, message.guild.channels);
-        } catch (error) {
-            message.channel.send('<@' + message.author.id + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000}));
-            return;
-        }
-                
-        // create and send embed message to channel with emoji collector
-        const msgEmbed = new MessageEmbed()
-            .setColor(this.botGuild.colors.embedColor)
-            .setTitle('Team Roulette Information')
-            .setDescription('Welcome to the team roulette section! If you are looking to join a random team, you are in the right place!')
-            .addField('How does this work?', 'Reacting to this message will get you or your team on a list. I will try to assign you a team of 4 as fast as possible. When I do I will notify you on a private text channel with your new team!')
-            .addField('Disclaimer!!', 'By participating in this activity, you will be assigned a random team with random hackers! You can only use this activity once!')
-            .addField('If you are solo', 'React with ' + this.soloEmoji + ' and I will send you instructions.')
-            .addField('If you are in a team of two or three', 'React with ' + this.teamEmoji + ' and I will send you instructions.');
-        
-        var cardMessage = await this.textChannel.send(msgEmbed);
-        cardMessage.react(this.soloEmoji);
-        cardMessage.react(this.teamEmoji);
-
-        // collect form reaction collector and its filter
-        const emojiFilter = (reaction, user) => !user.bot && (reaction.emoji.name === this.soloEmoji || reaction.emoji.name === this.teamEmoji);
-        var mainCollector = cardMessage.createReactionCollector(emojiFilter);
-
-        mainCollector.on('collect', async (reaction, teamLeaderUser) => {
-            // creator check
-            if (this.participants.has(teamLeaderUser.id)) {
-                sendEmbedToMember(teamLeaderUser, {
-                    title: 'Team Roulette',
-                    description: 'You are already signed up on the team roulette activity!',
-                }, true);
-                return;
-            }
-
-            // add team or solo to their team
-            let newTeam = new Team(this.teamNumber);
-            this.teamNumber ++;
-
-            // add team leader
-            newTeam.addTeamMember(teamLeaderUser);
-
-            // the emoji used to leave a team
-            let leaveTeamEmoji = '👎';
-            // the emoji used to remove a team from the roulette
-            let destroyTeamEmoji = '🛑';
-
-            if (reaction.emoji.name === this.teamEmoji) {
-                
-                try {
-                    var groupMembers = await MemberPrompt.multi({
-                        prompt: 'Please mention all your current team members in one message.', 
-                        channel: this.textChannel, userId: teamLeaderUser.id,
-                        time: 30,
-                    });
-                } catch (error) {
-                    reaction.users.remove(newTeam.leader);
-                    return;
-                }
-
-                // remove any self mentions
-                groupMembers.delete(newTeam.leader);
-
-                // check if they have more than 4 team members
-                if (groupMembers.size > 2) {
-                    sendEmbedToMember(teamLeaderUser, {
-                        title: 'Team Roulette',
-                        description: 'You just tried to use the team roulette, but you mentioned more than 2 members. That should mean you have a team of 4 already! If you mentioned yourself by accident, try again!',
-                    }, true);
-                    return;
-                }
-
-                // delete any mentions of users already in the activity.
-                groupMembers.forEach((teamMember, index) => {
-                    if (this.participants.has(teamMember.id)) {
-                        sendEmbedToMember(teamLeaderUser, {
-                            title: 'Team Roulette',
-                            description: 'We had to remove ' + teamMember.username + ' from your team roulette team because he already participated in the roulette.',
-                        }, true);
-                    } else {
-                        // push member to the team list and activity list
-                        newTeam.addTeamMember(teamMember);
-                        this.participants.set(teamMember.id, teamMember);
-
-                        sendEmbedToMember(teamMember, {
-                            title: 'Team Roulette',
-                            description: 'You have been added to ' + teamLeaderUser.username + ' team roulette team! I will ping you as soon as I find a team for all of you!',
-                            color: '#57f542',
-                            fields: [{
-                                title:'Leave the team',
-                                description: 'To leave the team please react to this message with ' + leaveTeamEmoji,
-                            }]
-                        }).then(memberMsg => {
-                            memberMsg.react(leaveTeamEmoji);
-
-                            // reaction to leave the team only works before the team has been completed!!
-                            memberMsg.awaitReactions((reaction, user) => !user.bot && !newTeam.hasBeenComplete && !newTeam.deleted && reaction.emoji.name === leaveTeamEmoji, {max: 1}).then(reactions => {
-                                // remove member from list
-                                let newSize = this.removeMemberFromTeam(newTeam, teamMember);
-
-                                memberMsg.delete();
-
-                                // add team without users to correct teamList and notify team leader
-                                if(newSize > 0) {
-                                    this.teamList.get(newSize).push(newTeam);
-                                    sendEmbedToMember(newTeam.members.get(newTeam.leader), {
-                                        title: 'Team Roulette',
-                                        description: teamMember.username + ' has left the team, but worry not, we are still working on getting you a team!',
-                                    });
-                                }
-                                
-                            });
-                        });
-                    }
-                });
-            }
-
-            this.teamList.get(newTeam.size()).push(newTeam);
-
-            // add team leader to activity list and notify of success
-            this.participants.set(teamLeaderUser.id, teamLeaderUser);
-            let leaderDM = await sendEmbedToMember(teamLeaderUser, {
-                title: 'Team Roulette',
-                description: 'You' + (reaction.emoji.name === this.teamEmoji ? ' and your team' : '') + ' have been added to the roulette. I will get back to you as soon as I have a team for you!',
-                color: '#57f542',
-                fields: [{
-                    title: 'Destroy your team',
-                    description: 'If you want to leave the roulette queue react to this message with ' + destroyTeamEmoji + '\n' 
-                    + 'Note that once you destroy your team, you will have to re-join the roulette and wait for longer!',
-                }]
-            });
-            leaderDM.react(destroyTeamEmoji);
-
-            // reaction to destroy the team only works before the team is completed
-            const destroyTeamCollector = leaderDM.createReactionCollector((reaction,user) => !user.bot && !newTeam.hasBeenComplete && reaction.emoji.name === destroyTeamEmoji, {max: 1});
-            this.destroyCollectors.set(teamLeaderUser.id, destroyTeamCollector);
-            destroyTeamCollector.on('collect', (reaction, leader) => {
-                // remove team from team list
-                this.teamList.get(newTeam.size()).splice(this.teamList.get(newTeam.size()).indexOf(newTeam), 1);
-                
-                // remove leader DM
-                leaderDM.delete();
-
-                // mark the team as deleted
-                newTeam.deleted = true;
-
-                // notify users of team deletion 
-                newTeam.members.forEach(user => {
-                    this.participants.delete(user.id);
-                    if (user.id === leader.id) {
-                        sendEmbedToMember(leader, {
-                            title: 'Team Roulette',
-                            description: 'Your team has been removed from the roulette!',
-                        }, true);
-                    } else {
-                        sendEmbedToMember(user, {
-                            title: 'Team Roulette',
-                            description: 'Your team with <@' + newTeam.leader + '> has been destroyed!',
-                        });
-                    }
-                });
-            });
-            this.runTeamCreator(message.guild.channels);
-        });
-    }
-
-    /**
-     * Ask user if new channels are needed, if so create them, else ask for current channels to use for TR.
-     * @param {TextChannel} promptChannel - channel to prompt on
-     * @param {Snowflake} promptId - user's ID to prompt
-     * @param {GuildChannelManager} guildChannelManager - manager to create channels
-     * @async
-     * @returns {Promise<TextChannel>}
-     * @throws Throws an error if the user cancels either of the two Prompts, the command should quit!
-     */
-    async getOrCreateChannel(promptChannel, promptId, guildChannelManager) {
-        let needChannel = await SpecialPrompt.boolean({prompt: 'Do you need a new channel and category or have you created one already?', channel: promptChannel, userId: promptId});
-
-        let channel;
-
-        if (needChannel) {
-            let category = await guildChannelManager.create('Team Roulette', {
-                type: 'category',
-            });
-
-            channel = await guildChannelManager.create('team-roulette-info', {
-                type: 'text',
-                topic: 'Channel should only be used for team roulette.',
-                parent: category,
-            });
-        } else {
-            channel = await ChannelPrompt.single({prompt: 'What channel would you like to use for team roulette, this channels category will be used for the new team channels.', channel: promptChannel, userId: promptId});
-            channel.bulkDelete(100, true);
-        }
-
-        // let user know everything is good to go
-        let listEmoji = '📰';
-
-        const adminEmbed = new MessageEmbed()
-            .setColor(this.botGuild.colors.embedColor)
-            .setTitle('Team Roulette Console')
-            .setDescription('Team roulette is ready and operational! <#' + channel.id + '>.')
-            .addField('Check the list!', 'React with ' + listEmoji + ' to get a message with the roulette team lists.');
-
-        let adminEmbedMsg = await promptChannel.send(adminEmbed);
-        adminEmbedMsg.react(listEmoji);
-
-        // emoji reaction to send team roulette information
-        let adminEmbedMsgCollector = adminEmbedMsg.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === listEmoji);
-        adminEmbedMsgCollector.on('collect', (reaction, user) => {
-            reaction.users.remove(user.id);
-
-            let infoEmbed = new MessageEmbed()
-                .setColor(this.botGuild.colors.embedColor)
-                .setTitle('Team Roulette Information')
-                .setDescription('These are all the teams that are still waiting.');
-
-            // loop over each list type and add them to one field
-            this.teamList.forEach((teams, key) => {
-                let teamListString = '';
-
-                teams.forEach((team, index) => {
-                    teamListString += team.toString() + ' ; ';
-                });
-
-                infoEmbed.addField('Lists of size: ' + key, '[ ' + teamListString + ' ]');
-            });
-
-            promptChannel.send(infoEmbed);
-        });
-
-        // add channel to black list
-        this.botGuild.blackList.set(channel.id, 5000);
-        this.botGuild.save();
-        return channel;
-    }
-
-
-    /**
-     * Will remove the team member from the team, notify the user of success, and remove the team from the teamList
-     * @param {Team} team - the team to remove user from 
-     * @param {User} teamMember - the user to remove from the team
-     * @returns {Number} - the new size of the team
-     */
-    removeMemberFromTeam(team, teamMember) {
-        // remove the team from the list
-        if (!team.isComplete()) this.teamList.get(team.size()).splice(this.teamList.get(team.size()).indexOf(team), 1);
-
-        // remove user from team and notify
-        this.participants.delete(teamMember.id);
-        let newSize = team.removeTeamMember(teamMember);
-        sendEmbedToMember(teamMember, {
-            title: 'Team Roulette',
-            description: 'You have been removed from the team!'
-        }, true);
-        return newSize;
-    }
-
-    /**
-     * Will try to create a team and set them up for success!
-     * @param {GuildChannelManager} channelManager
-     * @async
-     */
-    async runTeamCreator(channelManager) {
-        // call the team creator
-        let team = await this.findTeam();
-
-        // if no team then just return
-        if (!team) return;
-
-        // if team does NOT have a text channel
-        if (!team?.textChannel) {
-            // disable the ability to destroy a team after team has been formed
-            team.members.forEach((user,id) => {
-                if (this.destroyCollectors.has(id)) {
-                    this.destroyCollectors.get(id).stop();
-                    this.destroyCollectors.delete(id);
-                }
-            });
-
-            let privateChannelCategory = this.textChannel.parent;
-
-            await team.createTextChannel(channelManager, privateChannelCategory);
-
-            let leaveEmoji = '👋';
-
-            const infoEmbed = new MessageEmbed()
-                .setColor(this.botGuild.colors.embedColor)
-                .setTitle('WELCOME TO YOUR NEW TEAM!!!')
-                .setDescription('This is your new team, please get to know each other by creating a voice channel in a new Discord server or via this text channel. Best of luck!')
-                .addField('Leave the Team', 'If you would like to leave this team react to this message with ' + leaveEmoji);
-
-            let teamCard = await team.textChannel.send(infoEmbed);
-
-            let teamCardCollection = teamCard.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === leaveEmoji);
-
-            teamCardCollection.on('collect', (reaction, exitUser) => {
-                // remove user from team
-                this.removeMemberFromTeam(team, exitUser);
-
-                // search for more members depending on new team size
-                if (team.size()) {
-                    team.textChannel.send('<@' + exitUser.id + '> Has left the group, but worry not, we are working on getting you more members!');
-
-                    this.teamList.get(team.size()).push(team);
-                    this.runTeamCreator(channelManager);
-                }
-            });
-        }
-    }
-
-
-    /**
-     * Will try to create teams with the current groups signed up!
-     * @param {Number} teamSize - the size of the new team
-     * @private
-     * @returns {Promise<Team | null>}
-     * @async
-     */
-    async findTeam(teamSize) {
-        let newTeam;
-
-        if (teamSize === 3) newTeam = await this.assignGroupOf3();
-        else if (teamSize === 2) newTeam = await this.assignGroupOf2();
-        else {
-            if (this.teamList.get(3).length >=1) newTeam = await this.assignGroupOf3();
-            else if (this.teamList.get(2).length >= 1) newTeam = await this.assignGroupOf2();
-            else newTeam = await this.assignGroupsOf1();
-        }
-        return newTeam;
-    }
-
-
-    /**
-     * Will assign a team of 3 with a team of 1.
-     * @returns {Promise<Team | null>}
-     * @requires this.groupList to have a team of 3.
-     * @async
-     */
-    async assignGroupOf3() {
-        let listOf1 = this.teamList.get(1);
-        if (listOf1.length === 0) return null;
-        let teamOf3 = this.teamList.get(3).shift();
-        return await teamOf3.mergeTeam(listOf1.shift());
-    }
-
-    /**
-     * Will assign a team of 2 with a team of 2 or two of 1
-     * @returns {Promise<Team | null>}
-     * @requires this.groupList to have a team of 2
-     * @async
-     */
-    async assignGroupOf2() {
-        let listOf2 = this.teamList.get(2);
-        if (listOf2.length >= 2) {
-            return listOf2.shift().mergeTeam(listOf2.shift());
-        } else {
-            let listOf1 = this.teamList.get(1);
-            if (listOf1.length <= 1) return null;
-            return await (await listOf2.shift().mergeTeam(listOf1.shift())).mergeTeam(listOf1.shift());
-        }
-    }
-
-    /**
-     * Assigns 4 groups of 1 together.
-     * @returns {Promise<Team | null>}
-     * @async
-     */
-    async assignGroupsOf1() {
-        let groupOf1 = this.teamList.get(1);
-        if (groupOf1.length < 4) return null;
-        else return await (await (await groupOf1.shift().mergeTeam(groupOf1.shift())).mergeTeam(groupOf1.shift())).mergeTeam(groupOf1.shift());
-    }
-
-
-    /**
-     * Initializes the team list by creating three key value pairs.
-     * 1 -> empty array
-     * 2 -> empty array
-     * 3 -> empty array
-     * @private
-     */
-    initList() {
-        this.teamList.set(1, []);
-        this.teamList.set(2, []);
-        this.teamList.set(3, []);
-    }
-}
-module.exports = StartTeamRoulette;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_utility_change-pre-fix.js.html b/docs/commands_a_utility_change-pre-fix.js.html deleted file mode 100644 index 0b2b71ea..00000000 --- a/docs/commands_a_utility_change-pre-fix.js.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_utility/change-pre-fix.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_utility/change-pre-fix.js

-
- - - - - -
-
-
const { Message } = require('discord.js');
-const PermissionCommand = require('../../classes/permission-command');
-const BotGuildModel = require('../../classes/bot-guild');
-const { StringPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * Gives admin the ability to change the prefix used in the guild by the bot.
- */
-class ChangePreFix extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'change-prefix',
-            group: 'a_utility',
-            memberName: 'change guild prefix',
-            description: 'Change the prefix used in this guild by the bot.',
-            guildOnly: true,
-        }, {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild 
-     * @param {Message} message 
-     */
-    async runCommand(botGuild, message) {
-        let options = ['!', '#', '$', '%', '&', '?', '|', '°'];
-
-        let prefix = StringPrompt.restricted({ prompt: 'What would you like to use as the prefix?', channel: message.channel, userId: message.author.id }, options);
-
-        botGuild.prefix = prefix;
-        botGuild.save();
-
-        message.guild.commandPrefix = prefix;
-
-    }
-}
-module.exports = ChangePreFix;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_utility_clear-chat.js.html b/docs/commands_a_utility_clear-chat.js.html deleted file mode 100644 index e1d21942..00000000 --- a/docs/commands_a_utility_clear-chat.js.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_utility/clear-chat.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_utility/clear-chat.js

-
- - - - - -
-
-
// Discord.js commando requirements
-const PermissionCommand = require('../../classes/permission-command');
-const { discordLog } = require('../../discord-services');
-const { MessageEmbed, Message } = require('discord.js');
-const BotGuildModel = require('../../classes/bot-guild');
-
-/**
- * The clear chat command will clear a channel from at most 100 messages that are at least 2 weeks young.
- * Option to keep pinned messages available.
- * @category Commands
- * @subcategory Admin-Utility
- * @extends PermissionCommand
- */
-class ClearChat extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'clear-chat',
-            group: 'a_utility',
-            memberName: 'clear chat utility',
-            description: 'Will clear up to 100 newest messages from the channel. Messages older than two weeks will not be deleted. Then will send message with available commands in the channel, if any.',
-            guildOnly: true,
-            args: [
-                {
-                    key: 'keepPinned',
-                    prompt: 'if pinned messages should be kept',
-                    type: 'boolean',
-                    default: false,
-                },
-            ],
-        },
-        {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'Hey there, the command !clear-chat is only available to staff!',
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild - the botGuild instance given from PermissionCommand
-     * @param {Message} message - the message in which the command was run
-     * @param {Object} args - the command arguments
-     * @param {Boolean} args.keepPinned - if true any pinned messages will not be removed
-     */
-    async runCommand (botGuild, message, {keepPinned}) {
-
-        if (keepPinned) {
-            // other option is to get all channel messages, filter of the pined channels and pass those to bulkDelete, might be to costly?
-            var messagesToDelete = message.channel.messages.cache.filter(msg => !msg.pinned);
-            await message.channel.bulkDelete(messagesToDelete, true).catch(console.error);
-        } else {
-            // delete messages and log to console
-            await message.channel.bulkDelete(100, true).catch(console.error);
-        }
-
-        discordLog(message.guild, 'CHANNEL CLEAR ' + message.channel.name + '. By user: ' + message.author.username);
-    }
-}
-module.exports = ClearChat;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_utility_pronouns.js.html b/docs/commands_a_utility_pronouns.js.html deleted file mode 100644 index 9554c77b..00000000 --- a/docs/commands_a_utility_pronouns.js.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_utility/pronouns.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_utility/pronouns.js

-
- - - - - -
-
-
// Discord.js commando requirements
-const PermissionCommand = require('../../classes/permission-command');
-const { addRoleToMember, removeRolToMember, sendMsgToChannel } = require('../../discord-services');
-const { Message, MessageEmbed } = require('discord.js');
-
-/**
- * The pronouns command sends a role reaction console for users to select a pronoun role out of 4 options:
- * * she/her
- * * he/him
- * * they/them
- * * other pronouns
- * The roles must be already created on the server for this to work.
- * @category Commands
- * @subcategory Admin-Utility
- * @extends PermissionCommand
- */
-class Pronouns extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'pronouns',
-            group: 'a_utility',
-            memberName: 'pronoun role',
-            description: 'Set up pronouns reaction role message.',
-            guildOnly: true,
-        },
-        {
-            roleID: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'Hey there, the command !pronouns is only available to staff!',
-        });
-    }
-
-    /**
-     * 
-     * @param {Message} message - the command message
-     */
-    async runCommand(botGuild, message) {
-        const sheRole = message.guild.roles.cache.find(role => role.name === 'she/her');
-        const heRole = message.guild.roles.cache.find(role => role.name === 'he/him');
-        const theyRole = message.guild.roles.cache.find(role => role.name === 'they/them');
-        const otherRole = message.guild.roles.cache.find(role => role.name === 'other pronouns');
-
-        // check to make sure all 4 roles are available
-        if (!sheRole || !heRole || !theyRole || !otherRole) {
-            sendMsgToChannel(message.channel, message.author.id, 'Could not find all four roles! Make sure the role names are exactly like stated on the documentation.', 20);
-            return;
-        }
-
-        var emojis = ['1️⃣', '2️⃣', '3️⃣', '4️⃣'];
-
-        let embed = new MessageEmbed()
-            .setColor('#0DEFE1')
-            .setTitle('Set your pronouns by reacting to one of the emojis!')
-            .setDescription(
-                `${emojis[0]} she/her\n`
-                + `${emojis[1]} he/him\n`
-                + `${emojis[2]} they/them\n`
-                + `${emojis[3]} other pronouns\n`);
-
-        let messageEmbed = await message.channel.send(embed);
-        emojis.forEach(emoji => messageEmbed.react(emoji));
-
-        // create collector
-        const reactionCollector = messageEmbed.createReactionCollector((reaction, user) => user.bot != true && emojis.includes(reaction.emoji.name), {dispose: true});
-
-        // on emoji reaction
-        reactionCollector.on('collect', async (reaction, user) => {
-            if (reaction.emoji.name === emojis[0]) {
-                addRoleToMember(message.guild.member(user), sheRole);
-            } if (reaction.emoji.name === emojis[1]) {
-                addRoleToMember(message.guild.member(user), heRole);
-            } if (reaction.emoji.name === emojis[2]) {
-                addRoleToMember(message.guild.member(user), theyRole);
-            } if (reaction.emoji.name === emojis[3]) {
-                addRoleToMember(message.guild.member(user), otherRole);
-            }
-        });
-
-        reactionCollector.on('remove', async (reaction, user) => {
-            if (reaction.emoji.name === emojis[0]) {
-                removeRolToMember(message.guild.member(user), sheRole);
-            } if (reaction.emoji.name === emojis[1]) {
-                removeRolToMember(message.guild.member(user), heRole);
-            } if (reaction.emoji.name === emojis[2]) {
-                removeRolToMember(message.guild.member(user), theyRole);
-            } if (reaction.emoji.name === emojis[3]) {
-                removeRolToMember(message.guild.member(user), otherRole);
-            }
-        });
-
-    }
-}
-module.exports = Pronouns;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_utility_role-selector.js.html b/docs/commands_a_utility_role-selector.js.html deleted file mode 100644 index d9eb7848..00000000 --- a/docs/commands_a_utility_role-selector.js.html +++ /dev/null @@ -1,253 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_utility/role-selector.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_utility/role-selector.js

-
- - - - - -
-
-
// Discord.js commando requirements
-const PermissionCommand = require('../../classes/permission-command');
-const { checkForRole, addRoleToMember, removeRolToMember } = require('../../discord-services');
-const { MessageEmbed, Message, Role, Collection } = require('discord.js');
-const BotGuildModel = require('../../classes/bot-guild');
-const { MessagePrompt, SpecialPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * Make a message embed (console) available on the channel for users to react and un-react for roles. Staff can dynamically add 
- * roles to the console. Users can react to get the role, then un-react to loose the role.
- * @category Commands
- * @subcategory Admin-Utility
- * @extends PermissionCommand
- */
-class RoleSelector extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'role-selector',
-            group: 'a_utility',
-            memberName: 'transfer role',
-            description: 'Will let users transfer roles. Useful for sponsor reps that are also mentors!',
-            guildOnly: true,
-        },
-        {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'Hey there, the command !role-selector is only available to staff!',
-        });
-    }
-
-
-    /**
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message - the command message
-     */
-    async runCommand (botGuild, message) {
-
-        // the emoji for staff to add new transfers
-        let newTransferEmoji = '🆕';
-
-        /**
-         * @typedef Transfer
-         * @property {String} name - the transfer name
-         * @property {String} description - the transfer description
-         * @property {Role} role - the transfer role
-         */
-
-        /**
-         * The transfers on this role transfer card.
-         * @type {Collection<String, Transfer>}
-         */
-        let transfers = new Collection();
-
-        const cardEmbed = new MessageEmbed().setColor(botGuild.colors.embedColor)
-            .setTitle('Role Selector!')
-            .setDescription('React to the specified emoji to get the role, un-react to remove the role.');
-
-        let cardMsg = await message.channel.send(cardEmbed);
-        cardMsg.react(newTransferEmoji);
-
-        let filter = (reaction, user) => !user.bot && (transfers.has(reaction.emoji.name) || reaction.emoji.name === newTransferEmoji);
-        let reactionCollector = cardMsg.createReactionCollector(filter, {dispose: true});
-
-        // add role or a transfer depending on the emoji
-        reactionCollector.on('collect', async (reaction, user) => {
-            // admin add new transfer
-            if (reaction.emoji.name === newTransferEmoji && checkForRole(message.guild.member(user), botGuild.roleIDs.staffRole)) {
-                
-                try {
-                    var newTransferMsg = await MessagePrompt.prompt({
-                        prompt: 'What new transfer do you want to add? Your response should have (in this order, not including <>): @role <transfer name> - <transfer description>',
-                        channel: message.channel, 
-                        userId: user.id
-                    });
-                } catch (error) {
-                    reaction.users.remove(user.id);
-                    return;
-                }
-                
-                // grab the role, name and description from the prompt message
-                let role = newTransferMsg.mentions.roles.first();
-                let firstStop = newTransferMsg.cleanContent.indexOf('-');
-                let name = newTransferMsg.cleanContent.substring(0, firstStop);
-                let description = newTransferMsg.cleanContent.substring(firstStop + 1);
-
-                let emoji = await SpecialPrompt.singleEmoji({prompt: 'What emoji to you want to use for this transfer?', channel: message.channel, userId: message.author.id});
-
-                transfers.set(emoji.name, {
-                    name: name,
-                    description: description,
-                    role: role,
-                });
-
-                // edit original embed with transfer information and react with new role
-                reaction.message.edit(reaction.message.embeds[0].addField(name + ' -> ' + emoji.toString(), description));
-                reaction.message.react(emoji);
-                
-                reaction.users.remove(user.id);
-            }
-
-            // user add role
-            if (transfers.has(reaction.emoji.name)) {
-                let role = transfers.get(reaction.emoji.name).role;
-                addRoleToMember(message.guild.member(user), role);
-                message.channel.send('<@' + user.id + '> You have been given the role: ' + role.name).then(msg => msg.delete({timeout: 4000}));
-            }
-        });
-
-        // remove the role from the user if the emoji is a transfer emoji
-        reactionCollector.on('remove', (reaction, user) => {
-            if (transfers.has(reaction.emoji.name)) {
-                let role = transfers.get(reaction.emoji.name).role;
-                removeRolToMember(message.guild.member(user), role);
-                message.channel.send('<@' + user.id + '> You have lost the role: ' + role.name).then(msg => msg.delete({timeout: 4000}));
-            }
-        });
-    }
-}
-module.exports = RoleSelector;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_a_utility_self-care.js.html b/docs/commands_a_utility_self-care.js.html deleted file mode 100644 index 519d8550..00000000 --- a/docs/commands_a_utility_self-care.js.html +++ /dev/null @@ -1,263 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/a_utility/self-care.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/a_utility/self-care.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { discordLog } = require('../../discord-services');
-const { Message, MessageEmbed } = require('discord.js');
-const { getReminder } = require('../../db/firebase/firebase-services');
-const BotGuildModel = require('../../classes/bot-guild');
-const { NumberPrompt, SpecialPrompt, RolePrompt } = require('advanced-discord.js-prompts');
-
-/**
- * The self care command will send pre made reminders from firebase to the command channel. These reminders are self
- * care reminders. Will prompt a role to mention with each reminder. We recommend that be an opt-in role. 
- * @category Commands
- * @subcategory Admin-Utility
- * @extends PermissionCommand
- */
-class SelfCareReminders extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'self-care',
-            group: 'a_utility',
-            memberName: 'self care reminders',
-            description: 'Sends self-care reminders at designated times.',
-            guildOnly: true,
-        },
-        {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'Hey there, the command !self-care is only available to Staff!',
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message - the message in which this command was called
-     */
-    async runCommand(botGuild, message) {
-        var interval;
-
-        // helpful vars
-        let channel = message.channel;
-        let userId = message.author.id;
-
-        //ask user for time interval between reminders
-        var timeInterval;
-        try {
-            let num = await NumberPrompt.single({prompt: 'What is the time interval between reminders in minutes (integer only)? ', channel, userId});
-            timeInterval = 1000 * 60 * num;
-
-            // ask user whether to start sending reminders now(true) or after 1 interval (false)
-            var isStartNow = await SpecialPrompt.boolean({prompt: 'Type "yes" to send first reminder now, "no" to start one time interval from now. ', channel, userId, cancelable: true});
-
-            // id of role to mention when new reminders come out (use-case for self-care still tbd)
-            var roleId = (await RolePrompt.single({prompt: 'What is the hacker role to notify for self-care reminders?', channel, userId,cancelable: true})).id;
-        } catch (error) {
-            channel.send('<@' + userId + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000}));
-            return;
-        }
-
-        // keeps track of whether it has been paused
-        var paused = false;        
-
-        const startEmbed = new MessageEmbed()
-            .setColor(botGuild.colors.embedColor)
-            .setTitle('To encourage healthy hackathon habits, we will be sending hourly self-care reminders!')
-            // temp
-            .setDescription('For Staff:\n' +
-                '⏸️ to pause\n' +
-                '▶️ to resume\n');
-
-        channel.send('<@&' + roleId + '>', { embed: startEmbed }).then((msg) => {
-            msg.pin();
-            msg.react('⏸️');
-            msg.react('▶️');
-
-            //filters so that it will only respond to Staff who reacted with one of the 3 emojis 
-            const emojiFilter = (reaction, user) => !user.bot && (reaction.emoji.name === '⏸️' || reaction.emoji.name === '▶️') && message.guild.member(user).roles.cache.has(botGuild.roleIDs.staffRole);
-            const emojiCollector = msg.createReactionCollector(emojiFilter);
-            
-            emojiCollector.on('collect', (reaction, user) => {
-                reaction.users.remove(user.id);
-                if (reaction.emoji.name === '⏸️') {
-                    //if it isn't already paused, pause by clearing the interval
-                    if (interval != null && !paused) {
-                        clearInterval(interval);
-                        paused = true;
-                        channel.send('<@' + user.id + '> Self-care reminders have been paused!').then(msg => msg.delete({timeout: 10000}));
-                    }
-                } else if (reaction.emoji.name === '▶️') {
-                    //if it is currently paused, restart the interval and send the next reminder immediately
-                    if (paused) {
-                        sendReminder();
-                        interval = setInterval(sendReminder, timeInterval);
-                        paused = false;
-                        channel.send('<@' + user.id + '> Self-care reminders have been un-paused!').then(msg => msg.delete({timeout: 10000}));
-                    }
-                } 
-            });
-        });
-
-        //starts the interval, and sends the first reminder immediately if startNow is true
-        if (isStartNow) {
-            sendReminder();
-        }
-        interval = setInterval(sendReminder, timeInterval);
-
-        // sendReminder is the function that picks and sends the next reminder
-        async function sendReminder() {
-            //get reminders parameters from db 
-            var data = await getReminder(message.guild.id);
-
-            //report in admin logs that there are no more messages
-            //TODO: consider having it just loop through the db again?
-            if (data === null) {
-                discordLog(message.guild, '<@&' + botGuild.roleIDs.staffRole + '> HI, PLEASE FEED ME more self-care messages!!');
-                clearInterval(interval);
-                return;
-            }
-
-            let reminder = data.reminder;
-
-            const qEmbed = new MessageEmbed()
-                .setColor(botGuild.colors.embedColor)
-                .setTitle(reminder);
-                // .setDescription(reminder);
-            
-            channel.send(`Hey <@&${roleId}> remember:`, {embed: qEmbed});
-        }
-    }
-}
-module.exports = SelfCareReminders;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_attendance_attend.js.html b/docs/commands_attendance_attend.js.html deleted file mode 100644 index 8907f050..00000000 --- a/docs/commands_attendance_attend.js.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/attendance/attend.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/attendance/attend.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { Message } = require('discord.js');
-const { checkForRole, sendEmbedToMember } = require('../../discord-services');
-const Verification = require('../../classes/verification');
-const BotGuildModel = require('../../classes/bot-guild');
-
-/**
- * Attends the user who runs this command. The user must have the guild ID. Can only be run 
- * via DMs.
- * @category Commands
- * @subcategory Verification
- * @extends PermissionCommand
- * @dmonly
- */
-class Attend extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'attend',
-            group: 'attendance',
-            memberName: 'hacker attendance',
-            description: 'Will mark a hacker as attending and upgrade role to Attendee. Can only be called once!',
-            args: [
-                {
-                    key: 'guildId',
-                    prompt: 'Please provide the server ID, ask admins for it!',
-                    type: 'integer',
-                },
-            ],
-        },
-        {
-            dmOnly: true
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message
-     * @param {Object} args
-     * @param {String} args.guildId 
-     */
-    async runCommand(botGuild, message, { guildId }) {
-        // check if the user needs to attend, else warn and return
-        if (checkForRole(message.author, botGuild.attendance.attendeeRoleID)) {
-            sendEmbedToMember(message.author, {
-                title: 'Attend Error',
-                description: 'You do not need to attend! Happy hacking!!!'
-            }, true);
-            return;
-        }
-
-        let guild = this.client.guilds.cache.get(guildId);
-        if (!guild) {
-            sendEmbedToMember(message.author, {
-                title: 'Attendance Failure',
-                description: 'The given server ID is not valid. Please try again!',
-            });
-            return;
-        }
-        let member = guild.member(message.author.id);
-        
-        // call the firebase services attendHacker function
-        Verification.attend(member, botGuild);
-    }
-}
-module.exports = Attend;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_attendance_start-attend.js.html b/docs/commands_attendance_start-attend.js.html deleted file mode 100644 index 3098bfeb..00000000 --- a/docs/commands_attendance_start-attend.js.html +++ /dev/null @@ -1,246 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/attendance/start-attend.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/attendance/start-attend.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { checkForRole, sendEmbedToMember } = require('../../discord-services');
-const { MessageEmbed, Message } = require('discord.js');
-const Verification = require('../../classes/verification');
-const BotGuildModel = require('../../classes/bot-guild');
-const { StringPrompt, SpecialPrompt, ChannelPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * StartAttend makes a new channel called #attend, or uses an existing channel of the user's choice, as the channel where an embed 
- * is sent for users to react and get attend. Users don't need to send any information to attend.
- * @category Commands
- * @subcategory Verification
- * @extends PermissionCommand
- * @guildonly
- */
-class StartAttend extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'start-attend',
-            group: 'attendance',
-            memberName: 'initiate attend process',
-            description: 'identifies/makes a channel to be used for !attend and notifies people',
-            guildOnly: true,
-        },
-        {
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'This command can only be used in the admin console!',
-            role: PermissionCommand.FLAGS.ADMIN_ROLE,
-            roleMessage: 'Hey there, the command !start-attend is only available to Admins!',
-        });
-    }
-
-    /**
-     * If existsChannel is true, asks user to indicate the channel to use. Else asks user to indicate the category under which the
-     * channel should be created, and then creates it. In both cases it will send an embed containing the instructions for hackers to 
-     * check in.
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message - message containing command
-     */
-    async runCommand(botGuild, message) {
-        var channel;
-
-        // register the attend command just in case its needed
-        message.guild.setCommandEnabled('attend', true);
-
-        try {
-            let existsChannel = await SpecialPrompt.boolean({prompt: 'Is there already a channel that exists that hackers will be using !attend in?', channel: message.channel, userId: message.author.id, cancelable: true});
-
-            if (existsChannel) {
-                //ask user to mention channel to be used for !attend
-                channel = await ChannelPrompt.single({prompt: 'Please mention the channel to be used for the !attend command. ', channel: message.channel, userId: message.author.id, cancelable: true});
-            } else {
-                //ask user for category to create new attend channel under
-                let categoryReply = await StringPrompt.single({prompt: 'What category do you want the new attend channel under? ', channel: message.channel, userId: message.author.id, cancelable: true});
-                
-                var categoryName = categoryReply.content;
-
-
-                let category = message.guild.channels.cache.find(c => c.type == 'category' && c.name.toLowerCase() == categoryName.toLowerCase());
-                if (!category) {
-                    message.channel.send('Invalid category name. Please try the command again.')
-                        .then((msg) => msg.delete({timeout: 3000}));
-                    return;
-                }
-
-                //create the channel
-                channel = await message.guild.channels.create('attend', {
-                    parent: category,
-                    topic: 'Channel to attend the event!',
-                });
-            }
-        } catch (error) {
-            message.channel.send('<@' + message.author.id + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000}));
-            return;
-        }
-
-        channel.updateOverwrite(botGuild.roleIDs.everyoneRole, { SEND_MESSAGES: false });
-
-        //send embed with information and tagging hackers
-        let attendEmoji = '🔋';
-
-        const embed = new MessageEmbed()
-            .setColor(botGuild.colors.embedColor)
-            .setTitle('Hey there!')
-            .setDescription('In order to indicate that you are participating, please react to this message with ' + attendEmoji)
-            .addField('Do you need assistance?', 'Head over to the support channel and ping the admins!');
-        let embedMsg = await channel.send('<@&' + botGuild.roleIDs.memberRole + '>', {embed: embed});
-        embedMsg.pin();
-        embedMsg.react(attendEmoji);
-        botGuild.blackList.set(channel.id, 1000);
-        botGuild.save();
-        
-        // reaction collector to attend hackers
-        let embedMsgCollector = embedMsg.createReactionCollector((reaction, user) => !user.bot && reaction.emoji.name === attendEmoji);
-
-        embedMsgCollector.on('collect', (reaction, user) => {
-            let member = message.guild.member(user.id);
-
-            // check if user needs to attend
-            if (!checkForRole(member, botGuild.attendance.attendeeRoleID)) {
-                Verification.attend(member, botGuild);
-            } else {
-                sendEmbedToMember(member, {
-                    title: 'Attend Error',
-                    description: 'You do not need to attend, you are already attending or you are not a hacker!'
-                }, true);
-            }
-        });
-    }
-}
-module.exports = StartAttend;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_essentials_help.js.html b/docs/commands_essentials_help.js.html deleted file mode 100644 index 0978b6c4..00000000 --- a/docs/commands_essentials_help.js.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/essentials/help.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/essentials/help.js

-
- - - - - -
-
-
// Discord.js commando requirements
-const { Command, CommandoGuild } = require('discord.js-commando');
-const { deleteMessage, checkForRole } = require('../../discord-services');
-const { MessageEmbed } = require('discord.js');
-const BotGuild = require('../../db/mongo/BotGuild');
-
-/**
- * The help command shows all the available commands for the user via DM message.
- * @category Commands
- * @subcategory Essentials
- * @extends Command
- */
-class Help extends Command {
-    constructor(client) {
-        super(client, {
-            name: 'help',
-            group: 'essentials',
-            memberName: 'help user',
-            description: 'Will send available commands depending on role!',
-            hidden: true,
-        });
-    }
-
-    /**
-     * @param {Message} message
-     */
-    async run(message) {
-
-        let botGuild = await BotGuild.findById(message.guild.id);
-
-        /** @type {CommandoGuild} */
-        let guild = message.guild;
-        
-        /** @type {Command[]} */
-        var commands = [];
-
-        var commandGroups;
-
-        // if message on DM then send hacker commands
-        if (message.channel.type === 'dm') {
-            commandGroups = this.client.registry.findGroups('utility', true);
-        } else {
-            deleteMessage(message);
-
-            if ((checkForRole(message.member, botGuild.roleIDs.staffRole))) {
-                commandGroups = this.client.registry.groups;
-            } else {
-                commandGroups = this.client.registry.findGroups('utility', true);
-            }
-        }
-
-        // add all the commands from the command groups
-        commandGroups.forEach((value) => {
-            if (guild.isGroupEnabled(value)) {
-                value.commands.forEach((command, index) => {
-                    if (guild.isCommandEnabled(command)) commands.push(command);
-                });
-            }
-        });
-
-        var length = commands.length;
-
-        const textEmbed = new MessageEmbed()
-            .setColor(botGuild.colors.embedColor)
-            .setTitle('Commands Available for you')
-            .setDescription('All other interactions with me will be via emoji reactions!')
-            .setTimestamp();
-
-        // add each command as a field in the embed
-        for (var i = 0; i < length; i++) {
-            let command = commands[i];
-            if (command.format != null) {
-                textEmbed.addField(this.client.commandPrefix + command.name, command.description + ', arguments: ' + command.format);
-            } else {
-                textEmbed.addField(this.client.commandPrefix + command.name, command.description + ', no arguments');
-            }
-        }
-
-        message.author.send(textEmbed);
-    }
-}
-module.exports = Help;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_essentials_init-bot.js.html b/docs/commands_essentials_init-bot.js.html deleted file mode 100644 index 9d08ed6e..00000000 --- a/docs/commands_essentials_init-bot.js.html +++ /dev/null @@ -1,455 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/essentials/init-bot.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/essentials/init-bot.js

-
- - - - - -
-
-
const { Command, CommandoGuild } = require('discord.js-commando');
-const { Message, TextChannel, Snowflake, Guild, ColorResolvable, Role, } = require('discord.js');
-const { sendMsgToChannel, addRoleToMember, } = require('../../discord-services');
-const BotGuild = require('../../db/mongo/BotGuild');
-const winston = require('winston');
-const Console = require('../../classes/consoles/console');
-const { MessagePrompt, StringPrompt, NumberPrompt, SpecialPrompt, RolePrompt, ChannelPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * The InitBot command initializes the bot on the guild. It will prompt the user for information needed 
- * to set up the bot. It is only usable by server administrators. It can only be run once.
- * @category Commands
- * @subcategory Essentials
- * @extends Command
- */
-class InitBot extends Command {
-    constructor(client) {
-        super(client, {
-            name: 'init-bot',
-            group: 'essentials',
-            memberName: 'initialize the bot',
-            description: 'Will start the bot given some information.',
-            hidden: true,
-            args: []
-        });
-    }
-
-    /**
-     * @param {Message} message
-     */
-    async run(message) {
-        message.delete();
-
-        // easy constants to use
-        var channel = message.channel;
-        const userId = message.author.id;
-        /** @type {CommandoGuild} */
-        const guild = message.guild;
-        const everyoneRole = message.guild.roles.everyone;
-
-        const botGuild = await BotGuild.findById(guild.id);
-
-        // make sure the user had manage server permission
-        if (!message.member.hasPermission('MANAGE_GUILD')) {
-            message.reply('Only admins can use this command!').then(msg => msg.delete({timeout: 5000}));
-        }
-
-        if (botGuild?.isSetUpComplete) {
-            sendMsgToChannel(channel, userId, 'This guild is already set up!!', 30);
-            return;
-        }
-        
-        let initialMsg = await sendMsgToChannel(channel, userId, `This is the Factotum Bot Initialization procedure.
-            First, I will ask two short questions.
-            Then, we will move over to another channel and continue the initialization process.
-            PLEASE read every question carefully! All the questions will end with instructions on how to respond.
-            This is only the initial setup. You will be able to change things in the future!`);
-
-        await new Promise((resolve) => setTimeout(resolve, 15000));
-
-        // grab the admin role
-        const adminRole = await this.askOrCreate('admin', channel, userId, guild, '#008369');
-        addRoleToMember(message.member, adminRole);
-
-        // create the admin channel room
-        let {adminConsoleChannel, adminLog} = await BotGuild.createAdminChannels(guild, adminRole, everyoneRole);
-        await sendMsgToChannel(channel, userId, 'The admin channels have been created successfully! <#' + adminConsoleChannel.id + '>. Lets jump over there and continue yes?!', 10);
-
-        initialMsg.delete();
-
-        // transition to the admin console
-        channel = adminConsoleChannel;
-        await sendMsgToChannel(channel, userId, 'I am over here!!! Lets continue!');
-
-        const adminConsole = new Console({
-            title: 'Factotum Admin Console',
-            description: 'In this console you will be able to edit bot information.',
-            channel: adminConsoleChannel,
-            guild: guild,
-        });
-        adminConsole.addField('Role Changes', 'You are free to change role colors and role names! However, stamp role numbers can NOT be changed. You can add permissions but don\'t remove any!');
-        adminConsole.sendConsole();
-
-        // ask the user to move our role up the list
-        await sendMsgToChannel(channel, userId, `Before we move on, could you please move my role up the role list as high as possible, this will give me the ability to assign roles! 
-            I will wait for 15 seconds but feel free to take as long as you need!
-            You can learn more here: https://support.discord.com/hc/en-us/articles/214836687-Role-Management-101`, 30);
-        await new Promise((resolve) => setTimeout(resolve, 15000));
-
-        // grab the staff role
-        const staffRole = await this.askOrCreate('staff', channel, userId, guild, '#00D166');
-        adminConsole.addField('The staff role:', `<@&${staffRole.id}>`);
-
-        // get the regular member, this role will have the general member permissions
-        const memberRole = await this.askOrCreate('member', channel, userId, guild, '#006798');
-        adminConsole.addField('The member role:', `<@&${memberRole.id}>`);
-
-        // bot support channel prompt
-        let botSupportChannel = await ChannelPrompt.single({prompt: 'What channel can the bot use to contact users when DMs are not available?', channel, userId, cancelable: false});
-        adminConsole.addField('Channel used to contact Users with DM issues', `<#${botSupportChannel.id}>`);
-
-        botGuild.readyUp(this.client, {
-            roleIDs: {
-                adminRole: adminRole.id,
-                staffRole: staffRole.id,
-                everyoneRole: everyoneRole.id,
-                memberRole: memberRole.id,
-            },
-            channelIDs: {
-                adminLog: adminLog.id,
-                adminConsole: adminConsoleChannel.id,
-                botSupportChannel: botSupportChannel.id,
-            }
-        });
-        
-        // ask if verification will be used
-        var isVerification;
-        try {
-            isVerification = await SpecialPrompt.boolean({prompt: 'Will you be using the verification service?', channel, userId, cancelable: true});
-        } catch (error) {
-            winston.loggers.get(guild.id).warning(`Handled an error when setting up verification, and thus was not set up. Error was ${error.name}`, { event: 'InitBot Command', data: error });
-            isVerification = false;
-        }
-
-        if (isVerification) {
-            // ask for guest role
-            var guestRole = await this.askOrCreate('guest', channel, userId, guild, '#969C9F');
-
-            let infoMsg = await sendMsgToChannel(channel, userId, 'I need to know what types to verify when a user tries to verify. Please follow the instructions, it will let you add as many types as you wish.');
-
-            let types = await this.getVerificationTypes(channel, userId);
-            infoMsg.delete();
-            await botGuild.setUpVerification(this.client, guestRole.id, types);
-
-            sendMsgToChannel(channel, userId, 'The verification service has been set up correctly!', 8);
-            let typeListString = types.map(typeInfo => `${typeInfo.type} -> <@&${typeInfo.roleId}>`).join(', ');
-            adminConsole.addField('Verification Feature', `IS ENABLED!\n Guest Role: <@&${guestRole.id}>\nTypes: ${typeListString}`);
-        } else {
-            sendMsgToChannel(channel, userId, 'Verification service was not set due to Prompt cancellation.', 8);
-            adminConsole.addField('Verification Feature', 'IS NOT ENABLED!');
-        }
-
-        // only do attendance if verification is on!
-        if (isVerification) {
-            var isAttendance;
-            try {
-                isAttendance = await SpecialPrompt.boolean({prompt: 'Will you be using the attendance service?', channel, userId});
-            } catch (error) {
-                winston.loggers.get(guild.id).warning(`Handled an error when setting up verification, and thus was not set up. Error was ${error.name}`, { event: 'InitBot Command', data: error });
-                isAttendance = false;
-            }
-
-            if (isAttendance) {
-                const attendeeRole = await this.askOrCreate('attendee', channel, userId, guild, '#0099E1');
-                await botGuild.setUpAttendance(this.client, attendeeRole.id);
-
-                sendMsgToChannel(channel, userId, 'The attendance service has been set up correctly!', 8);
-                adminConsole.addField('Attendance Feature', `IS ENABLED!\n Attendance Role: <@&${attendeeRole.id}>`);
-            } else {
-                sendMsgToChannel(channel, userId, 'Attendance was not set up!', 8);
-                adminConsole.addField('Attendance Feature', 'IS NOT ENABLED!');
-            }
-        } else {
-            sendMsgToChannel(channel, userId, 'Attendance was not set up!', 8);
-            adminConsole.addField('Attendance Feature', 'IS NOT ENABLED!');
-        }
-
-        // ask if the announcements will be used
-        try {
-            if (await SpecialPrompt.boolean({prompt: 'Have firebase announcements been set up code-side? If not say no, or the bot will fail!', channel, userId})) {
-                let announcementChannel = await ChannelPrompt.single('What channel should announcements be sent to? If you don\'t have it, create it and come back, do not cancel.');
-                await botGuild.setUpAnnouncements(this.client, announcementChannel.id);
-
-                sendMsgToChannel(channel, userId, 'The announcements have been set up correctly!', 8);
-                adminConsole.addField('Announcement Feature', `IS ENABLED!\n Announcements Channel: <#${announcementChannel.id}>`);
-            } else {
-                sendMsgToChannel(channel, userId, 'Announcements functionality was not set up.', 8);
-                adminConsole.addField('Announcement Feature', 'IS NOT ENABLED!');
-            }
-        } catch (error) {
-            sendMsgToChannel(channel, userId, 'Announcements functionality was not set up due to a Prompt cancellation.', 8);
-            adminConsole.addField('Announcement Feature', 'IS NOT ENABLED!');
-        }
-
-
-        // ask if the stamps will be used
-        var isStamps;
-        try {
-            isStamps = await SpecialPrompt.boolean({prompt: 'Will you be using the stamp service?', channel, userId});
-        } catch {
-            isStamps = false;   
-        }
-        if (isStamps) {
-            var numberOfStamps = 0;
-            try {
-                numberOfStamps = await NumberPrompt.single({prompt: 'How many stamps do you want?', channel, userId, cancelable: true});
-            } catch (error) {/** Do nothing */}
-
-            await botGuild.setUpStamps(this.client, numberOfStamps);
-            guild.setGroupEnabled('stamps', true);
-
-            sendMsgToChannel(channel, userId, 'The stamp roles have been created, you can change their name and/or color, but their stamp number is final!', 8);
-            adminConsole.addField('Stamps Feature', 'IS ENABLED!\n You can change the role\'s name and color, but their number is final. For example, stamps 3 is stamp 3 even if its name changes.');
-        } else {
-            sendMsgToChannel(channel, userId, 'The stamp functionality was not set up.', 8);
-            adminConsole.addField('Stamps Feature', 'IS NOT ENABLED!');
-        }
-
-
-        // ask if the user will use the report functionality
-        var isReport;
-        try {
-            isReport = await SpecialPrompt.boolean({prompt: 'Will you be using the report functionality?', channel, userId});
-        } catch {
-            isReport = false;
-        }
-        if (isReport) {
-            var incomingReportChannel;
-            try {
-                incomingReportChannel = await ChannelPrompt.single({prompt: 'What channel should prompts be sent to? We recommend this channel be accessible to your staff.', channel, userId});
-            } catch {/** Do nothing */}
-
-            // Send report to report channel or admin log if none given!
-            let channelId = incomingReportChannel ? incomingReportChannel.id : adminLog.id;
-            await botGuild.setUpReport(this.client, channelId);
-
-            sendMsgToChannel(channel, userId, `The report command is available and reports will be sent to: <#${channelId}>`, 8);
-            adminConsole.addField('Report Feature', `IS ENABLED!\n Reports are sent to <#${channelId}>`);
-        } else {
-            sendMsgToChannel(channel, userId, 'Report command is not enabled.', 8);
-            adminConsole.addField('Report Feature', 'IS NOT ENABLED!');
-        }
-
-
-        // ask if the user wants to use the experimental !ask command
-        var isAsk;
-        try {
-            isAsk = await SpecialPrompt.boolean({prompt: 'Do you want to let users use the experimental !ask command?', channel, userId});
-        } catch {
-            isAsk = false;
-        }
-        if (isAsk) {
-            botGuild.setUpAsk(this.client);
-            sendMsgToChannel(channel, userId, 'The ask command is now available to the server users.', 8);
-            adminConsole.addField('Experimental Ask Command', 'IS ENABLED!');
-        } else {
-            sendMsgToChannel(channel, userId, 'Ask command is not enabled.', 8);
-            adminConsole.addField('Experimental Ask Command', 'IS NOT ENABLED!');
-        }
-
-        await botGuild.save();
-        botGuild.setCommandStatus(this.client);
-
-        sendMsgToChannel(channel, userId, 'The bot is set and ready to hack!', 8);
-    }
-
-    /**
-     * @typedef TypeInfo
-     * @property {String} type
-     * @property {String} roleId
-     */
-
-    /**
-     * Prompts the user for a verification type and if they want to add more. Will call itself if true 
-     * for a recursive call.
-     * @param {TextChannel} channel 
-     * @param {String} userId 
-     * @returns {Promise<TypeInfo[]>}
-     * @async
-     */
-    async getVerificationTypes(channel, userId) {
-        let typeMsg = await MessagePrompt.prompt({ prompt: `Please tell me the type and mention the role for a verification option. 
-            For example: hacker @hacker . Make sure you add nothing more to the message!`, channel, userId });
-        let type = typeMsg.content.replace(/<(@&?|#)[a-z0-9]*>/ , ''); // clean out any snowflakes
-        type = type.toLowerCase().trim();
-        let role = typeMsg.mentions.roles.first();
-
-        if (await SpecialPrompt.boolean({ prompt: 'Would you like to add another verification option?', channel, userId })) {
-            return (await this.getVerificationTypes(channel, userId)).concat([{
-                type: type,
-                roleId: role.id,
-            }]);
-        } else {
-            return [{
-                type: type,
-                roleId: role.id,
-            }];
-        }
-    }
-
-    /**
-     * Will ask the user if a role has been created, if so, then prompt it, else then create it.
-     * @param {String} roleName - the role name
-     * @param {TextChannel} channel - the text channel were to prompt
-     * @param {Snowflake} userId - the user id to prompt to 
-     * @param {Guild} guild - the current guild
-     * @param {ColorResolvable} - the role color
-     * @async
-     * @returns {Promise<Role>}
-     */
-    async askOrCreate(roleName, channel, userId, guild, color) {
-        try {
-            let hasRole = await SpecialPrompt.boolean({prompt: 'Have you created the ' + roleName + ' role? You can go ahead and create it if you wish, or let me do the hard work.', channel, userId});
-            if (hasRole) {
-                return await RolePrompt.single({prompt: 'What is the ' + roleName + ' role?', channel, userId});
-            } else {
-                return await guild.roles.create({
-                    data: {
-                        name: await StringPrompt.single({prompt: 'What name would you like the ' + roleName + ' role to have?', channel, userId}),
-                        color: color,
-                    }
-                });
-            }
-        } catch (error) {
-            sendMsgToChannel(channel, userId, 'You need to complete this prompt please try again!', 5);
-            return await this.askOrCreate(roleName, channel, userId, guild, color);
-        }
-    }
-}
-module.exports = InitBot;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_essentials_unknown-command.js.html b/docs/commands_essentials_unknown-command.js.html deleted file mode 100644 index 45a5c800..00000000 --- a/docs/commands_essentials_unknown-command.js.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/essentials/unknown-command.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/essentials/unknown-command.js

-
- - - - - -
-
-
// Discord.js commando requirements
-const { Command } = require('discord.js-commando');
-const { deleteMessage } = require('../../discord-services');
-const { Message } = require('discord.js');
-
-/**
- * This unknown command is used by the bot when a unknown command is run.
- * @category Commands
- * @subcategory Essentials
- * @extends Command
- */
-class UnknownCommand extends Command {
-    constructor(client) {
-        super(client, {
-            name: 'unknown-command',
-            group: 'essentials',
-            memberName: 'unknown-command',
-            description: 'Displays help information when an unknown command is used.',
-            unknown: true,
-            hidden: true,
-        });
-    }
-
-    /**
-     * @param {Message} message
-     */
-    async run(message) {
-        if (message.channel.type === 'dm') {
-            return;
-        } else {
-            deleteMessage(message);
-            message.reply('This is an unknown command!').then(msg => msg.delete({timeout: 3000}));
-        }
-        
-    }
-}
-module.exports = UnknownCommand;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_hacker_utility_ask.js.html b/docs/commands_hacker_utility_ask.js.html deleted file mode 100644 index b20b1940..00000000 --- a/docs/commands_hacker_utility_ask.js.html +++ /dev/null @@ -1,274 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/hacker_utility/ask.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/hacker_utility/ask.js

-
- - - - - -
-
-
// Discord.js commando requirements
-const PermissionCommand = require('../../classes/permission-command');
-const { checkForRole, sendMessageToMember, } = require('../../discord-services');
-const { MessageEmbed, Collection, Message, } = require('discord.js');
-const BotGuildModel = require('../../classes/bot-guild');
-
-/**
- * The ask command tries to imitate a thread like functionality from slack. Users can ask questions, and then other 
- * users can respond to the question, the responses are added on the same message embed, to keep the conversation on 
- * the same message.
- * @category Commands
- * @subcategory Hacker-Utility
- * @extends PermissionCommand
- */
-class AskQuestion extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'ask',
-            group: 'hacker_utility',
-            memberName: 'ask anonymous question with thread',
-            description: 'Will send the question to the same channel, and add emoji collector for thread like support.',
-            guildOnly: true,
-            args: [
-                {
-                    key: 'question',
-                    prompt: 'Question to ask',
-                    type: 'string',
-                    default: '',
-                }
-            ],
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild 
-     * @param {Message} message 
-     * @param {Object} args 
-     * @param {String} args.question
-     */
-    async runCommand(botGuild, message, {question}) {
-
-        // if question is blank let user know via DM and exit
-        if (question === '') {
-            sendMessageToMember(message.member, 'When using the !ask command, add your question on the same message!\n' + 
-                                                                'Like this: !ask This is a question');
-            return;
-        }
-        
-        // get current channel
-        var curChannel = message.channel;
-
-        // message embed to be used for question
-        const qEmbed = new MessageEmbed()
-            .setColor(botGuild.colors.questionEmbedColor)
-            .setTitle('Question from ' + message.author.username)
-            .setDescription(question);
-        
-        // send message and add emoji collector
-        curChannel.send(qEmbed).then(async (msg) => {
-
-            // list of users currently responding
-            var onResponse = new Collection();
-            
-            msg.react('🇷');  // respond emoji
-            msg.react('✅');  // answered emoji!
-            msg.react('⏫');  // up vote emoji
-            msg.react('⛔');  // delete emoji
-
-            // filter and collector
-            const emojiFilter = (reaction, user) => !user.bot && (reaction.emoji.name === '🇷' || reaction.emoji.name === '✅' || reaction.emoji.name === '⛔');
-            const collector = msg.createReactionCollector(emojiFilter);
-
-            collector.on('collect', async (reaction, user) => {
-                // delete the reaction
-                reaction.users.remove(user.id);
-
-                // add response to question
-                if (reaction.emoji.name === '🇷') {
-                    // make sure user is not already responding
-                    if (onResponse.has(user.id)) {
-                        return;
-                    } else {
-                        onResponse.set(user.id, user.username);
-                    }
-
-                    // prompt the response
-                    curChannel.send('<@' + user.id + '> Please send your response within 15 seconds! If you want to cancel write cancel.').then(prompt => {
-                        // filter and message await only one
-                        // only user who reacted this message will be able to add a reply to it
-                        curChannel.awaitMessages(m => m.author.id === user.id, {max: 1, time: 15000, errors: ['time']}).then((msgs) => {
-                            var response = msgs.first();
-
-                            // if cancel then do nothing
-                            if (response.content.toLowerCase() != 'cancel') {
-                                // if user has a mentor role, they get a special title
-                                if (checkForRole(response.member, botGuild.roleIDs.staffRole)) {
-                                    msg.edit(msg.embeds[0].addField('🤓 ' + user.username + ' Responded:', response.content));
-                                } else {
-                                    // add a field to the message embed with the response
-                                    msg.edit(msg.embeds[0].addField(user.username + ' Responded:', response.content));
-                                }
-                            }
-
-                            // delete messages
-                            prompt.delete();
-                            response.delete();
-
-                            // remove user from on response list
-                            onResponse.delete(user.id);
-                        }).catch((msgs) => {
-                            prompt.delete();
-                            curChannel.send('<@' + user.id + '> Time is up! When you are ready to respond, emoji again!').then(msg => msg.delete({timeout: 2000}));
-
-                            // remove user from on response list
-                            onResponse.delete(user.id);
-                        });
-                    });
-                }
-                // check for check-mark emoji and only user who asked the question
-                else if (reaction.emoji.name === '✅' && user.id === message.author.id) {
-                    // change color
-                    msg.embeds[0].setColor('#80c904');
-                    // change title and edit embed
-                    msg.edit(msg.embeds[0].setTitle('✅ ANSWERED ' + msg.embeds[0].title));
-                } 
-                // remove emoji will remove the message
-                else if (reaction.emoji.name === '⛔') {
-                    // check that user is staff
-                    if (checkForRole(msg.guild.member(user), botGuild.roleIDs.staffRole)) {
-                        msg.delete();
-                    } else {
-                        sendMessageToMember(user, 'Deleting a question is only available to staff!', true);
-                    }
-                    
-                } 
-            });
-        });
-    }
-}
-module.exports = AskQuestion;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_hacker_utility_report.js.html b/docs/commands_hacker_utility_report.js.html deleted file mode 100644 index dd280b5e..00000000 --- a/docs/commands_hacker_utility_report.js.html +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/hacker_utility/report.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/hacker_utility/report.js

-
- - - - - -
-
-
const { Command } = require('discord.js-commando');
-const { deleteMessage, sendMessageToMember, } = require('../../discord-services');
-const { MessageEmbed, Message } = require('discord.js');
-const BotGuild = require('../../db/mongo/BotGuild');
-
-/**
- * The report command allows users to report incidents from the server to the admins. Reports are made 
- * via the bot's DMs and are 100% anonymous. 
- * @category Commands
- * @subcategory Hacker-Utility
- * @extends Command
- */
-class Report extends Command {
-    constructor(client) {
-        super(client, {
-            name: 'report',
-            group: 'hacker_utility',
-            memberName: 'report to admins',
-            description: 'Will send report format to user via DM for user to send back via DM. Admins will get the report!',
-            // not guild only!
-            args: [],
-        });
-    }
-
-    /**
-     * @param {Message} message
-     */
-    async run (message) {
-        let botGuild = await BotGuild.findById(message.guild.id);
-
-        deleteMessage(message);
-
-        if (!botGuild.report.isEnabled) {
-            sendMessageToMember(message.author, 'The report functionality is disabled for this guild.');
-            return;
-        }
-
-        const embed = new MessageEmbed()
-            .setColor(botGuild.colors.embedColor)
-            .setTitle('Thank you for taking the time to report users who are not following server or MLH rules. You help makes our community safer!')
-            .setDescription('Please use the format below, be as precise and accurate as possible. \n ' + 
-                            'Everything you say will be 100% anonymous. We have no way of reaching back to you so again, be as detailed as possible!\n' + 
-                            'Copy paste the format and send it to me in this channel!')
-            .addField('Format:', 'User(s) discord username(s) (including discord id number(s)):\n' + 
-                                    'Reason for report (one line):\n' + 
-                                    'Detailed Explanation:\n' + 
-                                    'Name of channel where the incident occurred (if possible):');
-
-        // send message to user with report format
-        var msgEmbed = await message.author.send(embed);
-
-        // await response
-        msgEmbed.channel.awaitMessages(m => true, {max: 1}).then(async msgs => {
-            var msg = msgs.first();
-
-            msgEmbed.delete();
-            message.author.send('Thank you for the report! Our admin team will look at it ASAP!');
-
-            // send the report content to the admin report channel!
-            var incomingReportChn = await message.guild.channels.resolve(botGuild.report.incomingReportChannelID);
-
-            const adminMsgEmbed = new MessageEmbed()
-                .setColor(botGuild.colors.embedColor)
-                .setTitle('There is a new report that needs your attention!')
-                .setDescription(msg.content);
-
-            // send embed with text message to ping admin
-            incomingReportChn.send('<@&' + botGuild.roleIDs.adminRole + '> Incoming Report', {embed: adminMsgEmbed});
-        });
-        
-    }
-}
-module.exports = Report;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_stamps_change-stamp-time.js.html b/docs/commands_stamps_change-stamp-time.js.html deleted file mode 100644 index ba69651a..00000000 --- a/docs/commands_stamps_change-stamp-time.js.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/stamps/change-stamp-time.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/stamps/change-stamp-time.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { replyAndDelete } = require('../../discord-services');
-const { Message } = require('discord.js');
-const BotGuildModel = require('../../classes/bot-guild');
-
-/**
- * Change the time users get to react to get a stamp from activity stamp distributions. It defaults to 60 seconds.
- * @category Commands
- * @subcategory Stamps
- * @extends PermissionCommand
- */
-class ChangeStampTime extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'change-stamp-time',
-            group: 'stamps',
-            memberName: 'new stamp time',
-            description: 'Will set the given seconds as the new stamp time for activities.',
-            guildOnly: true,
-            args: [
-                {
-                    key: 'newTime',
-                    prompt: 'new time for stamp collectors to use',
-                    type: 'integer',
-                },
-            ],
-        },
-        {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'Hey there, the command !change-stamp-time is only available to staff!',
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild 
-     * @param {Message} message 
-     * @param {Object} args
-     * @param {Number} args.newTime 
-     */
-    async runCommand(botGuild, message, {newTime}) {
-
-        botGuild.stamps.stampCollectionTime = newTime;
-        botGuild.save();
-
-        replyAndDelete(message, 'Stamp collection will now give hackers ' + newTime + ' seconds to collect stamp.');
-    }
-}
-module.exports = ChangeStampTime;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_stamps_password-stamp.js.html b/docs/commands_stamps_password-stamp.js.html deleted file mode 100644 index 155b41e2..00000000 --- a/docs/commands_stamps_password-stamp.js.html +++ /dev/null @@ -1,299 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/stamps/password-stamp.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/stamps/password-stamp.js

-
- - - - - -
-
-
const { sendEmbedToMember, sendMessageToMember, deleteMessage } = require('../../discord-services');
-const { MessageEmbed, Message, Snowflake, Collection } = require('discord.js');
-const PermissionCommand = require('../../classes/permission-command');
-const BotGuildModel = require('../../classes/bot-guild');
-const StampsManager = require('../../classes/stamps-manager');
-const { StringPrompt, ChannelPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * Sends a reaction collector for users to react, send a password and receive a stamp. Used to give out stamps for activities that don't have 
- * an activity instance. The user who starts the password stamp must give the activity name, password, and stop time defaults to 120 seconds. Users 
- * have 3 attempts to get the password right within the stop time.
- * @category Commands
- * @subcategory Stamps
- * @extends PermissionCommand
- */
-class PasswordStamp extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'password-stamp',
-            group: 'stamps',
-            memberName: 'gives stamps requiring passwords',
-            description: 'gives a stamp to everyone who reacted and gave the correct password',
-            args: [
-                {   key: 'activityName',
-                    prompt: 'the workshop/activity name',
-                    type: 'string',
-                    default: '',
-                },
-                {
-                    key: 'password',
-                    prompt: 'the password for hackers to use to get stamp',
-                    type: 'string',
-                    default: '',
-                },
-                {
-                    key: 'stopTime',
-                    prompt: 'time for stamp collector to be open for, in minutes.',
-                    type: 'integer',
-                    default: 120,
-                }
-            ],
-        }, 
-        {
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'This command is only available on the admin console!',
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'This command can only available to staff!',
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message
-     * @param {Object} args
-     * @param {String} args.activityName
-     * @param {String} args.password
-     * @param {Number} args.stopTime
-     */
-    async runCommand(botGuild, message, {activityName, password, stopTime}) {
-        // helpful vars
-        let channel = message.channel;
-        let userId = message.author.id;
-        // check if arguments have been given and prompt for the channel to use
-        try {
-            if (activityName === '') {
-                activityName = await StringPrompt.single({prompt: 'Please respond with the workshop/activity name.', channel, userId, cancelable: true});
-            }
-
-            if(password === '') {
-                password = await StringPrompt.single({prompt: 'Please respond with the password for hackers to use to get stamp.', channel, userId, cancelable: true});
-            }
-
-            var targetChannel = await ChannelPrompt.single({prompt: 'What channel do you want to send the stamp collector to? Users should have access to this channel!', channel, userId, cancelable: true});
-        } catch (error) {
-            channel.send('<@' + userId + '> Command was canceled due to prompt being canceled.').then(msg => msg.delete({timeout: 5000}));
-            return;
-        }
-
-        const qEmbed = new MessageEmbed()
-            .setColor(botGuild.colors.embedColor)
-            .setTitle('React with anything to claim a stamp for attending ' + activityName)
-            .setDescription('Once you react to this message, check for a DM from this bot. **You can only emoji this message once!**')
-            .addField('A Password Is Required!', 'Through the Bot\'s DM, you will have 3 attempts in the first 60 seconds to enter the correct password.');
-        
-        targetChannel.send(qEmbed).then((msg) => {
-
-            let emoji = '👍';
-            msg.react(emoji);
-            
-            /**
-             * keeps track of which users have already reacted to the message so there are no duplicates
-             * @type {Collection<Snowflake, String>} - <User.id, User.username>
-             */
-            var seenUsers = new Collection();
-
-            // filter emoji reaction and collector
-            const emojiFilter = (reaction, user) => !user.bot && !seenUsers.has(user.id);
-            const collector = msg.createReactionCollector(emojiFilter, {time: (1000 * stopTime * 60)});  // stopTime is in minutes, multiply to get seconds, then milliseconds 
-
-            //send hacker a dm upon reaction
-            collector.on('collect', async(reaction, user) => {
-                seenUsers.set(user.id, user.username);
-
-                const member = message.guild.member(user);
-
-                // prompt member for password
-                var dmMessage = await sendEmbedToMember(user, {
-                    description: 'You have 60 seconds and 3 attempts to type the password correctly to get the ' + activityName + ' stamp.\n' +
-                    'Please enter the password (leave no stray spaces or anything):',
-                    title: 'Stamp Collector For ' + activityName,
-                    color: '#b2ff2e',
-                });
-
-                var correctPassword = false;
-                var incorrectPasswords = 0;
-
-                const filter = m => user.id === m.author.id;
-                //message collector for the user's password attempts
-                const pwdCollector = dmMessage.channel.createMessageCollector(filter,{time: 60000, max: 3});
-
-                pwdCollector.on('collect', async m => {
-                    //update role and stop collecting if password matches
-                    if (m.content.toLowerCase() === password.toLowerCase()) {
-
-                        StampsManager.parseRole(member, activityName, botGuild);
-                        
-                        correctPassword = true;
-                        pwdCollector.stop();
-                    } else if (incorrectPasswords < 2) {
-                        //add 1 to number of incorrect guesses and prompts user to try again
-                        await sendMessageToMember(user, 'Incorrect. Please try again.', true);
-                    }
-                    incorrectPasswords++;
-                });
-                pwdCollector.on('end', collected => {
-                    deleteMessage(dmMessage);
-
-                    //show different messages after password collection expires depending on circumstance
-                    if (!correctPassword) {
-                        if (incorrectPasswords < 3) {
-                            sendEmbedToMember(user, {
-                                title: 'Stamp Collector',
-                                description: 'Time\'s up! You took too long to enter the password for the ' + activityName + ' stamp. If you have extenuating circumstances please contact an organizer.',
-                            });
-                        } else {
-                            sendEmbedToMember(user, {
-                                title: 'Stamp Collector',
-                                description: 'Incorrect. You have no attempts left for the ' + activityName + ' stamp. If you have extenuating circumstances please contact an organizer.',
-                            });
-                        }
-                    }
-                });
-            });
-
-            //edits the embedded message to notify people when it stops collecting reacts
-            collector.on('end', collected => {
-                if (msg.guild.channels.cache.find(channel => channel.name === targetChannel.name)) {
-                    msg.edit(qEmbed.setTitle('Time\'s up! No more responses are being collected. Thanks for participating in ' + activityName + '\'s booth!'));
-                }
-            });
-        });
-    }
-}
-module.exports = PasswordStamp;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_stamps_raffle.js.html b/docs/commands_stamps_raffle.js.html deleted file mode 100644 index 77ed951f..00000000 --- a/docs/commands_stamps_raffle.js.html +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/stamps/raffle.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/stamps/raffle.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { Message, MessageEmbed } = require('discord.js');
-const BotGuildModel = require('../../classes/bot-guild');
-
-/**
- * Picks x amount of winners from the stamp contest. The more stamps a user has, the more chances they have of winning.
- * @category Commands
- * @subcategory Stamps
- * @extends PermissionCommand
- */
-class Raffle extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'raffle',
-            group: 'stamps',
-            memberName: 'draw raffle winners',
-            description: 'parses each hacker for their stamps and draws winners from them, one entry per stamp',
-            guildOnly: true,
-            args: [
-                {
-                    key: 'numberOfWinners',
-                    prompt: 'number of winners to be selected',
-                    type: 'integer'
-                },
-            ]
-        },
-        {
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'This command can only be used in the admin console!',
-            role: PermissionCommand.FLAGS.ADMIN_ROLE,
-            roleMessage: 'You do not have permission for this command, only admins can use it!',
-        });
-    }
-
-    /**
-     * Main function which looks at every member's roles, identifies all that end in a number, and adds the member's id that many times 
-     * into an array. Then it chooses random numbers and picks the id corresponding to that index until it has numberOfWinners unique 
-     * winners.
-     * 
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message - message used to call the command
-     * @param {Object} args
-     * @param {integer} args.numberOfWinners - number of winners to be drawn
-     */
-    async runCommand(botGuild, message, {numberOfWinners}) {
-
-        //check that numberOfWinners is less than the number of people with stamp roles or it will infinite loop
-        let validMembers = message.guild.members.cache.filter(member => member.roles.cache.has(botGuild.roleIDs.memberRole));
-        var memberCount = validMembers.size;
-        if (memberCount <= numberOfWinners) {
-            message.channel.send('Whoa there, you want more winners than hackers!').then((msg) => {
-                msg.delete({ timeout: 5000 });
-            });
-            return;
-        }
-
-        //array to contain the ids
-        var entries = new Array(); 
-        
-        validMembers.forEach(member => {
-            let roleId = member.roles.cache.find(role => botGuild.stamps.stampRoleIDs.has(role.id));
-            if (!roleId) return;
-            let stampNumber = botGuild.stamps.stampRoleIDs.get(roleId);
-
-            for (let i = 0; i < stampNumber; i++) {
-                entries.push(member.user.id);
-            }
-        });
-
-        //number of array spaces that are actually occupied by ids
-        var length = entries.length;
-
-        //set to keep track of winners
-        let winners = new Set();
-        //randomly generate a number and add the corresponding winner into the set
-        while (winners.size < numberOfWinners) {
-            let num = Math.floor(Math.random() * length);
-            let winner = entries[num];
-            if (!winners.has(winner)) winners.add(winner);
-        }
-        let winnersList = Array.from(winners);
-        const embed = new MessageEmbed()
-            .setColor(botGuild.colors.embedColor)
-            .setTitle('The winners of the raffle draw are:')
-            .setDescription( winnersList.map(id => `<@${id}>`).join(', '));
-        await message.channel.send(embed);
-    }
-}
-module.exports = Raffle;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_verification_add-members.js.html b/docs/commands_verification_add-members.js.html deleted file mode 100644 index 1cc1aee3..00000000 --- a/docs/commands_verification_add-members.js.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/verification/add-members.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/verification/add-members.js

-
- - - - - -
-
-
const csvParser = require('csv-parser');
-const { Message } = require('discord.js');
-const https = require('https');
-const PermissionCommand = require('../../classes/permission-command');
-const { addUserData } = require('../../db/firebase/firebase-services');
-const BotGuildModel = require('../../classes/bot-guild');
-const { sendMsgToChannel } = require('../../discord-services');
-const winston = require('winston');
-const { MessagePrompt } = require('advanced-discord.js-prompts');
-
-/**
- * Will prompt the user for a csv file to add members to firebase. The csv file must have the following columns with exactly those names:
- * * email -> the user's email, must be a string
- * * firstName -> the user's first name, must be a string
- * * lastName -> the user's last name, must be a string
- * * types -> the types the user will get, must be a list of strings separated by a comma, spaces are okay, types must be the same ones used when setting up verification
- * @category Commands
- * @subcategory Verification
- * @extends PermissionCommand
- * @guildOnly
- */
-class AddMembers extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'add-members',
-            group: 'verification',
-            memberName: 'add members to verification',
-            description: 'adds members to the verification system using via file',
-            guildOnly: true,
-        },
-        {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'Hey there, the !add-members command is only for staff!',
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'Hey there, the !add-members command is only available on the admin console!',
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild 
-     * @param {Message} message 
-     */
-    async runCommand(botGuild, message) {
-        
-        try {
-            // request file
-            let msg = await MessagePrompt.prompt({ prompt: 'Please send the csv file!', channel: message.channel, userId: message.author.id});
-
-            let fileUrl = msg.attachments.first().url;
-
-            https.get(fileUrl).on('error', (error) => winston.loggers.get(message.guild.id).warning(`There was an error while adding members- Error: ${error}`, { event: 'Add Member Command' }));
-
-            var holdMsg = await sendMsgToChannel(message.channel, message.author.id, 'Adding data please hold ...');
-            
-            https.get(fileUrl, (response) => {
-                response.pipe(csvParser()).on('data', async (data) => {
-
-                    if (!data.email || !data.firstName || !data.lastName || !data.types) {
-                        sendMsgToChannel(message.channel, message.author.id, 'The excel data is incomplete or the file type is not CSV (might be CSV UTF-8). Try again!', 10);
-                        return;
-                    }
-
-                    /** @type {String} */
-                    let typesString = data.types;
-    
-                    let typesList = typesString.split(',').map(string => string.trim().toLowerCase());
-    
-                    typesList = typesList.filter(type => botGuild.verification.verificationRoles.has(type));
-                
-                    if (typesList.length > 0) await addUserData(data.email, typesList, message.guild.id, undefined, data.firstName, data.lastName);
-                }).on('end', () => {
-                    holdMsg.delete();
-                    sendMsgToChannel(message.channel, message.author.id, 'The members have been added to the database!', 10);
-                    winston.loggers.get(message.guild.id).verbose(`Members have been added to the database by ${message.author.id}.`, { event: 'Add Member Command' });
-                });
-            }).on('error', (error) => {
-                holdMsg.delete();
-                sendMsgToChannel(message.channel, message.author.id, `There was an error, please try again! Error: ${error}`, 10);
-                winston.loggers.get(message.guild.id).warning(`There was an error while adding members- Error: ${error}`, { event: 'Add Member Command' });
-            });
-        } catch (error) {
-            winston.loggers.get(message.guild.id).warning(`There was an error when adding members. Error: ${error}`, { event: 'Add Member Command' });
-        }
-    }
-}
-module.exports = AddMembers;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_verification_check-member.js.html b/docs/commands_verification_check-member.js.html deleted file mode 100644 index 2a78d8a4..00000000 --- a/docs/commands_verification_check-member.js.html +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/verification/check-member.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/verification/check-member.js

-
- - - - - -
-
-
// Discord.js commando requirements
-const PermissionCommand = require('../../classes/permission-command');
-const firebaseServices = require('../../db/firebase/firebase-services');
-const BotGuildModel = require('../../classes/bot-guild');
-const { Message } = require('discord.js');
-
-/**
- * User can check if a member is in the database by email or name.
- * @category Commands
- * @subcategory Verification
- * @extends PermissionCommand
- */
-class CheckMember extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'check-member',
-            group: 'verification',
-            memberName: 'check if member is in database',
-            description: 'If given email, will tell user if email is valid, or suggest similar emails if not. If given name, returns corresponding email.',
-            args: [
-                {
-                    key: 'emailOrName',
-                    prompt: 'Please provide the email address or name to check (write names in the format firstName-lastName)',
-                    type: 'string',
-                    default: '',
-                },
-
-            ],
-        },
-        {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'Hey there, the !check-member command is only for staff!',
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'Hey there, the !check-member command is only available in the admin console channel.',
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild 
-     * @param {Message} message 
-     * @param {Object} args
-     * @param {String} args.emailOrName
-     */
-    async runCommand(botGuild, message, { emailOrName }) {
-        if (emailOrName.split('-').length === 1) { // check for similar emails if given argument is an email
-            let result = await firebaseServices.checkEmail(emailOrName, message.guild.id);
-            if (result.length > 0) { // if similar emails were found, print them
-                let listMembers = '';
-                result.forEach(member => {
-                    let listMember = member.email + ' (' + member.types.join(', ') + ') ';
-                    listMembers += listMember;
-                });
-                message.channel.send('Here are the results I found similar to ' + emailOrName + ': ' + listMembers);
-            } else { // message if no similar emails found
-                message.channel.send('No matches to this email were found').then(msg => msg.delete({ timeout: 8000 }));
-            }
-        } else { // check for members of the given name if argument was a name
-            let firstName = emailOrName.split('-')[0];
-            let lastName = emailOrName.split('-')[1];
-            let result = await firebaseServices.checkName(firstName, lastName, message.guild.id);
-            if (result != null) { // print email if member was found
-                message.channel.send('Email found for ' + firstName + ' ' + lastName + ' is: ' + result);
-            } else { // message if member was not found
-                message.channel.send('The name does not exist in our database!').then(msg => msg.delete({ timeout: 8000 }));
-            }
-        }
-    }
-}
-module.exports = CheckMember;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_verification_manual-verify.js.html b/docs/commands_verification_manual-verify.js.html deleted file mode 100644 index b97cbf35..00000000 --- a/docs/commands_verification_manual-verify.js.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/verification/manual-verify.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/verification/manual-verify.js

-
- - - - - -
-
-
// Discord.js commando requirements
-const PermissionCommand = require('../../classes/permission-command');
-const { addUserData } = require('../../db/firebase/firebase-services');
-const { sendMsgToChannel, checkForRole, validateEmail } = require('../../discord-services');
-const { Message } = require('discord.js');
-const Verification = require('../../classes/verification');
-const BotGuildModel = require('../../classes/bot-guild');
-const winston = require('winston');
-const { StringPrompt, NumberPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * Will manually verify a user to the server and the database. Asks the user for a user ID, email, and type(s) to add with. 
- * @category Commands
- * @subcategory Verification
- * @extends PermissionCommand
- * @guildonly
- */
-class ManualVerify extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'manual-verify',
-            group: 'verification',
-            memberName: 'manual hacker verification',
-            description: 'Will verify a guest to the specified role.',
-            guildOnly: true,
-        },
-        {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'Hey there, the !manual-verify command is only for staff!',
-            channel: PermissionCommand.FLAGS.ADMIN_CONSOLE,
-            channelMessage: 'The !manual-verify command is only available in the admin console!'
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message 
-     */
-    async runCommand(botGuild, message) {
-        try {
-            // helpful vars
-            let channel = message.channel;
-            let userId = message.author.id;
-
-            let guestId = await NumberPrompt.single({ prompt: 'What is the ID of the member you would like to verify?', channel, userId, cancelable: true});
-            var member = message.guild.member(guestId); // get member object by id
-            
-            // check for valid ID
-            if (!member) {
-                sendMsgToChannel(channel, userId, `${guestId.toString()} is an invalid ID!`, 5);
-                return;
-            }
-            // check for member to have guest role
-            if (!checkForRole(member, botGuild.verification.guestRoleID)) {
-                sendMsgToChannel(channel, userId, `<@${guestId.toString()}> does not have the guest role! Cant verify!`, 5);
-                return;
-            }
-
-            let availableTypes = Array.from(botGuild.verification.verificationRoles.keys()).join();
-            
-            let types = StringPrompt.multiRestricted({ prompt: 'Please respond with the types you want this user to verify.', channel, userId}, availableTypes);
-
-            let email = await StringPrompt.single({ prompt: 'What is their email?', channel, userId, cancelable: true});
-            
-            // validate the email
-            if(!validateEmail(email)) {
-                sendMsgToChannel(channel, userId, 'The email is not valid!', 5);
-                return;
-            }
-            
-            await addUserData(email, types, member.guild.id, member);
-            try {
-                await Verification.verify(member, email, message.guild, botGuild);
-            } catch (error) {
-                sendMsgToChannel(channel, userId, 'Email provided is not valid!', 5);
-            }
-            
-            message.channel.send('Verification complete!').then(msg => msg.delete({ timeout: 3000 }));
-            
-        } catch (error) {
-            winston.loggers.get(botGuild._id).warning(`While manually verifying, there was an error but it was handled. Error: ${error}.`, { event: 'Manual Verify Command' });
-            return;
-        }
-    }
-}
-module.exports = ManualVerify;
-
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_verification_start-verification.js.html b/docs/commands_verification_start-verification.js.html deleted file mode 100644 index e52774dd..00000000 --- a/docs/commands_verification_start-verification.js.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/verification/start-verification.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/verification/start-verification.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { sendEmbedToMember } = require('../../discord-services');
-const { Message, MessageEmbed } = require('discord.js');
-const Verification = require('../../classes/verification');
-const BotGuildModel = require('../../classes/bot-guild');
-const { StringPrompt } = require('advanced-discord.js-prompts');
-
-/**
- * Sends an embed with reaction collector for users to re-verify
- * via DMs with the bot from inside the server.
- * @category Commands
- * @subcategory Verification
- * @extends PermissionCommand
- * @guildonly
- */
-class StartVerification extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'start-verification',
-            group: 'verification',
-            memberName: 'welcome channel verification activation',
-            description: 'send another dm for verification',
-            guildOnly: true,
-        },
-        {
-            role: PermissionCommand.FLAGS.STAFF_ROLE,
-            roleMessage: 'Hey there, the !manual-verify command is only for staff!',
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message 
-     */
-    async runCommand(botGuild, message) {
-        var embed = new MessageEmbed()
-            .setTitle('If the bot does not respond when you click on the clover emoji in your DM, react to this message with any emoji to verify!');
-        let embedMsg = await message.channel.send(embed);
-        embedMsg.react('🍀');
-
-        const verifyCollector = embedMsg.createReactionCollector((reaction, user) => !user.bot);
-        verifyCollector.on('collect', async (reaction, user) => {
-            let member = message.guild.members.cache.get(user.id);
-
-            try {
-                var email = await StringPrompt.single({prompt: `Thanks for joining ${message.guild.name}! What email did you get accepted with? Please send it now!`, channel: (await member.user.createDM()), userId: member.id, time: 45});
-            } catch (error) {
-                sendEmbedToMember(member, {
-                    title: 'Verification Error',
-                    description: 'Email was not provided, please try again by reacting to the emoji again.!'
-                }, true);
-                return;
-            }
-
-            try {
-                await Verification.verify(member, email, member.guild, botGuild);
-            } catch (error) {
-                sendEmbedToMember(member, {
-                    title: 'Verification Error',
-                    description: 'Email provided is not valid! Please try again by reacting to the emoji again.'
-                }, true);
-            }
-        });
-    }
-}
-module.exports = StartVerification;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/commands_verification_verify.js.html b/docs/commands_verification_verify.js.html deleted file mode 100644 index cf52dbe0..00000000 --- a/docs/commands_verification_verify.js.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - - Factotum Documentation commands/verification/verify.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

commands/verification/verify.js

-
- - - - - -
-
-
const PermissionCommand = require('../../classes/permission-command');
-const { sendEmbedToMember, sendMessageToMember, checkForRole, validateEmail } = require('../../discord-services');
-const { Message } = require('discord.js');
-const Verification = require('../../classes/verification');
-const BotGuildModel = require('../../classes/bot-guild');
-
-/**
- * Will verify the user running the command, needs the user's email and guild ID. Can only 
- * be run through DM.
- * @category Commands
- * @subcategory Verification
- * @extends PermissionCommand
- * @dmonly
- */
-class Verify extends PermissionCommand {
-    constructor(client) {
-        super(client, {
-            name: 'verify',
-            group: 'verification',
-            memberName: 'hacker verification',
-            description: 'Will verify a guest to its correct role if their email is in our database.',
-            args: [
-                {
-                    key: 'email',
-                    prompt: 'Please provide your email address',
-                    type: 'string',
-                    default: '',
-                },
-                {
-                    key: 'guildId',
-                    prompt: 'Please provide the server ID, ask admins for it!',
-                    type: 'integer',
-                },
-            ],
-        },
-        {
-            dmOnly: true,
-        });
-    }
-
-    /**
-     * @param {BotGuildModel} botGuild
-     * @param {Message} message
-     * @param {Object} args 
-     * @param {String} args.email 
-     * @param {String} args.guildId
-     */
-    async runCommand(botGuild, message, { email, guildId }) {
-
-        // check if the user needs to verify, else warn and return
-        if (!checkForRole(member, botGuild.roleIDs.guestRole)) {
-            sendEmbedToMember(member, {
-                title: 'Verify Error',
-                description: 'You do not need to verify, you are already more than a guest!'
-            }, true);
-            return;
-        }
-
-        // let user know he has used the command incorrectly and exit
-        if (!validateEmail(email)) {
-            sendMessageToMember(message.author, 'You have used the verify command incorrectly! \nPlease write a valid email after the command like this: !verify email@gmail.com');
-            return;
-        }
-
-        let guild = this.client.guilds.cache.get(guildId);
-        if (!guild) {
-            sendEmbedToMember(message.author, {
-                title: 'Verification Failure',
-                description: 'The given server ID is not valid. Please try again!',
-            });
-            return;
-        }
-        let member = guild.member(message.author.id);
-
-        // Call the verify function
-        try {
-            Verification.verify(member, email, guild, botGuild);
-        } catch (error) {
-            sendEmbedToMember(member, {
-                title: 'Verification Error',
-                description: 'Email provided is not valid!'
-            }, true);
-        }
-    }
-}
-module.exports = Verify;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/db_firebase_firebase-services.js.html b/docs/db_firebase_firebase-services.js.html deleted file mode 100644 index 5dde60b8..00000000 --- a/docs/db_firebase_firebase-services.js.html +++ /dev/null @@ -1,423 +0,0 @@ - - - - - - - - - - Factotum Documentation db/firebase/firebase-services.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

db/firebase/firebase-services.js

-
- - - - - -
-
-
const { GuildMember, } = require('discord.js');
-const admin = require('firebase-admin');
-
-/**
- * The firebase services module has firebase related helper functions.
- * @module FirebaseServices
- */
-
-/**
- * All the firebase apps in play stored by their name.
- * @type {Map<String, admin.app.App>}
- */
-const apps = new Map();
-module.exports.apps = apps;
-
-/**
- * Will start an admin connection with the given name
- * @param {String} name - name of the connection
- * @param {JSON} adminSDK - the JSON file with admin config
- * @param {String} databaseURL - the database URL
- */
-function initializeFirebaseAdmin(name, adminSDK, databaseURL) {
-    let app = admin.initializeApp({
-        credential: admin.credential.cert(adminSDK),
-        databaseURL: databaseURL,
-    }, name);
-
-    apps.set(name, app);
-
-}
-module.exports.initializeFirebaseAdmin = initializeFirebaseAdmin;
-
-
-
-/**
- * @typedef UserType
- * @property {String} type
- * @property {Boolean} isVerified
- * @property {Date} timestamp
- */
-
-/**
- * @typedef FirebaseUser
- * @property {String} email
- * @property {String} discordId
- * @property {UserType[]} types
- */
-
-/**
- * Retrieves a question from the db that has not already been asked at the Discord Contests, then marks the question as having been 
- * asked in the db.
- * @param {String} guildId - the id of the guild
- * @returns {Object | null} - the data object of a question or null if no more questions
- */
-async function getQuestion(guildId) {
-    //checks that the question has not been asked
-    let questionReference = apps.get('nwPlusBotAdmin').firestore().collection('guilds').doc(guildId).collection('questions').where('asked', '==', false).limit(1);
-    let question = (await questionReference.get()).docs[0];
-    //if there exists an unasked question, change its status to asked
-    if (question != undefined) {
-        question.ref.update({
-            'asked': true,
-        });
-        return question.data();
-    }
-    return null;
-}
-module.exports.getQuestion = getQuestion;
-
-/**
- * Retrieves self-care reminder from the db that has not already been sent, 
- * then marks the reminder as having been asked in the db.
- * @param {String} guildId - the guild id
- * @returns {Object | null} - the data object of a reminder or null if no more reminders
- */
-async function getReminder(guildId) {
-    //checks that the reminder has not been sent
-    var qref = apps.get('nwPlusBotAdmin').firestore().collection('guilds').doc(guildId).collection('reminders').where('sent', '==', false).limit(1);
-    var reminder = (await qref.get()).docs[0];
-    //if there reminder unsent, change its status to asked
-    if (reminder != undefined) {
-        reminder.ref.update({
-            'sent' : true,
-        });
-        return reminder.data();
-    }
-    return null;
-}
-module.exports.getReminder = getReminder;
-
-
-/**
- * @typedef {Object} Member
- * @property {String} email - the email of the member
- * @property {Boolean} isVerified - whether member has already verified
- * @property {String} type - role a member has in the server
- */
-
-/**
- * Checks to see if the input email matches or is similar to emails in the database
- * Returns an array of objects containing emails that match or are similar, along with the verification status of each, 
- * and returns empty array if none match
- * @param {String} email - email to check
- * @param {String} guildId - the guild id
- * @returns {Promise<Array<Member>>} - array of members with similar emails to parameter email
- */
-async function checkEmail(email, guildId) {
-    const snapshot = (await apps.get('nwPlusBotAdmin').firestore().collection('guilds').doc(guildId).collection('members').get()).docs; // retrieve snapshot as an array of documents in the Firestore
-    var foundEmails = [];
-    snapshot.forEach(memberDoc => {
-        // compare each member's email with the given email
-        if (memberDoc.get('email') != null) {
-            let compare = memberDoc.get('email');
-            // if the member's emails is similar to the given email, retrieve and add the email, verification status, and member type of
-            // the member as an object to the array
-            if (compareEmails(email.split('@')[0], compare.split('@')[0])) {
-                foundEmails.push({
-                    email: compare,
-                    types: memberDoc.get('types').map(type => type.type),
-                });
-            }
-        }
-    });
-    return foundEmails;
-}
-module.exports.checkEmail = checkEmail;
-
-/**
- * Uses Levenshtein Distance to determine whether two emails are within 5 Levenshtein Distance
- * @param {String} searchEmail - email to search for similar emails for
- * @param {String} dbEmail - email from db to compare to searchEmail
- * @returns {Boolean} - Whether the two emails are similar
- * @private
- */
-function compareEmails(searchEmail, dbEmail) {
-    // matrix to track Levenshtein Distance with
-    var matrix = new Array(searchEmail.length);
-    var searchEmailChars = searchEmail.split('');
-    var dbEmailChars = dbEmail.split('');
-    // initialize second dimension of matrix and set all elements to 0
-    for (let i = 0; i < matrix.length; i++) {
-        matrix[i] = new Array(dbEmail.length);
-        for (let j = 0; j < matrix[i].length; j++) {
-            matrix[i][j] = 0;
-        }
-    }
-    // set all elements in the top row and left column to increment by 1
-    for (let i = 1; i < searchEmail.length; i++) {
-        matrix[i][0] = i;
-    }
-    for (let j = 1; j < dbEmail.length; j++) {
-        matrix[0][j] = j;
-    }
-    // increment Levenshtein Distance by 1 if there is a letter inserted, deleted, or swapped; store the running tally in the corresponding
-    // element of the matrix
-    let substitutionCost;
-    for (let j = 1; j < dbEmail.length; j++) {
-        for (let i = 1; i < searchEmail.length; i++) {
-            if (searchEmailChars[i] === dbEmailChars[j]) {
-                substitutionCost = 0;
-            } else {
-                substitutionCost = 1;
-            }
-            matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + substitutionCost);
-        }
-    }
-    return matrix[searchEmail.length - 1][dbEmail.length - 1] <= (Math.min(searchEmail.length, dbEmail.length) / 2);
-}
-
-/**
- * Finds the email of user with given first and last names 
- * @param {String} firstName - first name of member to match with database
- * @param {String} lastName - last name of member to match with database
- * @param {String} guildId - the guild id
- * @returns {Promise<String>} - email of given member
- * @private
- */
-async function checkName(firstName, lastName, guildId) {
-    const snapshot = (await apps.get('nwPlusBotAdmin').firestore().collection('guilds').doc(guildId).collection('members').get()).docs; // snapshot of Firestore as array of documents
-    snapshot.forEach(memberDoc => {
-        if (memberDoc.get('firstName') != null && memberDoc.get('lastName') != null && memberDoc.get('firstName').toLowerCase() === firstName.toLowerCase()
-            && memberDoc.get('lastName').toLowerCase() === lastName.toLowerCase()) { // for each document, check if first and last names match given names
-            return memberDoc.get('email');
-        }
-    });
-    return null;
-}
-module.exports.checkName = checkName;
-
-/**
- * Adds a new guild member to the guild's member collection. Email is used as ID, there can be no duplicates.
- * @param {String} email - email of member verified
- * @param {String[]} types - types this user might verify for
- * @param {String} guildId - the guild id
- * @param {GuildMember} [member={}] - member verified
- * @param {String} [firstName=''] - users first name
- * @param {String} [lastName=''] - users last name
- * @async
- */
-async function addUserData(email, types, guildId, member = {}, firstName = '', lastName = '') {
-    var newDocument = apps.get('nwPlusBotAdmin').firestore().collection('guilds').doc(guildId).collection('members').doc(email.toLowerCase());
-
-    /** @type {FirebaseUser} */
-    let data = {
-        email: email.toLowerCase(),
-        discordId: member?.id || null,
-        types: types.map((type, index, array) => {
-            /** @type {UserType} */
-            let userType = {
-                type: type,
-                isVerified: false,
-            };
-            return userType;
-        }),
-        firstName: firstName,
-        lastName: lastName,
-    };
-
-    await newDocument.set(data);
-}
-module.exports.addUserData = addUserData;
-
-/**
- * Verifies the any event member via their email.
- * @param {String} email - the user email
- * @param {String} id - the user's discord snowflake
- * @param {String} guildId - the guild id
- * @returns {Promise<String[]>} - the types this user is verified
- * @async
- * @throws Error if the email provided was not found.
- */
-async function verify(email, id, guildId) {
-    let emailLowerCase = email.toLowerCase();
-    var userRef = apps.get('nwPlusBotAdmin').firestore().collection('guilds').doc(guildId).collection('members').where('email', '==', emailLowerCase).limit(1);
-    var user = (await userRef.get()).docs[0];
-    if (user) {
-        let returnTypes = [];
-
-        /** @type {FirebaseUser} */
-        var data = user.data();
-
-        data.types.forEach((value, index, array) => {
-            if (!value.isVerified) {
-                value.isVerified = true;
-                value.VerifiedTimestamp = admin.firestore.Timestamp.now();
-                returnTypes.push(value.type);
-            }
-        });
-
-        data.discordId = id;
-
-        user.ref.update(data);
-
-        return returnTypes;
-    } else {
-        throw new Error('The email provided was not found!');
-    }
-}
-module.exports.verify = verify;
-
-/**
- * Attends the user via their discord id
- * @param {String} id - the user's discord snowflake
- * @param {String} guildId - the guild id
- * @returns {Promise<String[]>} - the types this user is verified
- * @async
- * @throws Error if the email provided was not found.
- */
-async function attend(id, guildId) {
-    var userRef = apps.get('nwPlusBotAdmin').firestore().collection('guilds').doc(guildId).collection('members').where('discordId', '==', id).limit(1);
-    var user = (await userRef.get()).docs[0];
-
-    if (user) {
-        /** @type {FirebaseUser} */
-        var data = user.data();
-
-        data.types.forEach((value, index, array) => {
-            if (value.isVerified) {
-                value.isAttending = true;
-                value.AttendingTimestamp = admin.firestore.Timestamp.now();
-            }
-        });
-
-        user.ref.update(data);
-    } else {
-        throw new Error('The discord id provided was not found!');
-    }
-}
-module.exports.attend = attend;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/db_firebase_parser.js.html b/docs/db_firebase_parser.js.html deleted file mode 100644 index 865c75a1..00000000 --- a/docs/db_firebase_parser.js.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - - - - - Factotum Documentation db/firebase/parser.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

db/firebase/parser.js

-
- - - - - -
-
-
const csv = require('csv-parser');
-const fs = require('fs');
-require('dotenv').config();
-const FirebaseServices = require('./firebase-services');
-
-/** 
- * The firebase parser module has scripts to parse csv data to upload to 
- * firebase for validation purposes.
- * @module FirebaseParser
- */
-
-
-const adminSDK = JSON.parse(process.env.NWPLUSADMINSDK);
-let app = FirebaseServices.initializeFirebaseAdmin('factotum', adminSDK, 'https://nwplus-bot.firebaseio.com');
-
-// save second argument as type of members to add
-let type = process.argv[2];
-if (type == undefined) {
-    throw new Error('no defined type!');
-}
-
-// third argument is guild id
-let guildId = process.argv[3];
-if (!guildId) throw new Error('The guild id was not defined as the third argument!');
-
-// optional fourth argument; if "true", all their previous types will be overwritten by this new one
-let overwrite = false;
-if (process.argv[4] === 'true') {
-    overwrite = true;
-}
-
-class Registration {
-    constructor(email) {
-        this.email = email;
-        this.types = [];
-    }
-}
-
-const results = [];
-const all_regs = {};
-fs.createReadStream('registrations.csv') // requires a registrations.csv file in root directory to run
-    .pipe(csv())
-    .on('data', (data) => results.push(data))
-    .on('end', () => {
-        results.forEach((row) => { // grab email from each csv entry and save to all_regs
-            const email = (row['What is your primary email that can we contact you with?'] || row['Email Address']).toLowerCase();
-
-            const r = new Registration(email);
-            all_regs[email] = r;
-        });
-
-        let db = FirebaseServices.apps.get('factotum').firestore();
-
-        var all = db.collection('guilds').doc(guildId).collection('members').get().then(snapshot => {
-            // get all ids and types of members already in collections and store in idMap
-            let idMap = new Map();
-            snapshot.docs.forEach(doc => idMap.set(doc.id, doc.get('types'))); // keys are doc ids, values are member types
-
-            let iterable = Object.entries(all_regs);
-            console.log(`found ${iterable.length} registrations total!!`);
-
-            console.log(`found ${idMap.size} existing registrations, actually patching ${iterable.length - idMap.size} new registrations`);
-            while (iterable.length > 0) {
-                var batch = db.batch();
-
-                for (let [key, value] of iterable.splice(0, 500)) {
-                    key = key.toLowerCase();
-                    var docRef = db.collection('guilds').doc(guildId).collection('members').doc(key);
-                    if (idMap.has(key)) {
-                        // if overwrite is on, replace the Registration's existing types with just the new type
-                        if (overwrite) {
-                            value.types = [{ isVerified: false, type: type }];
-                        } else {
-                            // else retrieve the Registration's existing types and push the new type
-                            value.types = idMap.get(key);
-                            if (!idMap.get(key).some(role => role.type === type)) {
-                                value.types.push({ isVerified: false, type: type });
-                            }
-                        }
-                    } else {
-                        // if member is new, just push current type into types array
-                        value.types.push({ isVerified: false, type: type });
-                    }
-                    batch.set(docRef, Object.assign({}, value));
-                }
-
-                batch.commit();
-                console.log('batch write success');
-            }
-            console.log('done!');
-        });
-
-    });
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/db_mongo_BotGuild.js.html b/docs/db_mongo_BotGuild.js.html deleted file mode 100644 index 5da98246..00000000 --- a/docs/db_mongo_BotGuild.js.html +++ /dev/null @@ -1,307 +0,0 @@ - - - - - - - - - - Factotum Documentation db/mongo/BotGuild.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

db/mongo/BotGuild.js

-
- - - - - -
-
-
const { Schema, model } = require('mongoose');
-
-const BotGuildClass = require('../../classes/bot-guild');
-
-/**
- * @class BotGuild
- */
-const BotGuildSchema = new Schema({
-
-    roleIDs: {
-        memberRole: {
-            type: String,
-        },
-        staffRole: {
-            type: String,
-        },
-        adminRole: {
-            type: String,
-        },
-        everyoneRole: {
-            type: String,
-        },
-    },
-
-    channelIDs: {
-        adminConsole: {
-            type: String,
-        },
-        adminLog: {
-            type: String,
-        },
-        botSupportChannel: {
-            type: String,
-        },
-        archiveCategory: {
-            type: String,
-        },
-    },
-
-    verification: {
-        isEnabled: {
-            type: Boolean,
-            default: false,
-        },
-        guestRoleID: {
-            type: String,
-        },
-        welcomeChannelID: {
-            type: String,
-        },
-        welcomeSupportChannelID: {
-            type: String,
-        },
-        verificationRoles: {
-            type: Map,
-            default: new Map(),
-        }
-    },
-
-    attendance: {
-        isEnabled: {
-            type: Boolean,
-            default: false,
-        },
-        attendeeRoleID: {
-            type: String,
-        },
-    },
-
-    stamps: {
-        isEnabled: {
-            type: Boolean,
-            default: false,
-        },
-        stampRoleIDs: {
-            type: Map,
-            default: new Map(),
-        },
-        stamp0thRoleId: {
-            type: String,
-        },
-        stampCollectionTime: {
-            type: Number,
-            default: 60,
-        },
-    },
-
-    report: {
-        isEnabled: {
-            type: Boolean,
-            default: false,
-        },
-        incomingReportChannelID: {
-            type: String,
-        },
-    },
-
-    announcement: {
-        isEnabled: {
-            type: Boolean,
-            default: false,
-        },
-        announcementChannelID: {
-            type: String,
-        },
-    },
-
-    ask: {
-        isEnabled: {
-            type: Boolean,
-            default: false,
-        },
-    },
-
-    blackList: {
-        type: Map,
-        default: new Map(),
-    },
-
-    caves: {
-        type: Map,
-        default: new Map(),
-    },
-
-    colors: {
-        embedColor: {
-            type: String,
-            default: '#26fff4',
-        },
-        questionEmbedColor: {
-            type: String,
-            default: '#f4ff26',
-        },
-        announcementEmbedColor: {
-            type: String,
-            default: '#9352d9',
-        },
-        tfTeamEmbedColor: {
-            type: String,
-            default: '#60c2e6',
-        },
-        tfHackerEmbedColor: {
-            type: String,
-            default: '#d470cd',
-        },
-        specialDMEmbedColor: {
-            type: String,
-            default: '#fc6b03',
-        }, 
-    },
-
-    _id: {
-        type: String,
-        required: true,
-    },
-
-    isSetUpComplete: {
-        type: Boolean,
-        default: false,
-    },
-
-    prefix: {
-        type: String,
-        default: '!',
-        required: true,
-    },
-});
-
-BotGuildSchema.loadClass(BotGuildClass);
-
-const BotGuild = model('BotGuild', BotGuildSchema);
-
-module.exports = BotGuild;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/db_mongo_mongoUtil.js.html b/docs/db_mongo_mongoUtil.js.html deleted file mode 100644 index d310a415..00000000 --- a/docs/db_mongo_mongoUtil.js.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - - - Factotum Documentation db/mongo/mongoUtil.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

db/mongo/mongoUtil.js

-
- - - - - -
-
-
const { MongoClient, Db,  } = require('mongodb');
-const mongoose = require('mongoose');
-
-/**
- * The mongo utility module has some useful mongo related helper functions.
- * @module MongoUtil
- */
-
-const url = 'mongodb+srv://dev-user:' + process.env.MONGODBPASSWORD + '@cluster-dev.j87rm.mongodb.net/test?retryWrites=true&w=majority&useNewUrlParser=true&useUnifiedTopology=true';
-const mongooseUrl = 'mongodb+srv://dev-user:' + process.env.MONGODBPASSWORD + '@cluster-dev.j87rm.mongodb.net/data';
-/** @type {Db} */
-var _db;
-
-module.exports = {
-
-    /**
-     * Starts a connection to MongoDB
-     */
-    async connect() {
-        const mongoClient = new MongoClient(url);
-
-        await mongoClient.connect();
-
-        console.log('Connected to mongoDB');
-        _db = mongoClient.db('data');
-    },
-
-    /**
-     * @returns {Db}
-     */
-    getDb() {
-        return _db;
-    },
-
-    /**
-     * @returns {Collection}
-     */
-    getBotGuildCol() {
-        return _db.collection('botGuilds');
-    },
-
-    async mongooseConnect() {
-        _db = await mongoose.connect(mongooseUrl, {useNewUrlParser: true, useUnifiedTopology: true});
-    }
-
-};
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/discord-services.js.html b/docs/discord-services.js.html deleted file mode 100644 index ec5bf0c5..00000000 --- a/docs/discord-services.js.html +++ /dev/null @@ -1,407 +0,0 @@ - - - - - - - - - - Factotum Documentation discord-services.js - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Source

-

discord-services.js

-
- - - - - -
-
-
const { GuildMember, TextChannel, Message, User, MessageEmbed, RoleResolvable, Guild } = require('discord.js');
-const winston = require('winston');
-const BotGuild = require('./db/mongo/BotGuild');
-
-/**
- * The discord services module has useful discord related functions.
- * These functions are helper, discord related functions.
- * @module DiscordServices
- */
-
-/**
- * Checks if the member has a role, returns true if it does
- * @param {GuildMember} member - member to check role
- * @param {String} role - role ID to check for
- */
-function checkForRole(member, role) {
-    winston.loggers.get(member.guild.id).verbose(`A role check was requested. Role ID: ${role}. Member ID: ${member.id}`);
-    return member.roles.cache.has(role);
-}
-module.exports.checkForRole = checkForRole;
-
-/**
- * Will send a message to a text channel and ping the user, can be deleted after a timeout.
- * @param {TextChannel} channel - the channel to send the message to
- * @param {String} userId - the user to tag on the message
- * @param {String} message - the message to send
- * @param {Number} timeout - timeout before delete if any, in seconds
- * @async
- * @returns {Promise<Message>}
- */
-async function sendMsgToChannel(channel, userId, message, timeout = 0) {
-    let msg = await channel.send('<@' + userId + '> ' + message);
-
-    if (timeout) msg.delete({timeout: timeout * 1000}); // convert to milliseconds
-    winston.loggers.get(channel.guild.id).verbose(`A message has been sent to the channel ${channel.name} for the user with id ${userId} ${timeout === 0 ? 'with no timeout requested' : 'with a ' + timeout + ' second timeout.'}`);
-    return msg;
-}
-module.exports.sendMsgToChannel = sendMsgToChannel;
-
-/**
- * Send a Direct message to a member, option to delete after a few seconds.
- * Helps user fix DM issue if the bot can't reach them over DM.
- * @param {User | GuildMember} member - the user or member to send a DM to
- * @param {String | MessageEmbed} message - the message to send
- * @param {Boolean} isDelete - weather to delete message after 60 seconds
- * @async
- * @return {Promise<Message>}
- */
-async function sendMessageToMember(member, message, isDelete = false) {
-    return await member.send(message).then(msg => {
-        winston.loggers.get(member?.guild?.id || 'main').verbose(`A DM message was sent to user with id ${member.id}.`);
-        if (isDelete === true) {
-            msg.delete({timeout: 60000});
-        }
-        return msg;
-    }).catch(async error => {
-        if (error.code === 50007) {
-            winston.loggers.get(member?.guild?.id || 'main').warning(`A DM message was sent to user with id ${member.id} but failed, he has been asked to fix this problem!`);
-            let botGuild;
-            if (member?.guild) botGuild = await BotGuild.findById(member.guild.id);
-            else {
-                winston.loggers.get(member.guild.id).error('While trying to help a user to get my DMs I could not find a botGuild for which this member is in. I could not help him!');
-                throw Error(`I could not help ${member.id} due to not finding the guild he is trying to access. I need a member and not a user!`);
-            }
-
-            member.guild.channels.resolve(botGuild.channelIDs.botSupportChannel).send('<@' + member.id + '> I couldn\'t reach you :(. Please turn on server DMs, explained in this link: https://support.discord.com/hc/en-us/articles/217916488-Blocking-Privacy-Settings-');
-        } else {
-            throw error;
-        }
-    });
-    
-}
-module.exports.sendMessageToMember = sendMessageToMember;
-
-
-/**
- * @typedef FieldInfo
- * @property {String} title - field title
- * @property {String} description - field description
- */
-
-/**
- * @typedef EmbedOptions
- * @property {String} title - embed title
- * @property {String} description - embed description
- * @property {String} color - embed color
- * @property {Array<FieldInfo>} fields - embed fields
- */
-
-/**
- * Sends an embed to a user via DM. Title and description are required, color and fields are optional.
- * @param {User | GuildMember} member - member to send embed to
- * @param {EmbedOptions} embedOptions - embed information
- * @param {Boolean} isDelete - should the message be deleted after some time?
- * @async
- * @returns {Promise<Message>}
- */
-async function sendEmbedToMember(member, embedOptions, isDelete = false) {
-    // check embedOptions
-    if (embedOptions?.title === undefined || embedOptions?.title === '') throw new Error('A title is needed for the embed!');
-    if (embedOptions?.description === undefined || embedOptions?.description === '') throw new Error('A description is needed for the embed!');
-    if (embedOptions?.color === undefined || embedOptions?.color === '') embedOptions.color === '#ff0000';
-
-    let embed = new MessageEmbed().setColor(embedOptions.color)
-        .setTitle(embedOptions.title)
-        .setDescription(embedOptions.description)
-        .setTimestamp();
-
-    if (embedOptions?.fields) embedOptions.fields.forEach((fieldInfo, index) => embed.addField(fieldInfo.title, fieldInfo.description));
-
-    return sendMessageToMember(member, embed, isDelete);
-}
-module.exports.sendEmbedToMember = sendEmbedToMember;
-
-/**
- * Add a role to a member
- * @param {GuildMember} member - the guild member to give a role to
- * @param {RoleResolvable} addRole - the role to add to the member
- */
-function addRoleToMember(member, addRole) {
-    if (!member?.guild) throw Error('I need a member not a user!!!');
-    
-    let role = member.guild.roles.resolve(addRole);
-    member.roles.add(addRole).catch(error => {
-        // try one more time
-        member.roles.add(addRole).catch(error => {
-            // now send error to admins
-            discordLog(member.guild, '@everyone The member <@' + member.id + '> did not get the role <@&' + role.id +'> please help me!');
-            winston.loggers.get(member.guild.id).error(`Could not give the member with id ${member.id} the role ${role.name} with id ${role.id}. The following error ocurred: ${error.name} - ${error.message}.`, { event: 'Error', data: error });
-        });
-    });
-    winston.loggers.get(member.guild.id).verbose(`A member with id ${member.id} was given the role ${role.name} with id ${role.id}`);
-}
-module.exports.addRoleToMember = addRoleToMember;
-
-/**
- * Remove a role to a member
- * @param {GuildMember} member - the guild member to give a role to
- * @param {RoleResolvable} removeRole - the role to add to the member
- */
-function removeRolToMember(member, removeRole) {
-    let role = member.guild.roles.resolve(removeRole);
-    member.roles.remove(removeRole).catch(error => {
-        // try one more time
-        member.roles.remove(removeRole).catch(error => {
-            // now send error to admins
-            discordLog(member.guild, '@everyone The member <@' + member.user.id + '> did not loose the role ' + member.guild.roles.cache.get(removeRole).id + ', please help me!');
-            winston.loggers.get(member.guild.id).error(`Could not remove the member with id ${member.id} the role ${role.name} with id ${role.id}. The following error ocurred: ${error.name} - ${error.message}.`);
-        });
-    });
-    winston.loggers.get(member.guild.id).verbose(`A member with id ${member.id} lost the role ${role.name} with id ${role.id}`);
-}
-module.exports.removeRolToMember = removeRolToMember;
-
-/**
- * Replaces one role for the other
- * @param {GuildMember} member - member to change roles to
- * @param {RoleResolvable} removeRole - role to remove
- * @param {RoleResolvable} addRole - role to add
- */
-function replaceRoleToMember(member, removeRole, addRole) {
-    addRoleToMember(member, addRole);
-    removeRolToMember(member, removeRole);
-}
-module.exports.replaceRoleToMember = replaceRoleToMember;
-
-/**
- * Log a message on the log channel
- * @param {Guild} guild - the guild being used
- * @param {String | MessageEmbed} message - message to send to the log channel
- * @async
- */
-async function discordLog(guild, message) {
-    let botGuild = await BotGuild.findById(guild.id);
-    if (botGuild?.channelIDs?.adminLog) {
-        guild.channels.cache.get(botGuild.channelIDs.adminLog)?.send(message);
-        winston.loggers.get(guild.id).silly(`The following was logged to discord: ${message}`);
-    }
-    else winston.loggers.get(guild.id).error('I was not able to log something to discord!! I could not find the botGuild or the adminLog channel!');
-}
-module.exports.discordLog = discordLog;
-
-/**
- * Reply to message and delete 5 seconds later
- * @param {Message} message - the message to reply to
- * @param {String} reply - the string to reply
- */
-async function replyAndDelete(message, reply) {
-    var msg = await message.reply(reply);
-    msg.delete({timeout: 5000});
-    winston.loggers.get(message?.guild.id || 'main').verbose(`A message with id ${message.id} is being replied to and then the reply is being deleted.`);
-}
-module.exports.replyAndDelete = replyAndDelete;
-
-/**
- * Deletes a message if the message hasn't been deleted already
- * @param {Message} message - the message to delete
- * @param {Number} timeout - the time to wait in milliseconds
- * @async
- */
-async function deleteMessage(message, timeout = 0) {
-    if (!message.deleted && message.deletable &&  message.channel.type != 'dm') {
-        winston.loggers.get(message.guild.id).verbose(`A message with id ${message.id} in the guild channel ${message.channel.name} with id ${message.channel.id} was deleted.`);
-        await message.delete({timeout: timeout});
-    } else if (message.channel.type === 'dm' && message.author.bot) {
-        winston.loggers.get('main').verbose(`A message with id ${message.id} in a DM channel with user id ${message.channel.recipient.id} from the bot was deleted.`);
-        await message.delete({timeout: timeout});
-    } else {
-        winston.loggers.get(message?.guild.id | 'main').warning(`A message with id ${message.id} in a DM channel from user with id ${message.author.id} tried to be deleted but was not possible.`);
-    }
-}
-module.exports.deleteMessage = deleteMessage;
-
-/**
- * Delete the given channel if it is not deleted already
- * @param {TextChannel} channel 
- */
-async function deleteChannel(channel) {
-    if (!channel.deleted && channel.deletable) {
-        winston.loggers.get(channel.guild.id).verbose(`The channel ${channel.name} with id ${channel.id} was deleted.`);
-        await channel.delete();
-    } else {
-        winston.loggers.get(channel.guild.id).warning(`The channel ${channel?.name} with id ${channel?.id} tried to be deleted but was not possible!`);
-    }
-}
-module.exports.deleteChannel = deleteChannel;
-
-/**
- * Returns a random color as a hex string.
- * @returns {String} - hex color
- */
-function randomColor() {
-    winston.loggers.get('main').silly('A random color has been used!');
-    return Math.floor(Math.random()*16777215).toString(16);
-}
-module.exports.randomColor = randomColor;
-
-/**
- * Validates an email using a reg exp.
- * @param {String} email - the email to validate
- * @returns {Boolean} true if valid email, false otherwise
- */
-function validateEmail(email) {
-    winston.loggers.get('main').silly('An email has been validated!');
-
-    // make email lowercase
-    email = email.toLowerCase();
-
-    // regex to validate email
-    const re = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
-
-    // let user know he has used the command incorrectly and exit
-    if (email === '' || !re.test(email)) {
-        
-        return false;
-    } else {
-        return true;
-    }
-}
-module.exports.validateEmail = validateEmail;
-
-/**
- * will shuffle an array as best and fast as possible
- * @param {Array<*>} array - array to shuffle
- * @private
- */
-function shuffleArray(array) {
-    for (let i = array.length - 1; i > 0; i--) {
-        const j = Math.floor(Math.random() * (i + 1));
-        [array[i], array[j]] = [array[j], array[i]];
-    }
-}
-module.exports.shuffleArray = shuffleArray;
-
-
- - - - -
- - - -
-
-
-
- - - - - - - - diff --git a/docs/global.html b/docs/global.html deleted file mode 100644 index a73c8032..00000000 --- a/docs/global.html +++ /dev/null @@ -1,7804 +0,0 @@ - - - - - - - - Factotum Documentation Global - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Title

-

Global

-
- - - - - -
- -
- -

- - -
- -
-
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
- - - - - - - - - - - - - - - - - - -
-

Type Definitions

-
- -
- -

- # - - - ActivityFeature - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
emoji - - -String - - - - the emoji as a string
name - - -String - - - -
description - - -String - - - -
callback - - -function - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 26 - -

- -
- - - - - -
- -
- -

- # - - - ActivityInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
activityName - - -string - - - - the name of this activity!
guild - - -Guild - - - - the guild where the new activity lives
roleParticipants - - -Collection.<String, Role> - - - - roles allowed to view activity
botGuild - - -BotGuildModel - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 11 - -

- -
- - - - - -
- -
- -

- # - - - AnnouncementInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
isEnabled - - -Boolean - - - -
announcementChannelID - - -String - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 77 - -

- -
- - - - - -
- -
- -

- # - - - AttendanceInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
isEnabled - - -Boolean - - - - true if attendance is enabled in this guild
attendeeRoleID - - -String - - - - the attendee role ID used for attendance
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 58 - -

- -
- - - - - -
- -
- -

- # - - - BotGuildInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
roleIDs - - -RoleIDs - - - -
channelIDs - - -ChannelIDs - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 83 - -

- -
- - - - - -
- -
- -

- # - - - CaveChannels - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
roleSelection - - -TextChannel - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 47 - -

- -
- - - - - -
- -
- -

- # - - - CaveOptions - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
name - - -String - - - - the name of the cave category
preEmojis - - -String - - - - any pre name emojis
preRoleText - - -String - - - - the text to add before every role name, not including '-'
color - - -String - - - - the role color to use for this cave
role - - -Role - - - - the role associated with this cave
emojis - - -Emojis - - - - object holding emojis to use in this cave
times - - -Times - - - - object holding times to use in this cave
publicRoles - - -Collection.<String, Role> - - - - the roles that can request tickets
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 11 - -

- -
- - - - - -
- -
- -

- # - - - ChannelIDs - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
adminConsole - - -String - - - - the admin console channel ID
adminLog - - -String - - - - the admin log channel ID
botSupportChannel - - -String - - - - the bot support channel ID
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 41 - -

- -
- - - - - -
- -
- - - - -Object - - - - -

- # - - - CommandPermissionInfo - - -

- - - - -
- Our custom command information for validation -
- - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
role - - -string - - - - the role this command can be run by
channel - - -string - - - - the channel ID where this command can be run
roleMessage - - -string - - - - the message to be sent for an incorrect role
channelMessage - - -string - - - - the message to be sent for an incorrect channel
dmOnly - - -Boolean - - - - true if this command can only be used on a DM
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/permission-command.js, line 16 - -

- -
- - - - - -
- -
- -

- # - - - ConsoleInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
title - - -String - - - - - - - - - - the console title
description - - -String - - - - - - - - - - the description of the console
channel - - -TextChannel -| - -DMChannel - - - - - - - - - - the channel this console lives in
features - - -Collection.<String, Feature> - - - - - - <optional>
- - - -
- - the collection of features mapped by emoji name
fields - - -Collection.<String, String> - - - - - - <optional>
- - - -
- - a collection of fields
color - - -String - - - - - - <optional>
- - - -
- - console color in hex
options - - -ReactionCollectorOptions - - - - - - <optional>
- - - -
- - {} - - collector options
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/console.js, line 6 - -

- -
- - - - - -
- -
- -

- # - - - Emojis - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
joinTicketEmoji - - -GuildEmoji -| - -ReactionEmoji - - - - emoji for mentors to accept a ticket
giveHelpEmoji - - -GuildEmoji -| - -ReactionEmoji - - - - emoji for mentors to join an ongoing ticket
requestTicketEmoji - - -GuildEmoji -| - -ReactionEmoji - - - - emoji for hackers to request a ticket
addRoleEmoji - - -GuildEmoji -| - -ReactionEmoji - - - - emoji for Admins to add a mentor role
deleteChannelsEmoji - - -GuildEmoji -| - -ReactionEmoji - - - - emoji for Admins to force delete ticket channels
excludeFromAutoDeleteEmoji - - -GuildEmoji -| - -ReactionEmoji - - - - emoji for Admins to opt tickets in/out of garbage collector
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 23 - -

- -
- - - - - -
- -
- - - -

- # - - - async - - - - - FeatureCallback(user, reaction, stopInteracting, console) - - -

- - - - -
- The function to be called when a feature is activated. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
user - - -User - - - - the user that reacted
reaction - - -MessageReaction - - - - the reaction
stopInteracting - - -StopInteractingCallback - - - - callback to let the console know the user has stopped interacting.
console - - -Console - - - - the console this feature is working on
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 5 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- -

- # - - - GarbageCollectorInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
isEnabled - - -Boolean - - - - if the garbage collector is enabled for this ticket system
inactivePeriod - - -Number - - - - number of minutes a ticket channel will be inactive before bot starts to delete it
bufferTime - - -Number - - - - number of minutes the bot will wait for a response before deleting ticket
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 26 - -

- -
- - - - - -
- -
- -

- # - - - MainHelperInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
role - - -Role - - - -
emoji - - -GuildEmoji -| - -ReactionEmoji - - - - can be a unicode emoji string
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 59 - -

- -
- - - - - -
- -
- - - -

- # - - - - NewTicketEmbedCreator(ticket) → {MessageEmbed} - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
ticket - - -Ticket - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 54 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -MessageEmbed - - -
- -
- - -
-
- - - - -
- -
- -

- # - - - PollInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
type - - -String - - - -
title - - -String - - - -
question - - -String - - - -
emojiName - - -String - - - - must be unicode emoji!
responses - - -Collection.<String, String> - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/workshop.js, line 11 - -

- -
- - - - - -
- -
- -

- # - - - ReminderInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
isEnabled - - -Boolean - - - - is this feature enabled
time - - -Number - - - - how long should I wait to remind helpers
reminders - - -Collection.<Number, NodeJS.Timeout> - - - - the timeout reminders mapped by the ticket ID
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 48 - -

- -
- - - - - -
- -
- -

- # - - - ReportInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
isEnabled - - -Boolean - - - - true if the report functionality is enabled
incomingReportChannelID - - -String - - - - channel where reports are sent
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 71 - -

- -
- - - - - -
- -
- -

- # - - - RoleIDs - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
memberRole - - -String - - - - regular guild member role ID
staffRole - - -String - - - - the staff role ID
adminRole - - -String - - - - the admin role ID
everyoneRole - - -String - - - - the everyone role ID
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 33 - -

- -
- - - - - -
- -
- -

- # - - - RolePermission - - -

- - - - -
- An object with a role and its permissions -
- - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
id - - -String - - - - the role snowflake
permissions - - -PermissionOverwriteOption - - - - the permissions to set to that role
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 19 - -

- -
- - - - - -
- -
- -

- # - - - RolePermission - - -

- - - - -
- An object with a role and its permissions -
- - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
id - - -String - - - - the role snowflake
permissions - - -PermissionOverwriteOption - - - - the permissions to set to that role
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 17 - -

- -
- - - - - -
- -
- -

- # - - - RoomChannels - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
category - - -CategoryChannel - - - -
generalText - - -TextChannel - - - -
generalVoice - - -VoiceChannel - - - -
nonLockedChannel - - -TextChannel - - - -
voiceChannels - - -Collection.<String, VoiceChannel> - - - -
textChannels - - -Collection.<String, TextChannel> - - - -
safeChannels - - -Collection.<String, (TextChannel|VoiceChannel)> - - - - , channels that can not be removed
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/room.js, line 6 - -

- -
- - - - - -
- -
- - - -

- # - - - async - - - - - ShuffleFilter(member, channel, userId, filteropt) → {Boolean} - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
member - - -GuildMember - - - - - - - - - -
channel - - -TextChannel - - - - - - - - - - channel to prompt user for specified voice channel
userId - - -String - - - - - - - - - - user to prompt for specified voice channel
filter - - -ShuffleFilter - - - - - - <optional>
- - - - - -
filter the users to shuffle
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/activity.js, line 307 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
- true if filtered - /** Shuffle all the general voice members on all other voice channels
- - -
- - -Boolean - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - - SignupEmbedCreator(teamEmoji, prospectEmoji, isNotificationEnabled) → {MessageEmbed} - - -

- - - - - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
teamEmoji - - -String - - - - the emoji used by teams to sign up
prospectEmoji - - -String - - - - the emoji used by prospects to sign up
isNotificationEnabled - - -Boolean - - - - true if parties will be notified when the other party has a new post
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 75 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -MessageEmbed - - -
- -
- - -
-
- - - - -
- -
- -

- # - - - StampInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
isEnabled - - -Boolean - - - - true if stamps are enabled
stampRoleIDs - - -Collection.<Number, String> - - - -
stampCollectionTime - - -Number - - - - time given to users to collect password stamps
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 64 - -

- -
- - - - - -
- -
- - - -

- # - - - - StopInteractingCallback(user) - - -

- - - - -
- The function used to signal the console the user interacting has finished. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
user - - -User - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/consoles/feature.js, line 16 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- -

- # - - - SubRole - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
name - - -String - - - - the role name
id - - -String - - - - the role id (snowflake)
activeUsers - - -Number - - - - number of users with this role
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 40 - -

- -
- - - - - -
- -
- -

- # - - - SystemWideTicketInfo - - -

- - - - -
- All the information needed for tickets in this ticket manager -
- - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
garbageCollectorInfo - - -GarbageCollectorInfo - - - - the garbage collector information for each tickets
isAdvancedMode - - -Boolean - - - - Information about the system being advanced. Advanced mode will create a category with channels for the users and the helpers. Regular will not create anything and expects the helper to DM the user or users.
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 19 - -

- -
- - - - - -
- -
- -

- # - - - TeamFormationChannels - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
info - - -TextChannel - - - - the info channel where users read about this activity
teamCatalogue - - -TextChannel - - - - the channel where team info is posted
prospectCatalogue - - -TextChannel - - - - the channel where prospect info is posted
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 68 - -

- -
- - - - - -
- -
- -

- # - - - TeamFormationInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
teamInfo - - -TeamFormationPartyInfo - - - - - - - -
prospectInfo - - -TeamFormationPartyInfo - - - - - - - -
guild - - -Guild - - - - - - - -
botGuild - - -BotGuildModel - - - - - - - -
activityRoles - - -Collection.<string, Role> - - - - - - - -
isNotificationsEnabled - - -Boolean - - - - - - <optional>
- - - -
signupEmbedCreator - - -SignupEmbedCreator - - - - - - <optional>
- - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 83 - -

- -
- - - - - -
- -
- -

- # - - - TeamFormationPartyInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDescription
emoji - - -GuildEmoji -| - -ReactionEmoji - - - - - - - - the emoji used to add this party to the team formation
role - - -Role - - - - - - - - the role given to the users of this party
form - - -String - - - - - - <optional>
- - - -
the form added to the signup embed for users to respond to. Will not be added if signupEmbed given!
signupEmbed - - -MessageEmbed - - - - - - <optional>
- - - -
the embed sent to users when they sign up, must include the form!
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/team-formation.js, line 60 - -

- -
- - - - - -
- -
- -

- # - - - TicketConsoles - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
groupLeader - - -Console - - - -
ticketManager - - -Console - - - - Message sent to incoming ticket channel for helpers to see.
ticketRoom - - -Console - - - - The message with the information embed sent to the ticket channel once the ticket is open.
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 11 - -

- -
- - - - - -
- -
- -

- # - - - TicketCreatorInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - the channel where users can create a ticket
console - - -Console - - - - the console used to let users create tickets
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 33 - -

- -
- - - - - -
- -
- -

- # - - - TicketDispatcherInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - - the channel where tickets are dispatched to
takeTicketEmoji - - -GuildEmoji -| - -ReactionEmoji - - - - emoji for mentors to accept/take a ticket, can be a unicode emoji string
joinTicketEmoji - - -GuildEmoji -| - -ReactionEmoji - - - - emoji for mentors to join a taken ticket, can be a unicode emoji string
embedCreator - - -NewTicketEmbedCreator - - - - function to create a Discord MessageEmbed
reminderInfo - - -ReminderInfo - - - - the reminder information
mainHelperInfo - - -MainHelperInfo - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket-manager.js, line 39 - -

- -
- - - - - -
- -
- -

- # - - - TicketGarbageInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
noHelperInterval - - -Number - - - - Interval ID for when there are no more helpers in the ticket
mentorDeletionSequence - - -Boolean - - - - Flag to check if a deletion sequence has already been triggered by all mentors leaving the ticket; if so, there will not be another sequence started for inactivity
exclude - - -Boolean - - - - Flag for whether this ticket is excluded from automatic garbage collection
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/tickets/ticket.js, line 18 - -

- -
- - - - - -
- -
- -

- # - - - Times - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
inactivePeriod - - -Number - - - - number of minutes a ticket channel will be inactive before bot starts to delete it
bufferTime - - -Number - - - - number of minutes the bot will wait for a response before deleting ticket
reminderTime - - -Number - - - - number of minutes the bot will wait before reminding mentors of unaccepted tickets
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/activities/cave.js, line 33 - -

- -
- - - - - -
- -
- -

- # - - - Transfer - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
name - - -String - - - - the transfer name
description - - -String - - - - the transfer description
role - - -Role - - - - the transfer role
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/a_utility/role-selector.js, line 40 - -

- -
- - - - - -
- -
- -

- # - - - TypeInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
type - - -String - - - -
roleId - - -String - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 244 - -

- -
- - - - - -
- -
- -

- # - - - TypeInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
type - - -String - - - -
roleId - - -String - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - commands/essentials/init-bot.js, line 257 - -

- -
- - - - - -
- -
- -

- # - - - VerificationChannels - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
welcomeChannelID - - -String - - - -
welcomeChannelSupportID - - -String - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 238 - -

- -
- - - - - -
- -
- -

- # - - - VerificationInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
isEnabled - - -Boolean - - - - true if verification is enabled
isVerifiedRoleID - - -String - - - - the verified role ID that holds basic permissions
isVerifiedRolePermissions - - -Array.<String> - - - - the permissions for the isVerified role
guestRoleID - - -String - - - - the guest role ID used for verification
welcomeChannelID - - -String - - - - the welcome channel where users learn to verify
welcomeSupportChannelID - - -String - - - - the support channel where the bot can contact users
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - classes/bot-guild.js, line 48 - -

- -
- - - - - -
- -
-
- - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 5dc4fb1d..00000000 --- a/docs/index.html +++ /dev/null @@ -1,236 +0,0 @@ - - - - - - - - Factotum Documentation Home - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

-

Home

-
- - - - - - - -

factotum-discord-bot 5.0.0

- - - - - - - - - - - - - - - -
-

Factotum The do-it-all Discord Bot

-

Previously known as nwPlus Discord Bot, Factotum started as a Discord bot to support small and medium hackathons run on the Discord platform for free. Now it has become a fully customizable, feature rich bot to support any and all types of events over discord.

-

Set up the bot

-

At the moment, the bot is still in development, but IT CAN BE USED. Please email me at juapgarc@gmail.com or reach out to me on discord JPGarcia99#8803 to talk about using the bot in its current state! If you would like to test the bot that is also a possibility, just reach out!

-

How does Factotum support hackathon teams and events?

-

Factotum brings a lot of features that would traditionally happen at in-person hacakthons all over the word to the Discord platform.

-

All features listed below are unique and requires an admin to start them. All features are highly customizable, from the emojis used to the text sent.

-

Most features rely on emoji reactions, custom emojis can be used as well!

-

Mentor Ticket System (WIP)

-

The mentor ticket system or more generally the ticketing system of Factotum is an advanced ticketing system for a set of users to inquire by the use of tickets, and another set of users can respond to such tickets both via text or voice.

-

The ticket system supports general tickets as well as sub-topic tickets (more detailed tickets intended for only a sub-section of the support team).

-

The system also has a opt-in/opt-out system for the support team to select what sub-topic they want to be a part of, and admins can always add sub-topics as they see the need for them.

-

When opening a ticket:

-
    -
  • Users can add other users (team members) to be part of the ticket
  • -
  • Can select a general ticket or a sub-topic ticket
  • -
  • Will need to write a short description of their problem
  • -
  • Will receive a DM with the ticket information as soon as it is submitted
  • -
-

When a ticket is submitted:

-
    -
  • Support staff can see the ticket information (description, people involved, sub-topic)
  • -
  • Can start the help process with a click of a button (a category with a voice and text channel is created)
  • -
  • Other support staff can always join a ticket, even if someone else is already helping
  • -
  • Support staff can leave the ticket at any time with the click of a button
  • -
  • As soon as everyone leaves the ticket, the ticket category and channels are destroyed
  • -
-

Things to come:

-
    -
  • Automatic ticket trash collector, Discord has a 500 channel limit, it is imperative old tickets get destroyed or the server could reach the limit very fast
  • -
-

Team Formation

-

Team formation is always a huge part of any hackathon, traditionally, staff hold team formation mini-events for hackers to try and form teams. However, with COVID-19 and the transition to discord, the bot brings two unique alternatives.

-

Team Formation Catalog

-

The team formation catalog system works by posting information of teams looking for members, or members looking for a team on a specific channel, and the letting users DM those teams or members they are interested on working with.

-
    -
  1. Team captain or user looking for a team start the process with a click of a button (emoji)
  2. -
  3. Bot DMs instructions and the user sends information via DM
  4. -
  5. Bot posts team or member information on the channel catalog (channel is view only!)
  6. -
  7. Users can read the available members or teams and DM the user who "posted" the information
  8. -
  9. As soon as a person finds a team or a member to join their team, they can eliminate their "post" with a click of a button
  10. -
-

As an added incentive for people to reach out to others, when someone joins the system either as a team looking for members, or as a member looking for a team, they will get notified every time a new counterpart is posted. For example, a user looking for a team will be notified of every new team looking for a member. This stops when they remove their information post.

-

Team Formation Roulette (WIP)

-

Team formation roulette is a more direct approach to team formation. Users join the queue as solos or teams of up to three (currently hard capped to create groups of 4, will change later). As people join the bot tries to create groups of 4 as efficiently as possible.

-

As this feature is a WIP, more information will be added once its production is complete.

-

Verify and Attend

-

Keeping your Discord server is very important, specially if your event is closed to only those hackers that were accepted. The bot gives you two useful commands to keep your server safe.

-

Members call !verify theiremail@gmail.com or !attend theiremail@gmail.com and if the email is found on a firestore db, then they "gain access" by receiving a higher permission role. Emails are always kept private, they are immediately removed from the channel.

-

We are working on changing how this works to use a local or free db, and make it easy for admins to add emails, possibly from Discord.

-

Role selector

-

Roles are an integral part of a well functioning Discord server. Sometimes it is best to let users select the roles they want to have, the role selector lets you do that with ease.

-

Admins can start a role selector on any text channel. Admins can then add new roles to the role selector for users to use. As an admin you can select what emojis to use, what roles to give, and what text to put on the role selector message.

-

Report

-

Keeping your server safe is always hard, specially if your server is very big. With the !report command, any users can report bad behavior anonymously via DM. The reports get sent to a Admin only text channel.

-

Threads

-

Many hackathons are transitioning from Slack to Discord. The one thing we love about Slack is the thread functionality. Factotum brings this functionality to Discord! Users can ask questions with the !ask command and other users can the respond to the question by clicking an emoji.

-

Clear Chat

-

Sometimes as an Admin you want to delete an entire text channel, well Factotum has a !clear-chat command that will delete 100 messages from the text channel. You can let the bot know if you want to keep any pinned messages.

-

Channel Creation (WIP)

-

Channel creation gives users the ability to create private voice or text channels for them to use with their team or group of friends. With a click of a button, the bot starts asking the user questions about what type of channel they want, the name of the channel, and who has access to this channel.

-

At the moment we do not recommend the use of this command with big events. Discord has a 50 channel limit per category so the channel creation category can get filled up very fast!

-

E-Room Directory

-

Sometimes you need to use other systems to connect users, for example zoom or microsoft teams. If this is the case, E-Room Directory gives you the ability to add links for such rooms and you or a specified role can open and close such rooms. The different states have different message colors and when opened, a role can be notified.

-

This is commonly used for boothing when it happens over zoom rooms. When sponsor staff are on the room, they open the room and hackers get notified of the change.

-

Activities and Workshops (WIP)

-

The bot has an extensive activity and workshop feature. More information will be added after an extensive review of the features.

-

Technology Used

-
    -
  • Discord.js
  • -
  • Node.js
  • -
  • JavaScript
  • -
  • Firebase Firestore
  • -
-

History

-

This bot started as a personal project to be used at nwHacks 2021. However, after seeing all the features this bot could bring to other events, we decided to also use it for HackCamp 2020 and other nwPlus events.

-

After receiving a lot of support from hackers and mentors, @Maggie and I decided to make the code open source and work hard to make this bot accessible to small and medium hacakthons.

-

Development

-

The bot is currently in development, we are constantly adding new features and improving current ones. For the next week or two we will stop work on any new features and concentrate on fixing all bugs!

-

Open Source

-

Need new features or found a bug?

-

Let us know by filing a ticket! We are always working on improving this bot to make it the best it can be!

-

Contribute

-

Creators

-

JP Garcia

-

Maggie Wang

-
- - - - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/jsdoc-styles.css b/docs/jsdoc-styles.css deleted file mode 100644 index 6bb0692e..00000000 --- a/docs/jsdoc-styles.css +++ /dev/null @@ -1,19 +0,0 @@ -.top-nav { - background-color: rgb(247, 255, 255); -} - -.core { - background-color: rgb(247, 255, 255); -} - -.content { - background-color: rgb(247, 255, 255); -} - -.sidebar { - background-color: rgb(247, 255, 255); -} - -.side-nav { - background-color: rgb(247, 255, 255); -} \ No newline at end of file diff --git a/docs/module-DiscordServices.html b/docs/module-DiscordServices.html deleted file mode 100644 index 62d8bb0c..00000000 --- a/docs/module-DiscordServices.html +++ /dev/null @@ -1,3098 +0,0 @@ - - - - - - - - Factotum Documentation DiscordServices - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Module

-

DiscordServices

-
- - - - - -
- -
- - - - - -
- -
-
- - -
The discord services module has useful discord related functions. These functions are helper, discord related functions.
- - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 5 - -

- -
- - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
-

Methods

-
- -
- - - -

- # - - - inner - - - - - addRoleToMember(member, addRole) - - -

- - - - -
- Add a role to a member -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
member - - -GuildMember - - - - the guild member to give a role to
addRole - - -RoleResolvable - - - - the role to add to the member
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 120 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - inner - - - - - checkForRole(member, role) - - -

- - - - -
- Checks if the member has a role, returns true if it does -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
member - - -GuildMember - - - - member to check role
role - - -String - - - - role ID to check for
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 16 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - inner - - - - - deleteChannel(channel) - - -

- - - - -
- Delete the given channel if it is not deleted already -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
channel - - -TextChannel - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 218 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - inner - - - - - deleteMessage(message, timeout) - - -

- - - - -
- Deletes a message if the message hasn't been deleted already -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDescription
message - - -Message - - - - - - the message to delete
timeout - - -Number - - - - - - 0 - - the time to wait in milliseconds
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 201 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - inner - - - - - discordLog(guild, message) - - -

- - - - -
- Log a message on the log channel -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
guild - - -Guild - - - - the guild being used
message - - -String -| - -MessageEmbed - - - - message to send to the log channel
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 173 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - inner - - - - - randomColor() → {String} - - -

- - - - -
- Returns a random color as a hex string. -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 232 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
- hex color
- - -
- - -String - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - inner - - - - - removeRolToMember(member, removeRole) - - -

- - - - -
- Remove a role to a member -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
member - - -GuildMember - - - - the guild member to give a role to
removeRole - - -RoleResolvable - - - - the role to add to the member
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 141 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - inner - - - - - replaceRoleToMember(member, removeRole, addRole) - - -

- - - - -
- Replaces one role for the other -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
member - - -GuildMember - - - - member to change roles to
removeRole - - -RoleResolvable - - - - role to remove
addRole - - -RoleResolvable - - - - role to add
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 161 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - inner - - - - - replyAndDelete(message, reply) - - -

- - - - -
- Reply to message and delete 5 seconds later -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
message - - -Message - - - - the message to reply to
reply - - -String - - - - the string to reply
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 188 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - inner - - - - - sendEmbedToMember(member, embedOptions, isDelete) → {Promise.<Message>} - - -

- - - - -
- Sends an embed to a user via DM. Title and description are required, color and fields are optional. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDescription
member - - -User -| - -GuildMember - - - - - - member to send embed to
embedOptions - - -EmbedOptions - - - - - - embed information
isDelete - - -Boolean - - - - - - false - - should the message be deleted after some time?
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 98 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Message> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - inner - - - - - sendMessageToMember(member, message, isDelete) → {Promise.<Message>} - - -

- - - - -
- Send a Direct message to a member, option to delete after a few seconds. Helps user fix DM issue if the bot can't reach them over DM. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDescription
member - - -User -| - -GuildMember - - - - - - the user or member to send a DM to
message - - -String -| - -MessageEmbed - - - - - - the message to send
isDelete - - -Boolean - - - - - - false - - weather to delete message after 60 seconds
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 49 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Message> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - inner - - - - - sendMsgToChannel(channel, userId, message, timeout) → {Promise.<Message>} - - -

- - - - -
- Will send a message to a text channel and ping the user, can be deleted after a timeout. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDefaultDescription
channel - - -TextChannel - - - - - - the channel to send the message to
userId - - -String - - - - - - the user to tag on the message
message - - -String - - - - - - the message to send
timeout - - -Number - - - - - - 0 - - timeout before delete if any, in seconds
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 31 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Promise.<Message> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - inner - - - - - validateEmail(email) → {Boolean} - - -

- - - - -
- Validates an email using a reg exp. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
email - - -String - - - - the email to validate
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 243 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
true if valid email, false otherwise
- - -
- - -Boolean - - -
- -
- - -
-
- - - - -
- -
-
- - - -
-

Type Definitions

-
- -
- -

- # - - - EmbedOptions - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
title - - -String - - - - embed title
description - - -String - - - - embed description
color - - -String - - - - embed color
fields - - -Array.<FieldInfo> - - - - embed fields
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 82 - -

- -
- - - - - -
- -
- -

- # - - - FieldInfo - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
title - - -String - - - - field title
description - - -String - - - - field description
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - discord-services.js, line 76 - -

- -
- - - - - -
- -
-
- - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/module-FirebaseParser.html b/docs/module-FirebaseParser.html deleted file mode 100644 index 6de30e6c..00000000 --- a/docs/module-FirebaseParser.html +++ /dev/null @@ -1,249 +0,0 @@ - - - - - - - - Factotum Documentation FirebaseParser - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Module

-

FirebaseParser

-
- - - - - -
- -
- - - - - -
- -
-
- - -
The firebase parser module has scripts to parse csv data to upload to firebase for validation purposes.
- - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/parser.js, line 6 - -

- -
- - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/module-FirebaseServices.html b/docs/module-FirebaseServices.html deleted file mode 100644 index 891e6e62..00000000 --- a/docs/module-FirebaseServices.html +++ /dev/null @@ -1,2454 +0,0 @@ - - - - - - - - Factotum Documentation FirebaseServices - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Module

-

FirebaseServices

-
- - - - - -
- -
- - - - - -
- -
-
- - -
The firebase services module has firebase related helper functions.
- - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 4 - -

- -
- - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -Map.<String, admin.app.App> - - - - -

- # - - - inner - - constant - - - - apps - - -

- - - - -
- All the firebase apps in play stored by their name. -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 13 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - async - - inner - - - - - addUserData(email, types, guildId, memberopt, firstNameopt, lastNameopt) - - -

- - - - -
- Adds a new guild member to the guild's member collection. Email is used as ID, there can be no duplicates. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
email - - -String - - - - - - - - - - - - email of member verified
types - - -Array.<String> - - - - - - - - - - - - types this user might verify for
guildId - - -String - - - - - - - - - - - - the guild id
member - - -GuildMember - - - - - - <optional>
- - - - - -
- - {} - - member verified
firstName - - -String - - - - - - <optional>
- - - - - -
- - '' - - users first name
lastName - - -String - - - - - - <optional>
- - - - - -
- - '' - - users last name
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 200 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - inner - - - - - attend(id, guildId) → {Promise.<Array.<String>>} - - -

- - - - -
- Attends the user via their discord id -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
id - - -String - - - - the user's discord snowflake
guildId - - -String - - - - the guild id
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 269 - -

- -
- - - - - - - - - - - - - - - - -
-
-
- - -
- - -
Error if the email provided was not found.
- - -
- - -
-
- - - -
-
-
- - - -
- -
- the types this user is verified
- - -
- - -Promise.<Array.<String>> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - inner - - - - - checkEmail(email, guildId) → {Promise.<Array.<Member>>} - - -

- - - - -
- Checks to see if the input email matches or is similar to emails in the database Returns an array of objects containing emails that match or are similar, along with the verification status of each, and returns empty array if none match -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
email - - -String - - - - email to check
guildId - - -String - - - - the guild id
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 107 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
- array of members with similar emails to parameter email
- - -
- - -Promise.<Array.<Member>> - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - inner - - - - - getQuestion(guildId) → {Object|null} - - -

- - - - -
- Retrieves a question from the db that has not already been asked at the Discord Contests, then marks the question as having been asked in the db. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
guildId - - -String - - - - the id of the guild
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 55 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
- the data object of a question or null if no more questions
- - -
- - -Object -| - -null - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - inner - - - - - getReminder(guildId) → {Object|null} - - -

- - - - -
- Retrieves self-care reminder from the db that has not already been sent, then marks the reminder as having been asked in the db. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
guildId - - -String - - - - the guild id
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 76 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
- the data object of a reminder or null if no more reminders
- - -
- - -Object -| - -null - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - inner - - - - - initializeFirebaseAdmin(name, adminSDK, databaseURL) - - -

- - - - -
- Will start an admin connection with the given name -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
name - - -String - - - - name of the connection
adminSDK - - -JSON - - - - the JSON file with admin config
databaseURL - - -String - - - - the database URL
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 22 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - async - - inner - - - - - verify(email, id, guildId) → {Promise.<Array.<String>>} - - -

- - - - -
- Verifies the any event member via their email. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
email - - -String - - - - the user email
id - - -String - - - - the user's discord snowflake
guildId - - -String - - - - the guild id
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 232 - -

- -
- - - - - - - - - - - - - - - - -
-
-
- - -
- - -
Error if the email provided was not found.
- - -
- - -
-
- - - -
-
-
- - - -
- -
- the types this user is verified
- - -
- - -Promise.<Array.<String>> - - -
- -
- - -
-
- - - - -
- -
-
- - - -
-

Type Definitions

-
- -
- -

- # - - - FirebaseUser - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
email - - -String - - - -
discordId - - -String - - - -
types - - -Array.<UserType> - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 42 - -

- -
- - - - - -
- -
- - - - -Object - - - - -

- # - - - Member - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
email - - -String - - - - the email of the member
isVerified - - -Boolean - - - - whether member has already verified
type - - -String - - - - role a member has in the server
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 92 - -

- -
- - - - - -
- -
- -

- # - - - UserType - - -

- - - - - - - - -
Properties:
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
type - - -String - - - -
isVerified - - -Boolean - - - -
timestamp - - -Date - - - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/firebase/firebase-services.js, line 35 - -

- -
- - - - - -
- -
-
- - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/module-MainApp.html b/docs/module-MainApp.html deleted file mode 100644 index badb1dcb..00000000 --- a/docs/module-MainApp.html +++ /dev/null @@ -1,1164 +0,0 @@ - - - - - - - - Factotum Documentation MainApp - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Module

-

MainApp

-
- - - - - -
- -
- - - - - -
- -
-
- - -
The Main App module houses the bot events, process events, and initializes the bot. It also handles new members and greets them.
- - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - app.js, line 14 - -

- -
- - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - -
-

Methods

-
- -
- - - -

- # - - - inner - - - - - createALogger(loggerName, loggerLabelopt, handleRejectionsExceptionsopt, LogToConsoleopt) → {winston.Logger} - - -

- - - - -
- Will create a default logger to use. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
loggerName - - -String - - - - - - - - - - - -
loggerLabel - - -String - - - - - - <optional>
- - - - - -
- - '' - - usually a more readable logger name
handleRejectionsExceptions - - -Boolean - - - - - - <optional>
- - - - - -
- - false - - will handle rejections and exceptions if true
LogToConsole - - -Boolean - - - - - - <optional>
- - - - - -
- - false - - will log all levels to console if true
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - app.js, line 310 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -winston.Logger - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - inner - - - - - fixDMIssue(error, member, botGuild) - - -

- - - - -
- Will let the member know how to fix their DM issue. -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
error - - -Error - - - - the error
member - - -Discord.GuildMember - - - - the member with the error
botGuild - - -BotGuildModel - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - app.js, line 415 - -

- -
- - - - - - - - - - - - - - - - -
-
-
- - -
- - -
Error if the given error is not a DM error
- - -
- - -
-
- - - - - - -
- -
- - - -

- # - - - inner - - - - - getConfig(args) → {Map} - - -

- - - - -
- Returns the config settings depending on the command line args. Read command line args to know if prod, dev, or test and what server First arg is one of prod, dev or test the second is the test server, but the first one must be test -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
args - - -Array.<string> - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - app.js, line 29 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- -
config settings
- - -
- - -Map - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - async - - inner - - - - - greetNewMember(member, botGuild) - - -

- - - - -
- Greets a member! -
- - - - - - - - - - -
Parameters:
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
member - - -Discord.GuildMember - - - - the member to greet
botGuild - - -BotGuildModel - - - -
-
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - app.js, line 360 - -

- -
- - - - - - - - - - - - - - - - -
-
-
- - -
- - -
Error if the user has server DMs off
- - -
- - -
-
- - - - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/module-MongoUtil.html b/docs/module-MongoUtil.html deleted file mode 100644 index a279a7ba..00000000 --- a/docs/module-MongoUtil.html +++ /dev/null @@ -1,681 +0,0 @@ - - - - - - - - Factotum Documentation MongoUtil - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-

Module

-

MongoUtil

-
- - - - - -
- -
- - - - - -
- -
-
- - -
The mongo utility module has some useful mongo related helper functions.
- - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/mongo/mongoUtil.js, line 4 - -

- -
- - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - -
-

Members

-
- -
- - - - -Db - - - - -

- # - - - inner - - - - _db - - -

- - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/mongo/mongoUtil.js, line 12 - -

- -
- - - - - -
- -
-
- - - -
-

Methods

-
- -
- - - -

- # - - - static - - - - - connect() - - -

- - - - -
- Starts a connection to MongoDB -
- - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/mongo/mongoUtil.js, line 19 - -

- -
- - - - - - - - - - - - - - - - - - - - - -
- -
- - - -

- # - - - static - - - - - getBotGuildCol() → {Collection} - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/mongo/mongoUtil.js, line 38 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Collection - - -
- -
- - -
-
- - - - -
- -
- - - -

- # - - - static - - - - - getDb() → {Db} - - -

- - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- View Source - - db/mongo/mongoUtil.js, line 31 - -

- -
- - - - - - - - - - - - - - - - - - -
-
-
- - - -
- - -
- - -Db - - -
- -
- - -
-
- - - - -
- -
-
- - - - - -
- -
- - - - -
- - - -
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/docs/scripts/app.min.js b/docs/scripts/app.min.js deleted file mode 100644 index 9411fc03..00000000 --- a/docs/scripts/app.min.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";$().ready(function(){});var sidebarIsVisible=!1,toggleSidebar=function(e){var a=!(0 h1").text();if(t){o.append($("

").text(t));var s=$("
    ");i.find(".members h4.name").each(function(e,a){var i=$(a),t=i.find(".code-name").clone().children().remove().end().text(),n=i.find("a").attr("href"),r=$('')).text(t);s.append($("
  • ").append(r)),c.push({link:r,offset:i.offset().top})}),o.append(s)}else i.find(".members h4.name").each(function(e,a){var i=$(a),t=i.find(".code-name").clone().children().remove().end().text(),n=i.find("a").attr("href"),r=$('
    ')).text(t);o.append(r),c.push({link:r,offset:i.offset().top})})}),!$.trim(o.text()))return o.hide();function e(){for(var e=n.scrollTop(),a=!1,i=c.length-1;0<=i;i--){var t=c[i];t.link.removeClass("is-active"),e+OFFSET>=t.offset?a?t.link.addClass("is-past"):(t.link.addClass("is-active"),a=!0):t.link.removeClass("is-past")}}var n=$("#main-content-wrapper");n.on("scroll",e),e(),c.forEach(function(e){e.link.click(function(){n.animate({scrollTop:e.offset-OFFSET+1},500)})})}),$().ready(function(){$("#sidebarNav a").each(function(e,a){var i=$(a).attr("href");window.location.pathname.match("/"+i)&&($(a).addClass("active"),$("#sidebarNav").scrollTop($(a).offset().top-150))})}); \ No newline at end of file diff --git a/docs/scripts/linenumber.js b/docs/scripts/linenumber.js deleted file mode 100644 index 1ba40576..00000000 --- a/docs/scripts/linenumber.js +++ /dev/null @@ -1,26 +0,0 @@ -/*global document */ - -(function() { - var source = document.getElementsByClassName('prettyprint source linenums'); - var i = 0; - var lineNumber = 0; - var lineId; - var lines; - var totalLines; - var anchorHash; - - if (source && source[0]) { - anchorHash = document.location.hash.substring(1); - lines = source[0].getElementsByTagName('li'); - totalLines = lines.length; - - for (; i < totalLines; i++) { - lineNumber++; - lineId = 'line' + lineNumber; - lines[i].id = lineId; - if (lineId === anchorHash) { - lines[i].className += ' selected'; - } - } - } -})(); diff --git a/docs/scripts/search.js b/docs/scripts/search.js deleted file mode 100644 index 540d451c..00000000 --- a/docs/scripts/search.js +++ /dev/null @@ -1,39 +0,0 @@ -(function() { - const input = document.querySelector('#search') - const targets = [ ...document.querySelectorAll('#sidebarNav li')] - input.addEventListener('keyup', () => { - // loop over each targets and hide the not corresponding ones - targets.forEach(target => { - if (!target.innerText.toLowerCase().includes(input.value.toLowerCase())) { - target.style.display = 'none' - - /** - * Detects an empty list - * Remove the list and the list's title if the list is not displayed - */ - const list = [...target.parentNode.childNodes].filter( elem => elem.style.display !== 'none') - - if (!list.length) { - target.parentNode.style.display = 'none' - target.parentNode.previousSibling.style.display = 'none' - } - - /** - * Detects empty category - * Remove the entire category if no item is displayed - */ - const category = [...target.parentNode.parentNode.childNodes] - .filter( elem => elem.tagName !== 'H2' && elem.style.display !== 'none') - - if (!category.length) { - target.parentNode.parentNode.style.display = 'none' - } - } else { - target.parentNode.style.display = 'block' - target.parentNode.previousSibling.style.display = 'block' - target.parentNode.parentNode.style.display = 'block' - target.style.display = 'block' - } - }) - }) -})() \ No newline at end of file diff --git a/docs/styles/app.min.css b/docs/styles/app.min.css deleted file mode 100644 index 16207525..00000000 --- a/docs/styles/app.min.css +++ /dev/null @@ -1 +0,0 @@ -/*! bulma.io v0.7.5 | MIT License | github.com/jgthms/bulma */@-webkit-keyframes spinAround{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{from{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.delete,.modal-close,.is-unselectable,.button,.file,.breadcrumb,.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis,.tabs{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select:not(.is-multiple):not(.is-loading)::after,.navbar-link:not(.is-arrowless)::after{border:3px solid rgba(0,0,0,0);border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:0.625em;margin-top:-0.4375em;pointer-events:none;position:absolute;top:50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:center;transform-origin:center;width:0.625em}.box:not(:last-child),.content:not(:last-child),.notification:not(:last-child),.progress:not(:last-child),.table:not(:last-child),.table-container:not(:last-child),.title:not(:last-child),.subtitle:not(:last-child),.block:not(:last-child),.highlight:not(:last-child),.breadcrumb:not(:last-child),.level:not(:last-child),.list:not(:last-child),.message:not(:last-child),.tabs:not(:last-child){margin-bottom:1.5rem}.delete,.modal-close{-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,0.2);border:none;border-radius:290486px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:none;position:relative;vertical-align:top;width:20px}.delete::before,.modal-close::before,.delete::after,.modal-close::after{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;-webkit-transform:translateX(-50%) translateY(-50%) rotate(45deg);transform:translateX(-50%) translateY(-50%) rotate(45deg);-webkit-transform-origin:center center;transform-origin:center center}.delete::before,.modal-close::before{height:2px;width:50%}.delete::after,.modal-close::after{height:50%;width:2px}.delete:hover,.modal-close:hover,.delete:focus,.modal-close:focus{background-color:rgba(10,10,10,0.3)}.delete:active,.modal-close:active{background-color:rgba(10,10,10,0.4)}.is-small.delete,.is-small.modal-close{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.delete,.is-medium.modal-close{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.delete,.is-large.modal-close{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.button.is-loading::after,.loader,.select.is-loading::after,.control.is-loading::after{-webkit-animation:spinAround 500ms infinite linear;animation:spinAround 500ms infinite linear;border:2px solid #dbdbdb;border-radius:290486px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.is-overlay,.image.is-square img,.image.is-square .has-ratio,.image.is-1by1 img,.image.is-1by1 .has-ratio,.image.is-5by4 img,.image.is-5by4 .has-ratio,.image.is-4by3 img,.image.is-4by3 .has-ratio,.image.is-3by2 img,.image.is-3by2 .has-ratio,.image.is-5by3 img,.image.is-5by3 .has-ratio,.image.is-16by9 img,.image.is-16by9 .has-ratio,.image.is-2by1 img,.image.is-2by1 .has-ratio,.image.is-3by1 img,.image.is-3by1 .has-ratio,.image.is-4by5 img,.image.is-4by5 .has-ratio,.image.is-3by4 img,.image.is-3by4 .has-ratio,.image.is-2by3 img,.image.is-2by3 .has-ratio,.image.is-3by5 img,.image.is-3by5 .has-ratio,.image.is-9by16 img,.image.is-9by16 .has-ratio,.image.is-1by2 img,.image.is-1by2 .has-ratio,.image.is-1by3 img,.image.is-1by3 .has-ratio,.modal,.modal-background,.hero-video{bottom:0;left:0;position:absolute;right:0;top:0}.button,.input,.textarea,.select select,.file-cta,.file-name,.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.25em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(0.375em - 1px);padding-left:calc(0.625em - 1px);padding-right:calc(0.625em - 1px);padding-top:calc(0.375em - 1px);position:relative;vertical-align:top}.button:focus,.input:focus,.textarea:focus,.select select:focus,.file-cta:focus,.file-name:focus,.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus,.pagination-ellipsis:focus,.is-focused.button,.is-focused.input,.is-focused.textarea,.select select.is-focused,.is-focused.file-cta,.is-focused.file-name,.is-focused.pagination-previous,.is-focused.pagination-next,.is-focused.pagination-link,.is-focused.pagination-ellipsis,.button:active,.input:active,.textarea:active,.select select:active,.file-cta:active,.file-name:active,.pagination-previous:active,.pagination-next:active,.pagination-link:active,.pagination-ellipsis:active,.is-active.button,.is-active.input,.is-active.textarea,.select select.is-active,.is-active.file-cta,.is-active.file-name,.is-active.pagination-previous,.is-active.pagination-next,.is-active.pagination-link,.is-active.pagination-ellipsis{outline:none}.button[disabled],.input[disabled],.textarea[disabled],.select select[disabled],.file-cta[disabled],.file-name[disabled],.pagination-previous[disabled],.pagination-next[disabled],.pagination-link[disabled],.pagination-ellipsis[disabled],fieldset[disabled] .button,fieldset[disabled] .input,fieldset[disabled] .textarea,fieldset[disabled] .select select,.select fieldset[disabled] select,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .pagination-previous,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-ellipsis{cursor:not-allowed}/*! minireset.css v0.0.4 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,embed,iframe,object,video{height:auto;max-width:100%}audio{max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:left}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,select,textarea{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue","Helvetica","Arial",sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:monospace}body{color:#4a4a4a;font-size:1em;font-weight:400;line-height:1.5}a{color:#3273dc;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:#f5f5f5;color:#ff3860;font-size:.875em;font-weight:normal;padding:0.25em 0.5em 0.25em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type="checkbox"],input[type="radio"]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#363636;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#4a4a4a;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:left}table th{color:#363636}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left !important}.is-pulled-right{float:right !important}.is-clipped{overflow:hidden !important}.is-size-1{font-size:3rem !important}.is-size-2{font-size:2.5rem !important}.is-size-3{font-size:2rem !important}.is-size-4{font-size:1.5rem !important}.is-size-5{font-size:1.25rem !important}.is-size-6{font-size:1rem !important}.is-size-7{font-size:.75rem !important}@media screen and (max-width: 768px){.is-size-1-mobile{font-size:3rem !important}.is-size-2-mobile{font-size:2.5rem !important}.is-size-3-mobile{font-size:2rem !important}.is-size-4-mobile{font-size:1.5rem !important}.is-size-5-mobile{font-size:1.25rem !important}.is-size-6-mobile{font-size:1rem !important}.is-size-7-mobile{font-size:.75rem !important}}@media screen and (min-width: 769px), print{.is-size-1-tablet{font-size:3rem !important}.is-size-2-tablet{font-size:2.5rem !important}.is-size-3-tablet{font-size:2rem !important}.is-size-4-tablet{font-size:1.5rem !important}.is-size-5-tablet{font-size:1.25rem !important}.is-size-6-tablet{font-size:1rem !important}.is-size-7-tablet{font-size:.75rem !important}}@media screen and (max-width: 1023px){.is-size-1-touch{font-size:3rem !important}.is-size-2-touch{font-size:2.5rem !important}.is-size-3-touch{font-size:2rem !important}.is-size-4-touch{font-size:1.5rem !important}.is-size-5-touch{font-size:1.25rem !important}.is-size-6-touch{font-size:1rem !important}.is-size-7-touch{font-size:.75rem !important}}@media screen and (min-width: 1024px){.is-size-1-desktop{font-size:3rem !important}.is-size-2-desktop{font-size:2.5rem !important}.is-size-3-desktop{font-size:2rem !important}.is-size-4-desktop{font-size:1.5rem !important}.is-size-5-desktop{font-size:1.25rem !important}.is-size-6-desktop{font-size:1rem !important}.is-size-7-desktop{font-size:.75rem !important}}@media screen and (min-width: 1216px){.is-size-1-widescreen{font-size:3rem !important}.is-size-2-widescreen{font-size:2.5rem !important}.is-size-3-widescreen{font-size:2rem !important}.is-size-4-widescreen{font-size:1.5rem !important}.is-size-5-widescreen{font-size:1.25rem !important}.is-size-6-widescreen{font-size:1rem !important}.is-size-7-widescreen{font-size:.75rem !important}}@media screen and (min-width: 1408px){.is-size-1-fullhd{font-size:3rem !important}.is-size-2-fullhd{font-size:2.5rem !important}.is-size-3-fullhd{font-size:2rem !important}.is-size-4-fullhd{font-size:1.5rem !important}.is-size-5-fullhd{font-size:1.25rem !important}.is-size-6-fullhd{font-size:1rem !important}.is-size-7-fullhd{font-size:.75rem !important}}.has-text-centered{text-align:center !important}.has-text-justified{text-align:justify !important}.has-text-left{text-align:left !important}.has-text-right{text-align:right !important}@media screen and (max-width: 768px){.has-text-centered-mobile{text-align:center !important}}@media screen and (min-width: 769px), print{.has-text-centered-tablet{text-align:center !important}}@media screen and (min-width: 769px) and (max-width: 1023px){.has-text-centered-tablet-only{text-align:center !important}}@media screen and (max-width: 1023px){.has-text-centered-touch{text-align:center !important}}@media screen and (min-width: 1024px){.has-text-centered-desktop{text-align:center !important}}@media screen and (min-width: 1024px) and (max-width: 1215px){.has-text-centered-desktop-only{text-align:center !important}}@media screen and (min-width: 1216px){.has-text-centered-widescreen{text-align:center !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-centered-widescreen-only{text-align:center !important}}@media screen and (min-width: 1408px){.has-text-centered-fullhd{text-align:center !important}}@media screen and (max-width: 768px){.has-text-justified-mobile{text-align:justify !important}}@media screen and (min-width: 769px), print{.has-text-justified-tablet{text-align:justify !important}}@media screen and (min-width: 769px) and (max-width: 1023px){.has-text-justified-tablet-only{text-align:justify !important}}@media screen and (max-width: 1023px){.has-text-justified-touch{text-align:justify !important}}@media screen and (min-width: 1024px){.has-text-justified-desktop{text-align:justify !important}}@media screen and (min-width: 1024px) and (max-width: 1215px){.has-text-justified-desktop-only{text-align:justify !important}}@media screen and (min-width: 1216px){.has-text-justified-widescreen{text-align:justify !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-justified-widescreen-only{text-align:justify !important}}@media screen and (min-width: 1408px){.has-text-justified-fullhd{text-align:justify !important}}@media screen and (max-width: 768px){.has-text-left-mobile{text-align:left !important}}@media screen and (min-width: 769px), print{.has-text-left-tablet{text-align:left !important}}@media screen and (min-width: 769px) and (max-width: 1023px){.has-text-left-tablet-only{text-align:left !important}}@media screen and (max-width: 1023px){.has-text-left-touch{text-align:left !important}}@media screen and (min-width: 1024px){.has-text-left-desktop{text-align:left !important}}@media screen and (min-width: 1024px) and (max-width: 1215px){.has-text-left-desktop-only{text-align:left !important}}@media screen and (min-width: 1216px){.has-text-left-widescreen{text-align:left !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-left-widescreen-only{text-align:left !important}}@media screen and (min-width: 1408px){.has-text-left-fullhd{text-align:left !important}}@media screen and (max-width: 768px){.has-text-right-mobile{text-align:right !important}}@media screen and (min-width: 769px), print{.has-text-right-tablet{text-align:right !important}}@media screen and (min-width: 769px) and (max-width: 1023px){.has-text-right-tablet-only{text-align:right !important}}@media screen and (max-width: 1023px){.has-text-right-touch{text-align:right !important}}@media screen and (min-width: 1024px){.has-text-right-desktop{text-align:right !important}}@media screen and (min-width: 1024px) and (max-width: 1215px){.has-text-right-desktop-only{text-align:right !important}}@media screen and (min-width: 1216px){.has-text-right-widescreen{text-align:right !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.has-text-right-widescreen-only{text-align:right !important}}@media screen and (min-width: 1408px){.has-text-right-fullhd{text-align:right !important}}.is-capitalized{text-transform:capitalize !important}.is-lowercase{text-transform:lowercase !important}.is-uppercase{text-transform:uppercase !important}.is-italic{font-style:italic !important}.has-text-white{color:#fff !important}a.has-text-white:hover,a.has-text-white:focus{color:#e6e6e6 !important}.has-background-white{background-color:#fff !important}.has-text-black{color:#0a0a0a !important}a.has-text-black:hover,a.has-text-black:focus{color:#000 !important}.has-background-black{background-color:#0a0a0a !important}.has-text-light{color:#f5f5f5 !important}a.has-text-light:hover,a.has-text-light:focus{color:#dbdbdb !important}.has-background-light{background-color:#f5f5f5 !important}.has-text-dark{color:#363636 !important}a.has-text-dark:hover,a.has-text-dark:focus{color:#1c1c1c !important}.has-background-dark{background-color:#363636 !important}.has-text-primary{color:#00d1b2 !important}a.has-text-primary:hover,a.has-text-primary:focus{color:#009e86 !important}.has-background-primary{background-color:#00d1b2 !important}.has-text-link{color:#3273dc !important}a.has-text-link:hover,a.has-text-link:focus{color:#205bbc !important}.has-background-link{background-color:#3273dc !important}.has-text-info{color:#209cee !important}a.has-text-info:hover,a.has-text-info:focus{color:#0f81cc !important}.has-background-info{background-color:#209cee !important}.has-text-success{color:#23d160 !important}a.has-text-success:hover,a.has-text-success:focus{color:#1ca64c !important}.has-background-success{background-color:#23d160 !important}.has-text-warning{color:#ffdd57 !important}a.has-text-warning:hover,a.has-text-warning:focus{color:#ffd324 !important}.has-background-warning{background-color:#ffdd57 !important}.has-text-danger{color:#ff3860 !important}a.has-text-danger:hover,a.has-text-danger:focus{color:#ff0537 !important}.has-background-danger{background-color:#ff3860 !important}.has-text-black-bis{color:#121212 !important}.has-background-black-bis{background-color:#121212 !important}.has-text-black-ter{color:#242424 !important}.has-background-black-ter{background-color:#242424 !important}.has-text-grey-darker{color:#363636 !important}.has-background-grey-darker{background-color:#363636 !important}.has-text-grey-dark{color:#4a4a4a !important}.has-background-grey-dark{background-color:#4a4a4a !important}.has-text-grey{color:#7a7a7a !important}.has-background-grey{background-color:#7a7a7a !important}.has-text-grey-light{color:#b5b5b5 !important}.has-background-grey-light{background-color:#b5b5b5 !important}.has-text-grey-lighter{color:#dbdbdb !important}.has-background-grey-lighter{background-color:#dbdbdb !important}.has-text-white-ter{color:#f5f5f5 !important}.has-background-white-ter{background-color:#f5f5f5 !important}.has-text-white-bis{color:#fafafa !important}.has-background-white-bis{background-color:#fafafa !important}.has-text-weight-light{font-weight:300 !important}.has-text-weight-normal{font-weight:400 !important}.has-text-weight-medium{font-weight:500 !important}.has-text-weight-semibold{font-weight:600 !important}.has-text-weight-bold{font-weight:700 !important}.is-family-primary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-secondary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-sans-serif{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue","Helvetica","Arial",sans-serif !important}.is-family-monospace{font-family:monospace !important}.is-family-code{font-family:monospace !important}.is-block{display:block !important}@media screen and (max-width: 768px){.is-block-mobile{display:block !important}}@media screen and (min-width: 769px), print{.is-block-tablet{display:block !important}}@media screen and (min-width: 769px) and (max-width: 1023px){.is-block-tablet-only{display:block !important}}@media screen and (max-width: 1023px){.is-block-touch{display:block !important}}@media screen and (min-width: 1024px){.is-block-desktop{display:block !important}}@media screen and (min-width: 1024px) and (max-width: 1215px){.is-block-desktop-only{display:block !important}}@media screen and (min-width: 1216px){.is-block-widescreen{display:block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-block-widescreen-only{display:block !important}}@media screen and (min-width: 1408px){.is-block-fullhd{display:block !important}}.is-flex{display:flex !important}@media screen and (max-width: 768px){.is-flex-mobile{display:flex !important}}@media screen and (min-width: 769px), print{.is-flex-tablet{display:flex !important}}@media screen and (min-width: 769px) and (max-width: 1023px){.is-flex-tablet-only{display:flex !important}}@media screen and (max-width: 1023px){.is-flex-touch{display:flex !important}}@media screen and (min-width: 1024px){.is-flex-desktop{display:flex !important}}@media screen and (min-width: 1024px) and (max-width: 1215px){.is-flex-desktop-only{display:flex !important}}@media screen and (min-width: 1216px){.is-flex-widescreen{display:flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-flex-widescreen-only{display:flex !important}}@media screen and (min-width: 1408px){.is-flex-fullhd{display:flex !important}}.is-inline{display:inline !important}@media screen and (max-width: 768px){.is-inline-mobile{display:inline !important}}@media screen and (min-width: 769px), print{.is-inline-tablet{display:inline !important}}@media screen and (min-width: 769px) and (max-width: 1023px){.is-inline-tablet-only{display:inline !important}}@media screen and (max-width: 1023px){.is-inline-touch{display:inline !important}}@media screen and (min-width: 1024px){.is-inline-desktop{display:inline !important}}@media screen and (min-width: 1024px) and (max-width: 1215px){.is-inline-desktop-only{display:inline !important}}@media screen and (min-width: 1216px){.is-inline-widescreen{display:inline !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-widescreen-only{display:inline !important}}@media screen and (min-width: 1408px){.is-inline-fullhd{display:inline !important}}.is-inline-block{display:inline-block !important}@media screen and (max-width: 768px){.is-inline-block-mobile{display:inline-block !important}}@media screen and (min-width: 769px), print{.is-inline-block-tablet{display:inline-block !important}}@media screen and (min-width: 769px) and (max-width: 1023px){.is-inline-block-tablet-only{display:inline-block !important}}@media screen and (max-width: 1023px){.is-inline-block-touch{display:inline-block !important}}@media screen and (min-width: 1024px){.is-inline-block-desktop{display:inline-block !important}}@media screen and (min-width: 1024px) and (max-width: 1215px){.is-inline-block-desktop-only{display:inline-block !important}}@media screen and (min-width: 1216px){.is-inline-block-widescreen{display:inline-block !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-block-widescreen-only{display:inline-block !important}}@media screen and (min-width: 1408px){.is-inline-block-fullhd{display:inline-block !important}}.is-inline-flex{display:inline-flex !important}@media screen and (max-width: 768px){.is-inline-flex-mobile{display:inline-flex !important}}@media screen and (min-width: 769px), print{.is-inline-flex-tablet{display:inline-flex !important}}@media screen and (min-width: 769px) and (max-width: 1023px){.is-inline-flex-tablet-only{display:inline-flex !important}}@media screen and (max-width: 1023px){.is-inline-flex-touch{display:inline-flex !important}}@media screen and (min-width: 1024px){.is-inline-flex-desktop{display:inline-flex !important}}@media screen and (min-width: 1024px) and (max-width: 1215px){.is-inline-flex-desktop-only{display:inline-flex !important}}@media screen and (min-width: 1216px){.is-inline-flex-widescreen{display:inline-flex !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-inline-flex-widescreen-only{display:inline-flex !important}}@media screen and (min-width: 1408px){.is-inline-flex-fullhd{display:inline-flex !important}}.is-hidden{display:none !important}.is-sr-only{border:none !important;clip:rect(0, 0, 0, 0) !important;height:0.01em !important;overflow:hidden !important;padding:0 !important;position:absolute !important;white-space:nowrap !important;width:0.01em !important}@media screen and (max-width: 768px){.is-hidden-mobile{display:none !important}}@media screen and (min-width: 769px), print{.is-hidden-tablet{display:none !important}}@media screen and (min-width: 769px) and (max-width: 1023px){.is-hidden-tablet-only{display:none !important}}@media screen and (max-width: 1023px){.is-hidden-touch{display:none !important}}@media screen and (min-width: 1024px){.is-hidden-desktop{display:none !important}}@media screen and (min-width: 1024px) and (max-width: 1215px){.is-hidden-desktop-only{display:none !important}}@media screen and (min-width: 1216px){.is-hidden-widescreen{display:none !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-hidden-widescreen-only{display:none !important}}@media screen and (min-width: 1408px){.is-hidden-fullhd{display:none !important}}.is-invisible{visibility:hidden !important}@media screen and (max-width: 768px){.is-invisible-mobile{visibility:hidden !important}}@media screen and (min-width: 769px), print{.is-invisible-tablet{visibility:hidden !important}}@media screen and (min-width: 769px) and (max-width: 1023px){.is-invisible-tablet-only{visibility:hidden !important}}@media screen and (max-width: 1023px){.is-invisible-touch{visibility:hidden !important}}@media screen and (min-width: 1024px){.is-invisible-desktop{visibility:hidden !important}}@media screen and (min-width: 1024px) and (max-width: 1215px){.is-invisible-desktop-only{visibility:hidden !important}}@media screen and (min-width: 1216px){.is-invisible-widescreen{visibility:hidden !important}}@media screen and (min-width: 1216px) and (max-width: 1407px){.is-invisible-widescreen-only{visibility:hidden !important}}@media screen and (min-width: 1408px){.is-invisible-fullhd{visibility:hidden !important}}.is-marginless{margin:0 !important}.is-paddingless{padding:0 !important}.is-radiusless{border-radius:0 !important}.is-shadowless{box-shadow:none !important}.is-relative{position:relative !important}.box{background-color:#fff;border-radius:6px;box-shadow:0 2px 3px rgba(10,10,10,0.1),0 0 0 1px rgba(10,10,10,0.1);color:#4a4a4a;display:block;padding:1.25rem}a.box:hover,a.box:focus{box-shadow:0 2px 3px rgba(10,10,10,0.1),0 0 0 1px #3273dc}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2),0 0 0 1px #3273dc}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#363636;cursor:pointer;justify-content:center;padding-bottom:calc(0.375em - 1px);padding-left:.75em;padding-right:.75em;padding-top:calc(0.375em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-small,.button .icon.is-medium,.button .icon.is-large{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-0.375em - 1px);margin-right:0.1875em}.button .icon:last-child:not(:first-child){margin-left:0.1875em;margin-right:calc(-0.375em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-0.375em - 1px);margin-right:calc(-0.375em - 1px)}.button:hover,.button.is-hovered{border-color:#b5b5b5;color:#363636}.button:focus,.button.is-focused{border-color:#3273dc;color:#363636}.button:focus:not(:active),.button.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(50,115,220,0.25)}.button:active,.button.is-active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#4a4a4a;text-decoration:underline}.button.is-text:hover,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text.is-focused{background-color:#f5f5f5;color:#363636}.button.is-text:active,.button.is-text.is-active{background-color:#e8e8e8;color:#363636}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white:hover,.button.is-white.is-hovered{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white:focus,.button.is-white.is-focused{border-color:transparent;color:#0a0a0a}.button.is-white:focus:not(:active),.button.is-white.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.button.is-white:active,.button.is-white.is-active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:transparent;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted:hover,.button.is-white.is-inverted.is-hovered{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined:hover,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined.is-focused{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-outlined.is-loading:hover::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined:hover,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined.is-focused{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading:hover::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black:hover,.button.is-black.is-hovered{background-color:#040404;border-color:transparent;color:#fff}.button.is-black:focus,.button.is-black.is-focused{border-color:transparent;color:#fff}.button.is-black:focus:not(:active),.button.is-black.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.button.is-black:active,.button.is-black.is-active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:transparent;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted:hover,.button.is-black.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined:hover,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined.is-focused{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-outlined.is-loading:hover::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined:hover,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined.is-focused{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading:hover::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #0a0a0a #0a0a0a !important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:#363636}.button.is-light:hover,.button.is-light.is-hovered{background-color:#eee;border-color:transparent;color:#363636}.button.is-light:focus,.button.is-light.is-focused{border-color:transparent;color:#363636}.button.is-light:focus:not(:active),.button.is-light.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.button.is-light:active,.button.is-light.is-active{background-color:#e8e8e8;border-color:transparent;color:#363636}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:transparent;box-shadow:none}.button.is-light.is-inverted{background-color:#363636;color:#f5f5f5}.button.is-light.is-inverted:hover,.button.is-light.is-inverted.is-hovered{background-color:#292929}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:#363636;border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent #363636 #363636 !important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined:hover,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined.is-focused{background-color:#f5f5f5;border-color:#f5f5f5;color:#363636}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-outlined.is-loading:hover::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363636 #363636 !important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-light.is-inverted.is-outlined:hover,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined.is-focused{background-color:#363636;color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading:hover::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark{background-color:#363636;border-color:transparent;color:#f5f5f5}.button.is-dark:hover,.button.is-dark.is-hovered{background-color:#2f2f2f;border-color:transparent;color:#f5f5f5}.button.is-dark:focus,.button.is-dark.is-focused{border-color:transparent;color:#f5f5f5}.button.is-dark:focus:not(:active),.button.is-dark.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.button.is-dark:active,.button.is-dark.is-active{background-color:#292929;border-color:transparent;color:#f5f5f5}.button.is-dark[disabled],fieldset[disabled] .button.is-dark{background-color:#363636;border-color:transparent;box-shadow:none}.button.is-dark.is-inverted{background-color:#f5f5f5;color:#363636}.button.is-dark.is-inverted:hover,.button.is-dark.is-inverted.is-hovered{background-color:#e8e8e8}.button.is-dark.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted{background-color:#f5f5f5;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-dark.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined:hover,.button.is-dark.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.button.is-dark.is-outlined.is-focused{background-color:#363636;border-color:#363636;color:#f5f5f5}.button.is-dark.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-outlined.is-loading:hover::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #f5f5f5 #f5f5f5 !important}.button.is-dark.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-dark.is-inverted.is-outlined:hover,.button.is-dark.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined.is-focused{background-color:#f5f5f5;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading:hover::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #363636 #363636 !important}.button.is-dark.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-primary{background-color:#00d1b2;border-color:transparent;color:#fff}.button.is-primary:hover,.button.is-primary.is-hovered{background-color:#00c4a7;border-color:transparent;color:#fff}.button.is-primary:focus,.button.is-primary.is-focused{border-color:transparent;color:#fff}.button.is-primary:focus:not(:active),.button.is-primary.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(0,209,178,0.25)}.button.is-primary:active,.button.is-primary.is-active{background-color:#00b89c;border-color:transparent;color:#fff}.button.is-primary[disabled],fieldset[disabled] .button.is-primary{background-color:#00d1b2;border-color:transparent;box-shadow:none}.button.is-primary.is-inverted{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted:hover,.button.is-primary.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],fieldset[disabled] .button.is-primary.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#00d1b2}.button.is-primary.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;color:#00d1b2}.button.is-primary.is-outlined:hover,.button.is-primary.is-outlined.is-hovered,.button.is-primary.is-outlined:focus,.button.is-primary.is-outlined.is-focused{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.button.is-primary.is-outlined.is-loading::after{border-color:transparent transparent #00d1b2 #00d1b2 !important}.button.is-primary.is-outlined.is-loading:hover::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-outlined.is-loading:focus::after,.button.is-primary.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-primary.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;box-shadow:none;color:#00d1b2}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined:hover,.button.is-primary.is-inverted.is-outlined.is-hovered,.button.is-primary.is-inverted.is-outlined:focus,.button.is-primary.is-inverted.is-outlined.is-focused{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-outlined.is-loading:hover::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #00d1b2 #00d1b2 !important}.button.is-primary.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link{background-color:#3273dc;border-color:transparent;color:#fff}.button.is-link:hover,.button.is-link.is-hovered{background-color:#276cda;border-color:transparent;color:#fff}.button.is-link:focus,.button.is-link.is-focused{border-color:transparent;color:#fff}.button.is-link:focus:not(:active),.button.is-link.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(50,115,220,0.25)}.button.is-link:active,.button.is-link.is-active{background-color:#2366d1;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#3273dc;border-color:transparent;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#3273dc}.button.is-link.is-inverted:hover,.button.is-link.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3273dc}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined{background-color:transparent;border-color:#3273dc;color:#3273dc}.button.is-link.is-outlined:hover,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined.is-focused{background-color:#3273dc;border-color:#3273dc;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #3273dc #3273dc !important}.button.is-link.is-outlined.is-loading:hover::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#3273dc;box-shadow:none;color:#3273dc}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined:hover,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined.is-focused{background-color:#fff;color:#3273dc}.button.is-link.is-inverted.is-outlined.is-loading:hover::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #3273dc #3273dc !important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info{background-color:#209cee;border-color:transparent;color:#fff}.button.is-info:hover,.button.is-info.is-hovered{background-color:#1496ed;border-color:transparent;color:#fff}.button.is-info:focus,.button.is-info.is-focused{border-color:transparent;color:#fff}.button.is-info:focus:not(:active),.button.is-info.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(32,156,238,0.25)}.button.is-info:active,.button.is-info.is-active{background-color:#118fe4;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#209cee;border-color:transparent;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#209cee}.button.is-info.is-inverted:hover,.button.is-info.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#209cee}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined{background-color:transparent;border-color:#209cee;color:#209cee}.button.is-info.is-outlined:hover,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined.is-focused{background-color:#209cee;border-color:#209cee;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #209cee #209cee !important}.button.is-info.is-outlined.is-loading:hover::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#209cee;box-shadow:none;color:#209cee}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined:hover,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined.is-focused{background-color:#fff;color:#209cee}.button.is-info.is-inverted.is-outlined.is-loading:hover::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #209cee #209cee !important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success{background-color:#23d160;border-color:transparent;color:#fff}.button.is-success:hover,.button.is-success.is-hovered{background-color:#22c65b;border-color:transparent;color:#fff}.button.is-success:focus,.button.is-success.is-focused{border-color:transparent;color:#fff}.button.is-success:focus:not(:active),.button.is-success.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(35,209,96,0.25)}.button.is-success:active,.button.is-success.is-active{background-color:#20bc56;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#23d160;border-color:transparent;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#23d160}.button.is-success.is-inverted:hover,.button.is-success.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#23d160}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined{background-color:transparent;border-color:#23d160;color:#23d160}.button.is-success.is-outlined:hover,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined.is-focused{background-color:#23d160;border-color:#23d160;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #23d160 #23d160 !important}.button.is-success.is-outlined.is-loading:hover::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#23d160;box-shadow:none;color:#23d160}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined:hover,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined.is-focused{background-color:#fff;color:#23d160}.button.is-success.is-inverted.is-outlined.is-loading:hover::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #23d160 #23d160 !important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-warning{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning:hover,.button.is-warning.is-hovered{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning:focus,.button.is-warning.is-focused{border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning:focus:not(:active),.button.is-warning.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,221,87,0.25)}.button.is-warning:active,.button.is-warning.is-active{background-color:#ffd83d;border-color:transparent;color:rgba(0,0,0,0.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffdd57;border-color:transparent;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);color:#ffdd57}.button.is-warning.is-inverted:hover,.button.is-warning.is-inverted.is-hovered{background-color:rgba(0,0,0,0.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,0.7);border-color:transparent;box-shadow:none;color:#ffdd57}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;color:#ffdd57}.button.is-warning.is-outlined:hover,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined.is-focused{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,0.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffdd57 #ffdd57 !important}.button.is-warning.is-outlined.is-loading:hover::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading.is-focused::after{border-color:transparent transparent rgba(0,0,0,0.7) rgba(0,0,0,0.7) !important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffdd57;box-shadow:none;color:#ffdd57}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);color:rgba(0,0,0,0.7)}.button.is-warning.is-inverted.is-outlined:hover,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined.is-focused{background-color:rgba(0,0,0,0.7);color:#ffdd57}.button.is-warning.is-inverted.is-outlined.is-loading:hover::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ffdd57 #ffdd57 !important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,0.7);box-shadow:none;color:rgba(0,0,0,0.7)}.button.is-danger{background-color:#ff3860;border-color:transparent;color:#fff}.button.is-danger:hover,.button.is-danger.is-hovered{background-color:#ff2b56;border-color:transparent;color:#fff}.button.is-danger:focus,.button.is-danger.is-focused{border-color:transparent;color:#fff}.button.is-danger:focus:not(:active),.button.is-danger.is-focused:not(:active){box-shadow:0 0 0 0.125em rgba(255,56,96,0.25)}.button.is-danger:active,.button.is-danger.is-active{background-color:#ff1f4b;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#ff3860;border-color:transparent;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#ff3860}.button.is-danger.is-inverted:hover,.button.is-danger.is-inverted.is-hovered{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#ff3860}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined{background-color:transparent;border-color:#ff3860;color:#ff3860}.button.is-danger.is-outlined:hover,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined.is-focused{background-color:#ff3860;border-color:#ff3860;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #ff3860 #ff3860 !important}.button.is-danger.is-outlined.is-loading:hover::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #fff #fff !important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#ff3860;box-shadow:none;color:#ff3860}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined:hover,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined.is-focused{background-color:#fff;color:#ff3860}.button.is-danger.is-inverted.is-outlined.is-loading:hover::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after{border-color:transparent transparent #ff3860 #ff3860 !important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-small{border-radius:2px;font-size:.75rem}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent !important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em / 2));top:calc(50% - (1em / 2));position:absolute !important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#7a7a7a;box-shadow:none;pointer-events:none}.button.is-rounded{border-radius:290486px;padding-left:1em;padding-right:1em}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:0.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:0.5rem}.buttons:last-child{margin-bottom:-0.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){border-radius:2px;font-size:.75rem}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button:hover,.buttons.has-addons .button.is-hovered{z-index:2}.buttons.has-addons .button:focus,.buttons.has-addons .button.is-focused,.buttons.has-addons .button:active,.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-selected{z-index:3}.buttons.has-addons .button:focus:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-selected:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:0.25rem;margin-right:0.25rem}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}@media screen and (min-width: 1024px){.container{max-width:960px}.container.is-fluid{margin-left:32px;margin-right:32px;max-width:none}}@media screen and (max-width: 1215px){.container.is-widescreen{max-width:1152px}}@media screen and (max-width: 1407px){.container.is-fullhd{max-width:1344px}}@media screen and (min-width: 1216px){.container{max-width:1152px}}@media screen and (min-width: 1408px){.container{max-width:1344px}}.content li+li{margin-top:0.25em}.content p:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content ul:not(:last-child),.content blockquote:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#363636;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:0.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:0.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:0.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:0.8em}.content h5{font-size:1.125em;margin-bottom:0.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol:not([type]).is-lower-alpha{list-style-type:lower-alpha}.content ol:not([type]).is-lower-roman{list-style-type:lower-roman}.content ol:not([type]).is-upper-alpha{list-style-type:upper-alpha}.content ol:not([type]).is-upper-roman{list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:0.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal}.content sup,.content sub{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.content table th{color:#363636}.content table th:not([align]){text-align:left}.content table thead td,.content table thead th{border-width:0 0 2px;color:#363636}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#363636}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small{font-size:.75rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.image{display:block;position:relative}.image img{display:block;height:auto;width:100%}.image img.is-rounded{border-radius:290486px}.image.is-square img,.image.is-square .has-ratio,.image.is-1by1 img,.image.is-1by1 .has-ratio,.image.is-5by4 img,.image.is-5by4 .has-ratio,.image.is-4by3 img,.image.is-4by3 .has-ratio,.image.is-3by2 img,.image.is-3by2 .has-ratio,.image.is-5by3 img,.image.is-5by3 .has-ratio,.image.is-16by9 img,.image.is-16by9 .has-ratio,.image.is-2by1 img,.image.is-2by1 .has-ratio,.image.is-3by1 img,.image.is-3by1 .has-ratio,.image.is-4by5 img,.image.is-4by5 .has-ratio,.image.is-3by4 img,.image.is-3by4 .has-ratio,.image.is-2by3 img,.image.is-2by3 .has-ratio,.image.is-3by5 img,.image.is-3by5 .has-ratio,.image.is-9by16 img,.image.is-9by16 .has-ratio,.image.is-1by2 img,.image.is-1by2 .has-ratio,.image.is-1by3 img,.image.is-1by3 .has-ratio{height:100%;width:100%}.image.is-square,.image.is-1by1{padding-top:100%}.image.is-5by4{padding-top:80%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-5by3{padding-top:60%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-3by1{padding-top:33.3333%}.image.is-4by5{padding-top:125%}.image.is-3by4{padding-top:133.3333%}.image.is-2by3{padding-top:150%}.image.is-3by5{padding-top:166.6666%}.image.is-9by16{padding-top:177.7777%}.image.is-1by2{padding-top:200%}.image.is-1by3{padding-top:300%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;padding:1.25rem 2.5rem 1.25rem 1.5rem;position:relative}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:transparent}.notification>.delete{position:absolute;right:0.5rem;top:0.5rem}.notification .title,.notification .subtitle,.notification .content{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:#363636}.notification.is-dark{background-color:#363636;color:#f5f5f5}.notification.is-primary{background-color:#00d1b2;color:#fff}.notification.is-link{background-color:#3273dc;color:#fff}.notification.is-info{background-color:#209cee;color:#fff}.notification.is-success{background-color:#23d160;color:#fff}.notification.is-warning{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.notification.is-danger{background-color:#ff3860;color:#fff}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:290486px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#dbdbdb}.progress::-webkit-progress-value{background-color:#4a4a4a}.progress::-moz-progress-bar{background-color:#4a4a4a}.progress::-ms-fill{background-color:#4a4a4a;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right, #fff 30%, #dbdbdb 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right, #0a0a0a 30%, #dbdbdb 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right, #f5f5f5 30%, #dbdbdb 30%)}.progress.is-dark::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate{background-image:linear-gradient(to right, #363636 30%, #dbdbdb 30%)}.progress.is-primary::-webkit-progress-value{background-color:#00d1b2}.progress.is-primary::-moz-progress-bar{background-color:#00d1b2}.progress.is-primary::-ms-fill{background-color:#00d1b2}.progress.is-primary:indeterminate{background-image:linear-gradient(to right, #00d1b2 30%, #dbdbdb 30%)}.progress.is-link::-webkit-progress-value{background-color:#3273dc}.progress.is-link::-moz-progress-bar{background-color:#3273dc}.progress.is-link::-ms-fill{background-color:#3273dc}.progress.is-link:indeterminate{background-image:linear-gradient(to right, #3273dc 30%, #dbdbdb 30%)}.progress.is-info::-webkit-progress-value{background-color:#209cee}.progress.is-info::-moz-progress-bar{background-color:#209cee}.progress.is-info::-ms-fill{background-color:#209cee}.progress.is-info:indeterminate{background-image:linear-gradient(to right, #209cee 30%, #dbdbdb 30%)}.progress.is-success::-webkit-progress-value{background-color:#23d160}.progress.is-success::-moz-progress-bar{background-color:#23d160}.progress.is-success::-ms-fill{background-color:#23d160}.progress.is-success:indeterminate{background-image:linear-gradient(to right, #23d160 30%, #dbdbdb 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffdd57}.progress.is-warning::-moz-progress-bar{background-color:#ffdd57}.progress.is-warning::-ms-fill{background-color:#ffdd57}.progress.is-warning:indeterminate{background-image:linear-gradient(to right, #ffdd57 30%, #dbdbdb 30%)}.progress.is-danger::-webkit-progress-value{background-color:#ff3860}.progress.is-danger::-moz-progress-bar{background-color:#ff3860}.progress.is-danger::-ms-fill{background-color:#ff3860}.progress.is-danger:indeterminate{background-image:linear-gradient(to right, #ff3860 30%, #dbdbdb 30%)}.progress:indeterminate{-webkit-animation-duration:1.5s;animation-duration:1.5s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-name:moveIndeterminate;animation-name:moveIndeterminate;-webkit-animation-timing-function:linear;animation-timing-function:linear;background-color:#dbdbdb;background-image:linear-gradient(to right, #4a4a4a 30%, #dbdbdb 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress.is-small{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@-webkit-keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#363636}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:0.5em 0.75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:#363636}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#f5f5f5}.table td.is-primary,.table th.is-primary{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.table td.is-link,.table th.is-link{background-color:#3273dc;border-color:#3273dc;color:#fff}.table td.is-info,.table th.is-info{background-color:#209cee;border-color:#209cee;color:#fff}.table td.is-success,.table th.is-success{background-color:#23d160;border-color:#23d160;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffdd57;border-color:#ffdd57;color:rgba(0,0,0,0.7)}.table td.is-danger,.table th.is-danger{background-color:#ff3860;border-color:#ff3860;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#00d1b2;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table th{color:#363636}.table th:not([align]){text-align:left}.table tr.is-selected{background-color:#00d1b2;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:rgba(0,0,0,0)}.table thead td,.table thead th{border-width:0 0 2px;color:#363636}.table tfoot{background-color:rgba(0,0,0,0)}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#363636}.table tbody{background-color:rgba(0,0,0,0)}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:0.25em 0.5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag{margin-bottom:0.5rem}.tags .tag:not(:last-child){margin-right:0.5rem}.tags:last-child{margin-bottom:-0.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag{margin-right:0.25rem;margin-left:0.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child){margin-left:0.5rem}.tags.is-right .tag:not(:last-child){margin-right:0}.tags.has-addons .tag{margin-right:0}.tags.has-addons .tag:not(:first-child){margin-left:0;border-bottom-left-radius:0;border-top-left-radius:0}.tags.has-addons .tag:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.tag:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#4a4a4a;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:0.75em;padding-right:0.75em;white-space:nowrap}.tag:not(body) .delete{margin-left:0.25rem;margin-right:-0.375rem}.tag:not(body).is-white{background-color:#fff;color:#0a0a0a}.tag:not(body).is-black{background-color:#0a0a0a;color:#fff}.tag:not(body).is-light{background-color:#f5f5f5;color:#363636}.tag:not(body).is-dark{background-color:#363636;color:#f5f5f5}.tag:not(body).is-primary{background-color:#00d1b2;color:#fff}.tag:not(body).is-link{background-color:#3273dc;color:#fff}.tag:not(body).is-info{background-color:#209cee;color:#fff}.tag:not(body).is-success{background-color:#23d160;color:#fff}.tag:not(body).is-warning{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.tag:not(body).is-danger{background-color:#ff3860;color:#fff}.tag:not(body).is-normal{font-size:.75rem}.tag:not(body).is-medium{font-size:1rem}.tag:not(body).is-large{font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child){margin-left:-0.375em;margin-right:0.1875em}.tag:not(body) .icon:last-child:not(:first-child){margin-left:0.1875em;margin-right:-0.375em}.tag:not(body) .icon:first-child:last-child{margin-left:-0.375em;margin-right:-0.375em}.tag:not(body).is-delete{margin-left:1px;padding:0;position:relative;width:2em}.tag:not(body).is-delete::before,.tag:not(body).is-delete::after{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;-webkit-transform:translateX(-50%) translateY(-50%) rotate(45deg);transform:translateX(-50%) translateY(-50%) rotate(45deg);-webkit-transform-origin:center center;transform-origin:center center}.tag:not(body).is-delete::before{height:1px;width:50%}.tag:not(body).is-delete::after{height:50%;width:1px}.tag:not(body).is-delete:hover,.tag:not(body).is-delete:focus{background-color:#e8e8e8}.tag:not(body).is-delete:active{background-color:#dbdbdb}.tag:not(body).is-rounded{border-radius:290486px}a.tag:hover{text-decoration:underline}.title,.subtitle{word-break:break-word}.title em,.title span,.subtitle em,.subtitle span{font-weight:inherit}.title sub,.subtitle sub{font-size:.75em}.title sup,.subtitle sup{font-size:.75em}.title .tag,.subtitle .tag{vertical-align:middle}.title{color:#363636;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title+.highlight{margin-top:-0.75rem}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#4a4a4a;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#363636;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.highlight{font-weight:400;max-width:100%;overflow:hidden;padding:0}.highlight pre{overflow:auto;max-width:100%}.number{align-items:center;background-color:#f5f5f5;border-radius:290486px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:0.25rem 0.5rem;text-align:center;vertical-align:top}.input,.textarea,.select select{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#363636}.input::-moz-placeholder,.textarea::-moz-placeholder,.select select::-moz-placeholder{color:rgba(54,54,54,0.3)}.input::-webkit-input-placeholder,.textarea::-webkit-input-placeholder,.select select::-webkit-input-placeholder{color:rgba(54,54,54,0.3)}.input:-moz-placeholder,.textarea:-moz-placeholder,.select select:-moz-placeholder{color:rgba(54,54,54,0.3)}.input:-ms-input-placeholder,.textarea:-ms-input-placeholder,.select select:-ms-input-placeholder{color:rgba(54,54,54,0.3)}.input:hover,.textarea:hover,.select select:hover,.is-hovered.input,.is-hovered.textarea,.select select.is-hovered{border-color:#b5b5b5}.input:focus,.textarea:focus,.select select:focus,.is-focused.input,.is-focused.textarea,.select select.is-focused,.input:active,.textarea:active,.select select:active,.is-active.input,.is-active.textarea,.select select.is-active{border-color:#3273dc;box-shadow:0 0 0 0.125em rgba(50,115,220,0.25)}.input[disabled],.textarea[disabled],.select select[disabled],fieldset[disabled] .input,fieldset[disabled] .textarea,fieldset[disabled] .select select,.select fieldset[disabled] select{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.input[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,.select select[disabled]::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder{color:rgba(122,122,122,0.3)}.input[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,.select select[disabled]::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder{color:rgba(122,122,122,0.3)}.input[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,.select select[disabled]:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder{color:rgba(122,122,122,0.3)}.input[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,.select select[disabled]:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder{color:rgba(122,122,122,0.3)}.input,.textarea{box-shadow:inset 0 1px 2px rgba(10,10,10,0.1);max-width:100%;width:100%}.input[readonly],.textarea[readonly]{box-shadow:none}.is-white.input,.is-white.textarea{border-color:#fff}.is-white.input:focus,.is-white.textarea:focus,.is-white.is-focused.input,.is-white.is-focused.textarea,.is-white.input:active,.is-white.textarea:active,.is-white.is-active.input,.is-white.is-active.textarea{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.is-black.input,.is-black.textarea{border-color:#0a0a0a}.is-black.input:focus,.is-black.textarea:focus,.is-black.is-focused.input,.is-black.is-focused.textarea,.is-black.input:active,.is-black.textarea:active,.is-black.is-active.input,.is-black.is-active.textarea{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.is-light.input,.is-light.textarea{border-color:#f5f5f5}.is-light.input:focus,.is-light.textarea:focus,.is-light.is-focused.input,.is-light.is-focused.textarea,.is-light.input:active,.is-light.textarea:active,.is-light.is-active.input,.is-light.is-active.textarea{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.is-dark.input,.is-dark.textarea{border-color:#363636}.is-dark.input:focus,.is-dark.textarea:focus,.is-dark.is-focused.input,.is-dark.is-focused.textarea,.is-dark.input:active,.is-dark.textarea:active,.is-dark.is-active.input,.is-dark.is-active.textarea{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.is-primary.input,.is-primary.textarea{border-color:#00d1b2}.is-primary.input:focus,.is-primary.textarea:focus,.is-primary.is-focused.input,.is-primary.is-focused.textarea,.is-primary.input:active,.is-primary.textarea:active,.is-primary.is-active.input,.is-primary.is-active.textarea{box-shadow:0 0 0 0.125em rgba(0,209,178,0.25)}.is-link.input,.is-link.textarea{border-color:#3273dc}.is-link.input:focus,.is-link.textarea:focus,.is-link.is-focused.input,.is-link.is-focused.textarea,.is-link.input:active,.is-link.textarea:active,.is-link.is-active.input,.is-link.is-active.textarea{box-shadow:0 0 0 0.125em rgba(50,115,220,0.25)}.is-info.input,.is-info.textarea{border-color:#209cee}.is-info.input:focus,.is-info.textarea:focus,.is-info.is-focused.input,.is-info.is-focused.textarea,.is-info.input:active,.is-info.textarea:active,.is-info.is-active.input,.is-info.is-active.textarea{box-shadow:0 0 0 0.125em rgba(32,156,238,0.25)}.is-success.input,.is-success.textarea{border-color:#23d160}.is-success.input:focus,.is-success.textarea:focus,.is-success.is-focused.input,.is-success.is-focused.textarea,.is-success.input:active,.is-success.textarea:active,.is-success.is-active.input,.is-success.is-active.textarea{box-shadow:0 0 0 0.125em rgba(35,209,96,0.25)}.is-warning.input,.is-warning.textarea{border-color:#ffdd57}.is-warning.input:focus,.is-warning.textarea:focus,.is-warning.is-focused.input,.is-warning.is-focused.textarea,.is-warning.input:active,.is-warning.textarea:active,.is-warning.is-active.input,.is-warning.is-active.textarea{box-shadow:0 0 0 0.125em rgba(255,221,87,0.25)}.is-danger.input,.is-danger.textarea{border-color:#ff3860}.is-danger.input:focus,.is-danger.textarea:focus,.is-danger.is-focused.input,.is-danger.is-focused.textarea,.is-danger.input:active,.is-danger.textarea:active,.is-danger.is-active.input,.is-danger.is-active.textarea{box-shadow:0 0 0 0.125em rgba(255,56,96,0.25)}.is-small.input,.is-small.textarea{border-radius:2px;font-size:.75rem}.is-medium.input,.is-medium.textarea{font-size:1.25rem}.is-large.input,.is-large.textarea{font-size:1.5rem}.is-fullwidth.input,.is-fullwidth.textarea{display:block;width:100%}.is-inline.input,.is-inline.textarea{display:inline;width:auto}.input.is-rounded{border-radius:290486px;padding-left:1em;padding-right:1em}.input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:0.625em;resize:vertical}.textarea:not([rows]){max-height:600px;min-height:120px}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.checkbox,.radio{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.checkbox input,.radio input{cursor:pointer}.checkbox:hover,.radio:hover{color:#363636}.checkbox[disabled],.radio[disabled],fieldset[disabled] .checkbox,fieldset[disabled] .radio{color:#7a7a7a;cursor:not-allowed}.radio+.radio{margin-left:0.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.25em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#3273dc;right:1.125em;z-index:4}.select.is-rounded select{border-radius:290486px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:none}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:0.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#363636}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select:hover,.select.is-white select.is-hovered{border-color:#f2f2f2}.select.is-white select:focus,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select.is-active{box-shadow:0 0 0 0.125em rgba(255,255,255,0.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select:hover,.select.is-black select.is-hovered{border-color:#000}.select.is-black select:focus,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select.is-active{box-shadow:0 0 0 0.125em rgba(10,10,10,0.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select:hover,.select.is-light select.is-hovered{border-color:#e8e8e8}.select.is-light select:focus,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select.is-active{box-shadow:0 0 0 0.125em rgba(245,245,245,0.25)}.select.is-dark:not(:hover)::after{border-color:#363636}.select.is-dark select{border-color:#363636}.select.is-dark select:hover,.select.is-dark select.is-hovered{border-color:#292929}.select.is-dark select:focus,.select.is-dark select.is-focused,.select.is-dark select:active,.select.is-dark select.is-active{box-shadow:0 0 0 0.125em rgba(54,54,54,0.25)}.select.is-primary:not(:hover)::after{border-color:#00d1b2}.select.is-primary select{border-color:#00d1b2}.select.is-primary select:hover,.select.is-primary select.is-hovered{border-color:#00b89c}.select.is-primary select:focus,.select.is-primary select.is-focused,.select.is-primary select:active,.select.is-primary select.is-active{box-shadow:0 0 0 0.125em rgba(0,209,178,0.25)}.select.is-link:not(:hover)::after{border-color:#3273dc}.select.is-link select{border-color:#3273dc}.select.is-link select:hover,.select.is-link select.is-hovered{border-color:#2366d1}.select.is-link select:focus,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select.is-active{box-shadow:0 0 0 0.125em rgba(50,115,220,0.25)}.select.is-info:not(:hover)::after{border-color:#209cee}.select.is-info select{border-color:#209cee}.select.is-info select:hover,.select.is-info select.is-hovered{border-color:#118fe4}.select.is-info select:focus,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select.is-active{box-shadow:0 0 0 0.125em rgba(32,156,238,0.25)}.select.is-success:not(:hover)::after{border-color:#23d160}.select.is-success select{border-color:#23d160}.select.is-success select:hover,.select.is-success select.is-hovered{border-color:#20bc56}.select.is-success select:focus,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select.is-active{box-shadow:0 0 0 0.125em rgba(35,209,96,0.25)}.select.is-warning:not(:hover)::after{border-color:#ffdd57}.select.is-warning select{border-color:#ffdd57}.select.is-warning select:hover,.select.is-warning select.is-hovered{border-color:#ffd83d}.select.is-warning select:focus,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select.is-active{box-shadow:0 0 0 0.125em rgba(255,221,87,0.25)}.select.is-danger:not(:hover)::after{border-color:#ff3860}.select.is-danger select{border-color:#ff3860}.select.is-danger select:hover,.select.is-danger select.is-hovered{border-color:#ff1f4b}.select.is-danger select:focus,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select.is-active{box-shadow:0 0 0 0.125em rgba(255,56,96,0.25)}.select.is-small{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#7a7a7a}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:0.625em;top:0.625em;-webkit-transform:none;transform:none}.select.is-loading.is-small:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white:hover .file-cta,.file.is-white.is-hovered .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white:focus .file-cta,.file.is-white.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,255,255,0.25);color:#0a0a0a}.file.is-white:active .file-cta,.file.is-white.is-active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black:hover .file-cta,.file.is-black.is-hovered .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black:focus .file-cta,.file.is-black.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(10,10,10,0.25);color:#fff}.file.is-black:active .file-cta,.file.is-black.is-active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:#363636}.file.is-light:hover .file-cta,.file.is-light.is-hovered .file-cta{background-color:#eee;border-color:transparent;color:#363636}.file.is-light:focus .file-cta,.file.is-light.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(245,245,245,0.25);color:#363636}.file.is-light:active .file-cta,.file.is-light.is-active .file-cta{background-color:#e8e8e8;border-color:transparent;color:#363636}.file.is-dark .file-cta{background-color:#363636;border-color:transparent;color:#f5f5f5}.file.is-dark:hover .file-cta,.file.is-dark.is-hovered .file-cta{background-color:#2f2f2f;border-color:transparent;color:#f5f5f5}.file.is-dark:focus .file-cta,.file.is-dark.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(54,54,54,0.25);color:#f5f5f5}.file.is-dark:active .file-cta,.file.is-dark.is-active .file-cta{background-color:#292929;border-color:transparent;color:#f5f5f5}.file.is-primary .file-cta{background-color:#00d1b2;border-color:transparent;color:#fff}.file.is-primary:hover .file-cta,.file.is-primary.is-hovered .file-cta{background-color:#00c4a7;border-color:transparent;color:#fff}.file.is-primary:focus .file-cta,.file.is-primary.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(0,209,178,0.25);color:#fff}.file.is-primary:active .file-cta,.file.is-primary.is-active .file-cta{background-color:#00b89c;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#3273dc;border-color:transparent;color:#fff}.file.is-link:hover .file-cta,.file.is-link.is-hovered .file-cta{background-color:#276cda;border-color:transparent;color:#fff}.file.is-link:focus .file-cta,.file.is-link.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(50,115,220,0.25);color:#fff}.file.is-link:active .file-cta,.file.is-link.is-active .file-cta{background-color:#2366d1;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#209cee;border-color:transparent;color:#fff}.file.is-info:hover .file-cta,.file.is-info.is-hovered .file-cta{background-color:#1496ed;border-color:transparent;color:#fff}.file.is-info:focus .file-cta,.file.is-info.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(32,156,238,0.25);color:#fff}.file.is-info:active .file-cta,.file.is-info.is-active .file-cta{background-color:#118fe4;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#23d160;border-color:transparent;color:#fff}.file.is-success:hover .file-cta,.file.is-success.is-hovered .file-cta{background-color:#22c65b;border-color:transparent;color:#fff}.file.is-success:focus .file-cta,.file.is-success.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(35,209,96,0.25);color:#fff}.file.is-success:active .file-cta,.file.is-success.is-active .file-cta{background-color:#20bc56;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffdd57;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-warning:hover .file-cta,.file.is-warning.is-hovered .file-cta{background-color:#ffdb4a;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-warning:focus .file-cta,.file.is-warning.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,221,87,0.25);color:rgba(0,0,0,0.7)}.file.is-warning:active .file-cta,.file.is-warning.is-active .file-cta{background-color:#ffd83d;border-color:transparent;color:rgba(0,0,0,0.7)}.file.is-danger .file-cta{background-color:#ff3860;border-color:transparent;color:#fff}.file.is-danger:hover .file-cta,.file.is-danger.is-hovered .file-cta{background-color:#ff2b56;border-color:transparent;color:#fff}.file.is-danger:focus .file-cta,.file.is-danger.is-focused .file-cta{border-color:transparent;box-shadow:0 0 0.5em rgba(255,56,96,0.25);color:#fff}.file.is-danger:active .file-cta,.file.is-danger.is-active .file-cta{background-color:#ff1f4b;border-color:transparent;color:#fff}.file.is-small{font-size:.75rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#363636}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#363636}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:none;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#4a4a4a}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:left;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:0.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#363636;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:0.5em}.label.is-small{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:0.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark{color:#363636}.help.is-primary{color:#00d1b2}.help.is-link{color:#3273dc}.help.is-info{color:#209cee}.help.is-success{color:#23d160}.help.is-warning{color:#ffdd57}.help.is-danger{color:#ff3860}.field:not(:last-child){margin-bottom:0.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .button:not([disabled]).is-hovered,.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control .input:not([disabled]).is-hovered,.field.has-addons .control .select select:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]).is-hovered{z-index:2}.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .button:not([disabled]).is-focused,.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button:not([disabled]).is-active,.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control .input:not([disabled]).is-focused,.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control .input:not([disabled]).is-active,.field.has-addons .control .select select:not([disabled]):focus,.field.has-addons .control .select select:not([disabled]).is-focused,.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select:not([disabled]).is-active{z-index:3}.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .button:not([disabled]).is-focused:hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button:not([disabled]).is-active:hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control .input:not([disabled]).is-focused:hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control .input:not([disabled]).is-active:hover,.field.has-addons .control .select select:not([disabled]):focus:hover,.field.has-addons .control .select select:not([disabled]).is-focused:hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select:not([disabled]).is-active:hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:0.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:0.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-0.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width: 769px), print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width: 768px){.field-label{margin-bottom:0.5rem}}@media screen and (min-width: 769px), print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small{font-size:.75rem;padding-top:0.375em}.field-label.is-normal{padding-top:0.375em}.field-label.is-medium{font-size:1.25rem;padding-top:0.375em}.field-label.is-large{font-size:1.5rem;padding-top:0.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width: 769px), print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:0.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:left}.control.has-icons-left .input:focus ~ .icon,.control.has-icons-left .select:focus ~ .icon,.control.has-icons-right .input:focus ~ .icon,.control.has-icons-right .select:focus ~ .icon{color:#7a7a7a}.control.has-icons-left .input.is-small ~ .icon,.control.has-icons-left .select.is-small ~ .icon,.control.has-icons-right .input.is-small ~ .icon,.control.has-icons-right .select.is-small ~ .icon{font-size:.75rem}.control.has-icons-left .input.is-medium ~ .icon,.control.has-icons-left .select.is-medium ~ .icon,.control.has-icons-right .input.is-medium ~ .icon,.control.has-icons-right .select.is-medium ~ .icon{font-size:1.25rem}.control.has-icons-left .input.is-large ~ .icon,.control.has-icons-left .select.is-large ~ .icon,.control.has-icons-right .input.is-large ~ .icon,.control.has-icons-right .select.is-large ~ .icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.25em;pointer-events:none;position:absolute;top:0;width:2.25em;z-index:4}.control.has-icons-left .input,.control.has-icons-left .select select{padding-left:2.25em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right .select select{padding-right:2.25em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute !important;right:0.625em;top:0.625em;z-index:4}.control.is-loading.is-small:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#3273dc;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#363636;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ul,.breadcrumb ol{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:0.5em}.breadcrumb .icon:last-child{margin-left:0.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;box-shadow:0 2px 3px rgba(10,10,10,0.1),0 0 0 1px rgba(10,10,10,0.1);color:#4a4a4a;max-width:100%;position:relative}.card-header{background-color:rgba(0,0,0,0);align-items:stretch;box-shadow:0 1px 2px rgba(10,10,10,0.1);display:flex}.card-header-title{align-items:center;color:#363636;display:flex;flex-grow:1;font-weight:700;padding:.75rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{align-items:center;cursor:pointer;display:flex;justify-content:center;padding:.75rem}.card-image{display:block;position:relative}.card-content{background-color:rgba(0,0,0,0);padding:1.5rem}.card-footer{background-color:rgba(0,0,0,0);border-top:1px solid #dbdbdb;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #dbdbdb}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:0 2px 3px rgba(10,10,10,0.1),0 0 0 1px rgba(10,10,10,0.1);padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#4a4a4a;display:block;font-size:0.875rem;line-height:1.5;padding:0.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:left;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#3273dc;color:#fff}.dropdown-divider{background-color:#dbdbdb;border:none;display:block;height:1px;margin:0.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width: 769px), print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .title,.level-item .subtitle{margin-bottom:0}@media screen and (max-width: 768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width: 769px), print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width: 768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width: 769px), print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width: 769px), print{.level-right{display:flex}}.list{background-color:#fff;border-radius:4px;box-shadow:0 2px 3px rgba(10,10,10,0.1),0 0 0 1px rgba(10,10,10,0.1)}.list-item{display:block;padding:0.5em 1em}.list-item:not(a){color:#4a4a4a}.list-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-item:last-child{border-bottom-left-radius:4px;border-bottom-right-radius:4px}.list-item:not(:last-child){border-bottom:1px solid #dbdbdb}.list-item.is-active{background-color:#3273dc;color:#fff}a.list-item{background-color:#f5f5f5;cursor:pointer}.media{align-items:flex-start;display:flex;text-align:left}.media .content:not(:last-child){margin-bottom:0.75rem}.media .media{border-top:1px solid rgba(219,219,219,0.5);display:flex;padding-top:0.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:0.5rem}.media .media .media{padding-top:0.5rem}.media .media .media+.media{margin-top:0.5rem}.media+.media{border-top:1px solid rgba(219,219,219,0.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:left}@media screen and (max-width: 768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#4a4a4a;display:block;padding:0.5em 0.75em}.menu-list a:hover{background-color:#f5f5f5;color:#363636}.menu-list a.is-active{background-color:#3273dc;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#7a7a7a;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff;color:#4d4d4d}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a;color:#090909}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:#363636}.message.is-light .message-body{border-color:#f5f5f5;color:#505050}.message.is-dark{background-color:#fafafa}.message.is-dark .message-header{background-color:#363636;color:#f5f5f5}.message.is-dark .message-body{border-color:#363636;color:#2a2a2a}.message.is-primary{background-color:#f5fffd}.message.is-primary .message-header{background-color:#00d1b2;color:#fff}.message.is-primary .message-body{border-color:#00d1b2;color:#021310}.message.is-link{background-color:#f6f9fe}.message.is-link .message-header{background-color:#3273dc;color:#fff}.message.is-link .message-body{border-color:#3273dc;color:#22509a}.message.is-info{background-color:#f6fbfe}.message.is-info .message-header{background-color:#209cee;color:#fff}.message.is-info .message-body{border-color:#209cee;color:#12537e}.message.is-success{background-color:#f6fef9}.message.is-success .message-header{background-color:#23d160;color:#fff}.message.is-success .message-body{border-color:#23d160;color:#0e301a}.message.is-warning{background-color:#fffdf5}.message.is-warning .message-header{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.message.is-warning .message-body{border-color:#ffdd57;color:#3b3108}.message.is-danger{background-color:#fff5f7}.message.is-danger .message-header{background-color:#ff3860;color:#fff}.message.is-danger .message-body{border-color:#ff3860;color:#cd0930}.message-header{align-items:center;background-color:#4a4a4a;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:0.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:0.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#4a4a4a;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:rgba(0,0,0,0)}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,0.86)}.modal-content,.modal-card{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width: 769px), print{.modal-content,.modal-card{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:none;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-head,.modal-card-foot{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#363636;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:0.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand>.navbar-item,.navbar.is-white .navbar-brand .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width: 1024px){.navbar.is-white .navbar-start>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-end .navbar-link{color:#0a0a0a}.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-start .navbar-link::after,.navbar.is-white .navbar-end .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand>.navbar-item,.navbar.is-black .navbar-brand .navbar-link{color:#fff}.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-black .navbar-start>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-end .navbar-link{color:#fff}.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end .navbar-link.is-active{background-color:#000;color:#fff}.navbar.is-black .navbar-start .navbar-link::after,.navbar.is-black .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:#363636}.navbar.is-light .navbar-brand>.navbar-item,.navbar.is-light .navbar-brand .navbar-link{color:#363636}.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand .navbar-link.is-active{background-color:#e8e8e8;color:#363636}.navbar.is-light .navbar-brand .navbar-link::after{border-color:#363636}.navbar.is-light .navbar-burger{color:#363636}@media screen and (min-width: 1024px){.navbar.is-light .navbar-start>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-end .navbar-link{color:#363636}.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end .navbar-link.is-active{background-color:#e8e8e8;color:#363636}.navbar.is-light .navbar-start .navbar-link::after,.navbar.is-light .navbar-end .navbar-link::after{border-color:#363636}.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link{background-color:#e8e8e8;color:#363636}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#363636}}.navbar.is-dark{background-color:#363636;color:#f5f5f5}.navbar.is-dark .navbar-brand>.navbar-item,.navbar.is-dark .navbar-brand .navbar-link{color:#f5f5f5}.navbar.is-dark .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand .navbar-link.is-active{background-color:#292929;color:#f5f5f5}.navbar.is-dark .navbar-brand .navbar-link::after{border-color:#f5f5f5}.navbar.is-dark .navbar-burger{color:#f5f5f5}@media screen and (min-width: 1024px){.navbar.is-dark .navbar-start>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.navbar.is-dark .navbar-end .navbar-link{color:#f5f5f5}.navbar.is-dark .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end .navbar-link.is-active{background-color:#292929;color:#f5f5f5}.navbar.is-dark .navbar-start .navbar-link::after,.navbar.is-dark .navbar-end .navbar-link::after{border-color:#f5f5f5}.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link{background-color:#292929;color:#f5f5f5}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#f5f5f5}}.navbar.is-primary{background-color:#00d1b2;color:#fff}.navbar.is-primary .navbar-brand>.navbar-item,.navbar.is-primary .navbar-brand .navbar-link{color:#fff}.navbar.is-primary .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand .navbar-link.is-active{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-primary .navbar-start>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.navbar.is-primary .navbar-end .navbar-link{color:#fff}.navbar.is-primary .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end .navbar-link.is-active{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-start .navbar-link::after,.navbar.is-primary .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active{background-color:#00d1b2;color:#fff}}.navbar.is-link{background-color:#3273dc;color:#fff}.navbar.is-link .navbar-brand>.navbar-item,.navbar.is-link .navbar-brand .navbar-link{color:#fff}.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand .navbar-link.is-active{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-link .navbar-start>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-end .navbar-link{color:#fff}.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end .navbar-link.is-active{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-start .navbar-link::after,.navbar.is-link .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link{background-color:#2366d1;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#3273dc;color:#fff}}.navbar.is-info{background-color:#209cee;color:#fff}.navbar.is-info .navbar-brand>.navbar-item,.navbar.is-info .navbar-brand .navbar-link{color:#fff}.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand .navbar-link.is-active{background-color:#118fe4;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-info .navbar-start>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-end .navbar-link{color:#fff}.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end .navbar-link.is-active{background-color:#118fe4;color:#fff}.navbar.is-info .navbar-start .navbar-link::after,.navbar.is-info .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link{background-color:#118fe4;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#209cee;color:#fff}}.navbar.is-success{background-color:#23d160;color:#fff}.navbar.is-success .navbar-brand>.navbar-item,.navbar.is-success .navbar-brand .navbar-link{color:#fff}.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand .navbar-link.is-active{background-color:#20bc56;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-success .navbar-start>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-end .navbar-link{color:#fff}.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end .navbar-link.is-active{background-color:#20bc56;color:#fff}.navbar.is-success .navbar-start .navbar-link::after,.navbar.is-success .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link{background-color:#20bc56;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#23d160;color:#fff}}.navbar.is-warning{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-brand>.navbar-item,.navbar.is-warning .navbar-brand .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand .navbar-link.is-active{background-color:#ffd83d;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,0.7)}@media screen and (min-width: 1024px){.navbar.is-warning .navbar-start>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-end .navbar-link{color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end .navbar-link.is-active{background-color:#ffd83d;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-start .navbar-link::after,.navbar.is-warning .navbar-end .navbar-link::after{border-color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ffd83d;color:rgba(0,0,0,0.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffdd57;color:rgba(0,0,0,0.7)}}.navbar.is-danger{background-color:#ff3860;color:#fff}.navbar.is-danger .navbar-brand>.navbar-item,.navbar.is-danger .navbar-brand .navbar-link{color:#fff}.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand .navbar-link.is-active{background-color:#ff1f4b;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width: 1024px){.navbar.is-danger .navbar-start>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-end .navbar-link{color:#fff}.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end .navbar-link.is-active{background-color:#ff1f4b;color:#fff}.navbar.is-danger .navbar-start .navbar-link::after,.navbar.is-danger .navbar-end .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link,.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link{background-color:#ff1f4b;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#ff3860;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}html.has-navbar-fixed-top,body.has-navbar-fixed-top{padding-top:3.25rem}html.has-navbar-fixed-bottom,body.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#4a4a4a;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;-webkit-transform-origin:center;transform-origin:center;transition-duration:86ms;transition-property:background-color, opacity, -webkit-transform;transition-property:background-color, opacity, transform;transition-property:background-color, opacity, transform, -webkit-transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,0.05)}.navbar-burger.is-active span:nth-child(1){-webkit-transform:translateY(5px) rotate(45deg);transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){-webkit-transform:translateY(-5px) rotate(-45deg);transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#4a4a4a;display:block;line-height:1.5;padding:0.5rem 0.75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-0.25rem;margin-right:-0.25rem}a.navbar-item,.navbar-link{cursor:pointer}a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover,a.navbar-item.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,.navbar-link.is-active{background-color:#fafafa;color:#3273dc}.navbar-item{display:block;flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(0.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:rgba(0,0,0,0);border-bottom-color:#3273dc}.navbar-item.is-tab.is-active{background-color:rgba(0,0,0,0);border-bottom-color:#3273dc;border-bottom-style:solid;border-bottom-width:3px;color:#3273dc;padding-bottom:calc(0.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#3273dc;margin-top:-0.375em;right:1.125em}.navbar-dropdown{font-size:0.875rem;padding-bottom:0.5rem;padding-top:0.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:0.5rem 0}@media screen and (max-width: 1023px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,0.1);padding:0.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}html.has-navbar-fixed-top-touch,body.has-navbar-fixed-top-touch{padding-top:3.25rem}html.has-navbar-fixed-bottom-touch,body.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width: 1024px){.navbar,.navbar-menu,.navbar-start,.navbar-end{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-start,.navbar.is-spaced .navbar-end{align-items:center}.navbar.is-spaced a.navbar-item,.navbar.is-spaced .navbar-link{border-radius:4px}.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent .navbar-link.is-active{background-color:transparent !important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent !important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#3273dc}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item{display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{-webkit-transform:rotate(135deg) translate(0.25em, -0.25em);transform:rotate(135deg) translate(0.25em, -0.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,0.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed{opacity:1;pointer-events:auto;-webkit-transform:translateY(0);transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,0.1);display:none;font-size:0.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:0.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#3273dc}.navbar.is-spaced .navbar-dropdown,.navbar-dropdown.is-boxed{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,0.1),0 0 0 1px rgba(10,10,10,0.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));-webkit-transform:translateY(-5px);transform:translateY(-5px);transition-duration:86ms;transition-property:opacity, -webkit-transform;transition-property:opacity, transform;transition-property:opacity, transform, -webkit-transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.navbar>.container .navbar-brand,.container>.navbar .navbar-brand{margin-left:-.75rem}.navbar>.container .navbar-menu,.container>.navbar .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,0.1)}.navbar.is-fixed-top-desktop{top:0}html.has-navbar-fixed-top-desktop,body.has-navbar-fixed-top-desktop{padding-top:3.25rem}html.has-navbar-fixed-bottom-desktop,body.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}html.has-spaced-navbar-fixed-top,body.has-spaced-navbar-fixed-top{padding-top:5.25rem}html.has-spaced-navbar-fixed-bottom,body.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}a.navbar-item.is-active,.navbar-link.is-active{color:#0a0a0a}a.navbar-item.is-active:not(:focus):not(:hover),.navbar-link.is-active:not(:focus):not(:hover){background-color:rgba(0,0,0,0)}.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link,.navbar-item.has-dropdown.is-active .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-previous,.pagination.is-rounded .pagination-next{padding-left:1em;padding-right:1em;border-radius:290486px}.pagination.is-rounded .pagination-link{border-radius:290486px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-previous,.pagination-next,.pagination-link,.pagination-ellipsis{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-previous,.pagination-next,.pagination-link{border-color:#dbdbdb;color:#363636;min-width:2.25em}.pagination-previous:hover,.pagination-next:hover,.pagination-link:hover{border-color:#b5b5b5;color:#363636}.pagination-previous:focus,.pagination-next:focus,.pagination-link:focus{border-color:#3273dc}.pagination-previous:active,.pagination-next:active,.pagination-link:active{box-shadow:inset 0 1px 2px rgba(10,10,10,0.2)}.pagination-previous[disabled],.pagination-next[disabled],.pagination-link[disabled]{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#7a7a7a;opacity:0.5}.pagination-previous,.pagination-next{padding-left:0.75em;padding-right:0.75em;white-space:nowrap}.pagination-link.is-current{background-color:#3273dc;border-color:#3273dc;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}@media screen and (max-width: 768px){.pagination{flex-wrap:wrap}.pagination-previous,.pagination-next{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width: 769px), print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel-heading,.panel-tabs,.panel-block{border-bottom:1px solid #dbdbdb;border-left:1px solid #dbdbdb;border-right:1px solid #dbdbdb}.panel-heading:first-child,.panel-tabs:first-child,.panel-block:first-child{border-top:1px solid #dbdbdb}.panel-heading{background-color:#f5f5f5;border-radius:4px 4px 0 0;color:#363636;font-size:1.25em;font-weight:300;line-height:1.25;padding:0.5em 0.75em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:0.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#4a4a4a}.panel-list a:hover{color:#3273dc}.panel-block{align-items:center;color:#363636;display:flex;justify-content:flex-start;padding:0.5em 0.75em}.panel-block input[type="checkbox"]{margin-right:0.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#3273dc;color:#363636}.panel-block.is-active .panel-icon{color:#3273dc}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#7a7a7a;margin-right:0.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#4a4a4a;display:flex;justify-content:center;margin-bottom:-1px;padding:0.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#363636;color:#363636}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#3273dc;color:#3273dc}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:0.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:0.75em;padding-right:0.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:0.75em}.tabs .icon:first-child{margin-right:0.5em}.tabs .icon:last-child{margin-left:0.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:rgba(0,0,0,0) !important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-radius:4px 0 0 4px}.tabs.is-toggle li:last-child a{border-radius:0 4px 4px 0}.tabs.is-toggle li.is-active a{background-color:#3273dc;border-color:#3273dc;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:290486px;border-top-left-radius:290486px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:290486px;border-top-right-radius:290486px;padding-right:1.25em}.tabs.is-small{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0%}.columns.is-mobile>.column.is-1{flex:none;width:8.33333%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333%}.columns.is-mobile>.column.is-2{flex:none;width:16.66667%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66667%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333%}.columns.is-mobile>.column.is-5{flex:none;width:41.66667%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66667%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333%}.columns.is-mobile>.column.is-8{flex:none;width:66.66667%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66667%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333%}.columns.is-mobile>.column.is-11{flex:none;width:91.66667%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66667%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width: 768px){.column.is-narrow-mobile{flex:none}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0%}.column.is-1-mobile{flex:none;width:8.33333%}.column.is-offset-1-mobile{margin-left:8.33333%}.column.is-2-mobile{flex:none;width:16.66667%}.column.is-offset-2-mobile{margin-left:16.66667%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333%}.column.is-offset-4-mobile{margin-left:33.33333%}.column.is-5-mobile{flex:none;width:41.66667%}.column.is-offset-5-mobile{margin-left:41.66667%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333%}.column.is-offset-7-mobile{margin-left:58.33333%}.column.is-8-mobile{flex:none;width:66.66667%}.column.is-offset-8-mobile{margin-left:66.66667%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333%}.column.is-offset-10-mobile{margin-left:83.33333%}.column.is-11-mobile{flex:none;width:91.66667%}.column.is-offset-11-mobile{margin-left:91.66667%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width: 769px), print{.column.is-narrow,.column.is-narrow-tablet{flex:none}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0%}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66667%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66667%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66667%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66667%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66667%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66667%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66667%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66667%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width: 1023px){.column.is-narrow-touch{flex:none}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0%}.column.is-1-touch{flex:none;width:8.33333%}.column.is-offset-1-touch{margin-left:8.33333%}.column.is-2-touch{flex:none;width:16.66667%}.column.is-offset-2-touch{margin-left:16.66667%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333%}.column.is-offset-4-touch{margin-left:33.33333%}.column.is-5-touch{flex:none;width:41.66667%}.column.is-offset-5-touch{margin-left:41.66667%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333%}.column.is-offset-7-touch{margin-left:58.33333%}.column.is-8-touch{flex:none;width:66.66667%}.column.is-offset-8-touch{margin-left:66.66667%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333%}.column.is-offset-10-touch{margin-left:83.33333%}.column.is-11-touch{flex:none;width:91.66667%}.column.is-offset-11-touch{margin-left:91.66667%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width: 1024px){.column.is-narrow-desktop{flex:none}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0%}.column.is-1-desktop{flex:none;width:8.33333%}.column.is-offset-1-desktop{margin-left:8.33333%}.column.is-2-desktop{flex:none;width:16.66667%}.column.is-offset-2-desktop{margin-left:16.66667%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333%}.column.is-offset-4-desktop{margin-left:33.33333%}.column.is-5-desktop{flex:none;width:41.66667%}.column.is-offset-5-desktop{margin-left:41.66667%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333%}.column.is-offset-7-desktop{margin-left:58.33333%}.column.is-8-desktop{flex:none;width:66.66667%}.column.is-offset-8-desktop{margin-left:66.66667%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333%}.column.is-offset-10-desktop{margin-left:83.33333%}.column.is-11-desktop{flex:none;width:91.66667%}.column.is-offset-11-desktop{margin-left:91.66667%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width: 1216px){.column.is-narrow-widescreen{flex:none}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0%}.column.is-1-widescreen{flex:none;width:8.33333%}.column.is-offset-1-widescreen{margin-left:8.33333%}.column.is-2-widescreen{flex:none;width:16.66667%}.column.is-offset-2-widescreen{margin-left:16.66667%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333%}.column.is-offset-4-widescreen{margin-left:33.33333%}.column.is-5-widescreen{flex:none;width:41.66667%}.column.is-offset-5-widescreen{margin-left:41.66667%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333%}.column.is-offset-7-widescreen{margin-left:58.33333%}.column.is-8-widescreen{flex:none;width:66.66667%}.column.is-offset-8-widescreen{margin-left:66.66667%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333%}.column.is-offset-10-widescreen{margin-left:83.33333%}.column.is-11-widescreen{flex:none;width:91.66667%}.column.is-offset-11-widescreen{margin-left:91.66667%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width: 1408px){.column.is-narrow-fullhd{flex:none}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0%}.column.is-1-fullhd{flex:none;width:8.33333%}.column.is-offset-1-fullhd{margin-left:8.33333%}.column.is-2-fullhd{flex:none;width:16.66667%}.column.is-offset-2-fullhd{margin-left:16.66667%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333%}.column.is-offset-4-fullhd{margin-left:33.33333%}.column.is-5-fullhd{flex:none;width:41.66667%}.column.is-offset-5-fullhd{margin-left:41.66667%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333%}.column.is-offset-7-fullhd{margin-left:58.33333%}.column.is-8-fullhd{flex:none;width:66.66667%}.column.is-offset-8-fullhd{margin-left:66.66667%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333%}.column.is-offset-10-fullhd{margin-left:83.33333%}.column.is-11-fullhd{flex:none;width:91.66667%}.column.is-offset-11-fullhd{margin-left:91.66667%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0 !important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width: 769px), print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width: 1024px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap: 0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable .column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap: 0rem}@media screen and (max-width: 768px){.columns.is-variable.is-0-mobile{--columnGap: 0rem}}@media screen and (min-width: 769px), print{.columns.is-variable.is-0-tablet{--columnGap: 0rem}}@media screen and (min-width: 769px) and (max-width: 1023px){.columns.is-variable.is-0-tablet-only{--columnGap: 0rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-0-touch{--columnGap: 0rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-0-desktop{--columnGap: 0rem}}@media screen and (min-width: 1024px) and (max-width: 1215px){.columns.is-variable.is-0-desktop-only{--columnGap: 0rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-0-widescreen{--columnGap: 0rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-0-widescreen-only{--columnGap: 0rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-0-fullhd{--columnGap: 0rem}}.columns.is-variable.is-1{--columnGap: .25rem}@media screen and (max-width: 768px){.columns.is-variable.is-1-mobile{--columnGap: .25rem}}@media screen and (min-width: 769px), print{.columns.is-variable.is-1-tablet{--columnGap: .25rem}}@media screen and (min-width: 769px) and (max-width: 1023px){.columns.is-variable.is-1-tablet-only{--columnGap: .25rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-1-touch{--columnGap: .25rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-1-desktop{--columnGap: .25rem}}@media screen and (min-width: 1024px) and (max-width: 1215px){.columns.is-variable.is-1-desktop-only{--columnGap: .25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-1-widescreen{--columnGap: .25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-1-widescreen-only{--columnGap: .25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-1-fullhd{--columnGap: .25rem}}.columns.is-variable.is-2{--columnGap: .5rem}@media screen and (max-width: 768px){.columns.is-variable.is-2-mobile{--columnGap: .5rem}}@media screen and (min-width: 769px), print{.columns.is-variable.is-2-tablet{--columnGap: .5rem}}@media screen and (min-width: 769px) and (max-width: 1023px){.columns.is-variable.is-2-tablet-only{--columnGap: .5rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-2-touch{--columnGap: .5rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-2-desktop{--columnGap: .5rem}}@media screen and (min-width: 1024px) and (max-width: 1215px){.columns.is-variable.is-2-desktop-only{--columnGap: .5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-2-widescreen{--columnGap: .5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-2-widescreen-only{--columnGap: .5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-2-fullhd{--columnGap: .5rem}}.columns.is-variable.is-3{--columnGap: .75rem}@media screen and (max-width: 768px){.columns.is-variable.is-3-mobile{--columnGap: .75rem}}@media screen and (min-width: 769px), print{.columns.is-variable.is-3-tablet{--columnGap: .75rem}}@media screen and (min-width: 769px) and (max-width: 1023px){.columns.is-variable.is-3-tablet-only{--columnGap: .75rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-3-touch{--columnGap: .75rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-3-desktop{--columnGap: .75rem}}@media screen and (min-width: 1024px) and (max-width: 1215px){.columns.is-variable.is-3-desktop-only{--columnGap: .75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-3-widescreen{--columnGap: .75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-3-widescreen-only{--columnGap: .75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-3-fullhd{--columnGap: .75rem}}.columns.is-variable.is-4{--columnGap: 1rem}@media screen and (max-width: 768px){.columns.is-variable.is-4-mobile{--columnGap: 1rem}}@media screen and (min-width: 769px), print{.columns.is-variable.is-4-tablet{--columnGap: 1rem}}@media screen and (min-width: 769px) and (max-width: 1023px){.columns.is-variable.is-4-tablet-only{--columnGap: 1rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-4-touch{--columnGap: 1rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-4-desktop{--columnGap: 1rem}}@media screen and (min-width: 1024px) and (max-width: 1215px){.columns.is-variable.is-4-desktop-only{--columnGap: 1rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-4-widescreen{--columnGap: 1rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-4-widescreen-only{--columnGap: 1rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-4-fullhd{--columnGap: 1rem}}.columns.is-variable.is-5{--columnGap: 1.25rem}@media screen and (max-width: 768px){.columns.is-variable.is-5-mobile{--columnGap: 1.25rem}}@media screen and (min-width: 769px), print{.columns.is-variable.is-5-tablet{--columnGap: 1.25rem}}@media screen and (min-width: 769px) and (max-width: 1023px){.columns.is-variable.is-5-tablet-only{--columnGap: 1.25rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-5-touch{--columnGap: 1.25rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-5-desktop{--columnGap: 1.25rem}}@media screen and (min-width: 1024px) and (max-width: 1215px){.columns.is-variable.is-5-desktop-only{--columnGap: 1.25rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-5-widescreen{--columnGap: 1.25rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-5-widescreen-only{--columnGap: 1.25rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-5-fullhd{--columnGap: 1.25rem}}.columns.is-variable.is-6{--columnGap: 1.5rem}@media screen and (max-width: 768px){.columns.is-variable.is-6-mobile{--columnGap: 1.5rem}}@media screen and (min-width: 769px), print{.columns.is-variable.is-6-tablet{--columnGap: 1.5rem}}@media screen and (min-width: 769px) and (max-width: 1023px){.columns.is-variable.is-6-tablet-only{--columnGap: 1.5rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-6-touch{--columnGap: 1.5rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-6-desktop{--columnGap: 1.5rem}}@media screen and (min-width: 1024px) and (max-width: 1215px){.columns.is-variable.is-6-desktop-only{--columnGap: 1.5rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-6-widescreen{--columnGap: 1.5rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-6-widescreen-only{--columnGap: 1.5rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-6-fullhd{--columnGap: 1.5rem}}.columns.is-variable.is-7{--columnGap: 1.75rem}@media screen and (max-width: 768px){.columns.is-variable.is-7-mobile{--columnGap: 1.75rem}}@media screen and (min-width: 769px), print{.columns.is-variable.is-7-tablet{--columnGap: 1.75rem}}@media screen and (min-width: 769px) and (max-width: 1023px){.columns.is-variable.is-7-tablet-only{--columnGap: 1.75rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-7-touch{--columnGap: 1.75rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-7-desktop{--columnGap: 1.75rem}}@media screen and (min-width: 1024px) and (max-width: 1215px){.columns.is-variable.is-7-desktop-only{--columnGap: 1.75rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-7-widescreen{--columnGap: 1.75rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-7-widescreen-only{--columnGap: 1.75rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-7-fullhd{--columnGap: 1.75rem}}.columns.is-variable.is-8{--columnGap: 2rem}@media screen and (max-width: 768px){.columns.is-variable.is-8-mobile{--columnGap: 2rem}}@media screen and (min-width: 769px), print{.columns.is-variable.is-8-tablet{--columnGap: 2rem}}@media screen and (min-width: 769px) and (max-width: 1023px){.columns.is-variable.is-8-tablet-only{--columnGap: 2rem}}@media screen and (max-width: 1023px){.columns.is-variable.is-8-touch{--columnGap: 2rem}}@media screen and (min-width: 1024px){.columns.is-variable.is-8-desktop{--columnGap: 2rem}}@media screen and (min-width: 1024px) and (max-width: 1215px){.columns.is-variable.is-8-desktop-only{--columnGap: 2rem}}@media screen and (min-width: 1216px){.columns.is-variable.is-8-widescreen{--columnGap: 2rem}}@media screen and (min-width: 1216px) and (max-width: 1407px){.columns.is-variable.is-8-widescreen-only{--columnGap: 2rem}}@media screen and (min-width: 1408px){.columns.is-variable.is-8-fullhd{--columnGap: 2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:-webkit-min-content;min-height:-moz-min-content;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0 !important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem !important}@media screen and (min-width: 769px), print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333%}.tile.is-2{flex:none;width:16.66667%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333%}.tile.is-5{flex:none;width:41.66667%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333%}.tile.is-8{flex:none;width:66.66667%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333%}.tile.is-11{flex:none;width:91.66667%}.tile.is-12{flex:none;width:100%}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:none}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,0.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width: 1023px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,0.7)}.hero.is-white a.navbar-item:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white .navbar-link:hover,.hero.is-white .navbar-link.is-active{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:0.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg, #e6e6e6 0%, #fff 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg, #e6e6e6 0%, #fff 71%, #fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,0.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-black a.navbar-item:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black .navbar-link:hover,.hero.is-black .navbar-link.is-active{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:0.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}@media screen and (max-width: 768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg, #000 0%, #0a0a0a 71%, #181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:#363636}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:#363636}.hero.is-light .subtitle{color:rgba(54,54,54,0.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:#363636}@media screen and (max-width: 1023px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(54,54,54,0.7)}.hero.is-light a.navbar-item:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light .navbar-link:hover,.hero.is-light .navbar-link.is-active{background-color:#e8e8e8;color:#363636}.hero.is-light .tabs a{color:#363636;opacity:0.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:#363636}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:#363636;border-color:#363636;color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}@media screen and (max-width: 768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg, #dfd8d9 0%, #f5f5f5 71%, #fff 100%)}}.hero.is-dark{background-color:#363636;color:#f5f5f5}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong{color:inherit}.hero.is-dark .title{color:#f5f5f5}.hero.is-dark .subtitle{color:rgba(245,245,245,0.9)}.hero.is-dark .subtitle a:not(.button),.hero.is-dark .subtitle strong{color:#f5f5f5}@media screen and (max-width: 1023px){.hero.is-dark .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.hero.is-dark .navbar-link{color:rgba(245,245,245,0.7)}.hero.is-dark a.navbar-item:hover,.hero.is-dark a.navbar-item.is-active,.hero.is-dark .navbar-link:hover,.hero.is-dark .navbar-link.is-active{background-color:#292929;color:#f5f5f5}.hero.is-dark .tabs a{color:#f5f5f5;opacity:0.9}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#f5f5f5}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#f5f5f5;border-color:#f5f5f5;color:#363636}.hero.is-dark.is-bold{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}@media screen and (max-width: 768px){.hero.is-dark.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1f191a 0%, #363636 71%, #46403f 100%)}}.hero.is-primary{background-color:#00d1b2;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong{color:inherit}.hero.is-primary .title{color:#fff}.hero.is-primary .subtitle{color:rgba(255,255,255,0.9)}.hero.is-primary .subtitle a:not(.button),.hero.is-primary .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-primary .navbar-menu{background-color:#00d1b2}}.hero.is-primary .navbar-item,.hero.is-primary .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-primary a.navbar-item:hover,.hero.is-primary a.navbar-item.is-active,.hero.is-primary .navbar-link:hover,.hero.is-primary .navbar-link.is-active{background-color:#00b89c;color:#fff}.hero.is-primary .tabs a{color:#fff;opacity:0.9}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#00d1b2}.hero.is-primary.is-bold{background-image:linear-gradient(141deg, #009e6c 0%, #00d1b2 71%, #00e7eb 100%)}@media screen and (max-width: 768px){.hero.is-primary.is-bold .navbar-menu{background-image:linear-gradient(141deg, #009e6c 0%, #00d1b2 71%, #00e7eb 100%)}}.hero.is-link{background-color:#3273dc;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,0.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-link .navbar-menu{background-color:#3273dc}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-link a.navbar-item:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link .navbar-link:hover,.hero.is-link .navbar-link.is-active{background-color:#2366d1;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:0.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3273dc}.hero.is-link.is-bold{background-image:linear-gradient(141deg, #1577c6 0%, #3273dc 71%, #4366e5 100%)}@media screen and (max-width: 768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg, #1577c6 0%, #3273dc 71%, #4366e5 100%)}}.hero.is-info{background-color:#209cee;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,0.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-info .navbar-menu{background-color:#209cee}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-info a.navbar-item:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info .navbar-link:hover,.hero.is-info .navbar-link.is-active{background-color:#118fe4;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:0.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#209cee}.hero.is-info.is-bold{background-image:linear-gradient(141deg, #04a6d7 0%, #209cee 71%, #3287f5 100%)}@media screen and (max-width: 768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg, #04a6d7 0%, #209cee 71%, #3287f5 100%)}}.hero.is-success{background-color:#23d160;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,0.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-success .navbar-menu{background-color:#23d160}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-success a.navbar-item:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success .navbar-link:hover,.hero.is-success .navbar-link.is-active{background-color:#20bc56;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:0.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#23d160}.hero.is-success.is-bold{background-image:linear-gradient(141deg, #12af2f 0%, #23d160 71%, #2ce28a 100%)}@media screen and (max-width: 768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg, #12af2f 0%, #23d160 71%, #2ce28a 100%)}}.hero.is-warning{background-color:#ffdd57;color:rgba(0,0,0,0.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,0.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,0.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,0.7)}@media screen and (max-width: 1023px){.hero.is-warning .navbar-menu{background-color:#ffdd57}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,0.7)}.hero.is-warning a.navbar-item:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning .navbar-link.is-active{background-color:#ffd83d;color:rgba(0,0,0,0.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,0.7);opacity:0.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,0.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,0.7);border-color:rgba(0,0,0,0.7);color:#ffdd57}.hero.is-warning.is-bold{background-image:linear-gradient(141deg, #ffaf24 0%, #ffdd57 71%, #fffa70 100%)}@media screen and (max-width: 768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ffaf24 0%, #ffdd57 71%, #fffa70 100%)}}.hero.is-danger{background-color:#ff3860;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,0.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width: 1023px){.hero.is-danger .navbar-menu{background-color:#ff3860}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,0.7)}.hero.is-danger a.navbar-item:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger .navbar-link.is-active{background-color:#ff1f4b;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:0.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,0.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#ff3860}.hero.is-danger.is-bold{background-image:linear-gradient(141deg, #ff0561 0%, #ff3860 71%, #ff5257 100%)}@media screen and (max-width: 768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg, #ff0561 0%, #ff3860 71%, #ff5257 100%)}}.hero.is-small .hero-body{padding-bottom:1.5rem;padding-top:1.5rem}@media screen and (min-width: 769px), print{.hero.is-medium .hero-body{padding-bottom:9rem;padding-top:9rem}}@media screen and (min-width: 769px), print{.hero.is-large .hero-body{padding-bottom:18rem;padding-top:18rem}}.hero.is-halfheight .hero-body,.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body{align-items:center;display:flex}.hero.is-halfheight .hero-body>.container,.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;-webkit-transform:translate3d(-50%, -50%, 0);transform:translate3d(-50%, -50%, 0)}.hero-video.is-transparent{opacity:0.3}@media screen and (max-width: 768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width: 768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:0.75rem}}@media screen and (min-width: 769px), print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-head,.hero-foot{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}.section{padding:3rem 1.5rem}@media screen and (min-width: 1024px){.section.is-medium{padding:9rem 1.5rem}.section.is-large{padding:18rem 1.5rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem}html{height:100%;width:100%}body{font-family:"TT Norms Medium",sans-serif;font-size:15px;position:relative;height:100%;width:100%;overflow-x:hidden}body.small-header .top-nav{height:75px}body.small-header #main{padding-top:75px}.top-nav{height:120px}@media screen and (max-width: 1023px){.top-nav{height:75px}}#main{height:100%;display:flex;flex-direction:row;background:#F8F8F9;padding-top:120px;overflow-x:hidden;width:100%}@media screen and (max-width: 1023px){#main{padding-top:75px}}#main>.sidebar{padding:40px 30px;flex-grow:0;flex-shrink:0;width:240px;border-right:1px solid #EAEAF1;height:100%;overflow:auto}#main>.sidebar.tutorials{width:320px}#main>.core{padding:28px;height:100%;overflow:auto;flex-grow:1}@media screen and (max-width: 768px){#main>.core{padding:0px}}#main>.core>.content{background:#fff;padding:40px;border-radius:4px;box-shadow:0 0 40px 0 rgba(115,134,160,0.24)}#main>.side-nav{width:240px;padding:40px 20px;flex-grow:0;flex-shrink:0;height:100%;border-left:1px solid #EAEAF1;overflow:auto}.content{margin-bottom:50px}.content blockquote{margin:30px 0 !important}.content .signature-attributes{margin-left:8px;margin-right:3px;font-style:italic}.content header.page-title p{font-size:13px;margin:0 0 5px;text-transform:uppercase}.content header p{font-size:20px}.content h1,.content header.page-title h1{font-family:"TT Norms Medium",sans-serif;font-size:47px;font-weight:bold;margin:8px 0}.content h2{font-size:26px;line-height:48px;font-weight:bold;margin-bottom:26px}.content h3,.content h4,.content h5,.content h6{font-family:"TT Norms Medium",sans-serif;font-weight:900;letter-spacing:0}.content code{color:#101010;font-family:"Inconsolata",monospace}.content .container-overview .prettyprint:last-child{margin-bottom:50px}.content .vertical-section{padding:16px 0}@media screen and (max-width: 1023px){#main-content-wrapper{padding:0 30px}}body.landing>.top-nav{box-shadow:none;transition:margin-top 0.3s;color:#fff;background:#4268F6}body.landing>.top-nav.hidden{transition:margin-top 0.3s;margin-top:-130px}@media screen and (max-width: 1023px){body.landing>.top-nav.hidden{margin-top:-85px}}body.landing>.top-nav.sticky{box-shadow:0 0 20px 0 rgba(0,0,255,0.5)}body.landing>.top-nav .inner{margin:0 auto;max-width:1226px}body.landing>.top-nav a.button{color:#fff;border-color:#fff;background:transparent}body.landing>.top-nav a.button:hover{background:#fff;border-color:#fff;color:#192035}body.landing>.top-nav .menu .navigation a.link{color:#fff}body.landing>.top-nav .menu .navigation a.link:hover{border-color:#fff}body.landing>.top-nav .image img{content:url("../images/logo.svg")}body.landing>.top-nav #hamburger{display:none}body.landing #main{display:block;height:auto}body.landing .main-hero{background:#4268F6;color:#fff;padding:300px 40% 160px;border-bottom-left-radius:50%;border-bottom-right-radius:50%;margin:-250px -30% 0;text-align:center}body.landing .main-hero .action-buttons{margin:60px 0;vertical-align:middle}body.landing .main-hero .action-buttons span{color:#fff}body.landing .main-hero h3{font-size:24px;line-height:65px;font-weight:lighter}body.landing .main-hero h1{font-size:52px;line-height:65px;font-weight:lighter;max-width:900px;margin-left:auto;margin-right:auto}body.landing .main-hero strong{color:#fff}body.landing .gif-box{margin-top:-140px;text-align:center}body.landing .grey-logos{text-align:center;margin-bottom:50px}body.landing .grey-logos .column{display:flex;align-items:center;justify-content:center}body.landing .white-oval{background:#fff;padding:110px 500px 100px;margin:0 -500px;text-align:center;border-bottom-left-radius:50%;border-bottom-right-radius:50%}body.landing h2{font-weight:bold;font-size:36px;line-height:48px;color:#101010;margin-bottom:15px}body.landing h2+p{color:#767676;font-size:16px}body.landing h4{font-weight:bold;font-size:24px;line-height:32px}body.landing .header-message{margin-bottom:80px}body.landing .todo-actions{text-align:left;padding:100px 0 100px 100px}@media screen and (max-width: 1215px){body.landing .todo-actions{padding-top:20px}}body.landing .todo-actions h4{margin-bottom:60px;position:relative}body.landing .todo-actions h4:before{content:'';position:absolute;left:-50px;top:0;height:30px;width:30px;background:url("../images/check.svg") no-repeat 50% 50%}body.landing .action-buttons span{line-height:36px;margin:0 10px;color:#4268F6}body.landing .credentials{text-align:center;padding:100px 0;background:url("../images/map.svg") no-repeat 50% 50%}body.landing .credentials .fa-youtube{color:#FF0000}body.landing .credentials .fa-reddit{color:#FF4500}body.landing .credentials .fa-github{color:#101010}body.landing .credentials .columns{margin-bottom:20px}body.landing .credentials .column{display:flex;flex-direction:column}body.landing .credentials .box{flex-direction:column;padding:30px;height:100%;box-shadow:0px 0px 40px rgba(115,134,160,0.25);display:flex;justify-content:center}body.landing .credentials .box:hover{box-shadow:0px 0px 40px rgba(115,134,160,0.5)}body.landing .credentials .box h5{align-self:center;font-size:22px;line-height:26px;margin-bottom:30px}body.landing .credentials .box span{font-size:11px}body.landing .stat-box{padding:40px 65px;box-shadow:0px 0px 40px rgba(115,134,160,0.25)}@media screen and (max-width: 768px){body.landing .stat-box .column:first-child{padding-bottom:40px}}body.landing .stat-box .fa-github{color:#101010}body.landing .stat-box h2{margin:0 0 60px}body.landing .stat-box h4{font-size:32px;font-weight:bolder;margin-top:15px;color:#101010}body.landing .stat-box h4 strong{color:#101010}body.landing .stat-box .level{border-bottom:#4C73F7 3px solid;margin-bottom:-3px}body.landing .stat-box .level img{position:relative;bottom:-3px}body.landing .stat-box .action-buttons{margin:50px 0 0}body.landing .feature-docs{margin-top:-200px;padding-top:300px}@media screen and (min-width: 1216px){body.landing .feature-docs .container .columns.is-multiline{margin:0 8.333%}}body.landing .feature-docs .columns.is-multiline .column{display:flex}body.landing .feature-docs .box{color:#101010}body.landing .feature-docs .box:hover{box-shadow:4px 8px 12px rgba(115,134,160,0.25)}body.landing .feature-docs .box img{margin:-10px 0}body.landing .feature-docs .box h4{line-height:36px;font-size:26px}body.landing .feature-docs .box p{font-size:20px;line-height:26px;margin:35px 0}body.landing .feature-docs .action-buttons{margin:100px 0 50px}body.landing .feature-side-blocks .bg-crud{background:url("../images/bg-crud.png") no-repeat 100% 50%}@media screen and (max-width: 1215px){body.landing .feature-side-blocks .bg-crud{background-position-x:150%}}@media screen and (max-width: 1023px){body.landing .feature-side-blocks .bg-crud{background:none;text-align:center}}body.landing .feature-side-blocks .bg-filter{background:url("../images/bg-filter.png") no-repeat 0% 50%}@media screen and (max-width: 1215px){body.landing .feature-side-blocks .bg-filter{background-position-x:-200px}}@media screen and (max-width: 1023px){body.landing .feature-side-blocks .bg-filter{background:none;text-align:center}}body.landing .feature-side-blocks .column{justify-content:center;display:flex;flex-direction:column}@media screen and (min-width: 1024px){body.landing .feature-side-blocks .column{height:700px}}body.landing .feature-side-blocks .container{margin-top:50px;margin-bottom:50px}body.landing .feature-side-blocks .action-buttons{margin:30px 0}body.landing .support-block{padding:80px 0 350px;background:#fff;margin-bottom:-200px}body.landing .support-block .column{display:flex;flex-direction:column}body.landing .support-block .column .box{flex-grow:1}body.landing .support-block .img{text-align:center;border-bottom:1px solid #D8D8D8;margin:0 -20px 20px}body.landing .support-block h4{font-weight:bolder;font-size:26px;line-height:48px}body.landing .support-block .text{padding:0 10px 20px}body.landing .support-block .form{background:#4268F6;padding:40px;border-radius:10px}body.landing .button.is-success{width:170px;height:50px}body.landing .form{color:#fff}body.landing .form .success-msg{display:none}body.landing .form .success-msg img{width:150px;margin:100px 0}body.landing .form.completed .success-msg{display:block}body.landing .form.completed .form-fields{display:none}body.landing .form h2{color:#fff;margin-bottom:30px}body.landing .form .label{font-size:20px}body.landing .form .field{margin-bottom:40px;color:#fff}body.landing .form .field label{color:#fff;font-weight:bold}body.landing .form .field input{border-radius:4px;height:54px}body.landing .form .checkbox{display:block;padding:8px 0;font-size:16px}body.landing .form .checkbox:hover{color:#fff}body.landing .form .checkbox input{margin-right:5px}body.landing .form .interested{padding:0 0 20px}body.landing .form .interested .label{color:#fff}body.landing .form textarea{height:80px}body.landing .form .notice{font-size:14px;font-weight:lighter;padding:10px 30px}body.landing .curved-footer{background:#4268F6;padding:110px 500px;margin:0 -500px;border-top-left-radius:50%;border-top-right-radius:50%;color:#fff}body.landing .curved-footer .the-part{position:relative}body.landing .curved-footer .the-part h2{font-size:90px;line-height:120px;opacity:0.08;color:#fff}@media screen and (max-width: 1023px){body.landing .curved-footer .the-part h2{font-size:70px}}body.landing .curved-footer .the-part h4{font-size:56px;line-height:65px;position:absolute;left:0;top:0;right:0;padding:80px 0}body.landing .button.is-link{background:transparent}body.landing .button.is-link span{border-bottom:1px solid #fff}body.landing .button.is-link:hover span{border-bottom:none}body.landing .top{border-bottom:1px solid rgba(255,255,255,0.2);padding-bottom:120px;text-align:center}body.landing .bottom{font-size:14px;padding:65px 0 0}@media screen and (max-width: 768px){body.landing .bottom{padding:20px}}body.landing .bottom strong{color:#fff}body.landing .bottom p{padding:6px 0}body.landing .bottom a{color:#fff}body.landing .bottom .sb{padding-top:40px}body.landing .bottom .logo{padding-bottom:30px}body.landing .bottom .button.is-success{margin-top:40px;height:54px}body.landing .bottom .form{margin-top:30px}.top-nav{background:#fff;padding:8px 24px;box-shadow:0 0 40px 0 rgba(115,134,160,0.24);position:fixed;top:0;left:0;right:0;z-index:5}@media screen and (max-width: 768px){.top-nav{padding:8px}}.top-nav h1{font-size:20px}.top-nav .inner{display:flex;align-items:center}.top-nav #hamburger{margin-left:0}@media screen and (max-width: 768px){.top-nav .logo{display:none}}.top-nav .menu{flex-grow:1}.top-nav .menu .top-buttons{text-align:right;margin-bottom:8px;margin-top:2px}@media screen and (max-width: 1023px){.top-nav .menu .top-buttons{display:none}}.top-nav .menu .top-buttons .button{margin-left:16px}.top-nav .menu .navigation{text-align:right;margin-bottom:4px}.top-nav .menu .navigation .link{border:none;display:inline-block;padding:4px 8px;color:#101010;margin-right:2px;line-height:48px;height:48px;vertical-align:middle;height:46px}.top-nav .menu .navigation .link:hover:not(.no-hover){border-bottom:2px solid #101010}@media screen and (max-width: 768px){.top-nav .menu .navigation .link.user-link{display:none}}.sidebar{padding-bottom:120px}.sidebar .search-wrapper{margin:-20px -15px 21px}.sidebar .search-wrapper input{border-radius:0}.sidebar a{color:#798897;overflow-wrap:break-word}.sidebar a:hover,.sidebar a.active{color:#E6282B}.sidebar h3{margin:1.6rem 0 .4rem;color:#211D1A;font-size:12px;text-transform:uppercase}.sidebar ul{padding:0 0 .26667rem 1.06667rem}.sidebar ul li{padding:.2rem 0}.sidebar li>ul{padding:0 0 0px 25px}.sidebar .category h2{color:#000;font-size:20px;margin-top:40px}#sidebarNav.sticky{left:0;transition:left 0.5s}@media screen and (max-width: 1023px){#sidebarNav{z-index:100;top:0;left:-300px;position:fixed;transition:left 0.5s;padding:28px;width:300px;bottom:0;overflow:auto;background:#fff}#sidebarNav .sidebar{padding-bottom:10px}}#stickyNavbarOverlay{position:absolute;left:0;right:0;bottom:0;top:0;z-index:40;background:rgba(0,0,0,0.2);display:none}#stickyNavbarOverlay.active{display:block}.side-nav a{color:#798897;overflow-wrap:break-word}.side-nav a:hover,.side-nav a.is-active{color:#E6282B}.side-nav a.is-past{opacity:0.7}.side-nav h3{margin:1.6rem 0 .4rem;color:#211D1A;font-size:12px;text-transform:uppercase}.side-nav ul{padding:0 0 .26667rem 1.06667rem}.side-nav ul li{padding:.2rem 0}@media screen and (max-width: 768px){.side-nav{display:none}}.footer{border-top:1px solid #EAEAF1;padding:20px;margin:0 -30px -30px;background:#F8F8F9}.footer .content{margin-bottom:0}.footer .fas{color:#E6282B}.footer a{font-weight:bold}.footer a:hover{color:#E6282B}.members{margin-top:24px}.member:not(:last-child):after{content:"";background:#EAEAF1;height:2px;display:block;margin:45px -40px 40px}.member>.is-pulled-right{position:relative;z-index:2}.member>.name{color:#211D1A;font-size:20px;line-height:26px;position:relative;margin-bottom:8px}.member>.name .code-name{font-family:"Inconsolata",monospace;display:block;font-size:25px;line-height:30px;margin-top:8px}.member>.name .code-name:first-child{margin-left:0}.member>.name .tag{position:relative;top:-1px;margin-right:3px}.member>.name .href-link{color:#211D1A;position:absolute;padding:1px;left:-20px;top:0;bottom:0;width:21px;opacity:0}.member>.name:hover .href-link{opacity:1}.member h5{font-size:20px}.member>.description{margin-bottom:25px}.member>.description p{font-size:20px;margin:25px 0}table.params,table.props{border:1px solid #EAEAF1;line-height:26px}table.params thead,table.props thead{border:none}table.params thead th,table.props thead th{font-weight:normal;padding:13px 26px}table.params tr,table.props tr{border-bottom:1px solid #EAEAF1}table.params td,table.props td{padding:13px 26px}table.params td.name code,table.props td.name code{background:transparent;padding:0;font-size:15px;color:#211D1A}table.params tr.deep-level-1,table.props tr.deep-level-1{background:#fafafa}table.params tr.deep-level-1 .name code,table.props tr.deep-level-1 .name code{padding-left:25px;margin-left:0px;border-left:1px solid #DEE1E5}table.params tr.deep-level-2,table.props tr.deep-level-2{background:#f5f5f5}table.params tr.deep-level-2 .name code,table.props tr.deep-level-2 .name code{padding-left:25px;margin-left:25px;border-left:1px solid #DEE1E5}table.params tr.deep-level-3,table.props tr.deep-level-3{background:#f0f0f0}table.params tr.deep-level-3 .name code,table.props tr.deep-level-3 .name code{padding-left:25px;margin-left:50px;border-left:1px solid #DEE1E5}table.params tr.deep-level-4,table.props tr.deep-level-4{background:#ebebeb}table.params tr.deep-level-4 .name code,table.props tr.deep-level-4 .name code{padding-left:25px;margin-left:75px;border-left:1px solid #DEE1E5}table.params tr.deep-level-5,table.props tr.deep-level-5{background:#e6e6e6}table.params tr.deep-level-5 .name code,table.props tr.deep-level-5 .name code{padding-left:25px;margin-left:100px;border-left:1px solid #DEE1E5}table.params tr.deep-level-6,table.props tr.deep-level-6{background:#e0e0e0}table.params tr.deep-level-6 .name code,table.props tr.deep-level-6 .name code{padding-left:25px;margin-left:125px;border-left:1px solid #DEE1E5}table.params tr.deep-level-7,table.props tr.deep-level-7{background:#dbdbdb}table.params tr.deep-level-7 .name code,table.props tr.deep-level-7 .name code{padding-left:25px;margin-left:150px;border-left:1px solid #DEE1E5}table.params tr.deep-level-8,table.props tr.deep-level-8{background:#d6d6d6}table.params tr.deep-level-8 .name code,table.props tr.deep-level-8 .name code{padding-left:25px;margin-left:175px;border-left:1px solid #DEE1E5}table.params tr.deep-level-9,table.props tr.deep-level-9{background:#d1d1d1}table.params tr.deep-level-9 .name code,table.props tr.deep-level-9 .name code{padding-left:25px;margin-left:200px;border-left:1px solid #DEE1E5}table.params tr.deep-level-10,table.props tr.deep-level-10{background:#ccc}table.params tr.deep-level-10 .name code,table.props tr.deep-level-10 .name code{padding-left:25px;margin-left:225px;border-left:1px solid #DEE1E5}.prettyprint{border-radius:2px;background-color:#2F4858}.prettyprint code{font-family:"Inconsolata",monospace}pre.prettyprint li.L0,pre.prettyprint li.L1,pre.prettyprint li.L2,pre.prettyprint li.L3,pre.prettyprint li.L4,pre.prettyprint li.L5,pre.prettyprint li.L6,pre.prettyprint li.L7,pre.prettyprint li.L8,pre.prettyprint li.L9{background:none}.button{transition:all 0.2s;border-radius:4px;padding:8px 24px;height:40px;border-color:#4268F6;color:#4268F6}.button:hover{color:#535B8E;border-color:#535B8E;transition:all 0.2s}.button.is-primary{background-color:#4268F6}.button.is-primary:hover{background-color:#535B8E}.button.is-primary.is-outlined{border-color:#4268F6;color:#4268F6}.button.is-primary.is-outlined:hover{border-color:#535B8E;color:#535B8E;background:transparent}.button.is-success{background:#69D6D4}.button.is-white.is-outlined{background:transparent;border-color:#fff}.button>i:first-child{margin-right:8px;margin-left:-8px}.tag-source{margin:28px 0}.tag-source span{display:inline-block;padding:13px 14px}.tag-source span a{color:#EAEAF1}.tag-source span a:hover{color:#798897}.method-parameter{font-size:20px}.method-parameter label{color:18px}.method-parameter ul{margin:0 0 0 25px}.mermaid .edgeLabel{background:white;color:#2F4858;font-size:15px;font-weight:normal}.mermaid .node circle,.mermaid .node ellipse,.mermaid .node polygon,.mermaid .node rect{fill:rgba(248,249,250,0.8) !important;stroke:rgba(121,136,151,0.6) !important}.mermaid .cluster rect{fill:rgba(125,132,255,0.1) !important;stroke:rgba(125,132,255,0.5) !important}.mermaid .node g.label{color:#2F4858}.mermaid .node g.label div{font-weight:normal;font-size:15px}.tag{text-transform:uppercase}.details dt{font-size:20px;border-left:2px solid #008DDF;padding-left:16px} diff --git a/docs/styles/iframe.css b/docs/styles/iframe.css deleted file mode 100644 index 84dec063..00000000 --- a/docs/styles/iframe.css +++ /dev/null @@ -1,13 +0,0 @@ -.bd__button { - padding: 10px 0; - text-align: right; -} -.bd__button > a{ - font-weight: 100; - text-decoration: none; - color: #BDC3CB; - font-family: sans-serif; -} -.bd__button > a:hover { - color: #798897; -} \ No newline at end of file diff --git a/docs/styles/prettify-jsdoc.css b/docs/styles/prettify-jsdoc.css deleted file mode 100644 index 5a2526e3..00000000 --- a/docs/styles/prettify-jsdoc.css +++ /dev/null @@ -1,111 +0,0 @@ -/* JSDoc prettify.js theme */ - -/* plain text */ -.pln { - color: #000000; - font-weight: normal; - font-style: normal; -} - -/* string content */ -.str { - color: #006400; - font-weight: normal; - font-style: normal; -} - -/* a keyword */ -.kwd { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* a comment */ -.com { - font-weight: normal; - font-style: italic; -} - -/* a type name */ -.typ { - color: #000000; - font-weight: normal; - font-style: normal; -} - -/* a literal value */ -.lit { - color: #006400; - font-weight: normal; - font-style: normal; -} - -/* punctuation */ -.pun { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* lisp open bracket */ -.opn { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* lisp close bracket */ -.clo { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* a markup tag name */ -.tag { - color: #006400; - font-weight: normal; - font-style: normal; -} - -/* a markup attribute name */ -.atn { - color: #006400; - font-weight: normal; - font-style: normal; -} - -/* a markup attribute value */ -.atv { - color: #006400; - font-weight: normal; - font-style: normal; -} - -/* a declaration */ -.dec { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* a variable name */ -.var { - color: #000000; - font-weight: normal; - font-style: normal; -} - -/* a function name */ -.fun { - color: #000000; - font-weight: bold; - font-style: normal; -} - -/* Specify class=linenums on a pre to get line numbering */ -ol.linenums { - margin-top: 0; - margin-bottom: 0; -} diff --git a/docs/styles/prettify-tomorrow.css b/docs/styles/prettify-tomorrow.css deleted file mode 100644 index b6f92a78..00000000 --- a/docs/styles/prettify-tomorrow.css +++ /dev/null @@ -1,132 +0,0 @@ -/* Tomorrow Theme */ -/* Original theme - https://github.com/chriskempson/tomorrow-theme */ -/* Pretty printing styles. Used with prettify.js. */ -/* SPAN elements with the classes below are added by prettyprint. */ -/* plain text */ -.pln { - color: #4d4d4c; } - -@media screen { - /* string content */ - .str { - color: #718c00; } - - /* a keyword */ - .kwd { - color: #8959a8; } - - /* a comment */ - .com { - color: #8e908c; } - - /* a type name */ - .typ { - color: #4271ae; } - - /* a literal value */ - .lit { - color: #f5871f; } - - /* punctuation */ - .pun { - color: #4d4d4c; } - - /* lisp open bracket */ - .opn { - color: #4d4d4c; } - - /* lisp close bracket */ - .clo { - color: #4d4d4c; } - - /* a markup tag name */ - .tag { - color: #c82829; } - - /* a markup attribute name */ - .atn { - color: #f5871f; } - - /* a markup attribute value */ - .atv { - color: #3e999f; } - - /* a declaration */ - .dec { - color: #f5871f; } - - /* a variable name */ - .var { - color: #c82829; } - - /* a function name */ - .fun { - color: #4271ae; } } -/* Use higher contrast and text-weight for printable form. */ -@media print, projection { - .str { - color: #060; } - - .kwd { - color: #006; - font-weight: bold; } - - .com { - color: #600; - font-style: italic; } - - .typ { - color: #404; - font-weight: bold; } - - .lit { - color: #044; } - - .pun, .opn, .clo { - color: #440; } - - .tag { - color: #006; - font-weight: bold; } - - .atn { - color: #404; } - - .atv { - color: #060; } } -/* Style */ -/* -pre.prettyprint { - background: white; - font-family: Consolas, Monaco, 'Andale Mono', monospace; - font-size: 12px; - line-height: 1.5; - border: 1px solid #ccc; - padding: 10px; } -*/ - -/* Specify class=linenums on a pre to get line numbering */ -ol.linenums { - margin-top: 0; - margin-bottom: 0; } - -/* IE indents via margin-left */ -li.L0, -li.L1, -li.L2, -li.L3, -li.L4, -li.L5, -li.L6, -li.L7, -li.L8, -li.L9 { - /* */ } - -/* Alternate shading for lines */ -li.L1, -li.L3, -li.L5, -li.L7, -li.L9 { - /* */ } diff --git a/docs/styles/reset.css b/docs/styles/reset.css deleted file mode 100644 index 5a808c77..00000000 --- a/docs/styles/reset.css +++ /dev/null @@ -1,44 +0,0 @@ -/* reset css */ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..ceca6b39 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,17 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + eslint.configs.recommended, + tseslint.configs.recommended, + { + rules: { + "@typescript-eslint/no-explicit-any": "warn", + }, + }, + { + // Ignore folder with old commands + // TODO: Remove this once the old commands are removed + ignores: ["commands/**/*"], + }, +); diff --git a/jsdoc-conf.json b/jsdoc-conf.json deleted file mode 100644 index b8969279..00000000 --- a/jsdoc-conf.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "plugins": [ - "node_modules/plain-js-only-better-docs/category" - ], - "recurseDepth": 10, - "source": { - "exclude": ["./node_modules/", "./logs/", "./docs/"], - "includePattern": ".+\\.js(doc|x)?$", - "excludePattern": "(^|\\/|\\\\)_" - }, - "sourceType": "module", - "tags": { - "allowUnknownTags": true, - "dictionaries": ["jsdoc","closure"] - }, - "templates": { - "cleverLinks": true, - "monospaceLinks": true, - "search" : true, - "better-docs" : { - "name" : "Factotum Discord Bot Documentation", - "title" : "Factotum Documentation", - "css" : "jsdoc-styles.css", - "navLinks" : [ - { - "label" : "Github", - "href" : "https://github.com/nwplus/Factotum" - } - ] - }, - "default" : { - "staticFiles" : { - "include" : [ - "./jsdoc-styles.css" - ] - } - } - }, - "opts" : { - "destination" : "./docs/", - "recurse" : true, - "access" : "all", - "package" : "./package.json", - "readme" : "./README.md", - "template" : "./node_modules/plain-js-only-better-docs" - } -} \ No newline at end of file diff --git a/jsdoc-styles.css b/jsdoc-styles.css deleted file mode 100644 index 6bb0692e..00000000 --- a/jsdoc-styles.css +++ /dev/null @@ -1,19 +0,0 @@ -.top-nav { - background-color: rgb(247, 255, 255); -} - -.core { - background-color: rgb(247, 255, 255); -} - -.content { - background-color: rgb(247, 255, 255); -} - -.sidebar { - background-color: rgb(247, 255, 255); -} - -.side-nav { - background-color: rgb(247, 255, 255); -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index efc3f55d..432171f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,96 +1,94 @@ { - "name": "factotum-discord-bot", - "version": "5.0.0", - "lockfileVersion": 2, + "name": "factotum", + "version": "6.0.0", + "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "factotum-discord-bot", - "version": "5.0.0", - "license": "SEE LICENSE IN ./LICENSE", - "dependencies": { - "@discordjs/voice": "^0.11.0", - "@google-cloud/firestore": "^4.15.1", - "@sapphire/framework": "^3.2.0", - "@sentry/node": "^6.14.3", - "@sentry/tracing": "^6.14.3", - "advanced-discord.js-prompts": "^1.7.0", - "csv-parser": "^3.0.0", - "discord.js": "^13.16.0", - "dotenv-flow": "^3.2.0", - "firebase-admin": "^9.11.1", - "fs": "0.0.1-security", - "get-random-emoji": "^1.0.0", - "mongodb": "^3.7.1", - "mongoose": "^5.13.9", - "winston": "^3.3.3" + "name": "factotum", + "version": "6.0.0", + "license": "ISC", + "dependencies": { + "@sapphire/decorators": "^6.2.0", + "@sapphire/discord.js-utilities": "^7.3.3", + "@sapphire/framework": "^5.3.6", + "@sapphire/utilities": "^3.18.2", + "discord.js": "^14.20.0", + "dotenv": "^16.5.0", + "firebase-admin": "^13.4.0" }, "devDependencies": { - "eslint": "^7.32.0", - "jsdoc": "^3.6.7", - "plain-js-only-better-docs": "^1.0.0" + "@sapphire/cli": "^1.9.3", + "@trivago/prettier-plugin-sort-imports": "^5.2.2", + "@types/node": "^22.15.30", + "eslint": "^9.28.0", + "prettier": "^3.5.3", + "tsx": "^4.19.4", + "typescript": "^5.8.3", + "typescript-eslint": "^8.33.1" } }, "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.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true, + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.17.tgz", - "integrity": "sha512-r1yKkiUTYMQ8LiEI0UcQx5ETw5dpTLn9wijn9hk6KkTtOK95FndDN10M+8/s6k/Ymlbivw0Av9q4SlgF80PtHg==", + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -98,5891 +96,3501 @@ "node": ">=6.0.0" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", - "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@discordjs/builders": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.16.0.tgz", - "integrity": "sha512-9/NCiZrLivgRub2/kBc0Vm5pMBE5AUdYbdXsLu/yg9ANgvnaJ0bZKTY8yYnLbsEc/LYUP79lEIdC73qEYhWq7A==", - "deprecated": "no longer supported", + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "license": "MIT", "dependencies": { - "@sapphire/shapeshift": "^3.5.1", - "discord-api-types": "^0.36.2", - "fast-deep-equal": "^3.1.3", - "ts-mixer": "^6.0.1", - "tslib": "^2.4.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" }, "engines": { - "node": ">=16.9.0" + "node": ">=6.9.0" } }, - "node_modules/@discordjs/collection": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.2.0.tgz", - "integrity": "sha512-VvrrtGb7vbfPHzbhGq9qZB5o8FOB+kfazrxdt0OtxzSkoBuw9dURMkCwWizZ00+rDpiK2HmLHBZX+y6JsG9khw==", + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=16.9.0" + "node": ">=4" } }, - "node_modules/@discordjs/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "node_modules/@babel/types": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "dev": true, + "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { - "node": ">= 6" + "node": ">=6.9.0" } }, - "node_modules/@discordjs/voice": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.11.0.tgz", - "integrity": "sha512-6+9cj1dxzBJm7WJ9qyG2XZZQ8rcLl6x2caW0C0OxuTtMLAaEDntpb6lqMTFiBg/rDc4Rd59g1w0gJmib33CuHw==", - "dependencies": { - "@types/ws": "^8.5.3", - "discord-api-types": "^0.36.2", - "prism-media": "^1.3.4", - "tslib": "^2.4.0", - "ws": "^8.8.1" + "node_modules/@discordjs/builders": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.11.2.tgz", + "integrity": "sha512-F1WTABdd8/R9D1icJzajC4IuLyyS8f3rTOz66JsSI3pKvpCAtsMBweu8cyNYsIyvcrKAVn9EPK+Psoymq+XC0A==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/formatters": "^0.6.1", + "@discordjs/util": "^1.1.1", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.38.1", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" }, "engines": { - "node": ">=16.9.0" + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, + "node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@firebase/app-types": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", - "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==" - }, - "node_modules/@firebase/auth-interop-types": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", - "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", - "peerDependencies": { - "@firebase/app-types": "0.x", - "@firebase/util": "1.x" + "node_modules/@discordjs/formatters": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.1.tgz", + "integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.1" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@firebase/component": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.5.tgz", - "integrity": "sha512-L41SdS/4a164jx2iGfakJgaBUPPBI3DI+RrUlmh3oHSUljTeCwfj/Nhcv3S7e2lyXsGFJtAyepfPUx4IQ05crw==", + "node_modules/@discordjs/rest": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.5.1.tgz", + "integrity": "sha512-Tg9840IneBcbrAjcGaQzHUJWFNq1MMWZjTdjJ0WS/89IffaNKc++iOvffucPxQTF/gviO9+9r8kEPea1X5J2Dw==", + "license": "Apache-2.0", "dependencies": { - "@firebase/util": "1.2.0", - "tslib": "^2.1.0" + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.1.1", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.38.1", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@firebase/database": { - "version": "0.10.9", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.10.9.tgz", - "integrity": "sha512-Jxi9SiE4cNOftO9YKlG71ccyWFw4kSM9AG/xYu6vWXUGBr39Uw1TvYougANOcU21Q0TP4J08VPGnOnpXk/FGbQ==", - "dependencies": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.5", - "@firebase/database-types": "0.7.3", - "@firebase/logger": "0.2.6", - "@firebase/util": "1.2.0", - "faye-websocket": "0.11.3", - "tslib": "^2.1.0" + "node_modules/@discordjs/util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", + "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@firebase/database-types": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", - "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", + "node_modules/@discordjs/ws": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", + "license": "Apache-2.0", "dependencies": { - "@firebase/app-types": "0.6.3" + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.38.1", + "tslib": "^2.6.2", + "ws": "^8.17.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "node_modules/@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==" - }, - "node_modules/@firebase/util": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.2.0.tgz", - "integrity": "sha512-8W9TTGImXr9cu+oyjBJ7yjoEd/IVAv0pBZA4c1uIuKrpGZi2ee38m+8xlZOBRmsAaOU/tR9DXz1WF/oeM6Fb7Q==", - "dependencies": { - "tslib": "^2.1.0" + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@google-cloud/common": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.7.2.tgz", - "integrity": "sha512-5Q9f74IbZaY6xAwJSNFy5SrGwbm1j7mpv+6A/r+K2dymjsXBH5UauB0tziaMwWoVVaMq1IQnZF9lgtfqqvxcUg==", + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", "optional": true, - "dependencies": { - "@google-cloud/projectify": "^2.0.0", - "@google-cloud/promisify": "^2.0.0", - "arrify": "^2.0.1", - "duplexify": "^4.1.1", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^7.0.2", - "retry-request": "^4.2.2", - "teeny-request": "^7.0.0" - }, + "os": [ + "android" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@google-cloud/firestore": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", - "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" - }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=10.10.0" + "node": ">=18" } }, - "node_modules/@google-cloud/paginator": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.6.tgz", - "integrity": "sha512-XCTm/GfQIlc1ZxpNtTSs/mnZxC2cePNhxU3X8EzHXKIJ2JFncmJj2Fcd2IP+gbmZaSZnY0juFxbUCkIeuu/2eQ==", + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - }, + "os": [ + "android" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@google-cloud/projectify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", - "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@google-cloud/promisify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", - "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@google-cloud/storage": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.14.2.tgz", - "integrity": "sha512-mO2OV2J5eHWtYbjYjqIzOnucZ0wxxVTS6PYU0v1Cfa3iNWRD6oiv+OUvSz6FCifrJHgGuqb9J4kR9N6x72C7nw==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", "optional": true, - "dependencies": { - "@google-cloud/common": "^3.7.0", - "@google-cloud/paginator": "^3.0.0", - "@google-cloud/promisify": "^2.0.0", - "arrify": "^2.0.0", - "async-retry": "^1.3.1", - "compressible": "^2.0.12", - "date-and-time": "^2.0.0", - "duplexify": "^4.0.0", - "extend": "^3.0.2", - "gcs-resumable-upload": "^3.3.0", - "get-stream": "^6.0.0", - "hash-stream-validation": "^0.2.2", - "mime": "^2.2.0", - "mime-types": "^2.0.8", - "p-limit": "^3.0.1", - "pumpify": "^2.0.0", - "snakeize": "^0.1.0", - "stream-events": "^1.0.1", - "xdg-basedir": "^4.0.0" - }, + "os": [ + "freebsd" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@grpc/grpc-js": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.7.tgz", - "integrity": "sha512-CKQVuwuSPh40tgOkR7c0ZisxYRiN05PcKPW72mQL5y++qd7CwBRoaJZvU5xfXnCJDFBmS3qZGQ71Frx6Ofo2XA==", - "dependencies": { - "@types/node": ">=12.12.47" - }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^8.13.0 || >=10.10.0" + "node": ">=18" } }, - "node_modules/@grpc/proto-loader": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.5.tgz", - "integrity": "sha512-GZdzyVQI1Bln/kCzIYgTKu+rQJ5dno0gVrfmLe4jqQu7T2e7svSwJzpCBqVU5hhBSJP3peuPjOMWsj5GR61YmQ==", - "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.10.0", - "yargs": "^16.1.1" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.10.0" + "node": ">=18" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "node_modules/@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.13.0" + "node": ">=18" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@sapphire/async-queue": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz", - "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sapphire/discord-utilities": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@sapphire/discord-utilities/-/discord-utilities-2.12.0.tgz", - "integrity": "sha512-E/Qqb8PwEoX/WLVfcGvTglTgEGGcc/2rGtKBqhMcHcEEtNIY8dhQVYbW/KMNJpR/J81OqUJquVzpkzRe6fQWiw==", - "dependencies": { - "discord-api-types": "^0.36.3" - }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sapphire/discord.js-utilities": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@sapphire/discord.js-utilities/-/discord.js-utilities-5.1.2.tgz", - "integrity": "sha512-zKXUkVzueT3Zag9D/ubpey0g/vLXLCVVFlmYoZqpkx1HsTLSTKz4hxbD7IQ/8q7rvI5Pm/Ex1jajPHMLXKmlpw==", - "dependencies": { - "@sapphire/discord-utilities": "^2.12.0", - "@sapphire/duration": "^1.0.0", - "@sapphire/utilities": "^3.11.0", - "tslib": "^2.4.1" - }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16.6.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sapphire/duration": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@sapphire/duration/-/duration-1.1.2.tgz", - "integrity": "sha512-m+DpXedUHdnH3rM6P9Hiyb9dpdXKb8WeTAVIug0QuN8tarQedbymbOor+UFmBfCbKOkoW9HvGK10xDwDvSfKrw==", + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sapphire/framework": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@sapphire/framework/-/framework-3.2.0.tgz", - "integrity": "sha512-GrxOlyxrydhwXH68zVwhaEys0XLqEJg6o0TCZLtEYhhDY6oRoOl1MwEiomS1cbDYNRpPkdY7ljHcxD44U6cZ9g==", - "dependencies": { - "@discordjs/builders": "^0.16.0", - "@sapphire/discord-utilities": "^2.12.0", - "@sapphire/discord.js-utilities": "^5.1.2", - "@sapphire/lexure": "^1.1.2", - "@sapphire/pieces": "^3.6.0", - "@sapphire/ratelimits": "^2.4.5", - "@sapphire/result": "^2.6.0", - "@sapphire/stopwatch": "^1.5.0", - "@sapphire/utilities": "^3.11.0" - }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=16.6.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sapphire/lexure": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@sapphire/lexure/-/lexure-1.1.2.tgz", - "integrity": "sha512-+v3P3EMDdFoybHH7c7cMcz30jEyxujkxWu5f958cf/Sm27fMM0IqwILnNFUpExZCBAueEM/eoSgbRl4q+K+0jg==", - "dependencies": { - "@sapphire/result": "^2.6.0" - }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sapphire/pieces": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@sapphire/pieces/-/pieces-3.6.0.tgz", - "integrity": "sha512-6Zd6as4e8501NLbjbjTxsi0lVkT850kJroqyjBZClBW32Izr+1XW6sGQM/hl3Pil1/L4QMQqu4khszHejtnbjA==", - "dependencies": { - "@discordjs/collection": "^1.2.0", - "@sapphire/utilities": "^3.11.0", - "tslib": "^2.4.0" - }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sapphire/ratelimits": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/@sapphire/ratelimits/-/ratelimits-2.4.5.tgz", - "integrity": "sha512-2wqpVPRaPUE+CWStLm6wGLj1uA4Ln/9qbH4Ue/eCHC6/R5lJz0+8nGD1LpiYOcyeVLTHbmwODGeD92obkPej2g==", - "dependencies": { - "@sapphire/timer-manager": "^1.0.0" - }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sapphire/result": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@sapphire/result/-/result-2.6.0.tgz", - "integrity": "sha512-gdW6n/oDZ8aC1439Ub3RiLQ6L4VHAxbN0AhGJWNkEZ6Z6Ww2V62fwRiA/73OPfgYQKXk9ljhAFiqNO91KAonHQ==", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/shapeshift": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.7.0.tgz", - "integrity": "sha512-A6vI1zJoxhjWo4grsxpBRBgk96SqSdjLX5WlzKp9H+bJbkM07mvwcbtbVAmUZHbi/OG3HLfiZ1rlw4BhH6tsBQ==", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "lodash.uniqwith": "^4.5.0" - }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sapphire/stopwatch": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@sapphire/stopwatch/-/stopwatch-1.5.0.tgz", - "integrity": "sha512-DtyKugdy3JTqm6JnEepTY64fGJAqlusDVrlrzifEgSCfGYCqpvB+SBldkWtDH+z+zLcp+PyaFLq7xpVfkhmvGg==", - "dependencies": { - "tslib": "^2.4.0" - }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sapphire/timer-manager": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sapphire/timer-manager/-/timer-manager-1.0.0.tgz", - "integrity": "sha512-vxxnv75QPMGKt6IB6nL2xRJfwzcUQ9DBGzJLg6G8eS5O4u7j3IR/yr/GQsa4gIpjw6kQOgn8lUdnSTlpnERTbQ==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sapphire/utilities": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@sapphire/utilities/-/utilities-3.11.0.tgz", - "integrity": "sha512-ich7J+329UTEgWxgk8b871rMhbFW/hvXdabdiKaUKd6g10eIMkIakWf+EGkDQsiDSiebIXll9TIPPmWtN3cVSw==", + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" + "node": ">=18" } }, - "node_modules/@sentry/core": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.14.3.tgz", - "integrity": "sha512-3yHmYZzkXlOqPi/CGlNhb2RzXFvYAryBhrMJV26KJ9ULJF8r4OJ7TcWlupDooGk6Knmq8GQML58OApUvYi8IKg==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", "dependencies": { - "@sentry/hub": "6.14.3", - "@sentry/minimal": "6.14.3", - "@sentry/types": "6.14.3", - "@sentry/utils": "6.14.3", - "tslib": "^1.9.3" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@sentry/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/hub": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.14.3.tgz", - "integrity": "sha512-ZRWLHcAcv4oZAbpSwvCkXlaa1rVFDxcb9lxo5/5v5n6qJq2IG5Z+bXuT2DZlIHQmuCuqRnFSwuBjmBXY7OTHaw==", - "dependencies": { - "@sentry/types": "6.14.3", - "@sentry/utils": "6.14.3", - "tslib": "^1.9.3" - }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@sentry/hub/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } }, - "node_modules/@sentry/minimal": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.14.3.tgz", - "integrity": "sha512-2KNOJuhBpMICoOgdxX56UcO9vGdxCw5mNGYdWvJdKrMwRQr7mC+Fc9lTuTbrYTj6zkfklj2lbdDc3j44Rg787A==", + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sentry/hub": "6.14.3", - "@sentry/types": "6.14.3", - "tslib": "^1.9.3" + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@sentry/minimal/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } }, - "node_modules/@sentry/node": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.14.3.tgz", - "integrity": "sha512-b7NjMdqpDOTxV0hiR90jlK52i9cTdAJgGjQykGFyBDf7rTGDohyEYsERgJ5+/VC3Inan/P3m12PctWA/TMwZCw==", + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sentry/core": "6.14.3", - "@sentry/hub": "6.14.3", - "@sentry/tracing": "6.14.3", - "@sentry/types": "6.14.3", - "@sentry/utils": "6.14.3", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@sentry/node/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/tracing": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.14.3.tgz", - "integrity": "sha512-laFayAxpO/dQL3K3ZcSjtaqJkSf70DH1hHJ8Oiiic0c/xBxh38WSx8yu3TMrbfka5MVIuMNlkq1Gi+SC+moe4w==", + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", "dependencies": { - "@sentry/hub": "6.14.3", - "@sentry/minimal": "6.14.3", - "@sentry/types": "6.14.3", - "@sentry/utils": "6.14.3", - "tslib": "^1.9.3" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@sentry/tracing/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@eslint/js": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", + "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } }, - "node_modules/@sentry/types": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.14.3.tgz", - "integrity": "sha512-GuyqvjQ/N0hIgAjGD1Rn0aQ8kpLBBsImk+Aoh7YFhnvXRhCNkp9N8BuXTfC/uMdMshcWa1OFik/udyjdQM3EJA==", + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@sentry/utils": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.14.3.tgz", - "integrity": "sha512-jsCnclEsR2sV9aHMuaLA5gvxSa0xV4Sc6IJCJ81NTTdb/A5fFbteFBbhuISGF9YoFW1pwbpjuTA6+efXwvLwNQ==", + "node_modules/@eslint/plugin-kit": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@sentry/types": "6.14.3", - "tslib": "^1.9.3" + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@sentry/utils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + "node_modules/@fastify/busboy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.1.tgz", + "integrity": "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==", + "license": "MIT" }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "optional": true, + "node_modules/@favware/colorette-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@favware/colorette-spinner/-/colorette-spinner-1.0.1.tgz", + "integrity": "sha512-PPYtcLzhSafdylp8NBOxMCYIcLqTUMNiQc7ciBoAIvxNG2egM+P7e2nNPui5+Svyk89Q+Tnbrp139ZRIIBw3IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "*" + }, "engines": { - "node": ">= 6" + "node": ">=v16" } }, - "node_modules/@types/body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "license": "Apache-2.0" }, - "node_modules/@types/bson": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", - "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==", - "dependencies": { - "@types/node": "*" - } + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "license": "Apache-2.0" }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/component": { + "version": "0.6.17", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.17.tgz", + "integrity": "sha512-M6DOg7OySrKEFS8kxA3MU5/xc37fiOpKPMz6cTsMUcsuKB6CiZxxNAvgFta8HGRgEpZbi8WjGIj6Uf+TpOhyzg==", + "license": "Apache-2.0", "dependencies": { - "@types/node": "*" + "@firebase/util": "1.12.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" + "node_modules/@firebase/database": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.19.tgz", + "integrity": "sha512-khE+MIYK+XlIndVn/7mAQ9F1fwG5JHrGKaG72hblCC6JAlUBDd3SirICH6SMCf2PQ0iYkruTECth+cRhauacyQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.17", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@types/express-jwt": { - "version": "0.0.42", - "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", - "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "node_modules/@firebase/database-compat": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.10.tgz", + "integrity": "sha512-3sjl6oGaDDYJw/Ny0E5bO6v+KM3KoD4Qo/sAfHGdRFmcJ4QnfxOX9RbG9+ce/evI3m64mkPr24LlmTDduqMpog==", + "license": "Apache-2.0", "dependencies": { - "@types/express": "*", - "@types/express-unless": "*" + "@firebase/component": "0.6.17", + "@firebase/database": "1.0.19", + "@firebase/database-types": "1.0.14", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.12.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@types/express-serve-static-core": { - "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==", + "node_modules/@firebase/database-types": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.14.tgz", + "integrity": "sha512-8a0Q1GrxM0akgF0RiQHliinhmZd+UQPrxEmUv7MnQBYfVFiLtKOgs3g6ghRt/WEGJHyQNslZ+0PocIwNfoDwKw==", + "license": "Apache-2.0", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.12.0" } }, - "node_modules/@types/express-unless": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.2.tgz", - "integrity": "sha512-Q74UyYRX/zIgl1HSp9tUX2PlG8glkVm+59r7aK4KGKzC5jqKIOX6rrVLRQrzpZUQ84VukHtRoeAuon2nIssHPQ==", + "node_modules/@firebase/logger": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", + "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "license": "Apache-2.0", "dependencies": { - "@types/express": "*" + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" - }, - "node_modules/@types/mongodb": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", - "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", + "node_modules/@firebase/util": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.0.tgz", + "integrity": "sha512-Z4rK23xBCwgKDqmzGVMef+Vb4xso2j5Q8OG0vVL4m4fA5ZjPMYQazu8OJJC3vtQRC3SQ/Pgx/6TPNVsCd70QRw==", + "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@types/bson": "*", - "@types/node": "*" + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@types/node": { - "version": "16.9.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.4.tgz", - "integrity": "sha512-KDazLNYAGIuJugdbULwFZULF9qQ13yNWEBFnfVpqlpgAAo6H/qnM9RjBgh0A0kmHf3XxAKLdN5mTIng9iUvVLA==" - }, - "node_modules/@types/node-fetch": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.8.tgz", - "integrity": "sha512-nnH5lV9QCMPsbEVdTb5Y+F3GQxLSw1xQgIydrb2gSfEavRPs50FnMr+KUaa+LoPSqibm2N+ZZxH7lavZlAT4GA==", + "node_modules/@google-cloud/firestore": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.1.tgz", + "integrity": "sha512-ZxOdH8Wr01hBDvKCQfMWqwUcfNcN3JY19k1LtS1fTFhEyorYPLsbWN+VxIRL46pOYGHTPkU3Or5HbT/SLQM5nA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" + "@opentelemetry/api": "^1.3.0", + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^4.3.3", + "protobufjs": "^7.2.6" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "node_modules/@types/serve-static": { - "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==", + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/ws": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.8.tgz", - "integrity": "sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==", - "dependencies": { - "@types/node": "*" + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "optional": true, "engines": { - "node": ">=6.5" + "node": ">=14" } }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" + "node_modules/@google-cloud/storage": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.16.0.tgz", + "integrity": "sha512-7/5LRgykyOfQENcm6hDKP8SX/u9XxE5YOiWOkgkwcoO+cG8xT/cyOvp9wwN3IxfdYgpHs8CE7Nq2PKX2lNaEXw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "<4.1.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^4.4.1", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" }, "engines": { - "node": ">=0.4.0" + "node": ">=14" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/advanced-discord.js-prompts": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/advanced-discord.js-prompts/-/advanced-discord.js-prompts-1.7.0.tgz", - "integrity": "sha512-8UFbB7UxkhIVxsltGJ2+WQ6c9wX6tiTar28wCGIfTGLFZn0WRPlUJXEAhuFDcu1sjvY9FDDBxnMxLMNLX3fFmA==", + "node_modules/@grpc/grpc-js": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", + "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "discord.js": "^12.5.1" + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" }, "engines": { - "node": ">=14.15.4" + "node": ">=12.10.0" } }, - "node_modules/advanced-discord.js-prompts/node_modules/@discordjs/collection": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", - "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==", - "deprecated": "no longer supported" - }, - "node_modules/advanced-discord.js-prompts/node_modules/discord.js": { - "version": "12.5.3", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", - "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", - "deprecated": "no longer supported", + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "optional": true, "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.9", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.4.4" + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" }, "engines": { - "node": ">=12.0.0" + "node": ">=6" } }, - "node_modules/advanced-discord.js-prompts/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=18.18.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "debug": "4" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=18.18.0" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" }, "funding": { "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6.0.0" } }, - "node_modules/async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "optional": true, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", "dependencies": { - "retry": "0.13.1" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/bignumber.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", - "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 8" } }, - "node_modules/bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", "dependencies": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8.0.0" + } }, - "node_modules/brace": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", - "integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=", - "dev": true + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause", + "optional": true }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "optional": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" } }, - "node_modules/bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", - "engines": { - "node": ">=0.6.19" - } + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause", + "optional": true }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause", + "optional": true }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" + "node_modules/@sapphire/cli": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@sapphire/cli/-/cli-1.9.3.tgz", + "integrity": "sha512-78sw7JN4FKDY7lDE3Ld+SavQmww7EEHGe/79pom3dF7r/vO+6lxH3ffcnqZKy3Hd/onBe5cfm26DygEOstGnxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@favware/colorette-spinner": "^1.0.1", + "@sapphire/node-utilities": "^1.0.2", + "@sapphire/result": "^2.6.6", + "colorette": "^2.0.20", + "commander": "^11.1.0", + "execa": "^8.0.1", + "find-up": "^5.0.0", + "js-yaml": "^4.1.0", + "prompts": "^2.4.2", + "tslib": "^2.6.2" + }, + "bin": { + "sapphire": "dist/cli.js" }, "engines": { - "node": ">= 10" + "node": ">=v18" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/@sapphire/decorators": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@sapphire/decorators/-/decorators-6.2.0.tgz", + "integrity": "sha512-st1DNDCNoZaZYz3fgCA99W87Bhe6XqM8y0G+Z9NBOBEqvOYkVNGcPMDrMiaZrPD9Z471k8fpLqJVUNSuWUx8Hg==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "tslib": "^2.8.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "node_modules/@sapphire/discord-utilities": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@sapphire/discord-utilities/-/discord-utilities-3.5.0.tgz", + "integrity": "sha512-H4SY5KTVDZrqA5QG7ob6etwqhdOb3TRSY2wv56f0tiobUdIr0irlrYvdmr8Kg/FRxWU+aiHDIISWGG5vBuxOGw==", + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "discord-api-types": "^0.38.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/chalk/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "node_modules/@sapphire/discord.js-utilities": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@sapphire/discord.js-utilities/-/discord.js-utilities-7.3.3.tgz", + "integrity": "sha512-WDj+zjWgNCUSvzYDD0wY3TVeTUseHq0Nhk0wVWxSDjY8z2gFEVcpY7wF8/fbTDWP44LUG5sUQ4haIrIj2OjmkQ==", + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "@sapphire/discord-utilities": "^3.5.0", + "@sapphire/duration": "^1.2.0", + "@sapphire/utilities": "^3.18.2", + "tslib": "^2.8.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=16.6.0", + "npm": ">=7.0.0" } }, - "node_modules/chalk/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/chalk/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/@sapphire/duration": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@sapphire/duration/-/duration-1.2.0.tgz", + "integrity": "sha512-LxjOAFXz81WmrI8XX9YaVcAZDjQj/1p78lZCvkAWZB1nphOwz/D0dU3CBejmhOWx5dO5CszTkLJMNR0xuCK+Zg==", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "node_modules/@sapphire/framework": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@sapphire/framework/-/framework-5.3.6.tgz", + "integrity": "sha512-VDNsW6S8uMTVXUGSu9fwOYZ3zaMIQbgVvrglnPpjKSmW4GA6M3iewPZgtH/PDtqOXQe6khj1gY1ouhZnyYitNg==", + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "@discordjs/builders": "^1.11.2", + "@sapphire/discord-utilities": "^3.5.0", + "@sapphire/discord.js-utilities": "^7.3.3", + "@sapphire/lexure": "^1.1.10", + "@sapphire/pieces": "^4.4.1", + "@sapphire/ratelimits": "^2.4.11", + "@sapphire/result": "^2.7.2", + "@sapphire/stopwatch": "^1.5.4", + "@sapphire/utilities": "^3.18.2" }, "engines": { - "node": ">=8" + "node": ">=v18", + "npm": ">=7" } }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/@sapphire/lexure": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@sapphire/lexure/-/lexure-1.1.10.tgz", + "integrity": "sha512-odE4FD0SkCxkwEOhzAOqEnCJ/oJlPUuyFEw2KJacIuGiwY86WRTPIHLg1rt6XmfSYLxGXiqRf74req43+wRV9g==", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "@sapphire/result": "^2.7.2" + }, + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/color": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", - "dependencies": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" + "node_modules/@sapphire/node-utilities": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@sapphire/node-utilities/-/node-utilities-1.0.2.tgz", + "integrity": "sha512-v4prFEQNbUfD3j0/gjYd93xYY52N3wrtwTgJc7n68a/GuQXiXLnfypczG5CDVX1nQdvdCB9t4wSUHpfB82zAbQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v16.0.0", + "npm": ">=7.0.0" } }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/@sapphire/pieces": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@sapphire/pieces/-/pieces-4.4.1.tgz", + "integrity": "sha512-2v49P++RzHGb23bdjSa9u7flkdvhrq94IUO9PneY538TILN5QiMxfaXVK+pDR2YR7jpQYBy9APwtFDes2Svykg==", + "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "@discordjs/collection": "^2.1.1", + "@sapphire/utilities": "^3.18.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/color-string": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz", - "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "node_modules/@sapphire/ratelimits": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/@sapphire/ratelimits/-/ratelimits-2.4.11.tgz", + "integrity": "sha512-O6FNA/P0wxU4Ve9gxL948CoZw7+sSpujyUR2CLyLLCNuNvuFGFxPCJVl5crFVLXMIyBIrc2qk+/H9bsqsyQK1Q==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "node_modules/@sapphire/result": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@sapphire/result/-/result-2.7.2.tgz", + "integrity": "sha512-DJbCGmvi8UZAu/hh85auQL8bODFlpcS3cWjRJZ5/cXTLekmGvs/CrRxrIzwbA6+poyYojo5rK4qu8trmjfneog==", + "license": "MIT", "engines": { - "node": ">=0.1.90" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/colorspace": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "node_modules/@sapphire/shapeshift": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", + "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "license": "MIT", "dependencies": { - "color": "3.0.x", - "text-hex": "1.0.x" + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "optional": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "optional": true, + "node_modules/@sapphire/stopwatch": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@sapphire/stopwatch/-/stopwatch-1.5.4.tgz", + "integrity": "sha512-IVI48D2yAz411bSttXyTkBH0p2vhrXoqWLn5loDDSAAEUGkM1r5KNCX2027ifQ8svdoMkUfIGjFueR+satLeWw==", + "license": "MIT", "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" + "tslib": "^2.8.1" }, "engines": { - "node": ">=8" - } - }, - "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "engines": { - "node": ">= 0.6" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, + "node_modules/@sapphire/utilities": { + "version": "3.18.2", + "resolved": "https://registry.npmjs.org/@sapphire/utilities/-/utilities-3.18.2.tgz", + "integrity": "sha512-QGLdC9+pT74Zd7aaObqn0EUfq40c4dyTL65pFnkM6WO1QYN7Yg/s4CdH+CXmx0Zcu6wcfCWILSftXPMosJHP5A==", + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=v14.0.0" } }, - "node_modules/crypto-random-string": { + "node_modules/@tootallnate/once": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/csv-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz", - "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "csv-parser": "bin/csv-parser" - }, "engines": { "node": ">= 10" } }, - "node_modules/date-and-time": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-2.0.0.tgz", - "integrity": "sha512-HJSzj25iPm8E01nt+rSmCIlwjsmjvKfUivG/kXBglpymcHF1FolWAqWwTEV4FvN1Lx5UjPf0J1W4H8yQsVBfFg==", - "optional": true - }, - "node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "node_modules/@trivago/prettier-plugin-sort-imports": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz", + "integrity": "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "ms": "2.1.2" + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.7", + "@babel/traverse": "^7.26.7", + "@babel/types": "^7.26.7", + "javascript-natural-sort": "^0.7.1", + "lodash": "^4.17.21" }, "engines": { - "node": ">=6.0" + "node": ">18.12" + }, + "peerDependencies": { + "@vue/compiler-sfc": "3.x", + "prettier": "2.x - 3.x", + "prettier-plugin-svelte": "3.x", + "svelte": "4.x || 5.x" }, "peerDependenciesMeta": { - "supports-color": { + "@vue/compiler-sfc": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + }, + "svelte": { "optional": true } } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" } }, - "node_modules/denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "engines": { - "node": ">=0.10" - } + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT", + "optional": true }, - "node_modules/dicer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", - "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", "dependencies": { - "streamsearch": "0.1.2" - }, - "engines": { - "node": ">=4.5.0" + "@types/node": "*" } }, - "node_modules/discord-api-types": { - "version": "0.36.3", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.36.3.tgz", - "integrity": "sha512-bz/NDyG0KBo/tY14vSkrwQ/n3HKPf87a0WFW/1M9+tXYK+vp5Z5EksawfCWo2zkAc6o7CClc0eff1Pjrqznlwg==" + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, - "node_modules/discord.js": { - "version": "13.16.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.16.0.tgz", - "integrity": "sha512-bOoCs1Ilojd/UshZVxmEcpxVmHcYOv2fPVZOVq3aFV8xrKLJfaF9mxlvGZ1D1z9aIqf2NkptDr+QndeNuQBTxQ==", + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "license": "MIT", "dependencies": { - "@discordjs/builders": "^0.16.0", - "@discordjs/collection": "^0.7.0", - "@sapphire/async-queue": "^1.5.0", - "@types/node-fetch": "^2.6.3", - "@types/ws": "^8.5.4", - "discord-api-types": "^0.33.5", - "form-data": "^4.0.0", - "node-fetch": "^2.6.7", - "ws": "^8.13.0" - }, - "engines": { - "node": ">=16.6.0", - "npm": ">=7.0.0" + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" } }, - "node_modules/discord.js/node_modules/@discordjs/collection": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.7.0.tgz", - "integrity": "sha512-R5i8Wb8kIcBAFEPLLf7LVBQKBDYUL+ekb23sOgpkpyGT+V4P7V83wTxcsqmX+PbqHt4cEHn053uMWfRqh/Z/nA==", - "deprecated": "no longer supported", - "engines": { - "node": ">=16.9.0" + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "node_modules/discord.js/node_modules/discord-api-types": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz", - "integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg==" + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } + "license": "MIT" }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "optional": true, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", + "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "license": "MIT", "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" + "@types/ms": "*", + "@types/node": "*" } }, - "node_modules/dotenv": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", - "engines": { - "node": ">=8" - } + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT", + "optional": true }, - "node_modules/dotenv-flow": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/dotenv-flow/-/dotenv-flow-3.2.0.tgz", - "integrity": "sha512-GEB6RrR4AbqDJvNSFrYHqZ33IKKbzkvLYiD5eo4+9aFXr4Y4G+QaFrB/fNp0y6McWBmvaPn3ZNjIufnj8irCtg==", + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.15.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", + "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", + "license": "MIT", "dependencies": { - "dotenv": "^8.0.0" - }, - "engines": { - "node": ">= 8.0.0" + "undici-types": "~6.21.0" } }, - "node_modules/duplexify": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", - "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "license": "MIT", + "optional": true, "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" } }, - "node_modules/duplexify/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "@types/mime": "^1", + "@types/node": "*" } }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "license": "MIT", "dependencies": { - "safe-buffer": "^5.0.1" + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" } }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT", + "optional": true }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", "dependencies": { - "once": "^1.4.0" + "@types/node": "*" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", + "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-colors": "^4.1.1" + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/type-utils": "8.33.1", + "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" }, "engines": { - "node": ">=8.6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.33.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "optional": true - }, - "node_modules/entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 4" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/@typescript-eslint/parser": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", + "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/typescript-estree": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", + "debug": "^4.3.4" + }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", + "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" + "@typescript-eslint/tsconfig-utils": "^8.33.1", + "@typescript-eslint/types": "^8.33.1", + "debug": "^4.3.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", + "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", "dev": true, + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1" }, "engines": { - "node": ">=8.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", + "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, + "license": "MIT", "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", + "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.33.1", + "@typescript-eslint/utils": "8.33.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, "engines": { - "node": ">=4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "node_modules/@typescript-eslint/types": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", + "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", + "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", "dev": true, + "license": "MIT", "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" + "@typescript-eslint/project-service": "8.33.1", + "@typescript-eslint/tsconfig-utils": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/visitor-keys": "8.33.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "engines": { - "node": ">=4" - } - }, - "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/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "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=", - "dev": true - }, - "node_modules/fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" - }, - "node_modules/fast-text-encoding": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", - "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" - }, - "node_modules/faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fecha": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", - "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/firebase-admin": { - "version": "9.11.1", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.11.1.tgz", - "integrity": "sha512-Y9fjelljy6MKqwsSbM/UN1k8gBQh5zfm5fCTe0Z6Gch2T3nDUIPsTcf+jfe4o40/MPYuybili9XJjTMmM2e5MQ==", - "dependencies": { - "@firebase/database": "^0.10.0", - "@firebase/database-types": "^0.7.2", - "@types/node": ">=12.12.47", - "dicer": "^0.3.0", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", - "node-forge": "^0.10.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "optionalDependencies": { - "@google-cloud/firestore": "^4.5.0", - "@google-cloud/storage": "^5.3.0" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" - }, - "node_modules/gaxios": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.0.tgz", - "integrity": "sha512-pHplNbslpwCLMyII/lHPWFQbJWOX0B3R1hwBEOvzYi1GmdKZruuEHK4N9V6f7tf1EaPYyF80mui1+344p6SmLg==", - "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gcp-metadata": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.0.tgz", - "integrity": "sha512-L9XQUpvKJCM76YRSmcxrR4mFPzPGsgZUH+GgHMxAET8qc6+BhRJq63RLhWakgEO2KKVgeSDVfyiNjkGSADwNTA==", - "dependencies": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gcs-resumable-upload": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.3.1.tgz", - "integrity": "sha512-WyC0i4VkslIdrdmeM5PNuGzANALLXTG5RoHb08OE30gYT+FEvCDPiA8KOjV2s1wOu9ngEW4+IuzBjtP/ni7UdQ==", - "optional": true, + "license": "MIT", "dependencies": { - "abort-controller": "^3.0.0", - "configstore": "^5.0.0", - "extend": "^3.0.2", - "gaxios": "^4.0.0", - "google-auth-library": "^7.0.0", - "pumpify": "^2.0.0", - "stream-events": "^1.0.4" - }, - "bin": { - "gcs-upload": "build/src/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-random-emoji": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-random-emoji/-/get-random-emoji-1.0.0.tgz", - "integrity": "sha512-6iF8FipG4qgdgWwDr05So1T/1WKTF80GQDNZKHiWctyAVUYwvztRCi/bEhJE2nRSINx9CHWxrwUVk5UbuJ+M3A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "balanced-match": "^1.0.0" } }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "node_modules/@typescript-eslint/utils": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", + "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", "dev": true, + "license": "MIT", "dependencies": { - "type-fest": "^0.20.2" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.33.1", + "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/typescript-estree": "8.33.1" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/google-auth-library": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.9.2.tgz", - "integrity": "sha512-HjxbJt660a+YUTYAgYor87JCuBZvjUSNBExk4bXTEaMuCn8IHSDeHmFxKqThuDPrLCiKJp8blk/Ze8f7SI4N6g==", - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-gax": { - "version": "2.25.4", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.25.4.tgz", - "integrity": "sha512-+Jd0FFOWyb8ieX53e6Sl5OYvHXoA1sWKfQ24ykR502NKgBTvPAh/RFcITihGePBJZ1E8pfh4MKWU0Sf+f1CK+A==", - "dependencies": { - "@grpc/grpc-js": "~1.3.0", - "@grpc/proto-loader": "^0.6.1", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.6.1", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^2.1.1", - "proto3-json-serializer": "^0.1.1", - "protobufjs": "6.11.2", - "retry-request": "^4.0.0" - }, - "bin": { - "compileProtos": "build/tools/compileProtos.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-gax/node_modules/google-auth-library": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.9.2.tgz", - "integrity": "sha512-HjxbJt660a+YUTYAgYor87JCuBZvjUSNBExk4bXTEaMuCn8IHSDeHmFxKqThuDPrLCiKJp8blk/Ze8f7SI4N6g==", - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/google-p12-pem": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.0.tgz", - "integrity": "sha512-JUtEHXL4DY/N+xhlm7TC3qL797RPAtk0ZGXNs3/gWyiDHYoA/8Rjes0pztkda+sZv4ej1EoO2KhWgW5V9KTrSQ==", - "dependencies": { - "node-forge": "^0.10.0" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "devOptional": true - }, - "node_modules/gtoken": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.0.tgz", - "integrity": "sha512-mCcISYiaRZrJpfqOs0QWa6lfEM/C1V9ASkzFmuz43XBb5s1Vynh+CZy1ECeeJXVGx2PRByjYzb4Y4/zr1byr0w==", - "dependencies": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.0.3", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-stream-validation": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", - "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", - "optional": true - }, - "node_modules/http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "optional": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "devOptional": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "optional": true - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/jose": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", - "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", - "dependencies": { - "@panva/asn1.js": "^1.0.0" - }, - "engines": { - "node": ">=10.13.0 < 13 || >=13.7.0" - }, - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js2xmlparser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", - "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", - "dev": true, - "dependencies": { - "xmlcreate": "^2.0.3" - } - }, - "node_modules/jsdoc": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.7.tgz", - "integrity": "sha512-sxKt7h0vzCd+3Y81Ey2qinupL6DpRSZJclS04ugHDNmRUXGzqicMJ6iwayhSA0S0DwwX30c5ozyUthr1QKF6uw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.9.4", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.1", - "klaw": "^3.0.0", - "markdown-it": "^10.0.0", - "markdown-it-anchor": "^5.2.7", - "marked": "^2.0.3", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.1" - }, - "bin": { - "jsdoc": "jsdoc.js" - }, - "engines": { - "node": ">=8.15.0" - } - }, - "node_modules/jsdoc/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=4", - "npm": ">=1.4.28" - } - }, - "node_modules/jsonwebtoken/node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jsonwebtoken/node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jwks-rsa": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.4.tgz", - "integrity": "sha512-iJqVCECYZZ+3oPmY1qXv3Fq+3ywDtuNEVBvG41pPlaR0zyGxa12nC0beAOBBUhETJmc05puS50mRQN4NkCGhmg==", - "dependencies": { - "@types/express-jwt": "0.0.42", - "debug": "^4.3.2", - "jose": "^2.0.5", - "limiter": "^1.1.5", - "lru-memoizer": "^2.1.4" - }, - "engines": { - "node": ">=10 < 13 || >=14" - } - }, - "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/kareem": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", - "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" - }, - "node_modules/klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/limiter": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", - "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" - }, - "node_modules/linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", - "dev": true, - "dependencies": { - "uc.micro": "^1.0.1" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "node_modules/lodash.uniqwith": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz", - "integrity": "sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q==" - }, - "node_modules/logform": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", - "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", - "dependencies": { - "colors": "^1.2.1", - "fast-safe-stringify": "^2.0.4", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "triple-beam": "^1.3.0" - } - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "node_modules/lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lru-memoizer": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", - "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", - "dependencies": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "~4.0.0" - } - }, - "node_modules/lru-memoizer/node_modules/lru-cache": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", - "dependencies": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" - } - }, - "node_modules/lru-memoizer/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "optional": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "optional": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - }, - "bin": { - "markdown-it": "bin/markdown-it.js" - } - }, - "node_modules/markdown-it-anchor": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", - "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", - "dev": true, - "peerDependencies": { - "markdown-it": "*" - } - }, - "node_modules/marked": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.7.tgz", - "integrity": "sha512-BJXxkuIfJchcXOJWTT2DOL+yFWifFv2yGYOUzvXg8Qz610QKw+sHCvTMYwA+qWGhlA2uivBezChZ/pBy1tWdkQ==", - "dev": true, - "bin": { - "marked": "bin/marked" - }, - "engines": { - "node": ">= 8.16.2" - } - }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true - }, - "node_modules/mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "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.31", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", - "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", - "dependencies": { - "mime-db": "1.48.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mongodb": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.1.tgz", - "integrity": "sha512-iSVgexYr8ID0ieeNFUbRfQeOZxOchRck6kEDVySQRaa8VIw/1Pm+/LgcpZcl/BWV6nT0L8lP9qyl7dRPJ6mnLw==", - "dependencies": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.0.3", - "safe-buffer": "^5.1.2" - }, - "engines": { - "node": ">=4" - }, - "optionalDependencies": { - "saslprep": "^1.0.0" - }, - "peerDependenciesMeta": { - "aws4": { - "optional": true - }, - "bson-ext": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "mongodb-extjson": { - "optional": true - }, - "snappy": { - "optional": true - } - } - }, - "node_modules/mongoose": { - "version": "5.13.9", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.9.tgz", - "integrity": "sha512-JbLw5ie0LJxm7V9LoNxRY//6cyFJf0cOpON2TWUWvF9pabil6ArfECL3xHV2N+mwwO4gXiIa+c0pwTzDUVTgqw==", - "dependencies": { - "@types/bson": "1.x || 4.0.x", - "@types/mongodb": "^3.5.27", - "bson": "^1.1.4", - "kareem": "2.3.2", - "mongodb": "3.6.11", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.4", - "mquery": "3.2.5", - "ms": "2.1.2", - "optional-require": "1.0.x", - "regexp-clone": "1.0.0", - "safe-buffer": "5.2.1", - "sift": "13.5.2", - "sliced": "1.0.1" - }, - "engines": { - "node": ">=4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/mongoose-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", - "peerDependencies": { - "mongoose": "*" - } - }, - "node_modules/mongoose/node_modules/mongodb": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.11.tgz", - "integrity": "sha512-4Y4lTFHDHZZdgMaHmojtNAlqkvddX2QQBEN0K//GzxhGwlI9tZ9R0vhbjr1Decw+TF7qK0ZLjQT292XgHRRQgw==", - "dependencies": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.0.3", - "safe-buffer": "^5.1.2" - }, - "engines": { - "node": ">=4" - }, - "optionalDependencies": { - "saslprep": "^1.0.0" - }, - "peerDependenciesMeta": { - "aws4": { - "optional": true - }, - "bson-ext": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "mongodb-extjson": { - "optional": true - }, - "snappy": { - "optional": true - } - } - }, - "node_modules/mongoose/node_modules/optional-require": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", - "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/mpath": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", - "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", - "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", - "dependencies": { - "bluebird": "3.5.1", - "debug": "3.1.0", - "regexp-clone": "^1.0.0", - "safe-buffer": "5.1.2", - "sliced": "1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery/node_modules/bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "node_modules/mquery/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/mquery/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/mquery/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "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==" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "dependencies": { - "fn.name": "1.x.x" - } - }, - "node_modules/optional-require": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.7.tgz", - "integrity": "sha512-cIeRZocXsZnZYn+SevbtSqNlLbeoS4mLzuNn4fvXRMDRNhTGg0sxuKXl0FnZCtnew85LorNxIbZp5OeliILhMw==", - "dependencies": { - "require-at": "^1.0.6" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "optional": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/plain-js-only-better-docs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/plain-js-only-better-docs/-/plain-js-only-better-docs-1.0.0.tgz", - "integrity": "sha512-voeNqP9uUQO5vQi0+TU/PB5TB9gwvizRmV733sVqT3eJ4p6qhi2wR4XaZODjde/kzilOoZgkPGXj3+KmFt4H5Q==", - "dev": true, - "dependencies": { - "brace": "^0.11.1", - "typescript": "^3.9.9", - "underscore": "^1.13.1" - }, - "peerDependencies": { - "typescript": "^3.6.4" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prism-media": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.4.tgz", - "integrity": "sha512-eW7LXORkTCQznZs+eqe9VjGOrLBxcBPXgNyHXMTSRVhphvd/RrxgIR7WaWt4fkLuhshcdT5KHL88LAfcvS3f5g==", - "peerDependencies": { - "@discordjs/opus": "^0.8.0", - "ffmpeg-static": "^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0", - "node-opus": "^0.3.3", - "opusscript": "^0.0.8" - }, - "peerDependenciesMeta": { - "@discordjs/opus": { - "optional": true - }, - "ffmpeg-static": { - "optional": true - }, - "node-opus": { - "optional": true - }, - "opusscript": { - "optional": true - } - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/proto3-json-serializer": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.3.tgz", - "integrity": "sha512-X0DAtxCBsy1NDn84huVFGOFgBslT2gBmM+85nY6/5SOAaCon1jzVNdvi74foIyFvs5CjtSbQsepsM5TsyNhqQw==" - }, - "node_modules/protobufjs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", - "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", - "optional": true, - "dependencies": { - "duplexify": "^4.1.1", - "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/regexp-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", - "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "optional": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", - "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "dependencies": { - "sparse-bitfield": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/sift": { - "version": "13.5.2", - "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", - "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" - }, - "node_modules/signal-exit": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", - "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", - "optional": true - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" - }, - "node_modules/snakeize": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", - "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", - "optional": true - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", - "optional": true, - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", - "engines": { - "node": "*" - } - }, - "node_modules/stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "optional": true, - "dependencies": { - "stubs": "^3.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" - }, - "node_modules/streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", - "optional": true - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", - "dev": true - }, - "node_modules/teeny-request": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.1.2.tgz", - "integrity": "sha512-Mr4NYZuniKDpgcLxdBkDE1CcWy98Aw1ennn6oNofen+XWUvDs+ZZzBAujy6XOAVwwLLZMwEQSfdljUI+ebs4Ww==", - "optional": true, - "dependencies": { - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "stream-events": "^1.0.5", - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/triple-beam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", - "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" - }, - "node_modules/ts-mixer": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.1.tgz", - "integrity": "sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg==" - }, - "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "optional": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "3.9.9", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", - "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "node_modules/underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", - "dev": true - }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "optional": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/winston": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", - "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", - "dependencies": { - "@dabh/diagnostics": "^2.0.2", - "async": "^3.1.0", - "is-stream": "^2.0.0", - "logform": "^2.2.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.4.0" - }, - "engines": { - "node": ">= 6.4.0" - } - }, - "node_modules/winston-transport": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", - "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", - "dependencies": { - "readable-stream": "^2.3.7", - "triple-beam": "^1.2.0" - }, - "engines": { - "node": ">= 6.4.0" - } - }, - "node_modules/winston/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "optional": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/xmlcreate": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", - "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", - "dev": true - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "optional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "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==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/highlight": { - "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.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - } - } - }, - "@babel/parser": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.17.tgz", - "integrity": "sha512-r1yKkiUTYMQ8LiEI0UcQx5ETw5dpTLn9wijn9hk6KkTtOK95FndDN10M+8/s6k/Ymlbivw0Av9q4SlgF80PtHg==", - "dev": true - }, - "@dabh/diagnostics": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", - "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", - "requires": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, - "@discordjs/builders": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.16.0.tgz", - "integrity": "sha512-9/NCiZrLivgRub2/kBc0Vm5pMBE5AUdYbdXsLu/yg9ANgvnaJ0bZKTY8yYnLbsEc/LYUP79lEIdC73qEYhWq7A==", - "requires": { - "@sapphire/shapeshift": "^3.5.1", - "discord-api-types": "^0.36.2", - "fast-deep-equal": "^3.1.3", - "ts-mixer": "^6.0.1", - "tslib": "^2.4.0" - } - }, - "@discordjs/collection": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.2.0.tgz", - "integrity": "sha512-VvrrtGb7vbfPHzbhGq9qZB5o8FOB+kfazrxdt0OtxzSkoBuw9dURMkCwWizZ00+rDpiK2HmLHBZX+y6JsG9khw==" - }, - "@discordjs/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "@discordjs/voice": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.11.0.tgz", - "integrity": "sha512-6+9cj1dxzBJm7WJ9qyG2XZZQ8rcLl6x2caW0C0OxuTtMLAaEDntpb6lqMTFiBg/rDc4Rd59g1w0gJmib33CuHw==", - "requires": { - "@types/ws": "^8.5.3", - "discord-api-types": "^0.36.2", - "prism-media": "^1.3.4", - "tslib": "^2.4.0", - "ws": "^8.8.1" - } - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - } - }, - "@firebase/app-types": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", - "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==" - }, - "@firebase/auth-interop-types": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", - "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==", - "requires": {} - }, - "@firebase/component": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.5.tgz", - "integrity": "sha512-L41SdS/4a164jx2iGfakJgaBUPPBI3DI+RrUlmh3oHSUljTeCwfj/Nhcv3S7e2lyXsGFJtAyepfPUx4IQ05crw==", - "requires": { - "@firebase/util": "1.2.0", - "tslib": "^2.1.0" - } - }, - "@firebase/database": { - "version": "0.10.9", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.10.9.tgz", - "integrity": "sha512-Jxi9SiE4cNOftO9YKlG71ccyWFw4kSM9AG/xYu6vWXUGBr39Uw1TvYougANOcU21Q0TP4J08VPGnOnpXk/FGbQ==", - "requires": { - "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.5", - "@firebase/database-types": "0.7.3", - "@firebase/logger": "0.2.6", - "@firebase/util": "1.2.0", - "faye-websocket": "0.11.3", - "tslib": "^2.1.0" - } - }, - "@firebase/database-types": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", - "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", - "requires": { - "@firebase/app-types": "0.6.3" - } - }, - "@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==" - }, - "@firebase/util": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.2.0.tgz", - "integrity": "sha512-8W9TTGImXr9cu+oyjBJ7yjoEd/IVAv0pBZA4c1uIuKrpGZi2ee38m+8xlZOBRmsAaOU/tR9DXz1WF/oeM6Fb7Q==", - "requires": { - "tslib": "^2.1.0" - } - }, - "@google-cloud/common": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.7.2.tgz", - "integrity": "sha512-5Q9f74IbZaY6xAwJSNFy5SrGwbm1j7mpv+6A/r+K2dymjsXBH5UauB0tziaMwWoVVaMq1IQnZF9lgtfqqvxcUg==", - "optional": true, - "requires": { - "@google-cloud/projectify": "^2.0.0", - "@google-cloud/promisify": "^2.0.0", - "arrify": "^2.0.1", - "duplexify": "^4.1.1", - "ent": "^2.2.0", - "extend": "^3.0.2", - "google-auth-library": "^7.0.2", - "retry-request": "^4.2.2", - "teeny-request": "^7.0.0" - } - }, - "@google-cloud/firestore": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", - "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", - "requires": { - "fast-deep-equal": "^3.1.1", - "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.24.1", - "protobufjs": "^6.8.6" - } - }, - "@google-cloud/paginator": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.6.tgz", - "integrity": "sha512-XCTm/GfQIlc1ZxpNtTSs/mnZxC2cePNhxU3X8EzHXKIJ2JFncmJj2Fcd2IP+gbmZaSZnY0juFxbUCkIeuu/2eQ==", - "optional": true, - "requires": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - } - }, - "@google-cloud/projectify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", - "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", - "optional": true - }, - "@google-cloud/promisify": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", - "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", - "optional": true - }, - "@google-cloud/storage": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.14.2.tgz", - "integrity": "sha512-mO2OV2J5eHWtYbjYjqIzOnucZ0wxxVTS6PYU0v1Cfa3iNWRD6oiv+OUvSz6FCifrJHgGuqb9J4kR9N6x72C7nw==", - "optional": true, - "requires": { - "@google-cloud/common": "^3.7.0", - "@google-cloud/paginator": "^3.0.0", - "@google-cloud/promisify": "^2.0.0", - "arrify": "^2.0.0", - "async-retry": "^1.3.1", - "compressible": "^2.0.12", - "date-and-time": "^2.0.0", - "duplexify": "^4.0.0", - "extend": "^3.0.2", - "gcs-resumable-upload": "^3.3.0", - "get-stream": "^6.0.0", - "hash-stream-validation": "^0.2.2", - "mime": "^2.2.0", - "mime-types": "^2.0.8", - "p-limit": "^3.0.1", - "pumpify": "^2.0.0", - "snakeize": "^0.1.0", - "stream-events": "^1.0.1", - "xdg-basedir": "^4.0.0" - } - }, - "@grpc/grpc-js": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.7.tgz", - "integrity": "sha512-CKQVuwuSPh40tgOkR7c0ZisxYRiN05PcKPW72mQL5y++qd7CwBRoaJZvU5xfXnCJDFBmS3qZGQ71Frx6Ofo2XA==", - "requires": { - "@types/node": ">=12.12.47" - } - }, - "@grpc/proto-loader": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.5.tgz", - "integrity": "sha512-GZdzyVQI1Bln/kCzIYgTKu+rQJ5dno0gVrfmLe4jqQu7T2e7svSwJzpCBqVU5hhBSJP3peuPjOMWsj5GR61YmQ==", - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.10.0", - "yargs": "^16.1.1" - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "@panva/asn1.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", - "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" - }, - "@sapphire/async-queue": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz", - "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==" - }, - "@sapphire/discord-utilities": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@sapphire/discord-utilities/-/discord-utilities-2.12.0.tgz", - "integrity": "sha512-E/Qqb8PwEoX/WLVfcGvTglTgEGGcc/2rGtKBqhMcHcEEtNIY8dhQVYbW/KMNJpR/J81OqUJquVzpkzRe6fQWiw==", - "requires": { - "discord-api-types": "^0.36.3" - } - }, - "@sapphire/discord.js-utilities": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@sapphire/discord.js-utilities/-/discord.js-utilities-5.1.2.tgz", - "integrity": "sha512-zKXUkVzueT3Zag9D/ubpey0g/vLXLCVVFlmYoZqpkx1HsTLSTKz4hxbD7IQ/8q7rvI5Pm/Ex1jajPHMLXKmlpw==", - "requires": { - "@sapphire/discord-utilities": "^2.12.0", - "@sapphire/duration": "^1.0.0", - "@sapphire/utilities": "^3.11.0", - "tslib": "^2.4.1" - } - }, - "@sapphire/duration": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@sapphire/duration/-/duration-1.1.2.tgz", - "integrity": "sha512-m+DpXedUHdnH3rM6P9Hiyb9dpdXKb8WeTAVIug0QuN8tarQedbymbOor+UFmBfCbKOkoW9HvGK10xDwDvSfKrw==" - }, - "@sapphire/framework": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@sapphire/framework/-/framework-3.2.0.tgz", - "integrity": "sha512-GrxOlyxrydhwXH68zVwhaEys0XLqEJg6o0TCZLtEYhhDY6oRoOl1MwEiomS1cbDYNRpPkdY7ljHcxD44U6cZ9g==", - "requires": { - "@discordjs/builders": "^0.16.0", - "@sapphire/discord-utilities": "^2.12.0", - "@sapphire/discord.js-utilities": "^5.1.2", - "@sapphire/lexure": "^1.1.2", - "@sapphire/pieces": "^3.6.0", - "@sapphire/ratelimits": "^2.4.5", - "@sapphire/result": "^2.6.0", - "@sapphire/stopwatch": "^1.5.0", - "@sapphire/utilities": "^3.11.0" - } - }, - "@sapphire/lexure": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@sapphire/lexure/-/lexure-1.1.2.tgz", - "integrity": "sha512-+v3P3EMDdFoybHH7c7cMcz30jEyxujkxWu5f958cf/Sm27fMM0IqwILnNFUpExZCBAueEM/eoSgbRl4q+K+0jg==", - "requires": { - "@sapphire/result": "^2.6.0" - } - }, - "@sapphire/pieces": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@sapphire/pieces/-/pieces-3.6.0.tgz", - "integrity": "sha512-6Zd6as4e8501NLbjbjTxsi0lVkT850kJroqyjBZClBW32Izr+1XW6sGQM/hl3Pil1/L4QMQqu4khszHejtnbjA==", - "requires": { - "@discordjs/collection": "^1.2.0", - "@sapphire/utilities": "^3.11.0", - "tslib": "^2.4.0" - } - }, - "@sapphire/ratelimits": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/@sapphire/ratelimits/-/ratelimits-2.4.5.tgz", - "integrity": "sha512-2wqpVPRaPUE+CWStLm6wGLj1uA4Ln/9qbH4Ue/eCHC6/R5lJz0+8nGD1LpiYOcyeVLTHbmwODGeD92obkPej2g==", - "requires": { - "@sapphire/timer-manager": "^1.0.0" - } - }, - "@sapphire/result": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@sapphire/result/-/result-2.6.0.tgz", - "integrity": "sha512-gdW6n/oDZ8aC1439Ub3RiLQ6L4VHAxbN0AhGJWNkEZ6Z6Ww2V62fwRiA/73OPfgYQKXk9ljhAFiqNO91KAonHQ==" - }, - "@sapphire/shapeshift": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.7.0.tgz", - "integrity": "sha512-A6vI1zJoxhjWo4grsxpBRBgk96SqSdjLX5WlzKp9H+bJbkM07mvwcbtbVAmUZHbi/OG3HLfiZ1rlw4BhH6tsBQ==", - "requires": { - "fast-deep-equal": "^3.1.3", - "lodash.uniqwith": "^4.5.0" - } - }, - "@sapphire/stopwatch": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@sapphire/stopwatch/-/stopwatch-1.5.0.tgz", - "integrity": "sha512-DtyKugdy3JTqm6JnEepTY64fGJAqlusDVrlrzifEgSCfGYCqpvB+SBldkWtDH+z+zLcp+PyaFLq7xpVfkhmvGg==", - "requires": { - "tslib": "^2.4.0" - } - }, - "@sapphire/timer-manager": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sapphire/timer-manager/-/timer-manager-1.0.0.tgz", - "integrity": "sha512-vxxnv75QPMGKt6IB6nL2xRJfwzcUQ9DBGzJLg6G8eS5O4u7j3IR/yr/GQsa4gIpjw6kQOgn8lUdnSTlpnERTbQ==" - }, - "@sapphire/utilities": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@sapphire/utilities/-/utilities-3.11.0.tgz", - "integrity": "sha512-ich7J+329UTEgWxgk8b871rMhbFW/hvXdabdiKaUKd6g10eIMkIakWf+EGkDQsiDSiebIXll9TIPPmWtN3cVSw==" - }, - "@sentry/core": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.14.3.tgz", - "integrity": "sha512-3yHmYZzkXlOqPi/CGlNhb2RzXFvYAryBhrMJV26KJ9ULJF8r4OJ7TcWlupDooGk6Knmq8GQML58OApUvYi8IKg==", - "requires": { - "@sentry/hub": "6.14.3", - "@sentry/minimal": "6.14.3", - "@sentry/types": "6.14.3", - "@sentry/utils": "6.14.3", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/hub": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.14.3.tgz", - "integrity": "sha512-ZRWLHcAcv4oZAbpSwvCkXlaa1rVFDxcb9lxo5/5v5n6qJq2IG5Z+bXuT2DZlIHQmuCuqRnFSwuBjmBXY7OTHaw==", - "requires": { - "@sentry/types": "6.14.3", - "@sentry/utils": "6.14.3", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/minimal": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.14.3.tgz", - "integrity": "sha512-2KNOJuhBpMICoOgdxX56UcO9vGdxCw5mNGYdWvJdKrMwRQr7mC+Fc9lTuTbrYTj6zkfklj2lbdDc3j44Rg787A==", - "requires": { - "@sentry/hub": "6.14.3", - "@sentry/types": "6.14.3", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/node": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.14.3.tgz", - "integrity": "sha512-b7NjMdqpDOTxV0hiR90jlK52i9cTdAJgGjQykGFyBDf7rTGDohyEYsERgJ5+/VC3Inan/P3m12PctWA/TMwZCw==", - "requires": { - "@sentry/core": "6.14.3", - "@sentry/hub": "6.14.3", - "@sentry/tracing": "6.14.3", - "@sentry/types": "6.14.3", - "@sentry/utils": "6.14.3", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/tracing": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.14.3.tgz", - "integrity": "sha512-laFayAxpO/dQL3K3ZcSjtaqJkSf70DH1hHJ8Oiiic0c/xBxh38WSx8yu3TMrbfka5MVIuMNlkq1Gi+SC+moe4w==", - "requires": { - "@sentry/hub": "6.14.3", - "@sentry/minimal": "6.14.3", - "@sentry/types": "6.14.3", - "@sentry/utils": "6.14.3", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@sentry/types": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.14.3.tgz", - "integrity": "sha512-GuyqvjQ/N0hIgAjGD1Rn0aQ8kpLBBsImk+Aoh7YFhnvXRhCNkp9N8BuXTfC/uMdMshcWa1OFik/udyjdQM3EJA==" - }, - "@sentry/utils": { - "version": "6.14.3", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.14.3.tgz", - "integrity": "sha512-jsCnclEsR2sV9aHMuaLA5gvxSa0xV4Sc6IJCJ81NTTdb/A5fFbteFBbhuISGF9YoFW1pwbpjuTA6+efXwvLwNQ==", - "requires": { - "@sentry/types": "6.14.3", - "tslib": "^1.9.3" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "optional": true - }, - "@types/body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/bson": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", - "integrity": "sha512-vVLwMUqhYJSQ/WKcE60eFqcyuWse5fGH+NMAXHuKrUAPoryq3ATxk5o4bgYNtg5aOM4APVg7Hnb3ASqUYG0PKg==", - "requires": { - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "requires": { - "@types/node": "*" - } - }, - "@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-jwt": { - "version": "0.0.42", - "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", - "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", - "requires": { - "@types/express": "*", - "@types/express-unless": "*" - } - }, - "@types/express-serve-static-core": { - "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==", - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/express-unless": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.2.tgz", - "integrity": "sha512-Q74UyYRX/zIgl1HSp9tUX2PlG8glkVm+59r7aK4KGKzC5jqKIOX6rrVLRQrzpZUQ84VukHtRoeAuon2nIssHPQ==", - "requires": { - "@types/express": "*" - } - }, - "@types/long": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" - }, - "@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" - }, - "@types/mongodb": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", - "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", - "requires": { - "@types/bson": "*", - "@types/node": "*" - } - }, - "@types/node": { - "version": "16.9.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.4.tgz", - "integrity": "sha512-KDazLNYAGIuJugdbULwFZULF9qQ13yNWEBFnfVpqlpgAAo6H/qnM9RjBgh0A0kmHf3XxAKLdN5mTIng9iUvVLA==" - }, - "@types/node-fetch": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.8.tgz", - "integrity": "sha512-nnH5lV9QCMPsbEVdTb5Y+F3GQxLSw1xQgIydrb2gSfEavRPs50FnMr+KUaa+LoPSqibm2N+ZZxH7lavZlAT4GA==", - "requires": { - "@types/node": "*", - "form-data": "^4.0.0" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" } }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "@types/serve-static": { - "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==", - "requires": { - "@types/mime": "^1", - "@types/node": "*" + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", + "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.33.1", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "@types/ws": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.8.tgz", - "integrity": "sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==", - "requires": { - "@types/node": "*" + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", + "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, - "abort-controller": { + "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { + "license": "MIT", + "optional": true, + "dependencies": { "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" } }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } }, - "acorn-jsx": { + "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "requires": {} - }, - "advanced-discord.js-prompts": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/advanced-discord.js-prompts/-/advanced-discord.js-prompts-1.7.0.tgz", - "integrity": "sha512-8UFbB7UxkhIVxsltGJ2+WQ6c9wX6tiTar28wCGIfTGLFZn0WRPlUJXEAhuFDcu1sjvY9FDDBxnMxLMNLX3fFmA==", - "requires": { - "discord.js": "^12.5.1" - }, - "dependencies": { - "@discordjs/collection": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", - "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" - }, - "discord.js": { - "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.9", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.4.4" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "requires": {} - } + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" } }, - "ajv": { + "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { + "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "license": "Python-2.0" }, - "arrify": { + "node_modules/arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } }, - "async-retry": { + "node_modules/async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", "optional": true, - "requires": { + "dependencies": { "retry": "0.13.1" } }, - "asynckit": { + "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT", + "optional": true }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, - "base64-js": { + "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "bignumber.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", - "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==" - }, - "bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "brace": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", - "integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=", - "dev": true - }, - "brace-expansion": { + "node_modules/bignumber.js": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz", + "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } }, - "buffer-equal-constant-time": { + "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } }, - "callsites": { + "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", "dev": true, - "requires": { - "lodash": "^4.17.15" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "chalk": { + "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "requires": { + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "optional": true, + "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, - "color": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "color-string": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz", - "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true, + "license": "MIT" }, - "colorspace": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", - "requires": { - "color": "3.0.x", - "text-hex": "1.0.x" - } + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" }, - "combined-stream": { + "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { + "license": "MIT", + "optional": true, + "dependencies": { "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "optional": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" } }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "optional": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, - "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "optional": true - }, - "csv-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz", - "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", - "requires": { - "minimist": "^1.2.0" - } - }, - "date-and-time": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-2.0.0.tgz", - "integrity": "sha512-HJSzj25iPm8E01nt+rSmCIlwjsmjvKfUivG/kXBglpymcHF1FolWAqWwTEV4FvN1Lx5UjPf0J1W4H8yQsVBfFg==", - "optional": true - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "requires": { - "ms": "2.1.2" + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "deep-is": { + "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, - "delayed-stream": { + "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" - }, - "dicer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", - "integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", - "requires": { - "streamsearch": "0.1.2" - } - }, - "discord-api-types": { - "version": "0.36.3", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.36.3.tgz", - "integrity": "sha512-bz/NDyG0KBo/tY14vSkrwQ/n3HKPf87a0WFW/1M9+tXYK+vp5Z5EksawfCWo2zkAc6o7CClc0eff1Pjrqznlwg==" - }, - "discord.js": { - "version": "13.16.0", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.16.0.tgz", - "integrity": "sha512-bOoCs1Ilojd/UshZVxmEcpxVmHcYOv2fPVZOVq3aFV8xrKLJfaF9mxlvGZ1D1z9aIqf2NkptDr+QndeNuQBTxQ==", - "requires": { - "@discordjs/builders": "^0.16.0", - "@discordjs/collection": "^0.7.0", - "@sapphire/async-queue": "^1.5.0", - "@types/node-fetch": "^2.6.3", - "@types/ws": "^8.5.4", - "discord-api-types": "^0.33.5", - "form-data": "^4.0.0", - "node-fetch": "^2.6.7", - "ws": "^8.13.0" - }, - "dependencies": { - "@discordjs/collection": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.7.0.tgz", - "integrity": "sha512-R5i8Wb8kIcBAFEPLLf7LVBQKBDYUL+ekb23sOgpkpyGT+V4P7V83wTxcsqmX+PbqHt4cEHn053uMWfRqh/Z/nA==" - }, - "discord-api-types": { - "version": "0.33.5", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz", - "integrity": "sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg==" - } + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.4.0" } }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" + "node_modules/discord-api-types": { + "version": "0.38.11", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.11.tgz", + "integrity": "sha512-XN0qhcQpetkyb/49hcDHuoeUPsQqOkb17wbV/t48gUkoEDi4ajhsxqugGcxvcN17BBtI9FPPWEgzv6IhQmCwyw==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, + "node_modules/discord.js": { + "version": "14.20.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.20.0.tgz", + "integrity": "sha512-5fRTptK2vpuz+bTuAEUQLSo/3AgCSLHl6Mm9+/ofb+8cbbnjWllhtaqRBq7XcpzlBnfNEugKv8HvCwcOtIHpCg==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/builders": "^1.11.2", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.6.1", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.1", + "@discordjs/ws": "^1.2.3", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "^0.38.1", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" } }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "optional": true, - "requires": { - "is-obj": "^2.0.0" + "node_modules/discord.js/node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.11.0" } }, - "dotenv": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", - "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } }, - "dotenv-flow": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/dotenv-flow/-/dotenv-flow-3.2.0.tgz", - "integrity": "sha512-GEB6RrR4AbqDJvNSFrYHqZ33IKKbzkvLYiD5eo4+9aFXr4Y4G+QaFrB/fNp0y6McWBmvaPn3ZNjIufnj8irCtg==", - "requires": { - "dotenv": "^8.0.0" + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" } }, - "duplexify": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", - "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", - "requires": { + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "optional": true, + "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } + "stream-shift": "^1.0.2" } }, - "ecdsa-sig-formatter": { + "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { + "license": "Apache-2.0", + "dependencies": { "safe-buffer": "^5.0.1" } }, - "enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true }, - "end-of-stream": { + "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { + "license": "MIT", + "optional": true, + "dependencies": { "once": "^1.4.0" } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" } }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "optional": true + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } }, - "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", - "dev": true + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "optional": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } }, - "escape-string-regexp": { + "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", + "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.28.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, + "license": "BSD-2-Clause", "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "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 + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, - "requires": { + "license": "BSD-3-Clause", + "dependencies": { "estraverse": "^5.1.0" }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } + "engines": { + "node": ">=0.10" } }, - "esrecurse": { + "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "requires": { + "license": "BSD-2-Clause", + "dependencies": { "estraverse": "^5.2.0" }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } + "engines": { + "node": ">=4.0" } }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } }, - "esutils": { + "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } }, - "event-target-shim": { + "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "extend": { + "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/farmhash-modern": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", + "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } }, - "fast-deep-equal": { + "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } }, - "fast-json-stable-stringify": { + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, - "fast-levenshtein": { + "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=", - "dev": true + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } }, - "fast-safe-stringify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } }, - "fast-text-encoding": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", - "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" - }, - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "requires": { + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" } }, - "fecha": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", - "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, - "requires": { - "flat-cache": "^3.0.4" + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" } }, - "firebase-admin": { - "version": "9.11.1", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-9.11.1.tgz", - "integrity": "sha512-Y9fjelljy6MKqwsSbM/UN1k8gBQh5zfm5fCTe0Z6Gch2T3nDUIPsTcf+jfe4o40/MPYuybili9XJjTMmM2e5MQ==", - "requires": { - "@firebase/database": "^0.10.0", - "@firebase/database-types": "^0.7.2", - "@google-cloud/firestore": "^4.5.0", - "@google-cloud/storage": "^5.3.0", - "@types/node": ">=12.12.47", - "dicer": "^0.3.0", - "jsonwebtoken": "^8.5.1", - "jwks-rsa": "^2.0.2", - "node-forge": "^0.10.0" + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true + "node_modules/firebase-admin": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.4.0.tgz", + "integrity": "sha512-Y8DcyKK+4pl4B93ooiy1G8qvdyRMkcNFfBSh+8rbVcw4cW8dgG0VXCCTp5NUwub8sn9vSPsOwpb9tE2OuFmcfQ==", + "license": "Apache-2.0", + "dependencies": { + "@fastify/busboy": "^3.0.0", + "@firebase/database-compat": "^2.0.0", + "@firebase/database-types": "^1.0.6", + "@types/node": "^22.8.7", + "farmhash-modern": "^1.1.0", + "google-auth-library": "^9.14.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.1.0", + "node-forge": "^1.3.1", + "uuid": "^11.0.2" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^7.11.0", + "@google-cloud/storage": "^7.14.0" + } }, - "fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/form-data": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.3.tgz", + "integrity": "sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==", + "license": "MIT", + "optional": true, + "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" } }, - "fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=" + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "functional-red-black-tree": { + "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "license": "MIT", + "optional": true }, - "gaxios": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.0.tgz", - "integrity": "sha512-pHplNbslpwCLMyII/lHPWFQbJWOX0B3R1hwBEOvzYi1GmdKZruuEHK4N9V6f7tf1EaPYyF80mui1+344p6SmLg==", - "requires": { - "abort-controller": "^3.0.0", + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", + "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" } }, - "gcp-metadata": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.0.tgz", - "integrity": "sha512-L9XQUpvKJCM76YRSmcxrR4mFPzPGsgZUH+GgHMxAET8qc6+BhRJq63RLhWakgEO2KKVgeSDVfyiNjkGSADwNTA==", - "requires": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, - "gcs-resumable-upload": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.3.1.tgz", - "integrity": "sha512-WyC0i4VkslIdrdmeM5PNuGzANALLXTG5RoHb08OE30gYT+FEvCDPiA8KOjV2s1wOu9ngEW4+IuzBjtP/ni7UdQ==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "configstore": "^5.0.0", - "extend": "^3.0.2", - "gaxios": "^4.0.0", - "google-auth-library": "^7.0.0", - "pumpify": "^2.0.0", - "stream-events": "^1.0.4" + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" } }, - "get-caller-file": { + "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "optional": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, - "get-random-emoji": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-random-emoji/-/get-random-emoji-1.0.0.tgz", - "integrity": "sha512-6iF8FipG4qgdgWwDr05So1T/1WKTF80GQDNZKHiWctyAVUYwvztRCi/bEhJE2nRSINx9CHWxrwUVk5UbuJ+M3A==" + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "optional": true + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "optional": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "requires": { - "is-glob": "^4.0.1" + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "requires": { - "type-fest": "^0.20.2" + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "google-auth-library": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.9.2.tgz", - "integrity": "sha512-HjxbJt660a+YUTYAgYor87JCuBZvjUSNBExk4bXTEaMuCn8IHSDeHmFxKqThuDPrLCiKJp8blk/Ze8f7SI4N6g==", - "optional": true, - "requires": { - "arrify": "^2.0.0", + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - }, - "google-gax": { - "version": "2.25.4", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.25.4.tgz", - "integrity": "sha512-+Jd0FFOWyb8ieX53e6Sl5OYvHXoA1sWKfQ24ykR502NKgBTvPAh/RFcITihGePBJZ1E8pfh4MKWU0Sf+f1CK+A==", - "requires": { - "@grpc/grpc-js": "~1.3.0", - "@grpc/proto-loader": "^0.6.1", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz", + "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.6.1", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^2.1.1", - "proto3-json-serializer": "^0.1.1", - "protobufjs": "6.11.2", - "retry-request": "^4.0.0" - }, - "dependencies": { - "google-auth-library": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.9.2.tgz", - "integrity": "sha512-HjxbJt660a+YUTYAgYor87JCuBZvjUSNBExk4bXTEaMuCn8IHSDeHmFxKqThuDPrLCiKJp8blk/Ze8f7SI4N6g==", - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - } + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" } }, - "google-p12-pem": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.0.tgz", - "integrity": "sha512-JUtEHXL4DY/N+xhlm7TC3qL797RPAtk0ZGXNs3/gWyiDHYoA/8Rjes0pztkda+sZv4ej1EoO2KhWgW5V9KTrSQ==", - "requires": { - "node-forge": "^0.10.0" + "node_modules/google-gax/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" } }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "devOptional": true + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } }, - "gtoken": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.0.tgz", - "integrity": "sha512-mCcISYiaRZrJpfqOs0QWa6lfEM/C1V9ASkzFmuz43XBb5s1Vynh+CZy1ECeeJXVGx2PRByjYzb4Y4/zr1byr0w==", - "requires": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.0.3", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "hash-stream-validation": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz", - "integrity": "sha512-Gjzu0Xn7IagXVkSu9cSFuK1fqzwtLwFhNhVL8IFJijRNMgUttFbBSIAzKuSIrsFMO1+g1RlsoN49zPIbwPDMGQ==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "optional": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT", "optional": true }, - "http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", "optional": true, - "requires": { - "@tootallnate/once": "1", + "dependencies": { + "@tootallnate/once": "2", "agent-base": "6", "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "requires": { - "agent-base": "6", + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" } }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "imurmurhash": { + "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "devOptional": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" + "license": "MIT", + "engines": { + "node": ">=0.8.19" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "optional": true }, - "is-extglob": { + "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "is-fullwidth-code-point": { + "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "optional": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" - }, - "is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "optional": true + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "isexe": { + "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" }, - "jose": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", - "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", - "requires": { - "@panva/asn1.js": "^1.0.0" + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" } }, - "js-tokens": { + "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "js2xmlparser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.1.tgz", - "integrity": "sha512-KrPTolcw6RocpYjdC7pL7v62e55q7qOMHvLX1UCLc5AAS8qeJ6nukarEJAF2KL2PZxlbGueEbINqZR2bDe/gUw==", - "dev": true, - "requires": { - "xmlcreate": "^2.0.3" - } - }, - "jsdoc": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.7.tgz", - "integrity": "sha512-sxKt7h0vzCd+3Y81Ey2qinupL6DpRSZJclS04ugHDNmRUXGzqicMJ6iwayhSA0S0DwwX30c5ozyUthr1QKF6uw==", - "dev": true, - "requires": { - "@babel/parser": "^7.9.4", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.1", - "klaw": "^3.0.0", - "markdown-it": "^10.0.0", - "markdown-it-anchor": "^5.2.7", - "marked": "^2.0.3", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "taffydb": "2.6.2", - "underscore": "~1.13.1" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" } }, - "json-bigint": { + "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "requires": { + "license": "MIT", + "dependencies": { "bignumber.js": "^9.0.0" } }, - "json-schema-traverse": { + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, - "json-stable-stringify-without-jsonify": { + "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "requires": { + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", @@ -5992,569 +3600,610 @@ "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^5.6.0" - }, - "dependencies": { - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" } }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, - "jwks-rsa": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.4.tgz", - "integrity": "sha512-iJqVCECYZZ+3oPmY1qXv3Fq+3ywDtuNEVBvG41pPlaR0zyGxa12nC0beAOBBUhETJmc05puS50mRQN4NkCGhmg==", - "requires": { - "@types/express-jwt": "0.0.42", - "debug": "^4.3.2", - "jose": "^2.0.5", + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", + "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", + "license": "MIT", + "dependencies": { + "@types/express": "^4.17.20", + "@types/jsonwebtoken": "^9.0.4", + "debug": "^4.3.4", + "jose": "^4.15.4", "limiter": "^1.1.5", - "lru-memoizer": "^2.1.4" + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" } }, - "jws": { + "node_modules/jws": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { + "license": "MIT", + "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, - "kareem": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", - "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" - }, - "klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "requires": { - "graceful-fs": "^4.1.9" + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" } }, - "kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "levn": { + "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "limiter": { + "node_modules/limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, - "linkify-it": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", - "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "requires": { - "uc.micro": "^1.0.1" + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "lodash": { + "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "license": "MIT" }, - "lodash.camelcase": { + "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT", + "optional": true }, - "lodash.clonedeep": { + "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" }, - "lodash.includes": { + "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" }, - "lodash.isboolean": { + "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" }, - "lodash.isinteger": { + "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" }, - "lodash.isnumber": { + "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" }, - "lodash.isplainobject": { + "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" }, - "lodash.isstring": { + "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" }, - "lodash.merge": { + "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, - "lodash.once": { + "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "lodash.uniqwith": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz", - "integrity": "sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q==" - }, - "logform": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", - "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", - "requires": { - "colors": "^1.2.1", - "fast-safe-stringify": "^2.0.4", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "triple-beam": "^1.3.0" - } + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "license": "MIT" }, - "lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=" + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0", + "optional": true }, - "lru-cache": { + "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { + "license": "ISC", + "dependencies": { "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, - "lru-memoizer": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", - "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", - "requires": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "~4.0.0" - }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "license": "MIT", "dependencies": { - "lru-cache": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", - "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", - "requires": { - "pseudomap": "^1.0.1", - "yallist": "^2.0.0" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - } + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" } }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/magic-bytes.js": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", + "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "optional": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "optional": true - } + "engines": { + "node": ">= 0.4" } }, - "markdown-it": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz", - "integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==", + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, - "requires": { - "argparse": "^1.0.7", - "entities": "~2.0.0", - "linkify-it": "^2.0.0", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" - } + "license": "MIT" }, - "markdown-it-anchor": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.3.0.tgz", - "integrity": "sha512-/V1MnLL/rgJ3jkMWo84UR+K+jF1cxNG1a+KwqeXqTIJ+jtA8aWSHuigx8lTzauiIjBDbwF3NcWQMotd0Dm39jA==", + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "requires": {} - }, - "marked": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.7.tgz", - "integrity": "sha512-BJXxkuIfJchcXOJWTT2DOL+yFWifFv2yGYOUzvXg8Qz610QKw+sHCvTMYwA+qWGhlA2uivBezChZ/pBy1tWdkQ==", - "dev": true + "license": "MIT", + "engines": { + "node": ">= 8" + } }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } }, - "memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "optional": true + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } }, - "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "optional": true + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } }, - "mime-db": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", - "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==" + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "optional": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } }, - "mime-types": { - "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.48.0" + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mongodb": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.1.tgz", - "integrity": "sha512-iSVgexYr8ID0ieeNFUbRfQeOZxOchRck6kEDVySQRaa8VIw/1Pm+/LgcpZcl/BWV6nT0L8lP9qyl7dRPJ6mnLw==", - "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.0.3", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" - } - }, - "mongoose": { - "version": "5.13.9", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.9.tgz", - "integrity": "sha512-JbLw5ie0LJxm7V9LoNxRY//6cyFJf0cOpON2TWUWvF9pabil6ArfECL3xHV2N+mwwO4gXiIa+c0pwTzDUVTgqw==", - "requires": { - "@types/bson": "1.x || 4.0.x", - "@types/mongodb": "^3.5.27", - "bson": "^1.1.4", - "kareem": "2.3.2", - "mongodb": "3.6.11", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.4", - "mquery": "3.2.5", - "ms": "2.1.2", - "optional-require": "1.0.x", - "regexp-clone": "1.0.0", - "safe-buffer": "5.2.1", - "sift": "13.5.2", - "sliced": "1.0.1" - }, - "dependencies": { - "mongodb": { - "version": "3.6.11", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.11.tgz", - "integrity": "sha512-4Y4lTFHDHZZdgMaHmojtNAlqkvddX2QQBEN0K//GzxhGwlI9tZ9R0vhbjr1Decw+TF7qK0ZLjQT292XgHRRQgw==", - "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.0.3", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" - } - }, - "optional-require": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", - "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" - } - } + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, - "mongoose-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", - "requires": {} - }, - "mpath": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", - "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==" - }, - "mquery": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", - "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", - "requires": { - "bluebird": "3.5.1", - "debug": "3.1.0", - "regexp-clone": "^1.0.0", - "safe-buffer": "5.1.2", - "sliced": "1.0.1" - }, - "dependencies": { - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true } } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==" + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { "wrappy": "1" } }, - "one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "requires": { - "fn.name": "1.x.x" - } - }, - "optional-require": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.7.tgz", - "integrity": "sha512-cIeRZocXsZnZYn+SevbtSqNlLbeoS4mLzuNn4fvXRMDRNhTGg0sxuKXl0FnZCtnew85LorNxIbZp5OeliILhMw==", - "requires": { - "require-at": "^1.0.6" + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" } }, - "p-limit": { + "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "optional": true, - "requires": { + "devOptional": true, + "license": "MIT", + "dependencies": { "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "parent-module": { + "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "path-key": { + "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "plain-js-only-better-docs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/plain-js-only-better-docs/-/plain-js-only-better-docs-1.0.0.tgz", - "integrity": "sha512-voeNqP9uUQO5vQi0+TU/PB5TB9gwvizRmV733sVqT3eJ4p6qhi2wR4XaZODjde/kzilOoZgkPGXj3+KmFt4H5Q==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "requires": { - "brace": "^0.11.1", - "typescript": "^3.9.9", - "underscore": "^1.13.1" + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "prelude-ls": { + "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } }, - "prism-media": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.4.tgz", - "integrity": "sha512-eW7LXORkTCQznZs+eqe9VjGOrLBxcBPXgNyHXMTSRVhphvd/RrxgIR7WaWt4fkLuhshcdT5KHL88LAfcvS3f5g==", - "requires": {} + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "proto3-json-serializer": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.3.tgz", - "integrity": "sha512-X0DAtxCBsy1NDn84huVFGOFgBslT2gBmM+85nY6/5SOAaCon1jzVNdvi74foIyFvs5CjtSbQsepsM5TsyNhqQw==" - }, - "protobufjs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", - "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", - "requires": { + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", @@ -6565,689 +4214,723 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, - "pumpify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-2.0.1.tgz", - "integrity": "sha512-m7KOje7jZxrmutanlkS1daj1dS6z6BgslzOXmcSEpIlCxM3VJH7lG5QLeck/6hgF6F4crFf01UtQmNsJfweTAw==", + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "optional": true, - "requires": { - "duplexify": "^4.1.1", + "dependencies": { "inherits": "^2.0.3", - "pump": "^3.0.0" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "regexp-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", - "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==" - }, - "require-directory": { + "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "requizzle": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", - "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", - "dev": true, - "requires": { - "lodash": "^4.17.14" + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" } }, - "resolve-from": { + "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } }, - "retry": { + "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "optional": true + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } }, - "retry-request": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", - "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", - "requires": { - "debug": "^4.1.1", - "extend": "^3.0.2" + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" } }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, - "requires": { - "glob": "^7.1.3" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" } }, - "safe-buffer": { + "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "saslprep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", - "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", - "optional": true, - "requires": { - "sparse-bitfield": "^3.0.3" - } + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - }, - "shebang-command": { + "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "shebang-regex": { + "node_modules/shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "sift": { - "version": "13.5.2", - "resolved": "https://registry.npmjs.org/sift/-/sift-13.5.2.tgz", - "integrity": "sha512-+gxdEOMA2J+AI+fVsCqeNn7Tgx3M9ZN9jdi95939l1IJ8cZsqS8sqpJyOkic2SJk+1+98Uwryt/gL6XDaV+UZA==" - }, - "signal-exit": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.4.tgz", - "integrity": "sha512-rqYhcAnZ6d/vTPGghdrw7iumdcbXpsk1b8IG/rz+VWV51DM0p7XCtMoJ3qhPLIbp3tvyt3pKRbaaEMZYpHto8Q==", - "optional": true - }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "requires": { - "is-arrayish": "^0.3.1" - } - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } + "license": "MIT", + "engines": { + "node": ">=8" } }, - "sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" - }, - "snakeize": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", - "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=", - "optional": true - }, - "sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", - "optional": true, - "requires": { - "memory-pager": "^1.0.2" + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" }, - "stream-events": { + "node_modules/stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", "optional": true, - "requires": { + "dependencies": { "stubs": "^3.0.0" } }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" - }, - "streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT", + "optional": true }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } + "safe-buffer": "~5.2.0" } }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "requires": { + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - } + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "strip-json-comments": { + "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true }, - "stubs": { + "node_modules/stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT", "optional": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "requires": { - "has-flag": "^3.0.0" + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "ajv": { - "version": "8.6.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" } }, - "taffydb": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", - "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", - "dev": true + "node_modules/teeny-request/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } }, - "teeny-request": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.1.2.tgz", - "integrity": "sha512-Mr4NYZuniKDpgcLxdBkDE1CcWy98Aw1ennn6oNofen+XWUvDs+ZZzBAujy6XOAVwwLLZMwEQSfdljUI+ebs4Ww==", + "node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", "optional": true, - "requires": { - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "stream-events": "^1.0.5", - "uuid": "^8.0.0" + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + "node_modules/teeny-request/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } }, - "tr46": { + "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, - "triple-beam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", - "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } }, - "ts-mixer": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.1.tgz", - "integrity": "sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg==" + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" }, - "tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "node_modules/tsx": { + "version": "4.19.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", + "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } }, - "type-check": { + "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "optional": true, - "requires": { - "is-typedarray": "^1.0.0" + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" } }, - "typescript": { - "version": "3.9.9", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", - "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", - "dev": true - }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", - "dev": true + "node_modules/typescript-eslint": { + "version": "8.33.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.1.tgz", + "integrity": "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.33.1", + "@typescript-eslint/parser": "8.33.1", + "@typescript-eslint/utils": "8.33.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "optional": true, - "requires": { - "crypto-random-string": "^2.0.0" + "node_modules/undici": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "license": "MIT", + "engines": { + "node": ">=18.17" } }, - "uri-js": { + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "requires": { + "license": "BSD-2-Clause", + "dependencies": { "punycode": "^2.1.0" } }, - "util-deprecate": { + "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", "optional": true }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } }, - "webidl-conversions": { + "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, - "websocket-driver": { + "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "requires": { + "license": "Apache-2.0", + "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" } }, - "websocket-extensions": { + "node_modules/websocket-extensions": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } }, - "whatwg-url": { + "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { + "license": "MIT", + "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, - "which": { + "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "winston": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", - "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", - "requires": { - "@dabh/diagnostics": "^2.0.2", - "async": "^3.1.0", - "is-stream": "^2.0.0", - "logform": "^2.2.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.4.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "winston-transport": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", - "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", - "requires": { - "readable-stream": "^2.3.7", - "triple-beam": "^1.2.0" + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { + "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { + "license": "MIT", + "optional": true, + "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - } + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "optional": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "requires": {} - }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", "optional": true }, - "xmlcreate": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.3.tgz", - "integrity": "sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ==", - "dev": true + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } }, - "y18n": { + "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } }, - "yallist": { + "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "requires": { - "cliui": "^7.0.2", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "optional": true, + "dependencies": { + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=12" + } }, - "yocto-queue": { + "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "optional": true + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 3a5a7898..ac2e433b 100644 --- a/package.json +++ b/package.json @@ -1,43 +1,43 @@ { - "name": "factotum-discord-bot", - "version": "5.0.0", - "description": "", - "main": "app.js", + "name": "factotum", + "version": "6.0.0", + "main": "src/index.js", + "type": "module", + "scripts": { + "start": "tsx src/index.ts", + "dev": "tsx watch src/index.ts", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write ." + }, "repository": { "type": "git", - "url": "https://github.com/nwplus/Factotum" + "url": "git+https://github.com/nwplus/Factotum.git" }, - "scripts": { - "test-1": "NODE_ENV=TEST SERVER=1 node app.js", - "test-2": "NODE_ENV=TEST SERVER=2 node app.js", - "test-3": "NODE_ENV=TEST SERVER=3 node app.js", - "test-4": "NODE_ENV=TEST SERVER=4 node app.js", - "dev": "NODE_ENV=DEV node app.js", - "docs": "jsdoc -c jsdoc-conf.json -r ." + "author": "JP Garcia, Maggie Wang, Shu Ting Hu, Daniel Pan", + "license": "ISC", + "bugs": { + "url": "https://github.com/nwplus/Factotum/issues" }, - "author": "JP Garcia, Maggie Wang, Shu Ting Hu", - "license": "SEE LICENSE IN ./LICENSE", - "homepage": "https://github.com/nwplus/Factotum", + "homepage": "https://github.com/nwplus/Factotum#readme", + "description": "", "dependencies": { - "@discordjs/voice": "^0.11.0", - "@google-cloud/firestore": "^4.15.1", - "@sapphire/framework": "^3.2.0", - "@sentry/node": "^6.14.3", - "@sentry/tracing": "^6.14.3", - "advanced-discord.js-prompts": "^1.7.0", - "csv-parser": "^3.0.0", - "discord.js": "^13.16.0", - "dotenv-flow": "^3.2.0", - "firebase-admin": "^9.11.1", - "fs": "0.0.1-security", - "get-random-emoji": "^1.0.0", - "mongodb": "^3.7.1", - "mongoose": "^5.13.9", - "winston": "^3.3.3" + "@sapphire/decorators": "^6.2.0", + "@sapphire/discord.js-utilities": "^7.3.3", + "@sapphire/framework": "^5.3.6", + "@sapphire/utilities": "^3.18.2", + "discord.js": "^14.20.0", + "dotenv": "^16.5.0", + "firebase-admin": "^13.4.0" }, "devDependencies": { - "eslint": "^7.32.0", - "jsdoc": "^3.6.7", - "plain-js-only-better-docs": "^1.0.0" + "@sapphire/cli": "^1.9.3", + "@trivago/prettier-plugin-sort-imports": "^5.2.2", + "@types/node": "^22.15.30", + "eslint": "^9.28.0", + "prettier": "^3.5.3", + "tsx": "^4.19.4", + "typescript": "^5.8.3", + "typescript-eslint": "^8.33.1" } } diff --git a/src/classes/BaseCommand.ts b/src/classes/BaseCommand.ts new file mode 100644 index 00000000..d943f576 --- /dev/null +++ b/src/classes/BaseCommand.ts @@ -0,0 +1,59 @@ +import { + ApplicationCommandRegistryRegisterOptions, + Command, +} from "@sapphire/framework"; +import { + SlashCommandBuilder, + SlashCommandOptionsOnlyBuilder, + SlashCommandSubcommandBuilder, + SlashCommandSubcommandsOnlyBuilder, +} from "discord.js"; + +/** + * A base class for all commands. + * Registers the command with the name and description provided in the constructor. + */ +class BaseCommand extends Command { + constructor(context: Command.LoaderContext, options: Command.Options) { + super(context, { + ...options, + }); + } + + public override registerApplicationCommands(registry: Command.Registry) { + registry.registerChatInputCommand( + (builder) => + this.buildCommand( + builder.setName(this.name).setDescription(this.description), + ), + this.setCommandOptions(), + ); + } + + /** + * Subclasses can override this method to add extra options to the command builder. + * @param builder - The command builder. + * @returns The command builder with the options set. + */ + protected buildCommand( + builder: SlashCommandBuilder, + ): + | SlashCommandBuilder + | SlashCommandOptionsOnlyBuilder + | SlashCommandSubcommandsOnlyBuilder + | SlashCommandSubcommandBuilder { + return builder; + } + + /** + * Subclasses can override this method to set the command options. + * @returns The command options. If no extra options are needed, return undefined. + */ + protected setCommandOptions(): + | ApplicationCommandRegistryRegisterOptions + | undefined { + return undefined; + } +} + +export default BaseCommand; diff --git a/src/commands/ClearChat.ts b/src/commands/ClearChat.ts new file mode 100644 index 00000000..88b5311c --- /dev/null +++ b/src/commands/ClearChat.ts @@ -0,0 +1,54 @@ +import BaseCommand from "@/classes/BaseCommand"; +import { idHints } from "@/constants/id-hints"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Command, CommandOptionsRunTypeEnum } from "@sapphire/framework"; +import { + GuildTextBasedChannel, + MessageFlags, + SlashCommandBuilder, +} from "discord.js"; + +@ApplyOptions({ + name: "clear-chat", + description: "Clear most recent 100 messages younger than 2 weeks.", + runIn: CommandOptionsRunTypeEnum.GuildText, + preconditions: ["AdminRoleOnly"], +}) +class ClearChat extends BaseCommand { + protected override buildCommand(builder: SlashCommandBuilder) { + return builder.addBooleanOption((option) => + option + .setName("keep_pinned") + .setDescription("If true any pinned messages will not be removed"), + ); + } + + protected override setCommandOptions() { + return { + idHints: [idHints.clearChat], + }; + } + + public override async chatInputRun( + interaction: Command.ChatInputCommandInteraction, + ) { + const channel = interaction.channel! as GuildTextBasedChannel; + + const keepPinned = interaction.options.getBoolean("keep_pinned", false); + if (keepPinned) { + const messagesToDelete = channel.messages.cache.filter( + (msg) => !msg.pinned, + ); + await channel.bulkDelete(messagesToDelete, true); + } else { + await channel.bulkDelete(100, true); + } + return interaction.reply({ + content: "Messages successfully deleted", + flags: MessageFlags.Ephemeral, + }); + } +} + +export default ClearChat; diff --git a/src/commands/InitBot.ts b/src/commands/InitBot.ts new file mode 100644 index 00000000..06f70a87 --- /dev/null +++ b/src/commands/InitBot.ts @@ -0,0 +1,159 @@ +import BaseCommand from "@/classes/BaseCommand"; +import { idHints } from "@/constants/id-hints"; +import { GuildDoc } from "@/types/db/guild"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Command } from "@sapphire/framework"; +import { + MessageFlags, + PermissionsBitField, + SlashCommandBuilder, +} from "discord.js"; + +/** + * The InitBot command initializes the bot on the guild. It will prompt the user for information needed + * to set up the bot. It is only usable by server administrators. It can only be run once. + */ +@ApplyOptions({ + name: "init-bot", + description: "Initializes the bot on the guild.", + requiredUserPermissions: [PermissionsBitField.Flags.Administrator], +}) +class InitBot extends BaseCommand { + protected override buildCommand(builder: SlashCommandBuilder) { + return builder + .addRoleOption((option) => + option + .setName("admin-role") + .setDescription("The admin role.") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("staff-role") + .setDescription("The staff role.") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("verified-role") + .setDescription("The verified role.") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("unverified-role") + .setDescription("The unverified role.") + .setRequired(true), + ) + .addChannelOption((option) => + option + .setName("admin-console") + .setDescription("The admin console channel.") + .setRequired(true), + ) + .addChannelOption((option) => + option + .setName("admin-log") + .setDescription("The admin log channel.") + .setRequired(true), + ) + .addStringOption((option) => + option + .setName("hackathon-name") + .setDescription( + "The hackathon name. Either 'HackCamp20xx', 'nwHacks20xx', or 'cmd-f20xx'.", + ) + .setRequired(true), + ); + } + + protected override setCommandOptions() { + return { + idHints: [idHints.initBot], + }; + } + + public override async chatInputRun( + interaction: Command.ChatInputCommandInteraction, + ) { + const guild = interaction.guild!; + if (!guild) { + return interaction.reply({ + content: "This command can only be run in a server.", + flags: MessageFlags.Ephemeral, + }); + } + + const guildDocRef = getGuildDocRef(guild.id); + + const guildDoc = await guildDocRef.get(); + if (guildDoc.exists && guildDoc.get("setupComplete")) { + this.container.logger.info( + `Bot already initialized on guild ${guild.id}`, + ); + return interaction.reply({ + content: "The bot is already initialized on this server.", + flags: MessageFlags.Ephemeral, + }); + } + + const adminRole = interaction.options.getRole("admin-role")!; + const staffRole = interaction.options.getRole("staff-role")!; + const verifiedRole = interaction.options.getRole("verified-role")!; + const unverifiedRole = interaction.options.getRole("unverified-role")!; + const adminConsole = interaction.options.getChannel("admin-console")!; + const adminLog = interaction.options.getChannel("admin-log")!; + + const hackathonName = interaction.options.getString("hackathon-name"); + if (!hackathonName?.match(/(HackCamp|nwHacks|cmd-f)20\d{2}/)) { + return interaction.reply({ + content: + "Invalid hackathon name. Please use the format 'HackCamp20xx', 'nwHacks20xx', or 'cmd-f20xx'.", + flags: MessageFlags.Ephemeral, + }); + } + + const adminConsoleChannel = await guild.channels.fetch(adminConsole!.id); + if (!adminConsoleChannel?.isTextBased()) { + return interaction.reply({ + content: "Admin console channel must be an existing text channel.", + flags: MessageFlags.Ephemeral, + }); + } + + const adminLogChannel = await guild.channels.fetch(adminLog!.id); + if (!adminLogChannel?.isTextBased()) { + return interaction.reply({ + content: "Admin log channel must be an existing text channel.", + flags: MessageFlags.Ephemeral, + }); + } + + this.container.logger.info(`Initializing bot on guild ${guild.id}`); + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); + + await guildDocRef.set({ + setupComplete: true, + hackathonName, + roleIds: { + admin: adminRole.id, + staff: staffRole.id, + verified: verifiedRole.id, + unverified: unverifiedRole.id, + }, + channelIds: { + adminConsole: adminConsole?.id, + adminLog: adminLog?.id, + }, + } satisfies GuildDoc); + + return interaction.followUp({ + content: "The bot has been successfully initialized on this server.", + flags: MessageFlags.Ephemeral, + }); + } +} + +export default InitBot; diff --git a/src/commands/StartAddMembers.ts b/src/commands/StartAddMembers.ts new file mode 100644 index 00000000..4423e013 --- /dev/null +++ b/src/commands/StartAddMembers.ts @@ -0,0 +1,92 @@ +import BaseCommand from "@/classes/BaseCommand"; +import { idHints } from "@/constants/id-hints"; +import { VerificationDoc } from "@/types/db/verification"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Command, CommandOptionsRunTypeEnum } from "@sapphire/framework"; +import { + ActionRowBuilder, + EmbedBuilder, + MessageFlags, + StringSelectMenuBuilder, + StringSelectMenuOptionBuilder, +} from "discord.js"; + +@ApplyOptions({ + name: "start-add-members", + description: "Start prompt to add verified members.", + runIn: CommandOptionsRunTypeEnum.GuildText, + preconditions: ["AdminRoleOnly"], +}) +class StartAddMembers extends BaseCommand { + protected override setCommandOptions() { + return { + idHints: [idHints.startAddMembers], + }; + } + + public override async chatInputRun( + interaction: Command.ChatInputCommandInteraction, + ) { + const guildId = interaction.guildId!; + + const guildDocRef = getGuildDocRef(guildId); + const verificationDocRef = guildDocRef + .collection("command-data") + .doc("verification"); + + const verificationDoc = await verificationDocRef.get(); + if (!verificationDoc.exists) { + return interaction.reply({ + content: + "Verification system is not initialized! Please run /start-verification first.", + flags: MessageFlags.Ephemeral, + }); + } + + const verificationData = verificationDoc.data() as VerificationDoc; + + const defaultRoleIds = { ...verificationData.roleIds } as Record< + string, + string + >; + delete defaultRoleIds["hacker"]; + const roleOptions = [ + ...Object.keys(defaultRoleIds), + ...Object.keys(verificationData.extraRoles), + ].map((roleName) => + new StringSelectMenuOptionBuilder().setLabel(roleName).setValue(roleName), + ); + + if (roleOptions.length === 0) { + return interaction.reply({ + content: "No roles are available for adding members!", + flags: MessageFlags.Ephemeral, + }); + } + + const selectMenu = new StringSelectMenuBuilder() + .setCustomId(`add-members-select`) + .setPlaceholder("Select a role type for the members") + .addOptions(roleOptions); + + const row = new ActionRowBuilder().addComponents( + selectMenu, + ); + + const embed = new EmbedBuilder() + .setTitle("Add Verified Members") + .setDescription( + "Select the role type you want to assign to the new members. You'll then be prompted to enter their emails.", + ) + .setColor("#0099ff"); + + await interaction.reply({ + embeds: [embed], + components: [row], + }); + } +} + +export default StartAddMembers; diff --git a/src/commands/StartLoadQuestions.ts b/src/commands/StartLoadQuestions.ts new file mode 100644 index 00000000..5c8ae115 --- /dev/null +++ b/src/commands/StartLoadQuestions.ts @@ -0,0 +1,87 @@ +import BaseCommand from "@/classes/BaseCommand"; +import { idHints } from "@/constants/id-hints"; +import { TriviaQuestionDoc } from "@/types/db/trivia"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Command, CommandOptionsRunTypeEnum } from "@sapphire/framework"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + MessageFlags, + SlashCommandBuilder, +} from "discord.js"; + +@ApplyOptions({ + name: "start-load-questions", + description: "Starts question management prompt in current channel.", + runIn: CommandOptionsRunTypeEnum.GuildText, + preconditions: ["AdminRoleOnly"], +}) +class StartLoadQuestions extends BaseCommand { + protected override buildCommand(builder: SlashCommandBuilder) { + return builder; + } + + protected override setCommandOptions() { + return { + idHints: [idHints.startLoadQuestions], + }; + } + + public override async chatInputRun( + interaction: Command.ChatInputCommandInteraction, + ) { + const guild = interaction.guild!; + const guildDocRef = getGuildDocRef(guild.id); + const triviaDocRef = guildDocRef.collection("command-data").doc("trivia"); + + // Fetch existing questions + const questionsSnapshot = await triviaDocRef.collection("questions").get(); + const questions = questionsSnapshot.docs.map((doc) => ({ + id: doc.id, + ...(doc.data() as TriviaQuestionDoc), + })); + + // Create embed showing existing questions + const embed = new EmbedBuilder().setTitle("Manage Trivia Questions"); + + if (questions.length === 0) { + embed.setDescription( + "No questions found. Click the button below to add your first question!", + ); + } else { + const questionList = questions + .map((q, index) => { + const truncatedQuestion = + q.question.length > 100 + ? q.question.substring(0, 97) + "..." + : q.question; + const questionType = q.answers ? "MCQ" : "Manual Review"; + return `**${index + 1}.** ${truncatedQuestion} *(${questionType})*`; + }) + .join("\n\n"); + + embed.setDescription(questionList); + embed.setFooter({ text: `Total: ${questions.length} questions` }); + } + + // Create button to add new question + const addButton = new ButtonBuilder() + .setCustomId("add-question") + .setLabel("Add New Question") + .setStyle(ButtonStyle.Primary); + + const row = new ActionRowBuilder().addComponents(addButton); + + await interaction.reply({ + embeds: [embed], + components: [row], + flags: MessageFlags.Ephemeral, + }); + } +} + +export default StartLoadQuestions; diff --git a/src/commands/StartOrganizerCheckIn.ts b/src/commands/StartOrganizerCheckIn.ts new file mode 100644 index 00000000..e4305c34 --- /dev/null +++ b/src/commands/StartOrganizerCheckIn.ts @@ -0,0 +1,94 @@ +import BaseCommand from "@/classes/BaseCommand"; +import { idHints } from "@/constants/id-hints"; +import { OrganizerCheckInDoc } from "@/types/db/organizer-check-in"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Command, CommandOptionsRunTypeEnum } from "@sapphire/framework"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + GuildTextBasedChannel, + SlashCommandBuilder, +} from "discord.js"; + +@ApplyOptions({ + name: "start-organizer-check-in", + description: "Starts organizer check-in/out panel in current channel.", + runIn: CommandOptionsRunTypeEnum.GuildText, + preconditions: ["AdminRoleOnly"], +}) +class StartOrganizerCheckIn extends BaseCommand { + protected override buildCommand(builder: SlashCommandBuilder) { + return builder; + } + + protected override setCommandOptions() { + return { + idHints: [idHints.startOrganizerCheckIn], + }; + } + + public override async chatInputRun( + interaction: Command.ChatInputCommandInteraction, + ) { + const guildId = interaction.guildId!; + const channel = interaction.channel! as GuildTextBasedChannel; + + const embed = StartOrganizerCheckIn.generateAttendanceEmbed({}); + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("organizer-check-in") + .setLabel("Check In") + .setStyle(ButtonStyle.Primary), + new ButtonBuilder() + .setCustomId("organizer-check-out") + .setLabel("Check Out") + .setStyle(ButtonStyle.Primary), + ); + + const checkInPanel = await channel.send({ + content: "Make sure to check in/out when you enter and leave the venue!", + embeds: [embed], + components: [row], + }); + + await interaction.reply({ + content: "Organizer check-in started!", + ephemeral: true, + }); + + const guildDocRef = getGuildDocRef(guildId); + const organizerCheckInDocRef = guildDocRef + .collection("command-data") + .doc("organizer-check-in"); + + await organizerCheckInDocRef.set({ + organizerAttendance: {}, + savedMessage: { + messageId: checkInPanel.id, + channelId: checkInPanel.channel.id, + }, + } satisfies OrganizerCheckInDoc); + } + + public static generateAttendanceEmbed(organizerAttendance: { + [username: string]: string; + }) { + const organizers = Object.values(organizerAttendance); + organizers.sort(); + const formattedMessage = + organizers.length > 0 + ? organizers.map((organizer) => `- ${organizer}`).join("\n") + : "No organizers currently checked in"; + + return new EmbedBuilder() + .setColor(0x0defe1) + .setTitle("Organizers Present") + .setDescription(formattedMessage); + } +} + +export default StartOrganizerCheckIn; diff --git a/src/commands/StartPronouns.ts b/src/commands/StartPronouns.ts new file mode 100644 index 00000000..2e9de78a --- /dev/null +++ b/src/commands/StartPronouns.ts @@ -0,0 +1,103 @@ +import BaseCommand from "@/classes/BaseCommand"; +import { idHints } from "@/constants/id-hints"; +import { PRONOUN_REACTION_EMOJIS, PronounsDoc } from "@/types/db/pronouns"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { EmbedBuilder } from "@discordjs/builders"; +import { ApplyOptions } from "@sapphire/decorators"; +import { Command, CommandOptionsRunTypeEnum } from "@sapphire/framework"; +import { + GuildTextBasedChannel, + MessageFlags, + SlashCommandBuilder, +} from "discord.js"; + +@ApplyOptions({ + name: "start-pronouns", + description: "Start pronoun selector.", + runIn: CommandOptionsRunTypeEnum.GuildText, + preconditions: ["AdminRoleOnly"], +}) +class StartPronouns extends BaseCommand { + protected override buildCommand(builder: SlashCommandBuilder) { + return builder + .addRoleOption((option) => + option + .setName("he_him_role") + .setDescription("The he/him role.") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("she_her_role") + .setDescription("The she/her role.") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("they_them_role") + .setDescription("The they/them role.") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("other_role") + .setDescription("The other role.") + .setRequired(true), + ); + } + + protected override setCommandOptions() { + return { + idHints: [idHints.startPronouns], + }; + } + + public override async chatInputRun( + interaction: Command.ChatInputCommandInteraction, + ) { + const channel = interaction.channel! as GuildTextBasedChannel; + const heHimRole = interaction.options.getRole("he_him_role")!; + const sheHerRole = interaction.options.getRole("she_her_role")!; + const theyThemRole = interaction.options.getRole("they_them_role")!; + const otherRole = interaction.options.getRole("other_role")!; + + const message = await channel.send({ embeds: [this.makePronounsEmbed()] }); + PRONOUN_REACTION_EMOJIS.forEach((emoji) => message.react(emoji)); + + await interaction.reply({ + content: "Pronouns selector started!", + flags: MessageFlags.Ephemeral, + }); + + const guildDocRef = getGuildDocRef(interaction.guildId!); + const pronounsDocRef = guildDocRef + .collection("command-data") + .doc("pronouns"); + await pronounsDocRef.set({ + roleIds: { + heHimRole: heHimRole.id, + sheHerRole: sheHerRole.id, + theyThemRole: theyThemRole.id, + otherRole: otherRole.id, + }, + savedMessage: { + messageId: message.id, + channelId: channel.id, + }, + } satisfies PronounsDoc); + } + + private makePronounsEmbed() { + return new EmbedBuilder() + .setTitle("Set your pronouns by reacting to one or more of the emojis!") + .setDescription( + `${PRONOUN_REACTION_EMOJIS[0]} he/him\n` + + `${PRONOUN_REACTION_EMOJIS[1]} she/her\n` + + `${PRONOUN_REACTION_EMOJIS[2]} they/them\n` + + `${PRONOUN_REACTION_EMOJIS[3]} other pronouns\n`, + ); + } +} + +export default StartPronouns; diff --git a/src/commands/StartReport.ts b/src/commands/StartReport.ts new file mode 100644 index 00000000..39c35283 --- /dev/null +++ b/src/commands/StartReport.ts @@ -0,0 +1,64 @@ +import BaseCommand from "@/classes/BaseCommand"; +import { idHints } from "@/constants/id-hints"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Command, CommandOptionsRunTypeEnum } from "@sapphire/framework"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + SlashCommandBuilder, +} from "discord.js"; + +@ApplyOptions({ + name: "start-report", + description: "Starts report prompt in current channel.", + runIn: CommandOptionsRunTypeEnum.GuildText, + preconditions: ["AdminRoleOnly"], +}) +class StartReport extends BaseCommand { + protected override buildCommand(builder: SlashCommandBuilder) { + return builder; + } + + protected override setCommandOptions() { + return { + idHints: [idHints.startReport], + }; + } + + public override async chatInputRun( + interaction: Command.ChatInputCommandInteraction, + ) { + const embed = new EmbedBuilder() + .setTitle( + "Anonymously report users who are not following server or MLH rules. Help makes our community safer!", + ) + .setDescription( + "Please use the format below, be as precise and accurate as possible. \n " + + "Everything you say will be 100% anonymous. We have no way of reaching back to you so again, be as detailed as possible!", + ) + .addFields({ + name: "Format:", + value: + "User(s) discord username(s) (including discord id number(s)):\n" + + "Reason for report (one line):\n" + + "Detailed Explanation:\n" + + "Name of channel where the incident occurred (if possible):", + }); + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("report") + .setLabel("Report an issue") + .setStyle(ButtonStyle.Primary), + ); + + await interaction.reply({ + embeds: [embed], + components: [row as ActionRowBuilder], + }); + } +} + +export default StartReport; diff --git a/src/commands/StartTickets.ts b/src/commands/StartTickets.ts new file mode 100644 index 00000000..61d8b8a8 --- /dev/null +++ b/src/commands/StartTickets.ts @@ -0,0 +1,252 @@ +import BaseCommand from "@/classes/BaseCommand"; +import { idHints } from "@/constants/id-hints"; +import { GuildDoc } from "@/types/db/guild"; +import { MENTOR_SPECIALTIES_MAP, TicketDoc } from "@/types/db/ticket"; +import { VerificationDoc } from "@/types/db/verification"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Command, CommandOptionsRunTypeEnum } from "@sapphire/framework"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + Guild, + GuildTextBasedChannel, + MessageFlags, + SlashCommandBuilder, + StringSelectMenuBuilder, +} from "discord.js"; + +@ApplyOptions({ + name: "start-tickets", + description: "Starts ticket prompt for hackers in current channel.", + runIn: CommandOptionsRunTypeEnum.GuildText, + preconditions: ["AdminRoleOnly"], +}) +class StartTickets extends BaseCommand { + protected override buildCommand(builder: SlashCommandBuilder) { + return builder + .addIntegerOption((option) => + option + .setName("unanswered_ticket_time") + .setDescription( + "How long (minutes) a ticket should go unaccepted before the bot sends a reminder to all mentors", + ) + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("request_ticket_role") + .setDescription("Tag the role that is allowed to request tickets") + .setRequired(true), + ) + .addChannelOption((option) => + option + .setName("mentor_specialty_channel") + .setDescription( + "The channel where mentors can select their specialties", + ) + .setRequired(true), + ) + .addChannelOption((option) => + option + .setName("incoming_tickets_channel") + .setDescription("The channel where mentor tickets will be sent") + .setRequired(true), + ); + } + + protected override setCommandOptions() { + return { + idHints: [idHints.startTickets], + }; + } + + public override async chatInputRun( + interaction: Command.ChatInputCommandInteraction, + ) { + const guild = interaction.guild!; + + const unansweredTicketTime = interaction.options.getInteger( + "unanswered_ticket_time", + )!; + const requestTicketRole = interaction.options.getRole( + "request_ticket_role", + )!; + const mentorSpecialtySelectionChannelName = interaction.options.getChannel( + "mentor_specialty_channel", + )!; + const incomingTicketsChannelName = interaction.options.getChannel( + "incoming_tickets_channel", + )!; + + const mentorSpecialtySelectionChannel = await guild.channels.fetch( + mentorSpecialtySelectionChannelName.id, + ); + const incomingTicketsChannel = await guild.channels.fetch( + incomingTicketsChannelName.id, + ); + + if ( + !mentorSpecialtySelectionChannel?.isTextBased() || + !incomingTicketsChannel?.isTextBased() + ) { + return interaction.reply({ + content: + "Please tag valid text channels for mentor role selection and incoming tickets channels.", + flags: [MessageFlags.Ephemeral], + }); + } + + await interaction.deferReply(); + + const guildDocRef = getGuildDocRef(guild.id); + const guildData = (await guildDocRef.get()).data() as GuildDoc; + const verificationData = ( + await guildDocRef.collection("command-data").doc("verification").get() + ).data() as VerificationDoc; + + // Create all base mentor roles if not already created + await this.createMentorSpecialtyRoles( + guild, + verificationData.roleIds.mentor, + ); + + // Mentor specialty selection message + const mentorSpecialtySelectionMessage = + await mentorSpecialtySelectionChannel.send({ + embeds: [StartTickets.makeMentorSpecialtySelectionEmbed()], + }); + for (const emoji of MENTOR_SPECIALTIES_MAP.keys()) { + await mentorSpecialtySelectionMessage.react(emoji); + } + + // Admin console message for adding new specialty roles + const { embed: adminConsoleEmbed, actionRow: adminConsoleActionRow } = + this.makeAdminConsoleComponents(); + const adminConsole = await guild.channels.fetch( + guildData.channelIds.adminConsole, + )!; + await (adminConsole as GuildTextBasedChannel).send({ + embeds: [adminConsoleEmbed], + components: [adminConsoleActionRow], + }); + + // Request ticket message + const { embed, actionRow } = StartTickets.makeRequestTicketComponents(); + const requestTicketMessage = await interaction.followUp({ + embeds: [embed], + components: [actionRow], + }); + + const ticketDocRef = guildDocRef.collection("command-data").doc("tickets"); + await ticketDocRef.set({ + currentTicketCount: 0, + unansweredTicketTime: unansweredTicketTime, + extraSpecialties: {}, + roleIds: { + requestTicketRole: requestTicketRole.id, + }, + channelIds: { + incomingTicketsChannel: incomingTicketsChannel.id, + }, + savedMessages: { + mentorSpecialtySelection: { + messageId: mentorSpecialtySelectionMessage.id, + channelId: mentorSpecialtySelectionMessage.channel.id, + }, + requestTicket: { + messageId: requestTicketMessage.id, + channelId: requestTicketMessage.channel.id, + }, + }, + } satisfies TicketDoc); + } + + private async createMentorSpecialtyRoles(guild: Guild, mentorRoleId: string) { + const mentorRole = guild.roles.cache.find( + (role) => role.id === mentorRoleId, + )!; + + for (const specialtyStr of MENTOR_SPECIALTIES_MAP.values()) { + const findRole = guild.roles.cache.find( + (role) => role.name.toLowerCase() === `M-${specialtyStr}`.toLowerCase(), + ); + if (!findRole) { + await guild.roles.create({ + name: `M-${specialtyStr}`, + color: mentorRole.hexColor, + }); + } + } + } + + public static makeMentorSpecialtySelectionEmbed(extraSpecialties?: { + [emoji: string]: string; + }) { + const specialties = [ + ...Array.from(MENTOR_SPECIALTIES_MAP.entries()), + ...Object.entries(extraSpecialties ?? {}), + ].map(([key, value]) => ({ + name: key + " --> " + value, + value: "\u200b", + })); + + return new EmbedBuilder() + .setTitle("Choose what you would like to help hackers with!") + .setDescription( + "Note: You will be notified every time a hacker creates a ticket in one of your selected categories!", + ) + .addFields(specialties); + } + + public static makeRequestTicketComponents(extraSpecialties?: { + [emoji: string]: string; + }) { + const embed = new EmbedBuilder() + .setTitle("Need 1:1 mentor help?") + .setDescription( + "Select a technology you need help with and follow the instructions!", + ); + + const options = [ + ...Array.from(MENTOR_SPECIALTIES_MAP.values()), + ...Object.values(extraSpecialties ?? {}), + ].map((value) => ({ + label: value, + value, + })); + options.push({ label: "None of the above", value: "None of the above" }); + + const actionRow = + new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId("request-ticket") + .setPlaceholder("Select a technology you need help with") + .addOptions(options) + .setMinValues(1) + .setMaxValues(options.length), + ); + + return { embed, actionRow }; + } + + private makeAdminConsoleComponents() { + const embed = new EmbedBuilder() + .setTitle("Tickets Console") + .setDescription("Configure ticketing settings here!"); + + const actionRow = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("add-specialty-role") + .setLabel("Add Mentor Specialty Role") + .setStyle(ButtonStyle.Primary), + ); + + return { embed, actionRow }; + } +} + +export default StartTickets; diff --git a/src/commands/StartTrivia.ts b/src/commands/StartTrivia.ts new file mode 100644 index 00000000..06a68d7d --- /dev/null +++ b/src/commands/StartTrivia.ts @@ -0,0 +1,444 @@ +import BaseCommand from "@/classes/BaseCommand"; +import { idHints } from "@/constants/id-hints"; +import { GuildDoc } from "@/types/db/guild"; +import { + TriviaDoc, + TriviaLeaderboardDoc, + TriviaQuestionDoc, +} from "@/types/db/trivia"; +import { checkMemberRoles } from "@/util/discord"; +import { getGuildDocRef, logToAdminLog } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Command, CommandOptionsRunTypeEnum } from "@sapphire/framework"; +import { + APIRole, + GuildMember, + GuildTextBasedChannel, + Message, + MessageFlags, + Role, + StringSelectMenuInteraction, + UserSelectMenuBuilder, +} from "discord.js"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + SlashCommandBuilder, +} from "discord.js"; +import { + DocumentReference, + FieldPath, + FieldValue, +} from "firebase-admin/firestore"; + +@ApplyOptions({ + name: "start-trivia", + description: "Starts trivia contest in current channel.", + runIn: CommandOptionsRunTypeEnum.GuildText, + preconditions: ["AdminRoleOnly"], +}) +class StartTrivia extends BaseCommand { + protected override buildCommand(builder: SlashCommandBuilder) { + return builder + .addIntegerOption((option) => + option + .setName("interval") + .setDescription("Time (minutes) between questions") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("notify") + .setDescription("Role to notify when a question drops") + .setRequired(true), + ) + .addBooleanOption((option) => + option + .setName("start_question_now") + .setDescription( + "True to start first question now, false to start it after one interval", + ) + .setRequired(false), + ); + } + + protected override setCommandOptions() { + return { + idHints: [idHints.startTrivia], + }; + } + + public override async chatInputRun( + interaction: Command.ChatInputCommandInteraction, + ) { + const guild = interaction.guild!; + const channel = interaction.channel as GuildTextBasedChannel; + + const interval = interaction.options.getInteger("interval")!; + const notify = interaction.options.getRole("notify")!; + const startQuestionNow = + interaction.options.getBoolean("start_question_now")!; + + const guildDocRef = getGuildDocRef(guild.id); + + const { embed, row } = StartTrivia.makeAdminConsoleComponents( + interval, + false, + ); + + const guildData = (await guildDocRef.get()).data() as GuildDoc; + const adminConsole = (await guild.channels.fetch( + guildData.channelIds.adminConsole, + )) as GuildTextBasedChannel; + const triviaControlPanelMessage = await adminConsole.send({ + embeds: [embed], + components: [row], + }); + + const startEmbed = StartTrivia.makeStartEmbed(); + const leaderboard = new EmbedBuilder().setTitle("Leaderboard"); + const triviaInfoMessage = await channel.send({ + content: "<@&" + notify.id + ">", + embeds: [startEmbed, leaderboard], + }); + triviaInfoMessage.pin(); + triviaInfoMessage.react("🍀"); + + await interaction.reply({ + content: "Trivia contest has been started!", + flags: MessageFlags.Ephemeral, + }); + + logToAdminLog(guild, `Trivia contest started by <@${interaction.user.id}>`); + + const triviaDocRef = guildDocRef.collection("command-data").doc("trivia"); + await triviaDocRef.set( + { + interval, + paused: false, + askedQuestions: [], + roleIds: { + notify: notify.id, + }, + savedMessages: { + triviaInfoMessage: { + messageId: triviaInfoMessage.id, + channelId: triviaInfoMessage.channelId, + }, + triviaControlPanelMessage: { + messageId: triviaControlPanelMessage.id, + channelId: triviaControlPanelMessage.channelId, + }, + }, + } satisfies Partial, + { merge: true }, + ); + + // Clear any existing leaderboard entries + const leaderboardDocs = await triviaDocRef + .collection("leaderboard") + .listDocuments(); + await Promise.all(leaderboardDocs.map((doc) => doc.delete())); + + if (startQuestionNow) { + await this.sendNextQuestion( + triviaDocRef, + guildData, + channel, + notify, + adminConsole, + triviaInfoMessage, + startEmbed, + ); + } + + const sendQuestionIntervalId = setInterval(async () => { + const triviaDoc = await triviaDocRef.get(); + const paused = triviaDoc.data()?.paused; + if (!paused) { + const questionsRemaining = await this.sendNextQuestion( + triviaDocRef, + guildData, + channel, + notify, + adminConsole, + triviaInfoMessage, + startEmbed, + ); + if (!questionsRemaining) { + clearInterval(sendQuestionIntervalId); + await logToAdminLog( + guild, + `<@&${guildData.roleIds.staff}> Trivia has ended!`, + ); + } + } + }, interval * 60000); + } + + public static makeStartEmbed() { + return new EmbedBuilder() + .setTitle( + "Trivia contest starting soon! Answer questions for a chance to win prizes!", + ) + .setDescription( + "Note: Short-answer questions are non-case sensitive but any extra or missing symbols will be considered incorrect.", + ) + .addFields([ + { + name: "Click the 🍀 emoji below to be notified when a new question drops!", + value: "You can un-react to stop.", + }, + ]); + } + + public static makeAdminConsoleComponents(interval: number, paused: boolean) { + const embed = new EmbedBuilder() + .setTitle("Trivia Contest") + .setDescription("Trivia contest has been started!") + .addFields({ name: "Interval", value: `${interval} minutes` }) + .addFields({ name: "Status", value: paused ? "Paused" : "Active" }); + + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId("play-trivia") + .setLabel("Play") + .setStyle(ButtonStyle.Primary), + ) + .addComponents( + new ButtonBuilder() + .setCustomId("pause-trivia") + .setLabel("Pause") + .setStyle(ButtonStyle.Primary), + ) + .addComponents( + new ButtonBuilder() + .setCustomId("refresh-trivia") + .setLabel("Refresh leaderboard") + .setStyle(ButtonStyle.Secondary), + ); + + return { embed, row }; + } + + /** Returns true if there are still questions left and false if all questions have been asked */ + private async sendNextQuestion( + triviaDocRef: DocumentReference, + guildDocData: GuildDoc, + channel: GuildTextBasedChannel, + notify: Role | APIRole, + adminConsoleChannel: GuildTextBasedChannel, + triviaInfoMessage: Message, + startEmbed: EmbedBuilder, + ) { + const triviaDoc = (await triviaDocRef.get()).data() as TriviaDoc; + const { interval, askedQuestions } = triviaDoc; + + const nextQuestion = await triviaDocRef + .collection("questions") + .where( + FieldPath.documentId(), + "not-in", + askedQuestions.length ? askedQuestions : ["non-exist"], // not-in array cannot be empty + ) + .limit(1) + .get(); + + if (nextQuestion.empty) return false; + + const question = nextQuestion.docs[0].data() as TriviaQuestionDoc; + const questionId = nextQuestion.docs[0].id; + + const qEmbed = new EmbedBuilder() + .setTitle( + `New trivia question! Answer within ${interval * 0.75 * 60} seconds:`, + ) + .setDescription(question.question); + + const qMessage = await channel.send({ + content: "<@&" + notify.id + ">", + embeds: [qEmbed], + }); + + let winner: GuildMember | undefined; + if (question.answers) { + winner = await this.selectMcqWinner( + question, + channel, + qMessage, + interval, + ); + } else { + const selectRow = + new ActionRowBuilder().addComponents( + new UserSelectMenuBuilder().setCustomId("winner").setMaxValues(1), + ); + const questionMsg = await adminConsoleChannel.send({ + content: `<@&${guildDocData.roleIds.staff}> need manual review!`, + embeds: [qEmbed], + components: [selectRow], + }); + + winner = await this.selectShortAnswerWinner( + guildDocData.roleIds.admin, + guildDocData.roleIds.staff, + channel, + questionMsg, + ); + } + + if (winner) { + await this.updateLeaderboard(triviaDocRef, winner.id, questionId); + const leaderboardEmbed = + await StartTrivia.getLeaderboardEmbed(triviaDocRef); + await triviaInfoMessage.edit({ embeds: [startEmbed, leaderboardEmbed] }); + } + + await triviaDocRef.update({ + askedQuestions: FieldValue.arrayUnion(questionId), + }); + + return true; + } + + private async selectMcqWinner( + question: TriviaQuestionDoc, + channel: GuildTextBasedChannel, + message: Message, + interval: number, + ) { + const filter = (m: Message) => !m.author.bot; + const collector = channel.createMessageCollector({ + filter, + time: interval * 60000 * 0.75, + }); + + const stringAnswerValid = ( + m: Message, + answers: string[], + needAllAnswers: boolean, + ) => { + const pred = (answer: string) => + m.content.toLowerCase().includes(answer.trim().toLowerCase()); + return needAllAnswers ? answers.every(pred) : answers.some(pred); + }; + + const numberAnswerValid = (m: Message, answers: string[]) => { + return answers.some((correctAnswer) => m.content === correctAnswer); + }; + + const answers = question.answers!; + return new Promise((resolve) => { + collector.on("collect", async (m) => { + if ( + (question.needAllAnswers && stringAnswerValid(m, answers, true)) || + (!question.needAllAnswers && + ((!isNaN(Number(answers[0])) && numberAnswerValid(m, answers)) || + stringAnswerValid(m, answers, false))) + ) { + await channel.send( + `Congrats <@${m.author.id}> for getting the correct answer! The correct answer is ${question.needAllAnswers ? "all of" : "any of"}: "${answers.join(", ")}".`, + ); + collector.stop(); + resolve(m.member ?? undefined); + } + }); + + collector.on("end", async () => { + await channel.send( + "Answers are no longer being accepted. Stay tuned for the next question!", + ); + resolve(undefined); + }); + }); + } + + private async selectShortAnswerWinner( + adminRoleId: string, + staffRoleId: string, + channel: GuildTextBasedChannel, + message: Message, + ) { + const collector = message.createMessageComponentCollector({ + filter: (i) => + !i.user.bot && + i.customId === "winner" && + checkMemberRoles(i.member!, [adminRoleId, staffRoleId]), + }); + + return new Promise((resolve) => { + collector.on("collect", async (i: StringSelectMenuInteraction) => { + const users = i.values; + await i.deferReply({ flags: [MessageFlags.Ephemeral] }); + + const member = i.guild!.members.cache.get(users[0])!; + await message.delete(); + await i.followUp({ + content: `<@${member.id}> has been recorded!`, + flags: [MessageFlags.Ephemeral], + }); + await channel.send( + "Congrats <@" + + member.id + + "> for the best answer to the last question!", + ); + collector.stop(); + resolve(member); + }); + }); + } + + public static async getLeaderboardEmbed(triviaDocRef: DocumentReference) { + const leaderboard = await triviaDocRef.collection("leaderboard").get(); + const leaderboardData = leaderboard.docs.map((doc) => { + const leaderboardDoc = doc.data() as TriviaLeaderboardDoc; + return { + id: doc.id, + points: leaderboardDoc.score, + }; + }); + + leaderboardData.sort((a, b) => b.points - a.points); + + const leaderboardEmbed = new EmbedBuilder().setTitle("Leaderboard"); + leaderboardEmbed.setDescription( + leaderboardData + .map( + (l) => + `<@${l.id}>: ${l.points} ${l.points === 1 ? "point" : "points"}`, + ) + .join("\n"), + ); + + return leaderboardEmbed; + } + + private async updateLeaderboard( + triviaDocRef: DocumentReference, + winnerId: string, + questionId: string, + points: number = 1, + ) { + const leaderboardDocRef = triviaDocRef + .collection("leaderboard") + .doc(winnerId); + + if ((await leaderboardDocRef.get()).exists) { + await leaderboardDocRef.update({ + answeredQuestions: FieldValue.arrayUnion(questionId), + score: FieldValue.increment(points), + }); + } else { + await leaderboardDocRef.set({ + answeredQuestions: [questionId], + score: points, + } satisfies TriviaLeaderboardDoc); + } + } +} + +export default StartTrivia; diff --git a/src/commands/StartVerification.ts b/src/commands/StartVerification.ts new file mode 100644 index 00000000..31651775 --- /dev/null +++ b/src/commands/StartVerification.ts @@ -0,0 +1,154 @@ +import BaseCommand from "@/classes/BaseCommand"; +import { idHints } from "@/constants/id-hints"; +import { GuildDoc } from "@/types/db/guild"; +import { VerificationDoc } from "@/types/db/verification"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Command, CommandOptionsRunTypeEnum } from "@sapphire/framework"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + GuildTextBasedChannel, + RoleSelectMenuBuilder, + SlashCommandBuilder, +} from "discord.js"; + +@ApplyOptions({ + name: "start-verification", + description: "Starts verification prompt in current channel.", + runIn: CommandOptionsRunTypeEnum.GuildText, + preconditions: ["AdminRoleOnly"], +}) +class StartVerification extends BaseCommand { + protected override buildCommand(builder: SlashCommandBuilder) { + return builder + .addRoleOption((option) => + option + .setName("hacker_role") + .setDescription("The hacker role.") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("sponsor_role") + .setDescription("The sponsor role.") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("mentor_role") + .setDescription("The mentor role.") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("organizer_role") + .setDescription("The organizer role.") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("photographer_role") + .setDescription("The photographer role.") + .setRequired(true), + ) + .addRoleOption((option) => + option + .setName("volunteer_role") + .setDescription("The volunteer role.") + .setRequired(true), + ); + } + + protected override setCommandOptions() { + return { + idHints: [idHints.startVerification], + }; + } + + public override async chatInputRun( + interaction: Command.ChatInputCommandInteraction, + ) { + const guildId = interaction.guildId!; + + const embed = new EmbedBuilder().setTitle( + `Please click the button below to check-in to the ${interaction.guild?.name} server! Make sure you know which email you used to apply to ${interaction.guild?.name}!`, + ); + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("verify") + .setLabel("Check In") + .setStyle(ButtonStyle.Primary), + ); + + await interaction.reply({ + content: + "If you have not already, make sure to enable DMs, emojis, and embeds/link previews in your personal Discord settings! If you have any issues, please find an organizer!", + embeds: [embed], + components: [row as ActionRowBuilder], + }); + + const { row: addRoleRow, embed: addRoleEmbed } = + this.makeAddExtraRoleComponents(); + + const guildDocRef = getGuildDocRef(guildId); + + const guildDocData = (await guildDocRef.get()).data() as GuildDoc; + const adminConsoleChannel = (await interaction.guild?.channels.fetch( + guildDocData.channelIds.adminConsole, + )) as GuildTextBasedChannel; + + await adminConsoleChannel.send({ + components: [addRoleRow], + embeds: [addRoleEmbed], + }); + + const hackerRole = interaction.options.getRole("hacker_role")!; + const sponsorRole = interaction.options.getRole("sponsor_role")!; + const mentorRole = interaction.options.getRole("mentor_role")!; + const organizerRole = interaction.options.getRole("organizer_role")!; + const photographerRole = interaction.options.getRole("photographer_role")!; + const volunteerRole = interaction.options.getRole("volunteer_role")!; + + const verificationDocRef = guildDocRef + .collection("command-data") + .doc("verification"); + + await verificationDocRef.set({ + extraRoles: {}, + roleIds: { + hacker: hackerRole.id, + sponsor: sponsorRole.id, + mentor: mentorRole.id, + organizer: organizerRole.id, + photographer: photographerRole.id, + volunteer: volunteerRole.id, + }, + } satisfies VerificationDoc); + } + + private makeAddExtraRoleComponents() { + const embed = new EmbedBuilder() + .setTitle("Add Extra Verification Role") + .setDescription( + "Select a role from the dropdown below to add it as a new verification role option.", + ); + + const roleSelect = new RoleSelectMenuBuilder() + .setCustomId("add-extra-role-select") + .setPlaceholder("Select a role to add...") + .setMinValues(1) + .setMaxValues(1); + + const row = new ActionRowBuilder().addComponents( + roleSelect, + ); + + return { embed, row }; + } +} + +export default StartVerification; diff --git a/src/constants/id-hints.ts b/src/constants/id-hints.ts new file mode 100644 index 00000000..b363332c --- /dev/null +++ b/src/constants/id-hints.ts @@ -0,0 +1,33 @@ +/** + * ID hints are linked to the Discord developer bot account. + * Since we have separate dev and prod bot accounts, we need separate id hints. + */ + +const devIdHints = { + clearChat: "1402483606294630420", + initBot: "1381884387972612129", + startAddMembers: "1402483614603542649", + startLoadQuestions: "1402483616302366791", + startOrganizerCheckIn: "1402483603123863672", + startPronouns: "1386121970944442559", + startReport: "1402483690331705406", + startTickets: "1402483611156086924", + startTrivia: "1386423290645712967", + startVerification: "1385475309876412540", +}; + +const prodIdHints = { + clearChat: "1051737346720137246", + initBot: "1051737348502728764", + startAddMembers: "1402484807379714088", + startLoadQuestions: "1402484809653162086", + startOrganizerCheckIn: "1402484796579643433", + startPronouns: "1402484814208045240", + startReport: "1214159059880517652", + startTickets: "1402484799821578270", + startTrivia: "1402484818427514910", + startVerification: "1060545714133938309", +}; + +export const idHints = + process.env.NODE_ENV === "production" ? prodIdHints : devIdHints; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..57deb171 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,102 @@ +import { LogLevel, SapphireClient } from "@sapphire/framework"; +import { GatewayIntentBits } from "discord.js"; +import "dotenv/config"; + +import { GuildDoc } from "./types/db/guild"; +import { PronounsDoc } from "./types/db/pronouns"; +import { TicketDoc } from "./types/db/ticket"; +import { getSavedMessage } from "./util/discord"; +import { initializeFirebase } from "./util/firestore"; +import { getFactotumBaseDocRef, getGuildDocRef } from "./util/nwplus-firestore"; + +const ENV_KEYS = ["DISCORD_BOT_TOKEN", "FIREBASE_SERVICE_ACCOUNT"] as const; +const env = Object.fromEntries( + ENV_KEYS.map((key) => { + // Dev environment variables are prefixed with DEV_ + const envKey = process.env.NODE_ENV === "development" ? `DEV_${key}` : key; + if (!process.env[envKey]) + throw new Error(`Missing environment variable: ${envKey}`); + return [key, process.env[envKey]]; + }), +) as Record<(typeof ENV_KEYS)[number], string>; + +const initializeBot = async () => { + const client = new SapphireClient({ + intents: [ + GatewayIntentBits.GuildModeration, + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildExpressions, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.GuildMessageReactions, + GatewayIntentBits.DirectMessageReactions, + GatewayIntentBits.MessageContent, + ], + shards: "auto", + logger: { + level: + process.env.NODE_ENV === "development" ? LogLevel.Debug : LogLevel.Info, + }, + }); + + await client.login(env.DISCORD_BOT_TOKEN); + initializeFirebase(env.FIREBASE_SERVICE_ACCOUNT); + + console.log("Fetching saved messages into cache to listen for emojis..."); + + const querySnapshot = await getFactotumBaseDocRef() + .collection("guilds") + .get(); + + const loadMessagePromises = querySnapshot.docs.map(async (doc) => { + const guild = await client.guilds.fetch(doc.id); + console.log(`Processing guild: ${doc.id} - ${guild.name}`); + + const commandDataDocRef = doc.ref.collection("command-data"); + + // Load pronouns message + const pronounsDataDoc = await commandDataDocRef.doc("pronouns").get(); + if (pronounsDataDoc.exists) { + const { savedMessage } = pronounsDataDoc.data() as PronounsDoc; + const pronounsMessage = await getSavedMessage( + guild, + savedMessage.messageId, + savedMessage.channelId, + ); + if (pronounsMessage) { + pronounsMessage.fetch(); + console.log(`Loaded pronouns message into listener cache for ${guild.name}`); + } + } + + // Load request ticket message + const ticketsDataDoc = await commandDataDocRef.doc("tickets").get(); + if (ticketsDataDoc.exists) { + const { savedMessages } = ticketsDataDoc.data() as TicketDoc; + const mentorSpecialtySelectionMessage = await getSavedMessage( + guild, + savedMessages.mentorSpecialtySelection.messageId, + savedMessages.mentorSpecialtySelection.channelId, + ); + if (mentorSpecialtySelectionMessage) { + await mentorSpecialtySelectionMessage.fetch(); + console.log( + `Loaded mentor specialty selection message into listener cache for ${guild.name}`, + ); + } + } + }); + + // Keep going if there are any errors with loading any messages + await Promise.allSettled(loadMessagePromises); + + console.log("Finished processing all guild documents"); + + client.on("guildMemberAdd", async (member) => { + const guildDocRef = await getGuildDocRef(member.guild.id).get(); + const guildDocData = guildDocRef.data() as GuildDoc; + await member.roles.add(guildDocData.roleIds.unverified); + }); +}; + +initializeBot(); diff --git a/src/interaction-handlers/AddExtraRoleHandler.ts b/src/interaction-handlers/AddExtraRoleHandler.ts new file mode 100644 index 00000000..52cd9bdb --- /dev/null +++ b/src/interaction-handlers/AddExtraRoleHandler.ts @@ -0,0 +1,120 @@ +import { VerificationDoc } from "@/types/db/verification"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { + InteractionHandler, + InteractionHandlerTypes, +} from "@sapphire/framework"; +import { + ActionRowBuilder, + MessageFlags, + ModalBuilder, + type RoleSelectMenuInteraction, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; + +@ApplyOptions({ + interactionHandlerType: InteractionHandlerTypes.SelectMenu, +}) +class AddExtraRoleHandler extends InteractionHandler { + public async run(interaction: RoleSelectMenuInteraction) { + const selectedRole = interaction.roles.first(); + if (!selectedRole) { + return interaction.reply({ + content: "No role was selected. Please try again.", + flags: [MessageFlags.Ephemeral], + }); + } + + const modal = new ModalBuilder() + .setCustomId("add-extra-role-modal") + .setTitle("Add Extra Role") + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("role-name") + .setLabel("Role Name") + .setStyle(TextInputStyle.Short) + .setPlaceholder("Enter the role name (e.g., 'judge', 'mlh')") + .setRequired(true) + .setMaxLength(50), + ), + ); + + await interaction.showModal(modal); + + const submitted = await interaction.awaitModalSubmit({ + time: 300000, + filter: (j) => j.user.id === interaction.user.id, + }); + + if (!submitted) { + return interaction.reply({ + content: "You did not submit the modal in time.", + flags: [MessageFlags.Ephemeral], + }); + } + + await submitted.deferReply({ flags: [MessageFlags.Ephemeral] }); + + const roleName = submitted.fields + .getTextInputValue("role-name") + .trim() + .toLowerCase(); + + if (!roleName) { + return submitted.followUp({ + content: "Role name cannot be empty. Please try again.", + flags: [MessageFlags.Ephemeral], + }); + } + + try { + const guildDocRef = getGuildDocRef(interaction.guildId!); + const verificationDocRef = guildDocRef + .collection("command-data") + .doc("verification"); + + const verificationDoc = await verificationDocRef.get(); + const verificationData = verificationDoc.data() as VerificationDoc; + + // Check if role name already exists in main roles or extra roles + if ( + verificationData.roleIds[ + roleName as keyof typeof verificationData.roleIds + ] || + verificationData.extraRoles[roleName] + ) { + return submitted.followUp({ + content: `Role name "${roleName}" is already in use. Please choose a different name.`, + flags: [MessageFlags.Ephemeral], + }); + } + + await verificationDocRef.update({ + [`extraRoles.${roleName}`]: selectedRole.id, + }); + + await submitted.followUp({ + content: `Successfully added extra role mapping: "${roleName}" → ${selectedRole.name} (${selectedRole.id})`, + flags: [MessageFlags.Ephemeral], + }); + } catch (error) { + console.error("Error adding extra role:", error); + await submitted.followUp({ + content: + "An error occurred while adding the extra role. Please try again or contact an administrator.", + flags: [MessageFlags.Ephemeral], + }); + } + } + + public override parse(interaction: RoleSelectMenuInteraction) { + if (interaction.customId !== "add-extra-role-select") return this.none(); + return this.some(); + } +} + +export default AddExtraRoleHandler; diff --git a/src/interaction-handlers/AddMembersRoleSelectHandler.ts b/src/interaction-handlers/AddMembersRoleSelectHandler.ts new file mode 100644 index 00000000..4f43d4b0 --- /dev/null +++ b/src/interaction-handlers/AddMembersRoleSelectHandler.ts @@ -0,0 +1,159 @@ +import { OtherAttendeesDoc } from "@/types/db/verification"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { + InteractionHandler, + InteractionHandlerTypes, +} from "@sapphire/framework"; +import { + ActionRowBuilder, + MessageFlags, + ModalBuilder, + ModalSubmitInteraction, + type StringSelectMenuInteraction, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; + +@ApplyOptions({ + interactionHandlerType: InteractionHandlerTypes.SelectMenu, +}) +class AddMembersRoleSelectHandler extends InteractionHandler { + public async run(interaction: StringSelectMenuInteraction) { + const selectedRole = interaction.values[0]; + + const modal = new ModalBuilder() + .setCustomId(`emails-modal-${selectedRole}`) + .setTitle(`Enter all emails to be added as ${selectedRole}`) + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("emails") + .setLabel("Newline-separated Emails") + .setStyle(TextInputStyle.Paragraph) + .setPlaceholder("Enter emails separated by new lines or commas") + .setRequired(true), + ), + ); + + await interaction.showModal(modal); + + const submitted = await interaction.awaitModalSubmit({ + time: 300000, + filter: (j) => j.user.id === interaction.user.id, + }); + + if (!submitted) { + return interaction.reply({ + content: "You did not submit the modal in time.", + flags: [MessageFlags.Ephemeral], + }); + } + + await submitted.deferReply({ flags: [MessageFlags.Ephemeral] }); + + await this.handleEmailSubmission(submitted, selectedRole); + } + + private async handleEmailSubmission( + interaction: ModalSubmitInteraction, + participantsType: string, + ) { + const emailsRaw = interaction.fields.getTextInputValue("emails"); + const emails = emailsRaw + .split(/[\r?\n|\r|\n|,]+/g) + .map((email: string) => email.trim()) + .filter(Boolean); + + if (emails.length === 0) { + return interaction.followUp({ + content: "No valid emails were provided!", + flags: [MessageFlags.Ephemeral], + }); + } + + const guildDocRef = getGuildDocRef(interaction.guildId!); + const verificationDocRef = guildDocRef + .collection("command-data") + .doc("verification"); + + const otherAttendeesCollectionRef = + verificationDocRef.collection("other-attendees"); + + try { + // Perform batch operations + const db = otherAttendeesCollectionRef.firestore; + const batch = db.batch(); + let addCount = 0; + let updateCount = 0; + + for (const email of emails) { + const existingDocs = await otherAttendeesCollectionRef + .where("email", "==", email) + .limit(1) + .get(); + + if (!existingDocs.empty) { + // Update existing record - append role to existing roles + const docRef = existingDocs.docs[0]!.ref; + const existingData = + existingDocs.docs[0]!.data() as OtherAttendeesDoc; + const currentRoles = existingData.roles || []; + + // Add the new role if it's not already present + const updatedRoles = currentRoles.includes(participantsType) + ? currentRoles + : [...currentRoles, participantsType]; + + batch.update(docRef, { + email, + roles: updatedRoles, + }); + updateCount++; + } else { + // Create new record + const newDocRef = otherAttendeesCollectionRef.doc(); + const userData: OtherAttendeesDoc = { + email, + roles: [participantsType], + }; + batch.set(newDocRef, userData); + addCount++; + } + } + + // Execute batch operation + await batch.commit(); + + const successCount = addCount + updateCount; + let responseMessage = `Successfully processed ${successCount} emails as ${participantsType}`; + + if (addCount > 0 && updateCount > 0) { + responseMessage += `\n• Added: ${addCount} new emails\n• Updated: ${updateCount} existing emails`; + } else if (addCount > 0) { + responseMessage += `\n• Added: ${addCount} new emails`; + } else if (updateCount > 0) { + responseMessage += `\n• Updated: ${updateCount} existing emails`; + } + + await interaction.followUp({ + content: responseMessage, + flags: [MessageFlags.Ephemeral], + }); + } catch (error) { + console.error("Error processing emails in batch:", error); + await interaction.followUp({ + content: `An error occurred while processing the emails. Please try again.`, + flags: [MessageFlags.Ephemeral], + }); + } + } + + public override parse(interaction: StringSelectMenuInteraction) { + if (interaction.customId !== "add-members-select") return this.none(); + return this.some(); + } +} + +export default AddMembersRoleSelectHandler; diff --git a/src/interaction-handlers/ReportHandler.ts b/src/interaction-handlers/ReportHandler.ts new file mode 100644 index 00000000..6f16a51a --- /dev/null +++ b/src/interaction-handlers/ReportHandler.ts @@ -0,0 +1,95 @@ +import { logToAdminLog } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { + InteractionHandler, + InteractionHandlerTypes, +} from "@sapphire/framework"; +import { + ActionRowBuilder, + type ButtonInteraction, + MessageFlags, + ModalBuilder, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; + +@ApplyOptions({ + interactionHandlerType: InteractionHandlerTypes.Button, +}) +class ReportHandler extends InteractionHandler { + public async run(interaction: ButtonInteraction) { + const modal = new ModalBuilder() + .setCustomId("reportModal") + .setTitle("Report") + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("users") + .setLabel("User(s) to report") + .setStyle(TextInputStyle.Short) + .setPlaceholder("user1, user2") + .setRequired(true), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("reason") + .setLabel("Reason for report") + .setStyle(TextInputStyle.Short) + .setPlaceholder("Reason for report") + .setRequired(true), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("explanation") + .setLabel("Detailed explanation") + .setStyle(TextInputStyle.Paragraph) + .setPlaceholder("Detailed explanation") + .setMaxLength(300) + .setRequired(true), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("channel") + .setLabel("Channel (if possible)") + .setStyle(TextInputStyle.Short) + .setPlaceholder("#channel-name") + .setRequired(false), + ), + ); + + await interaction.showModal(modal); + + const submitted = await interaction.awaitModalSubmit({ + time: 300000, + filter: (j) => j.user.id === interaction.user.id, + }); + + if (!submitted) { + return interaction.reply({ + content: "You did not submit the modal in time.", + flags: [MessageFlags.Ephemeral], + }); + } + + const message = `Report from ${interaction.user.username} in ${submitted.fields.getTextInputValue("channel") ?? "unknown"} channel: +User(s): ${submitted.fields.getTextInputValue("users")} +Reason: ${submitted.fields.getTextInputValue("reason")} +Detailed explanation: ${submitted.fields.getTextInputValue("explanation")} + `; + + await logToAdminLog(interaction.guild!, message); + await submitted.reply({ + content: + "Thank you for taking the time to report users who are not following server or MLH rules. You help makes our community safer!", + flags: [MessageFlags.Ephemeral], + }); + } + + public override parse(interaction: ButtonInteraction) { + if (interaction.customId !== "report") return this.none(); + return this.some(); + } +} + +export default ReportHandler; diff --git a/src/interaction-handlers/VerifyHandler.ts b/src/interaction-handlers/VerifyHandler.ts new file mode 100644 index 00000000..4b1aa759 --- /dev/null +++ b/src/interaction-handlers/VerifyHandler.ts @@ -0,0 +1,245 @@ +import { GuildDoc } from "@/types/db/guild"; +import { + HackersDoc, + OtherAttendeesDoc, + VerificationDoc, +} from "@/types/db/verification"; +import { checkMemberRoles } from "@/util/discord"; +import { getGuildDocRef, getHackathonDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { + InteractionHandler, + InteractionHandlerTypes, +} from "@sapphire/framework"; +import { + ActionRowBuilder, + type ButtonInteraction, + GuildMember, + MessageFlags, + ModalBuilder, + ModalSubmitInteraction, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; +import { DocumentReference, FieldValue } from "firebase-admin/firestore"; + +enum VerifyResult { + SUCCESS = 0, + FAILURE = 1, + ALREADY_VERIFIED = 2, +} + +interface VerifyHandlerProps { + guildDocRef: DocumentReference; + email: string; + member: GuildMember; +} + +@ApplyOptions({ + interactionHandlerType: InteractionHandlerTypes.Button, +}) +class VerifyHandler extends InteractionHandler { + public async run(interaction: ButtonInteraction) { + const member = interaction.member as GuildMember; + + const guildDocRef = getGuildDocRef(interaction.guildId!); + const guildDocData = (await guildDocRef.get()).data() as GuildDoc; + if (!checkMemberRoles(member, [guildDocData.roleIds.unverified])) { + return interaction.reply({ + content: + "You are not eligible to be checked in! If you don't have correct access to the server, please contact an organizer.", + flags: [MessageFlags.Ephemeral], + }); + } + + const modal = new ModalBuilder() + .setCustomId("verifyModal") + .setTitle("Verify") + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("email") + .setLabel("Email") + .setStyle(TextInputStyle.Short) + .setPlaceholder("Email") + .setRequired(true), + ), + ); + + await interaction.showModal(modal); + + const submitted = await interaction.awaitModalSubmit({ + time: 300000, + filter: (j) => j.user.id === interaction.user.id, + }); + + if (!submitted) { + return interaction.reply({ + content: "You did not submit the modal in time.", + flags: [MessageFlags.Ephemeral], + }); + } + + await submitted.deferReply({ flags: [MessageFlags.Ephemeral] }); + + await this.handleVerify( + submitted, + [this.verifyHacker, this.verifyOtherRole], + { + guildDocRef, + email: submitted.fields.getTextInputValue("email"), + member, + }, + ); + } + + /** + * Returns early for success and already verified cases. + * If failure, keeps trying all handlers until the last one + */ + private async handleVerify( + interaction: ModalSubmitInteraction, + verificationHandlers: (( + props: VerifyHandlerProps, + ) => Promise)[], + props: VerifyHandlerProps, + ) { + for (let i = 0; i < verificationHandlers.length; i++) { + const result = await verificationHandlers[i](props); + switch (result) { + case VerifyResult.FAILURE: + if (i < verificationHandlers.length - 1) continue; + await interaction.followUp({ + content: + "Unable to verify your email. Please make sure you are using the same email you used to apply. If you are still having trouble verifying, please use the check-in support channel to contact an organizer.", + }); + break; + case VerifyResult.ALREADY_VERIFIED: + await interaction.followUp({ + content: + "You have already been verified! If you are having trouble seeing any channels, please use the check-in support channel to contact an organizer.", + flags: [MessageFlags.Ephemeral], + }); + return; + case VerifyResult.SUCCESS: + default: + await interaction.followUp({ + content: + "Successfully verified! If you are still unable to see any channels, please use the check-in support channel to contact an organizer.", + flags: [MessageFlags.Ephemeral], + }); + return; + } + } + } + + private async verifyHacker({ + guildDocRef, + email, + member, + }: VerifyHandlerProps) { + const guildDocData = (await guildDocRef.get()).data() as GuildDoc; + const hackathonDocRef = getHackathonDocRef(guildDocData.hackathonName); + const hackerDoc = await hackathonDocRef + .collection("Applicants") + .where("basicInfo.email", "==", email) + .where("status.applicationStatus", "==", "acceptedAndAttending") + .limit(1) + .get(); + if (hackerDoc.empty) return VerifyResult.FAILURE; + + const verificationDocRef = guildDocRef + .collection("command-data") + .doc("verification"); + + const existingHackerDocByHackerIdRef = verificationDocRef + .collection("hackers") + .doc(hackerDoc.docs[0].id); + + const existingHackerDocByDiscordIdRef = verificationDocRef + .collection("hackers") + .where("discordId", "==", member.user.id); + + if ( + (await existingHackerDocByHackerIdRef.get()).exists || + !(await existingHackerDocByDiscordIdRef.get()).empty + ) { + return VerifyResult.ALREADY_VERIFIED; + } + + await verificationDocRef + .collection("hackers") + .doc(hackerDoc.docs[0].id) + .set({ + discordId: member.user.id, + verifiedTimestamp: FieldValue.serverTimestamp(), + } as HackersDoc); + + const verificationDocData = ( + await verificationDocRef.get() + ).data() as VerificationDoc; + await member.roles.remove(guildDocData.roleIds.unverified); + await member.roles.add([ + guildDocData.roleIds.verified, + verificationDocData.roleIds.hacker, + ]); + return VerifyResult.SUCCESS; + } + + private async verifyOtherRole({ + guildDocRef, + email, + member, + }: VerifyHandlerProps) { + const verificationDocRef = guildDocRef + .collection("command-data") + .doc("verification"); + const otherRoleDoc = await verificationDocRef + .collection("other-attendees") + .where("email", "==", email) + .limit(1) + .get(); + if (otherRoleDoc.empty) return VerifyResult.FAILURE; + + const existingDocByDiscordIdRef = verificationDocRef + .collection("other-attendees") + .where("discordId", "==", member.user.id); + + if ( + otherRoleDoc.docs[0].get("discordId") || + !(await existingDocByDiscordIdRef.get()).empty + ) { + return VerifyResult.ALREADY_VERIFIED; + } + + await otherRoleDoc.docs[0].ref.update({ + discordId: member.user.id, + verifiedTimestamp: FieldValue.serverTimestamp(), + } as Partial); + + const guildDocData = (await guildDocRef.get()).data() as GuildDoc; + const verificationDocData = ( + await verificationDocRef.get() + ).data() as VerificationDoc; + + await member.roles.remove(guildDocData.roleIds.unverified); + await member.roles.add([ + guildDocData.roleIds.verified, + ...(otherRoleDoc.docs[0].get("roles") as string[]).map( + (roleName) => + // @ts-expect-error already accounting for if roleName is not indexable in roleIds + verificationDocData.roleIds[roleName] ?? + verificationDocData.extraRoles[roleName], + ), + ]); + return VerifyResult.SUCCESS; + } + + public override parse(interaction: ButtonInteraction) { + if (interaction.customId !== "verify") return this.none(); + return this.some(); + } +} + +export default VerifyHandler; diff --git a/src/interaction-handlers/organizer-checkin/CheckInOutHandler.ts b/src/interaction-handlers/organizer-checkin/CheckInOutHandler.ts new file mode 100644 index 00000000..3ed73029 --- /dev/null +++ b/src/interaction-handlers/organizer-checkin/CheckInOutHandler.ts @@ -0,0 +1,99 @@ +import StartOrganizerCheckIn from "@/commands/StartOrganizerCheckIn"; +import { GuildDoc } from "@/types/db/guild"; +import { OrganizerCheckInDoc } from "@/types/db/organizer-check-in"; +import { checkMemberRoles } from "@/util/discord"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { + InteractionHandler, + InteractionHandlerTypes, +} from "@sapphire/framework"; +import { type ButtonInteraction, MessageFlags } from "discord.js"; + +@ApplyOptions({ + interactionHandlerType: InteractionHandlerTypes.Button, +}) +class CheckInOutHandler extends InteractionHandler { + public async run(interaction: ButtonInteraction) { + const guild = interaction.guild!; + const member = guild.members.cache.get(interaction.user.id)!; + + await interaction.deferReply({ flags: [MessageFlags.Ephemeral] }); + + // Get guild data to check roles + const guildDocRef = getGuildDocRef(guild.id); + const guildDoc = await guildDocRef.get(); + const guildData = guildDoc.data() as GuildDoc; + + // Check if user has admin or staff role + if ( + !checkMemberRoles(member, [ + guildData.roleIds.admin, + guildData.roleIds.staff, + ]) + ) { + return interaction.reply({ + content: "You do not have permissions to use this command!", + flags: [MessageFlags.Ephemeral], + }); + } + + // Get organizer check-in data + const organizerCheckInDocRef = guildDocRef + .collection("command-data") + .doc("organizer-check-in"); + + const organizerCheckInDoc = await organizerCheckInDocRef.get(); + if (!organizerCheckInDoc.exists) { + return interaction.reply({ + content: "Organizer check-in system not initialized!", + flags: [MessageFlags.Ephemeral], + }); + } + + const organizerCheckInData = + organizerCheckInDoc.data() as OrganizerCheckInDoc; + + const updatedAttendance = { + ...organizerCheckInData.organizerAttendance, + }; + + const isCheckIn = interaction.customId === "organizer-check-in"; + + if (isCheckIn) { + updatedAttendance[member.user.username] = member.displayName; + } else { + delete updatedAttendance[member.user.username]; + } + + // Update Firestore + await organizerCheckInDocRef.update({ + organizerAttendance: updatedAttendance, + }); + + // Update the message embed + const updatedEmbed = + StartOrganizerCheckIn.generateAttendanceEmbed(updatedAttendance); + await interaction.message.edit({ embeds: [updatedEmbed] }); + + await interaction.followUp({ + content: isCheckIn + ? `Checked in ${member.displayName}. Remember to check out when you leave the venue!` + : `Checked out ${member.displayName}. Please come back soon 🥹`, + flags: [MessageFlags.Ephemeral], + }); + } + + public override parse(interaction: ButtonInteraction) { + if ( + !["organizer-check-in", "organizer-check-out"].includes( + interaction.customId, + ) + ) + return this.none(); + return this.some(); + } +} + +export default CheckInOutHandler; diff --git a/src/interaction-handlers/tickets/AddSpecialtyHandler.ts b/src/interaction-handlers/tickets/AddSpecialtyHandler.ts new file mode 100644 index 00000000..f71ba306 --- /dev/null +++ b/src/interaction-handlers/tickets/AddSpecialtyHandler.ts @@ -0,0 +1,168 @@ +import StartTickets from "@/commands/StartTickets"; +import { MENTOR_SPECIALTIES_MAP, TicketDoc } from "@/types/db/ticket"; +import { VerificationDoc } from "@/types/db/verification"; +import { getSavedMessage } from "@/util/discord"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { + InteractionHandler, + InteractionHandlerTypes, +} from "@sapphire/framework"; +import { + ActionRowBuilder, + type ButtonInteraction, + GuildTextBasedChannel, + MessageFlags, + ModalBuilder, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; + +@ApplyOptions({ + interactionHandlerType: InteractionHandlerTypes.Button, +}) +class AddSpecialtyHandler extends InteractionHandler { + public async run(interaction: ButtonInteraction) { + const guild = interaction.guild!; + const channel = interaction.channel! as GuildTextBasedChannel; + + const guildDocRef = getGuildDocRef(interaction.guildId!); + + const verificationDocRef = guildDocRef + .collection("command-data") + .doc("verification"); + const verificationDocData = ( + await verificationDocRef.get() + ).data() as VerificationDoc; + + await interaction.showModal(this.makeSpecialtyModal()); + const submitted = await interaction.awaitModalSubmit({ + time: 300000, + filter: (j) => j.user.id === interaction.user.id, + }); + + if (!submitted) { + return interaction.reply({ + content: "You did not submit the modal in time.", + flags: [MessageFlags.Ephemeral], + }); + } + await submitted.deferReply(); + + // Get role for new specialty + const specialtyName = submitted.fields + .getTextInputValue("specialty-name") + .replace(/\s+/g, "-"); + + const mentorRole = guild.roles.cache.find( + (role) => role.id === verificationDocData.roleIds.mentor, + )!; + const existingSpecialtyRole = guild.roles.cache.find( + (role) => role.name.toLowerCase() === `M-${specialtyName}`.toLowerCase(), + ); + if (!existingSpecialtyRole) { + await guild.roles.create({ + name: `M-${specialtyName}`, + color: mentorRole.hexColor, + }); + } + + // Wait for emoji reaction + const ticketDocRef = guildDocRef.collection("command-data").doc("tickets"); + const ticketDocData = (await ticketDocRef.get()).data() as TicketDoc; + // Replying doesn't return the correct message type, so we need to use followUp + const askForEmoji = await submitted.followUp({ + content: + "New role received. Please react to this message with the emoji for the role!", + }); + const emojiCollector = askForEmoji.createReactionCollector({ + filter: (reaction, user) => user.id === interaction.user.id, + }); + emojiCollector.on("collect", async (collected) => { + if ( + MENTOR_SPECIALTIES_MAP.has(collected.emoji.name!) || + ticketDocData.extraSpecialties[collected.emoji.name!] + ) { + const rejectMsg = await channel.send( + `<@${interaction.user.id}> Emoji is already used in another role. Please react again.`, + ); + setTimeout(() => rejectMsg.delete(), 5000); + } else { + await askForEmoji.delete(); + emojiCollector.stop(); + + const mentorSpecialtySelectionMessage = await getSavedMessage( + guild, + ticketDocData.savedMessages.mentorSpecialtySelection.messageId, + ticketDocData.savedMessages.mentorSpecialtySelection.channelId, + ); + const requestTicketMessage = await getSavedMessage( + guild, + ticketDocData.savedMessages.requestTicket.messageId, + ticketDocData.savedMessages.requestTicket.channelId, + ); + + if (!mentorSpecialtySelectionMessage || !requestTicketMessage) { + this.container.logger.error( + "Mentor specialty selection message or request ticket message not found", + ); + } + + // Update saved messages with new specialty + await mentorSpecialtySelectionMessage!.edit({ + embeds: [ + StartTickets.makeMentorSpecialtySelectionEmbed({ + ...ticketDocData.extraSpecialties, + [collected.emoji.name!]: specialtyName, + }), + ], + }); + await mentorSpecialtySelectionMessage!.react(collected.emoji); + await requestTicketMessage!.edit({ + components: [ + StartTickets.makeRequestTicketComponents({ + ...ticketDocData.extraSpecialties, + [collected.emoji.name!]: specialtyName, + }).actionRow, + ], + }); + + // Save new specialty to firestore + await ticketDocRef.update({ + extraSpecialties: { + ...ticketDocData.extraSpecialties, + [collected.emoji.name!]: specialtyName, + }, + }); + const successMsg = await channel.send( + `<@${interaction.user.id}> M-${specialtyName} role with emoji ${collected.emoji.name} added!`, + ); + setTimeout(() => successMsg.delete(), 5000); + } + }); + } + + public override parse(interaction: ButtonInteraction) { + if (interaction.customId !== "add-specialty-role") return this.none(); + return this.some(); + } + + private makeSpecialtyModal() { + return new ModalBuilder() + .setCustomId("add-specialty-role-modal") + .setTitle("Add Mentor Specialty Role") + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("specialty-name") + .setLabel("Specialty Name") + .setStyle(TextInputStyle.Short) + .setPlaceholder("Specialty Name") + .setRequired(true), + ), + ); + } +} + +export default AddSpecialtyHandler; diff --git a/src/interaction-handlers/tickets/RequestTicketHandler.ts b/src/interaction-handlers/tickets/RequestTicketHandler.ts new file mode 100644 index 00000000..8579c2b0 --- /dev/null +++ b/src/interaction-handlers/tickets/RequestTicketHandler.ts @@ -0,0 +1,255 @@ +import { TicketDoc } from "@/types/db/ticket"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { + InteractionHandler, + InteractionHandlerTypes, +} from "@sapphire/framework"; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonInteraction, + ButtonStyle, + ComponentType, + EmbedBuilder, + GuildTextBasedChannel, + Message, + MessageFlags, + ModalBuilder, + type StringSelectMenuInteraction, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; + +@ApplyOptions({ + interactionHandlerType: InteractionHandlerTypes.SelectMenu, +}) +class RequestTicketHandler extends InteractionHandler { + public async run(interaction: StringSelectMenuInteraction) { + const ticketTypes = interaction.values; + + if (ticketTypes.length === 0) { + return interaction.reply({ + content: "You must select at least one ticket type.", + flags: [MessageFlags.Ephemeral], + }); + } + + await interaction.showModal(this.makeRequestTicketModal()); + const submitted = await interaction.awaitModalSubmit({ + time: 300000, + filter: (j) => j.user.id === interaction.user.id, + }); + + if (!submitted) { + return interaction.reply({ + content: "You did not submit the modal in time.", + flags: [MessageFlags.Ephemeral], + }); + } + await submitted.deferReply({ flags: [MessageFlags.Ephemeral] }); + + const ticketDocRef = getGuildDocRef(interaction.guild!.id) + .collection("command-data") + .doc("tickets"); + + const description = + submitted.fields.getTextInputValue("ticket-description"); + const location = submitted.fields.getTextInputValue("location"); + const ticketRoleStrings = ticketTypes.map((type) => { + const role = interaction.guild!.roles.cache.find( + (role) => role.name.toLowerCase() === `M-${type}`.toLowerCase(), + ); + if (!role) return ""; + return `<@&${role.id}>`; + }); + + // Send ticket to incoming tickets channel + const ticketData = (await ticketDocRef.get()).data() as TicketDoc; + const ticketNumber = ticketData.currentTicketCount + 1; + const { newTicketEmbed, acceptTicketRow } = this.makeNewTicketComponents( + ticketNumber, + description, + location, + ); + const incomingTicketsChannel = await interaction.guild!.channels.fetch( + ticketData.channelIds.incomingTicketsChannel, + ); + const ticketMsg = await ( + incomingTicketsChannel as GuildTextBasedChannel + ).send({ + content: `${ticketRoleStrings.join(", ")}: ticket requested by <@${submitted.user.id}>`, + embeds: [newTicketEmbed], + components: [acceptTicketRow], + }); + + await ticketDocRef.update({ + currentTicketCount: ticketNumber, + }); + const ticketReminder = setTimeout(() => { + ticketMsg.reply( + `${ticketRoleStrings.join(", ")}: ticket ${ticketNumber} still needs help!`, + ); + }, ticketData.unansweredTicketTime * 60000); + + // Send ticket confirmation to user + const { confirmationEmbed, deleteTicketRow } = + this.makeTicketConfirmationComponents( + ticketNumber, + description, + location, + ); + const ticketReceipt = await submitted.user.send({ + embeds: [confirmationEmbed], + content: "You will be notified when a mentor accepts your ticket!", + components: [deleteTicketRow], + }); + + // Listen for delete ticket button + this.listenToButton(ticketReceipt, async (deleteInteraction) => { + clearTimeout(ticketReminder); + + await ticketMsg.edit({ + embeds: [ + new EmbedBuilder(ticketMsg.embeds[0].data) + .setColor("#FFCCCB") + .addFields([{ name: "Ticket closed", value: "Deleted by hacker" }]), + ], + components: [], + }); + await deleteInteraction.reply("Ticket deleted!"); + await ticketReceipt.edit({ components: [] }); + }); + + // Listen for accept ticket button + this.listenToButton(ticketMsg, async (acceptInteraction) => { + clearTimeout(ticketReminder); + + await ticketMsg.edit({ + embeds: [ + new EmbedBuilder(ticketMsg.embeds[0].data) + .setColor("#0096FF") + .addFields([ + { + name: "Helped by:", + value: "<@" + acceptInteraction.user.id + ">", + }, + ]), + ], + components: [], + }); + await submitted.user.send( + "Your ticket number " + + ticketNumber + + " has been accepted by a mentor! They will be making their way to you shortly.", + ); + await ticketReceipt.edit({ components: [] }); + + await acceptInteraction.reply({ + content: + "Thanks for accepting their ticket! Please head to their stated location. If you need to contact them, you can click on their username above to DM them!", + flags: [MessageFlags.Ephemeral], + }); + }); + + await submitted.followUp({ + content: "Your ticket has been submitted!", + flags: [MessageFlags.Ephemeral], + }); + } + + public override parse(interaction: StringSelectMenuInteraction) { + if (interaction.customId !== "request-ticket") return this.none(); + return this.some(); + } + + private makeRequestTicketModal() { + return new ModalBuilder() + .setCustomId("request-ticket-modal") + .setTitle("Request Ticket") + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("ticket-description") + .setLabel("Brief description of your problem") + .setMaxLength(300) + .setStyle(TextInputStyle.Paragraph) + .setPlaceholder("Describe your problem here") + .setRequired(true), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("location") + .setLabel("Where would you like to meet your mentor?") + .setPlaceholder("Help your mentor find you!") + .setMaxLength(300) + .setStyle(TextInputStyle.Paragraph) + .setRequired(true), + ), + ); + } + + private makeNewTicketComponents( + ticketNumber: number, + description: string, + location: string, + ) { + const newTicketEmbed = new EmbedBuilder() + .setTitle(`Ticket #${ticketNumber}`) + .setColor("#d3d3d3") + .addFields([ + { name: "Problem description", value: description }, + { name: "Where to meet", value: location }, + ]); + + const acceptTicketRow = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("accept-ticket") + .setLabel("Accept ticket") + .setStyle(ButtonStyle.Success), + ); + + return { newTicketEmbed, acceptTicketRow }; + } + + private makeTicketConfirmationComponents( + ticketNumber: number, + description: string, + location: string, + ) { + const confirmationEmbed = new EmbedBuilder() + .setTitle("Your ticket is number " + ticketNumber) + .addFields([ + { + name: "Problem description", + value: description, + }, + { + name: "Where to meet", + value: location, + }, + ]); + const deleteTicketRow = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId("delete-ticket") + .setLabel("Delete ticket") + .setStyle(ButtonStyle.Danger), + ); + + return { confirmationEmbed, deleteTicketRow }; + } + + private listenToButton( + message: Message, + callback: (interaction: ButtonInteraction) => Promise, + ) { + const collector = + message.createMessageComponentCollector({ + filter: (i) => !i.user.bot && i.isButton(), + }); + collector.on("collect", callback); + } +} + +export default RequestTicketHandler; diff --git a/src/interaction-handlers/trivia/AddQuestionHandler.ts b/src/interaction-handlers/trivia/AddQuestionHandler.ts new file mode 100644 index 00000000..e5294f57 --- /dev/null +++ b/src/interaction-handlers/trivia/AddQuestionHandler.ts @@ -0,0 +1,148 @@ +import { TriviaQuestionDoc } from "@/types/db/trivia"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { + InteractionHandler, + InteractionHandlerTypes, +} from "@sapphire/framework"; +import { + ActionRowBuilder, + type ButtonInteraction, + MessageFlags, + ModalBuilder, + TextInputBuilder, + TextInputStyle, +} from "discord.js"; + +@ApplyOptions({ + interactionHandlerType: InteractionHandlerTypes.Button, +}) +class AddQuestionHandler extends InteractionHandler { + public async run(interaction: ButtonInteraction) { + const modal = new ModalBuilder() + .setCustomId("add-question-modal") + .setTitle("Add New Trivia Question") + .addComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("question") + .setLabel("Question Text") + .setStyle(TextInputStyle.Paragraph) + .setPlaceholder("Enter your trivia question here...") + .setMaxLength(1000) + .setRequired(true), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("answers") + .setLabel("Answers (optional)") + .setStyle(TextInputStyle.Short) + .setPlaceholder( + "answer1, answer2, answer3 (leave empty for manual review)", + ) + .setMaxLength(500) + .setRequired(false), + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setCustomId("needAllAnswers") + .setLabel("Need All Answers? (optional)") + .setStyle(TextInputStyle.Short) + .setPlaceholder( + "yes/true if all answers required, otherwise leave empty", + ) + .setMaxLength(10) + .setRequired(false), + ), + ); + + await interaction.showModal(modal); + + const submitted = await interaction.awaitModalSubmit({ + time: 300000, + filter: (j) => j.user.id === interaction.user.id, + }); + + if (!submitted) { + return interaction.reply({ + content: "You did not submit the modal in time.", + flags: [MessageFlags.Ephemeral], + }); + } + + // Extract form data + const questionText = submitted.fields.getTextInputValue("question").trim(); + const answersText = submitted.fields.getTextInputValue("answers").trim(); + const needAllAnswersText = submitted.fields + .getTextInputValue("needAllAnswers") + .trim() + .toLowerCase(); + + // Validate question text + if (!questionText) { + return submitted.reply({ + content: "Question text cannot be empty!", + flags: [MessageFlags.Ephemeral], + }); + } + + // Process answers + let answers: string[] | undefined; + let needAllAnswers: boolean | undefined; + + if (answersText) { + answers = answersText + .split(",") + .map((answer) => answer.trim()) + .filter((answer) => answer.length > 0); + + if (answers.length === 0) { + answers = undefined; + } + } + + // Process needAllAnswers flag + if (answers && needAllAnswersText) { + needAllAnswers = + needAllAnswersText === "yes" || needAllAnswersText === "true"; + } + + // Create question document + const questionDoc: TriviaQuestionDoc = { + question: questionText, + ...(answers && { answers }), + ...(needAllAnswers !== undefined && { needAllAnswers }), + }; + + try { + // Save to Firestore + const guildDocRef = getGuildDocRef(interaction.guildId!); + const triviaDocRef = guildDocRef.collection("command-data").doc("trivia"); + await triviaDocRef.collection("questions").add(questionDoc); + + const questionType = answers ? "MCQ" : "Manual Review"; + const answerInfo = answers + ? `\nAnswers: ${answers.join(", ")}${needAllAnswers ? " (all required)" : ""}` + : ""; + + await submitted.reply({ + content: `Question added successfully!\n\n**Question:** ${questionText}${answerInfo}\n**Type:** ${questionType}`, + flags: [MessageFlags.Ephemeral], + }); + } catch (error) { + console.error("Error saving question:", error); + await submitted.reply({ + content: "Failed to save question. Please try again later.", + flags: [MessageFlags.Ephemeral], + }); + } + } + + public override parse(interaction: ButtonInteraction) { + if (interaction.customId !== "add-question") return this.none(); + return this.some(); + } +} + +export default AddQuestionHandler; diff --git a/src/interaction-handlers/trivia/ControlPanelHandler.ts b/src/interaction-handlers/trivia/ControlPanelHandler.ts new file mode 100644 index 00000000..0d8da0a4 --- /dev/null +++ b/src/interaction-handlers/trivia/ControlPanelHandler.ts @@ -0,0 +1,101 @@ +import StartTrivia from "@/commands/StartTrivia"; +import { TriviaDoc } from "@/types/db/trivia"; +import { getSavedMessage } from "@/util/discord"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { + InteractionHandler, + InteractionHandlerTypes, +} from "@sapphire/framework"; +import { type ButtonInteraction, MessageFlags } from "discord.js"; +import { DocumentReference } from "firebase-admin/firestore"; + +@ApplyOptions({ + interactionHandlerType: InteractionHandlerTypes.Button, +}) +class ControlPanelHandler extends InteractionHandler { + public async run(interaction: ButtonInteraction) { + const guild = interaction.guild!; + const guildDocRef = getGuildDocRef(guild.id); + const triviaDocRef = guildDocRef.collection("command-data").doc("trivia"); + + switch (interaction.customId) { + case "play-trivia": + await this.toggleTrivia(interaction, triviaDocRef, false); + break; + case "pause-trivia": + await this.toggleTrivia(interaction, triviaDocRef, true); + break; + case "refresh-trivia": + await this.refreshTrivia(interaction, triviaDocRef); + break; + } + } + + public override parse(interaction: ButtonInteraction) { + const triviaCustomIds = ["play-trivia", "pause-trivia", "refresh-trivia"]; + if (!triviaCustomIds.includes(interaction.customId)) return this.none(); + return this.some(); + } + + private async toggleTrivia( + interaction: ButtonInteraction, + triviaDocRef: DocumentReference, + paused: boolean, + ) { + await triviaDocRef.update({ paused }); + + const triviaDoc = await triviaDocRef.get(); + const triviaDocData = triviaDoc.data() as TriviaDoc; + + const { channelId, messageId } = + triviaDocData.savedMessages.triviaControlPanelMessage; + const triviaControlPanelMessage = await getSavedMessage( + interaction.guild!, + messageId, + channelId, + ); + if (!triviaControlPanelMessage) return; + + const { embed } = StartTrivia.makeAdminConsoleComponents( + triviaDocData.interval, + paused, + ); + await triviaControlPanelMessage.edit({ + embeds: [embed], + }); + + await interaction.reply({ + content: `Trivia has been ${paused ? "paused" : "un-paused"}!`, + flags: MessageFlags.Ephemeral, + }); + } + + private async refreshTrivia( + interaction: ButtonInteraction, + triviaDocRef: DocumentReference, + ) { + const triviaDoc = await triviaDocRef.get(); + const triviaDocData = triviaDoc.data() as TriviaDoc; + + const triviaInfoMessage = await getSavedMessage( + interaction.guild!, + triviaDocData.savedMessages.triviaInfoMessage.messageId, + triviaDocData.savedMessages.triviaInfoMessage.channelId, + ); + if (!triviaInfoMessage) return; + const leaderboardEmbed = + await StartTrivia.getLeaderboardEmbed(triviaDocRef); + await triviaInfoMessage.edit({ + embeds: [StartTrivia.makeStartEmbed(), leaderboardEmbed], + }); + + await interaction.reply({ + content: "Leaderboard refreshed!", + flags: MessageFlags.Ephemeral, + }); + } +} + +export default ControlPanelHandler; diff --git a/src/listeners/ChatInputCommandDenied.ts b/src/listeners/ChatInputCommandDenied.ts new file mode 100644 index 00000000..f360ad99 --- /dev/null +++ b/src/listeners/ChatInputCommandDenied.ts @@ -0,0 +1,36 @@ +import { ApplyOptions } from "@sapphire/decorators"; +import { + ChatInputCommandDeniedPayload, + Events, + Listener, + UserError, +} from "@sapphire/framework"; +import { MessageFlags } from "discord.js"; + +/** + * Used to send a reply to the user when a command is denied, ex. by a precondition. + */ +@ApplyOptions({ + event: Events.ChatInputCommandDenied, +}) +class ChatInputCommandDenied extends Listener< + typeof Events.ChatInputCommandDenied +> { + public override run( + error: UserError, + { interaction }: ChatInputCommandDeniedPayload, + ) { + if (interaction.deferred || interaction.replied) { + return interaction.editReply({ + content: error.message, + }); + } + + return interaction.reply({ + content: error.message, + flags: MessageFlags.Ephemeral, + }); + } +} + +export default ChatInputCommandDenied; diff --git a/src/listeners/pronouns/PronounsReactionAdd.ts b/src/listeners/pronouns/PronounsReactionAdd.ts new file mode 100644 index 00000000..c0b82a0b --- /dev/null +++ b/src/listeners/pronouns/PronounsReactionAdd.ts @@ -0,0 +1,41 @@ +import { PRONOUN_REACTION_EMOJIS, PronounsDoc } from "@/types/db/pronouns"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Events, Listener } from "@sapphire/framework"; +import { MessageReaction, User } from "discord.js"; + +@ApplyOptions({ + event: Events.MessageReactionAdd, +}) +class PronounsReactionAdd extends Listener { + public override async run(reaction: MessageReaction, user: User) { + const guildDocRef = getGuildDocRef(reaction.message.guildId!); + const pronounsDocRef = guildDocRef + .collection("command-data") + .doc("pronouns"); + + const pronounsDoc = await pronounsDocRef.get(); + if (!pronounsDoc.exists) return; + + const pronounsDocData = pronounsDoc.data() as PronounsDoc; + + const { savedMessage } = pronounsDocData; + if (reaction.message.id !== savedMessage.messageId) return; + + const { heHimRole, sheHerRole, theyThemRole, otherRole } = + pronounsDocData.roleIds; + + const roleOrder = [heHimRole, sheHerRole, theyThemRole, otherRole]; + + const roleIndex = PRONOUN_REACTION_EMOJIS.findIndex( + (emoji) => emoji === reaction.emoji.name, + ); + if (roleIndex === -1) return; + + const member = reaction.message.guild?.members.cache.get(user.id); + if (member) await member.roles.add(roleOrder[roleIndex]); + } +} + +export default PronounsReactionAdd; diff --git a/src/listeners/pronouns/PronounsReactionRemove.ts b/src/listeners/pronouns/PronounsReactionRemove.ts new file mode 100644 index 00000000..9e982b73 --- /dev/null +++ b/src/listeners/pronouns/PronounsReactionRemove.ts @@ -0,0 +1,43 @@ +import { PRONOUN_REACTION_EMOJIS, PronounsDoc } from "@/types/db/pronouns"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Events, Listener } from "@sapphire/framework"; +import { MessageReaction, User } from "discord.js"; + +@ApplyOptions({ + event: Events.MessageReactionRemove, +}) +class PronounsReactionRemove extends Listener< + typeof Events.MessageReactionRemove +> { + public override async run(reaction: MessageReaction, user: User) { + const guildDocRef = getGuildDocRef(reaction.message.guildId!); + const pronounsDocRef = guildDocRef + .collection("command-data") + .doc("pronouns"); + + const pronounsDoc = await pronounsDocRef.get(); + if (!pronounsDoc.exists) return; + + const pronounsDocData = pronounsDoc.data() as PronounsDoc; + + const { savedMessage } = pronounsDocData; + if (reaction.message.id !== savedMessage.messageId) return; + + const { heHimRole, sheHerRole, theyThemRole, otherRole } = + pronounsDocData.roleIds; + + const roleOrder = [heHimRole, sheHerRole, theyThemRole, otherRole]; + + const roleIndex = PRONOUN_REACTION_EMOJIS.findIndex( + (emoji) => emoji === reaction.emoji.name, + ); + if (roleIndex === -1) return; + + const member = reaction.message.guild?.members.cache.get(user.id); + if (member) await member.roles.remove(roleOrder[roleIndex]); + } +} + +export default PronounsReactionRemove; diff --git a/src/listeners/tickets/MentorSpecialtyReactionAdd.ts b/src/listeners/tickets/MentorSpecialtyReactionAdd.ts new file mode 100644 index 00000000..8440f1ec --- /dev/null +++ b/src/listeners/tickets/MentorSpecialtyReactionAdd.ts @@ -0,0 +1,51 @@ +import { MENTOR_SPECIALTIES_MAP, TicketDoc } from "@/types/db/ticket"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Events, Listener } from "@sapphire/framework"; +import { MessageReaction, User } from "discord.js"; + +@ApplyOptions({ + event: Events.MessageReactionAdd, +}) +class MentorSpecialtyReactionAdd extends Listener< + typeof Events.MessageReactionAdd +> { + public override async run(reaction: MessageReaction, user: User) { + const guildId = reaction.message.guildId; + if (!guildId) return; + + const guildDocRef = getGuildDocRef(guildId); + const ticketsDocRef = guildDocRef.collection("command-data").doc("tickets"); + + const ticketsDoc = await ticketsDocRef.get(); + if (!ticketsDoc.exists) return; + + const ticketDocData = ticketsDoc.data() as TicketDoc; + const { savedMessages, extraSpecialties } = ticketDocData; + + // Only handle reactions on the mentor specialty selection message + if ( + reaction.message.id !== savedMessages.mentorSpecialtySelection.messageId + ) + return; + + const emojiName = reaction.emoji.name; + if (!emojiName) return; + + const specialtyName = + MENTOR_SPECIALTIES_MAP.get(emojiName) ?? extraSpecialties?.[emojiName]; + if (!specialtyName) return; + + const roleName = `M-${specialtyName}`; + + const member = reaction.message.guild?.members.cache.get(user.id); + const role = reaction.message.guild?.roles.cache.find( + (r) => r.name.toLowerCase() === roleName.toLowerCase(), + ); + + if (member && role) await member.roles.add(role); + } +} + +export default MentorSpecialtyReactionAdd; diff --git a/src/listeners/tickets/MentorSpecialtyReactionRemove.ts b/src/listeners/tickets/MentorSpecialtyReactionRemove.ts new file mode 100644 index 00000000..e123ac03 --- /dev/null +++ b/src/listeners/tickets/MentorSpecialtyReactionRemove.ts @@ -0,0 +1,51 @@ +import { MENTOR_SPECIALTIES_MAP, TicketDoc } from "@/types/db/ticket"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Events, Listener } from "@sapphire/framework"; +import { MessageReaction, User } from "discord.js"; + +@ApplyOptions({ + event: Events.MessageReactionRemove, +}) +class MentorSpecialtyReactionRemove extends Listener< + typeof Events.MessageReactionRemove +> { + public override async run(reaction: MessageReaction, user: User) { + const guildId = reaction.message.guildId; + if (!guildId) return; + + const guildDocRef = getGuildDocRef(guildId); + const ticketsDocRef = guildDocRef.collection("command-data").doc("tickets"); + + const ticketsDoc = await ticketsDocRef.get(); + if (!ticketsDoc.exists) return; + + const ticketDocData = ticketsDoc.data() as TicketDoc; + const { savedMessages, extraSpecialties } = ticketDocData; + + // Only handle reactions on the mentor specialty selection message + if ( + reaction.message.id !== savedMessages.mentorSpecialtySelection.messageId + ) + return; + + const emojiName = reaction.emoji.name; + if (!emojiName) return; + + const specialtyName = + MENTOR_SPECIALTIES_MAP.get(emojiName) ?? extraSpecialties?.[emojiName]; + if (!specialtyName) return; + + const roleName = `M-${specialtyName}`; + + const member = reaction.message.guild?.members.cache.get(user.id); + const role = reaction.message.guild?.roles.cache.find( + (r) => r.name.toLowerCase() === roleName.toLowerCase(), + ); + + if (member && role) await member.roles.remove(role); + } +} + +export default MentorSpecialtyReactionRemove; diff --git a/src/listeners/trivia/NotifyReactionAdd.ts b/src/listeners/trivia/NotifyReactionAdd.ts new file mode 100644 index 00000000..b40fb822 --- /dev/null +++ b/src/listeners/trivia/NotifyReactionAdd.ts @@ -0,0 +1,32 @@ +import { TriviaDoc } from "@/types/db/trivia"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Events, Listener } from "@sapphire/framework"; +import { MessageReaction, User } from "discord.js"; + +@ApplyOptions({ + event: Events.MessageReactionAdd, +}) +class TriviaReactionAdd extends Listener { + public override async run(reaction: MessageReaction, user: User) { + const guildDocRef = getGuildDocRef(reaction.message.guildId!); + const triviaDocRef = guildDocRef.collection("command-data").doc("trivia"); + + const triviaDoc = await triviaDocRef.get(); + if (!triviaDoc.exists) return; + + const triviaDocData = triviaDoc.data() as TriviaDoc; + const { roleIds, savedMessages } = triviaDocData; + + if (reaction.message.id !== savedMessages.triviaInfoMessage.messageId) + return; + + if (reaction.emoji.name !== "🍀") return; + + const member = reaction.message.guild?.members.cache.get(user.id); + if (member) await member.roles.add(roleIds.notify); + } +} + +export default TriviaReactionAdd; diff --git a/src/listeners/trivia/NotifyReactionRemove.ts b/src/listeners/trivia/NotifyReactionRemove.ts new file mode 100644 index 00000000..6bbf0763 --- /dev/null +++ b/src/listeners/trivia/NotifyReactionRemove.ts @@ -0,0 +1,34 @@ +import { TriviaDoc } from "@/types/db/trivia"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Events, Listener } from "@sapphire/framework"; +import { MessageReaction, User } from "discord.js"; + +@ApplyOptions({ + event: Events.MessageReactionRemove, +}) +class NotifyReactionRemove extends Listener< + typeof Events.MessageReactionRemove +> { + public override async run(reaction: MessageReaction, user: User) { + const guildDocRef = getGuildDocRef(reaction.message.guildId!); + const triviaDocRef = guildDocRef.collection("command-data").doc("trivia"); + + const triviaDoc = await triviaDocRef.get(); + if (!triviaDoc.exists) return; + + const triviaDocData = triviaDoc.data() as TriviaDoc; + const { roleIds, savedMessages } = triviaDocData; + + if (reaction.message.id !== savedMessages.triviaInfoMessage.messageId) + return; + + if (reaction.emoji.name !== "🍀") return; + + const member = reaction.message.guild?.members.cache.get(user.id); + if (member) await member.roles.remove(roleIds.notify); + } +} + +export default NotifyReactionRemove; diff --git a/src/preconditions/AdminRoleOnly.ts b/src/preconditions/AdminRoleOnly.ts new file mode 100644 index 00000000..6e5ffe62 --- /dev/null +++ b/src/preconditions/AdminRoleOnly.ts @@ -0,0 +1,47 @@ +import { GuildDoc } from "@/types/db/guild"; +import { checkMemberRoles } from "@/util/discord"; +import { getGuildDocRef } from "@/util/nwplus-firestore"; + +import { Precondition } from "@sapphire/framework"; +import { ChatInputCommandInteraction } from "discord.js"; + +/** + * A precondition that requires the user to have the admin or staff role to run the command. + * Also enforces that the command is run in a server. + */ +class AdminRoleOnlyPrecondition extends Precondition { + public override async chatInputRun( + interaction: ChatInputCommandInteraction, + ): Precondition.AsyncResult { + if (!interaction.guildId) { + return this.error({ message: "This command must be run in a server!" }); + } + const guildDocRef = getGuildDocRef(interaction.guildId); + const guildDoc = await guildDocRef.get(); + const data = guildDoc.data() as GuildDoc; + if (!guildDoc.exists || !data?.setupComplete) { + return this.error({ + message: + "This server is not setup yet. Run /init-bot to setup the server.", + }); + } + + const adminRole = data.roleIds.admin; + const staffRole = data.roleIds.staff; + if (!checkMemberRoles(interaction.member!, [adminRole, staffRole])) { + return this.error({ + message: "Only admins and staff can use this command!", + }); + } + + return this.ok(); + } +} + +declare module "@sapphire/framework" { + interface Preconditions { + AdminRoleOnly: never; + } +} + +export default AdminRoleOnlyPrecondition; diff --git a/src/types/db/common.ts b/src/types/db/common.ts new file mode 100644 index 00000000..a3ae07b2 --- /dev/null +++ b/src/types/db/common.ts @@ -0,0 +1,4 @@ +export interface SavedMessage { + messageId: string; + channelId: string; +} diff --git a/src/types/db/guild.ts b/src/types/db/guild.ts new file mode 100644 index 00000000..904ce4ae --- /dev/null +++ b/src/types/db/guild.ts @@ -0,0 +1,15 @@ +export interface GuildDoc { + setupComplete: boolean; + hackathonName: string; + roleIds: { + admin: string; + staff: string; + verified: string; + unverified: string; + }; + /** Guaranteed to be guild text-based channels */ + channelIds: { + adminConsole: string; + adminLog: string; + }; +} diff --git a/src/types/db/organizer-check-in.ts b/src/types/db/organizer-check-in.ts new file mode 100644 index 00000000..0c5c5c8f --- /dev/null +++ b/src/types/db/organizer-check-in.ts @@ -0,0 +1,9 @@ +import { SavedMessage } from "./common"; + +export interface OrganizerCheckInDoc { + /** Map of username to display name */ + organizerAttendance: { + [username: string]: string; + }; + savedMessage: SavedMessage; +} diff --git a/src/types/db/pronouns.ts b/src/types/db/pronouns.ts new file mode 100644 index 00000000..ba6600ca --- /dev/null +++ b/src/types/db/pronouns.ts @@ -0,0 +1,13 @@ +import { SavedMessage } from "./common"; + +export interface PronounsDoc { + roleIds: { + heHimRole: string; + sheHerRole: string; + theyThemRole: string; + otherRole: string; + }; + savedMessage: SavedMessage; +} + +export const PRONOUN_REACTION_EMOJIS = ["1️⃣", "2️⃣", "3️⃣", "4️⃣"]; diff --git a/src/types/db/ticket.ts b/src/types/db/ticket.ts new file mode 100644 index 00000000..48d48a3a --- /dev/null +++ b/src/types/db/ticket.ts @@ -0,0 +1,55 @@ +import { SavedMessage } from "./common"; + +export interface TicketDoc { + unansweredTicketTime: number; + currentTicketCount: number; + extraSpecialties: { + [emoji: string]: string; + }; + roleIds: { + requestTicketRole: string; + }; + channelIds: { + incomingTicketsChannel: string; + }; + savedMessages: { + mentorSpecialtySelection: SavedMessage; + requestTicket: SavedMessage; + }; +} + +const htmlCssEmoji = "💻"; +const jsTsEmoji = "🕸️"; +const pythonEmoji = "🐍"; +const sqlEmoji = "🐬"; +const reactEmoji = "⚛️"; +const noSqlEmoji = "🔥"; +const javaEmoji = "☕"; +const cEmoji = "🎮"; +const cSharpEmoji = "💼"; +const reduxEmoji = "☁️"; +const figmaEmoji = "🎨"; +const unityEmoji = "🧊"; +const rustEmoji = "⚙️"; +const awsEmoji = "🙂"; +const ideationEmoji = "💡"; +const pitchingEmoji = "🎤"; + +export const MENTOR_SPECIALTIES_MAP = new Map([ + [htmlCssEmoji, "HTML/CSS"], + [jsTsEmoji, "JavaScript/TypeScript"], + [pythonEmoji, "Python"], + [sqlEmoji, "SQL"], + [reactEmoji, "React"], + [noSqlEmoji, "NoSQL"], + [javaEmoji, "Java"], + [cEmoji, "C/C++"], + [cSharpEmoji, "C#"], + [reduxEmoji, "Redux"], + [figmaEmoji, "Figma"], + [unityEmoji, "Unity"], + [rustEmoji, "Rust"], + [awsEmoji, "AWS"], + [ideationEmoji, "Ideation"], + [pitchingEmoji, "Pitching"], +]); diff --git a/src/types/db/trivia.ts b/src/types/db/trivia.ts new file mode 100644 index 00000000..e0067451 --- /dev/null +++ b/src/types/db/trivia.ts @@ -0,0 +1,26 @@ +import { SavedMessage } from "./common"; + +export interface TriviaDoc { + interval: number; + paused: boolean; + askedQuestions: string[]; + roleIds: { + notify: string; + }; + savedMessages: { + triviaInfoMessage: SavedMessage; + triviaControlPanelMessage: SavedMessage; + }; +} + +export interface TriviaQuestionDoc { + question: string; + /** Leave undefined if answer should be selected by manual review */ + answers?: string[]; + needAllAnswers?: boolean; +} + +export interface TriviaLeaderboardDoc { + score: number; + answeredQuestions: string[]; +} diff --git a/src/types/db/verification.ts b/src/types/db/verification.ts new file mode 100644 index 00000000..a726790f --- /dev/null +++ b/src/types/db/verification.ts @@ -0,0 +1,29 @@ +import { Timestamp } from "firebase-admin/firestore"; + +export interface VerificationDoc { + extraRoles: { + [roleName: string]: string; + }; + roleIds: { + hacker: string; + sponsor: string; + mentor: string; + organizer: string; + photographer: string; + volunteer: string; + }; +} + +/** Created at time of verification since we pull valid hackers from hackathon doc */ +export interface HackersDoc { + discordId: string; + verifiedTimestamp: Timestamp; +} + +/** Manually created beforehand by organizers */ +export interface OtherAttendeesDoc { + roles: string[]; + email: string; + discordId?: string; + verifiedTimestamp?: Timestamp; +} diff --git a/src/util/discord.ts b/src/util/discord.ts new file mode 100644 index 00000000..fe9a304e --- /dev/null +++ b/src/util/discord.ts @@ -0,0 +1,31 @@ +import { + APIInteractionGuildMember, + Guild, + GuildMember, + GuildMemberRoleManager, +} from "discord.js"; + +/** Checks if the provided member has any of the given roles */ +export const checkMemberRoles = ( + member: GuildMember | APIInteractionGuildMember, + rolesToCheck: string[], +) => { + return rolesToCheck.some((role) => { + const memberRoles = member.roles; + if (memberRoles instanceof GuildMemberRoleManager) { + return memberRoles.cache.has(role); + } + return memberRoles.includes(role); + }); +}; + +/** Fetches the message from the provided guild using given message and channel IDs */ +export const getSavedMessage = async ( + guild: Guild, + messageId: string, + channelId: string, +) => { + const channel = await guild.channels.fetch(channelId); + if (!channel?.isTextBased()) return null; + return channel.messages.fetch(messageId); +}; diff --git a/src/util/firestore.ts b/src/util/firestore.ts new file mode 100644 index 00000000..814a79b2 --- /dev/null +++ b/src/util/firestore.ts @@ -0,0 +1,32 @@ +import { App, cert, initializeApp } from "firebase-admin/app"; +import { Firestore, getFirestore } from "firebase-admin/firestore"; + +let app: App; +let db: Firestore; + +export const initializeFirebase = (serviceAccountJson: string) => { + try { + const serviceAccount = JSON.parse(serviceAccountJson); + + // Initialize Firebase Admin with service account + app = initializeApp({ + credential: cert(serviceAccount), + projectId: serviceAccount.project_id, + }); + + // Initialize Firestore + db = getFirestore(app); + + // Set Firestore settings + db.settings({ + ignoreUndefinedProperties: true, + }); + + console.log("Firebase Admin initialized successfully"); + } catch (error) { + console.error("Failed to initialize Firebase Admin:", error); + throw error; + } +}; + +export { app, db }; diff --git a/src/util/nwplus-firestore.ts b/src/util/nwplus-firestore.ts new file mode 100644 index 00000000..6e29fe91 --- /dev/null +++ b/src/util/nwplus-firestore.ts @@ -0,0 +1,29 @@ +import { GuildDoc } from "@/types/db/guild"; + +import { Guild } from "discord.js"; + +import { db } from "./firestore"; + +export const getFactotumBaseDocRef = () => { + return db.collection("ExternalProjects").doc("Factotum"); +}; + +export const getGuildDocRef = (guildId: string) => { + return getFactotumBaseDocRef().collection("guilds").doc(guildId); +}; + +export const getHackathonDocRef = (hackathonName: string) => { + return db.collection("Hackathons").doc(hackathonName); +}; + +export const logToAdminLog = async (guild: Guild, message: string) => { + const guildDocRef = getGuildDocRef(guild.id); + const guildDocData = (await guildDocRef.get()).data() as GuildDoc; + + const adminLogChannel = guild.channels.cache.get( + guildDocData.channelIds.adminLog, + ); + if (!adminLogChannel || !adminLogChannel.isTextBased()) return; + + await adminLogChannel.send(message); +}; diff --git a/templates/slashcommand.ts.sapphire b/templates/slashcommand.ts.sapphire new file mode 100644 index 00000000..98b7ca14 --- /dev/null +++ b/templates/slashcommand.ts.sapphire @@ -0,0 +1,33 @@ +{ "category": "commands" } +--- +import BaseCommand from "@/classes/BaseCommand"; + +import { ApplyOptions } from "@sapphire/decorators"; +import { Command, CommandOptionsRunTypeEnum } from "@sapphire/framework"; +import { MessageFlags, SlashCommandBuilder } from "discord.js"; + +@ApplyOptions({ + name: "new-command", + description: "A basic command", + runIn: CommandOptionsRunTypeEnum.GuildText, + preconditions: ["AdminRoleOnly"], +}) +class {{name}} extends BaseCommand { + protected override buildCommand(builder: SlashCommandBuilder) { + return builder; + } + + protected override setCommandOptions() { + return { + idHints: [], + }; + } + + public override async chatInputRun( + interaction: Command.ChatInputCommandInteraction, + ) { + return interaction.reply({ content: 'hi!', flags: MessageFlags.Ephemeral }) + } +} + +export default {{name}}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..f299a17c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,115 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "libReplacement": true, /* Enable lib replacement. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "paths": { + "@/*": ["./src/*"] + } /* Specify a set of entries that re-map imports to additional lookup locations. */, + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "erasableSyntaxOnly": true, /* Do not allow runtime constructs that are not part of ECMAScript. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}