commit 8d9a47b93ee271dbd4dad4769884df6acb9d0369 Author: Hiroyuki Date: Fri Mar 19 21:23:38 2021 -0400 Resolve merge conflicts diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..70d1a95 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,47 @@ +{ + "settings": { + "import/resolver": { + "node": { + "extensions": [".js", ".jsx", ".ts", ".tsx"] + } + } + }, + "env": { + "es6": true, + "node": true + }, + "extends": ["airbnb-base"], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "rules": { + "linebreak-style": "off", + "no-unused-vars": "off", + "max-len": "off", + "import/no-dynamic-require": "off", + "global-require": "off", + "class-methods-use-this": "off", + "no-restricted-syntax": "off", + "camelcase": "off", + "indent": "warn", + "object-curly-newline": "off", + "import/prefer-default-export": "off", + "no-useless-constructor": "off", + "@typescript-eslint/no-useless-constructor": 2, + "import/extensions": "off", + "no-param-reassign": "off", + "no-underscore-dangle": "off", + "keyword-spacing": "off", + "no-multiple-empty-lines": "off", + "consistent-return": "off", + "no-continue": "off" + }, + "ignorePatterns": "**/*.js" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8eb2218 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Package Management & Libraries +node_modules + +# Configuration Files +config.yaml +configs/config.yaml +src/config.yaml +build/config.yaml +.vscode +yarn-error.log +google.json +src/google.json +build/google.json + +# Build/Distribution Files +build +dist + +# Storage/DB Files +localstorage diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..9a4871d --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,23 @@ +stages: + - lint + - build + +lint: + stage: lint + script: | + yarn install + yarn lint + only: + - pushes + - merge_requests + - web + +tsc: + stage: build + script: | + yarn install + tsc -p tsconfig.json -noEmit + only: + - pushes + - merge_requests + - web \ No newline at end of file diff --git a/.gitlab/issue_templates/Bug Report.md b/.gitlab/issue_templates/Bug Report.md new file mode 100644 index 0000000..644777f --- /dev/null +++ b/.gitlab/issue_templates/Bug Report.md @@ -0,0 +1,13 @@ +# BUG REPORT + +**Brief Description:** + +**Priority:** (1-5, 5 being the most urgent) + +**Steps to Reproduce:** (separated with numbers) + +**Expected Result:** + +**Actual Result:** + +**Notes:** (delete if none) \ No newline at end of file diff --git a/.gitlab/issue_templates/Feature Request.md b/.gitlab/issue_templates/Feature Request.md new file mode 100644 index 0000000..72d106f --- /dev/null +++ b/.gitlab/issue_templates/Feature Request.md @@ -0,0 +1,5 @@ +## FEATURE REQUEST / SUGGESTION + +**Request:** + +**Description:** \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9364d83 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# CONTRIBUTIONS + +We accept contributions from the community, however there's a few steps you need to do first before you're able to fork the project. + +1. Join the [Discord Server](https://loc.sh/discord). +2. Send a DM to @Ramirez in the server, provide your GitLab username. +3. We'll let you know when you'll be able to fork the project. + + +## Issues +If you're interested in tackling an issue, please comment on that particular issue that you're handling it so Maintainers can label it appropriately. + +## Other Information +* Make sure your contributions match the current style of the code, run `yarn run lint` to find issues with the style. Requests will be denied if they do not comply with styling. +* Submit your merge requests to the **dev** branch only. +* If you can use TypeScript functionality, do it. For example, don't declare something as `any` if it can be typed. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2dea433 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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. + + Community Relations + Copyright (C) 2020 Engineering Management + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ef34253 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +all: clean build + +clean: + @-rm -rf build + +build: + -npx tsc -p ./tsconfig.json + +run: + cd build && node main diff --git a/package.json b/package.json new file mode 100644 index 0000000..25bb4be --- /dev/null +++ b/package.json @@ -0,0 +1,64 @@ +{ + "name": "loccr", + "version": "1.0.0", + "description": "The official system for handling Community Relations in the LOC Discord server.", + "main": "build/main.js", + "scripts": { + "lint": "eslint -c ./.eslintrc.json src --ext ts" + }, + "repository": "https://gitlab.libraryofcode.org/engineering/communityrelations.git", + "author": "Matthew R, AD, FSEN ", + "license": "AGPL-3.0", + "private": false, + "devDependencies": { + "@types/ari-client": "^2.2.2", + "@types/bull": "^3.14.4", + "@types/cron": "^1.7.2", + "@types/express": "^4.17.6", + "@types/helmet": "^0.0.47", + "@types/jsonwebtoken": "^8.5.0", + "@types/mathjs": "^6.0.7", + "@types/mongoose": "^5.7.19", + "@types/node": "^14.14.25", + "@types/nodemailer": "^6.4.0", + "@types/puppeteer": "^5.4.3", + "@types/signale": "^1.4.1", + "@types/uuid": "^7.0.3", + "@typescript-eslint/eslint-plugin": "^2.33.0", + "@typescript-eslint/parser": "^2.33.0", + "eslint": "^7.19.0", + "eslint-config-airbnb-base": "^14.1.0", + "eslint-plugin-import": "^2.20.2", + "tslib": "^2.1.0", + "typescript": "^3.9.2" + }, + "dependencies": { + "@google-cloud/text-to-speech": "^3.1.2", + "ari-client": "^2.2.0", + "asterisk-manager": "^0.1.16", + "awesome-phonenumber": "^2.45.0", + "axios": "^0.19.2", + "body-parser": "^1.19.0", + "brain.js": "^2.0.0-beta.2", + "bull": "^3.20.1", + "cheerio": "^1.0.0-rc.5", + "cron": "^1.8.2", + "eris": "^0.14.0", + "eris-pagination": "github:bsian03/eris-pagination", + "express": "^4.17.1", + "helmet": "^3.22.0", + "jsonwebtoken": "^8.5.1", + "mathjs": "^7.6.0", + "moment": "^2.25.3", + "mongoose": "^5.11.15", + "nodemailer": "^6.4.8", + "pluris": "^0.2.5", + "puppeteer": "^5.5.0", + "sd-notify": "^2.8.0", + "signale": "^1.4.0", + "stock-info": "^1.2.0", + "stripe": "^8.120.0", + "uuid": "^8.0.0", + "yaml": "^1.9.2" + } +} diff --git a/src/api/board.ins/main.ts b/src/api/board.ins/main.ts new file mode 100644 index 0000000..999fd95 --- /dev/null +++ b/src/api/board.ins/main.ts @@ -0,0 +1,3 @@ +import { Server, ServerManagement } from '../../class'; + +export default (management: ServerManagement) => new Server(management, 3892, `${__dirname}/routes`); diff --git a/src/api/board.ins/routes/index.ts b/src/api/board.ins/routes/index.ts new file mode 100644 index 0000000..fb1d2b2 --- /dev/null +++ b/src/api/board.ins/routes/index.ts @@ -0,0 +1 @@ +export { default as Root } from './root'; diff --git a/src/api/board.ins/routes/root.ts b/src/api/board.ins/routes/root.ts new file mode 100644 index 0000000..8f50c18 --- /dev/null +++ b/src/api/board.ins/routes/root.ts @@ -0,0 +1,920 @@ +import { Guild, GuildTextableChannel, Member, TextChannel } from 'eris'; +import { v4 as genUUID } from 'uuid'; +import { Request } from 'express'; +import { RichEmbed, Route, Server } from '../../../class'; +import { StaffInterface } from '../../../models'; + +export default class Root extends Route { + constructor(server: Server) { + super(server); + this.conf = { + path: '/api', + }; + + this.server.client.once('ready', async () => { + this.guild = this.server.client.guilds.get(this.server.client.config.guildID) || await this.server.client.getRESTGuild(this.server.client.config.guildID); + this.directorRole = '662163685439045632'; + this.directorLogs = this.guild.channels.get('807444198969835550'); + this.chairman = '278620217221971968'; + }); + } + + private guild: Guild; + + private directorRole: string; + + private directorLogs: GuildTextableChannel; + + private chairman: string; + + private async authenticate(req: Request) { + if (!req.headers.authorization) return false; + + const users = await this.server.client.db.Score.find(); + const director = users.find((user) => user.pin.join('-') === req.headers.authorization); + if (!director) return false; + + const member = this.guild.members.get(director.userID); + if (!member) return false; + if (!member.roles.includes(this.directorRole)) return false; + + const staffProfile = await this.server.client.db.Staff.findOne({ userID: member.id }); + + return { member, director: staffProfile }; + } + + private genEmbed( + id: string, + type: 'eo' | 'motion' | 'proc' | 'res' | 'confirmMotion', + director: { + user: StaffInterface, + member: Member + }, + payload: { + subject: string, + body: string, + }, + ) { + let title: string; + let color: number; + + switch (type) { + case 'eo': + title = 'Executive Order'; + color = 0xff00a7; + break; + + case 'motion': + title = 'Motion'; + color = 0xffbb5f; + break; + + case 'proc': + title = 'Proclamation'; + color = 0x9b89ff; + break; + + case 'res': + title = 'Resolution'; + color = 0x27b17a; + break; + + case 'confirmMotion': + title = 'Motion Confirmed'; + color = 0x4a6cc5; + break; + + default: + throw new TypeError('You\'ve specified an invalid type for this action log. Valid types: "eo", "motion", "proc" and "res"'); + } + + const embed = new RichEmbed(); + embed.setTitle(title); + embed.setAuthor(`${director.member.username}#${director.member.discriminator}, ${director.user.pn.join(', ')}`, director.member.avatarURL); + embed.setDescription(`${id}\n\n_This action is available on the Board Register System Directory. You can make changes or edit it [here](https://board.ins/directory?id=${id})._`); + embed.addField('Subject', payload.subject); + embed.addField('Body', payload.body); + embed.setColor(color); + embed.setFooter('Library of Code sp-us | Board Register System', 'https://static.libraryofcode.org/library_of_code.png'); + embed.setTimestamp(); + + return embed; + } + + public bind() { + this.router.all('/', (_req, res, next) => { + res.setHeader('Control-Allow-Access-Origin', '*'); + next(); + }); + + this.router.get('/', async (_req, res) => { + const eo = await this.server.client.db.ExecutiveOrder.countDocuments(); + const motion = await this.server.client.db.Motion.countDocuments(); + const proc = await this.server.client.db.Proclamation.countDocuments(); + const resolution = await this.server.client.db.Proclamation.countDocuments(); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + counts: { + eo, + motion, + proc, + resolution, + total: eo + motion + proc + resolution, + }, + }); + }); + + this.router.get('/identify', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).send({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: `You have authenticated as a Director. Welcome ${authenticated.member.username}#${authenticated.member.discriminator}.`, + user: { + username: authenticated.member.username, + discriminator: authenticated.member.discriminator, + id: authenticated.member.id, + avatarURL: authenticated.member.avatarURL || authenticated.member.defaultAvatarURL, + }, + }); + }); + + this.router.post('/eo', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).send({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + if (typeof req.body.subject !== 'string' || typeof req.body.body !== 'string' || req.body.subject.length > 256 || req.body.body.length > 1024) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + + const id = genUUID(); + const embed = this.genEmbed( + id, + 'eo', + { + user: authenticated.director, + member: authenticated.member, + }, + { + subject: req.body.subject, + body: req.body.body, + }, + ); + const msg = await this.directorLogs.createMessage({ embed }); + + const eo = await this.server.client.db.ExecutiveOrder.create({ + issuer: authenticated.director.userID, + subject: req.body.subject, + body: req.body.body, + at: Date.now(), + oID: id, + msg: msg.id, + }); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: `Created new Executive Order with ID ${eo.oID} by ${authenticated.member.username}#${authenticated.member.discriminator}`, + }); + }); + + this.router.post('/motion', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).send({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + if (typeof req.body.subject !== 'string' || typeof req.body.body !== 'string' || req.body.subject.length > 256 || req.body.body.length > 1024) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + + const id = genUUID(); + const embed = this.genEmbed( + id, + 'motion', + { + user: authenticated.director, + member: authenticated.member, + }, + { + subject: req.body.subject, + body: req.body.body, + }, + ); + const msg = await this.directorLogs.createMessage({ embed }); + + const motion = await this.server.client.db.Motion.create({ + issuer: authenticated.director.userID, + subject: req.body.subject, + body: req.body.body, + at: Date.now(), + oID: id, + processed: false, + msg: msg.id, + }); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: `Created new Motion with ID ${motion.oID} by ${authenticated.member.username}#${authenticated.member.discriminator}`, + }); + }); + + this.router.post('/proc', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).send({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + if (typeof req.body.subject !== 'string' || typeof req.body.body !== 'string' || req.body.subject.length > 256 || req.body.body.length > 1024) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + + const id = genUUID(); + const embed = this.genEmbed( + id, + 'proc', + { + user: authenticated.director, + member: authenticated.member, + }, + { + subject: req.body.subject, + body: req.body.body, + }, + ); + const msg = await this.directorLogs.createMessage({ embed }); + await msg.addReaction('modSuccess:578750988907970567'); + await msg.addReaction('modError:578750737920688128'); + await msg.addReaction('🙋'); + + const proc = await this.server.client.db.Proclamation.create({ + issuer: authenticated.director.userID, + subject: req.body.subject, + body: req.body.body, + at: Date.now(), + oID: id, + processed: false, + msg: msg.id, + }); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: `Created new Proclamation with ID ${proc.oID} by ${authenticated.member.username}#${authenticated.member.discriminator}`, + }); + }); + + this.router.delete('/eo/:id', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const eo = await this.server.client.db.ExecutiveOrder.findOne({ oID: req.params.id }); + + if (!eo) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + if (eo.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) { + return res.status(403).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.codes.UNAUTHORIZED, + }); + } + + await eo.deleteOne(); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: 'Executive Order deleted.', + }); + }); + + this.router.delete('/motion/:id', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const motion = await this.server.client.db.Motion.findOne({ oID: req.params.id }); + + if (!motion) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + if (motion.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) { + return res.status(403).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.codes.UNAUTHORIZED, + }); + } + + await motion.deleteOne(); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: 'Motion deleted.', + }); + }); + + this.router.delete('/proc/:id', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const proc = await this.server.client.db.Proclamation.findOne({ oID: req.params.id }); + + if (!proc) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + if (proc.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) { + return res.status(403).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.codes.UNAUTHORIZED, + }); + } + + await proc.deleteOne(); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: 'Proclamation deleted.', + }); + }); + + this.router.get('/eo/:id', async (req, res) => { + const eo = await this.server.client.db.ExecutiveOrder.findOne({ oID: req.params.id }); + + if (!eo) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + const issuer = this.guild.members.get(eo.issuer); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + issuer: { + username: issuer.username, + discriminator: issuer.discriminator, + avatarURL: issuer.avatarURL, + }, + subject: eo.subject, + body: eo.body, + issuedAt: eo.at, + id: eo.oID, + }); + }); + + this.router.get('/motion/:id', async (req, res) => { + const motion = await this.server.client.db.Motion.findOne({ oID: req.params.id }); + + if (!motion) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + const issuer = this.guild.members.get(motion.issuer); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + issuer: { + username: issuer.username, + discriminator: issuer.discriminator, + avatarURL: issuer.avatarURL, + }, + subject: motion.subject, + body: motion.body, + issuedAt: motion.at, + id: motion.oID, + voteResults: motion.results || null, + }); + }); + + this.router.get('/proc/:id', async (req, res) => { + const proc = await this.server.client.db.Proclamation.findOne({ oID: req.params.id }); + + if (!proc) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + const issuer = this.guild.members.get(proc.issuer); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + issuer: { + username: issuer.username, + discriminator: issuer.discriminator, + avatarURL: issuer.avatarURL, + }, + subject: proc.subject, + body: proc.body, + issuedAt: proc.at, + id: proc.oID, + voteResults: proc.results || null, + }); + }); + + this.router.get('/resolution/:id', async (req, res) => { + const resolution = await this.server.client.db.Resolution.findOne({ oID: req.params.id }); + + if (!resolution) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + const issuer = this.guild.members.get(resolution.issuer); + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + issuer: { + username: issuer.username, + discriminator: issuer.discriminator, + avatarURL: issuer.avatarURL, + }, + subject: resolution.subject, + body: resolution.body, + issuedAt: resolution.at, + id: resolution.oID, + voteResults: resolution.results || null, + }); + }); + + this.router.patch('/eo/:id', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const eo = await this.server.client.db.ExecutiveOrder.findOne({ oID: req.params.id }); + + if (!eo) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + if (eo.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) { + return res.status(403).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.codes.UNAUTHORIZED, + }); + } + + await eo.updateOne({ + subject: req.body.subject || eo.subject, + body: req.body.body || eo.body, + }); + + if (eo.subject !== req.body.subject || eo.body !== req.body.body) { + const message = await this.directorLogs.getMessage(eo.msg); + const embed = this.genEmbed( + eo.oID, + 'eo', + { + user: authenticated.director, + member: authenticated.member, + }, + { + subject: req.body.subject, + body: req.body.body, + }, + ); + + await message.edit({ embed }); + } + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: 'Updated Executive Order.', + }); + }); + + this.router.patch('/motion/:id', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const motion = await this.server.client.db.Motion.findOne({ oID: req.params.id }); + + if (!motion) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + if (motion.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) { + return res.status(403).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.codes.UNAUTHORIZED, + }); + } + + await motion.updateOne({ + subject: req.body.subject || motion.subject, + body: req.body.body || motion.body, + }); + + if (motion.subject !== req.body.subject || motion.body !== req.body.body) { + const message = await this.directorLogs.getMessage(motion.msg); + const embed = this.genEmbed( + motion.oID, + 'motion', + { + user: authenticated.director, + member: authenticated.member, + }, + { + subject: req.body.subject, + body: req.body.body, + }, + ); + + await message.edit({ embed }); + } + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: 'Updated Motion.', + }); + }); + + this.router.patch('/proc/:id', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const proc = await this.server.client.db.Proclamation.findOne({ oID: req.params.id }); + + if (!proc) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + if (proc.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) { + return res.status(403).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.codes.UNAUTHORIZED, + }); + } + + await proc.updateOne({ + subject: req.body.subject || proc.subject, + body: req.body.body || proc.body, + }); + + if (proc.subject !== req.body.subject || proc.body !== req.body.body) { + const message = await this.directorLogs.getMessage(proc.msg); + const embed = this.genEmbed( + proc.oID, + 'proc', + { + user: authenticated.director, + member: authenticated.member, + }, + { + subject: req.body.subject, + body: req.body.body, + }, + ); + + await message.edit({ embed }); + } + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: 'Updated Proclamation.', + }); + }); + + this.router.patch('/resolution/:id', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const resolution = await this.server.client.db.Resolution.findOne({ oID: req.params.id }); + + if (!resolution) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + if (resolution.issuer !== authenticated.member.id && authenticated.member.id !== this.chairman) { + return res.status(403).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.codes.UNAUTHORIZED, + }); + } + + await resolution.updateOne({ + subject: req.body.subject || resolution.subject, + body: req.body.body || resolution.body, + }); + + if (resolution.subject !== req.body.subject || resolution.body !== req.body.body) { + const message = await this.directorLogs.getMessage(resolution.msg); + const embed = this.genEmbed( + resolution.oID, + 'res', + { + user: authenticated.director, + member: authenticated.member, + }, + { + subject: req.body.subject, + body: req.body.body, + }, + ); + + await message.edit({ embed }); + } + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: 'Updated Resolution.', + }); + }); + + this.router.get('/eo', async (req, res) => { + const page = !Number.isNaN(Number(req.query.page)) ? Number(req.query.page) : 0; + const skipped = page || page * 10; + + const eo = await this.server.client.db.ExecutiveOrder.find().skip(skipped); + const returned: { + oID: string; + author: string; + issuedAt: number; + }[] = []; + + for (const result of eo) { + const director = this.guild.members.get(result.issuer); + const author = director ? `${director.username}#${director.discriminator}` : 'Deleted User#0000'; + + returned.push({ + oID: result.oID, + author, + issuedAt: result.at, + }); + } + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + results: returned, + }); + }); + + this.router.get('/motion', async (req, res) => { + const page = !Number.isNaN(Number(req.query.page)) ? Number(req.query.page) : 0; + const skipped = page || page * 10; + + const motion = await this.server.client.db.Motion.find().skip(skipped); + const returned: { + oID: string; + author: string; + issuedAt: number; + }[] = []; + + for (const result of motion) { + const director = this.guild.members.get(result.issuer); + const author = director ? `${director.username}#${director.discriminator}` : 'Deleted User#0000'; + + returned.push({ + oID: result.oID, + author, + issuedAt: result.at, + }); + } + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + results: returned, + }); + }); + + this.router.get('/proc', async (req, res) => { + const page = !Number.isNaN(Number(req.query.page)) ? Number(req.query.page) : 0; + const skipped = page || page * 10; + + const proc = await this.server.client.db.Proclamation.find().skip(skipped); + const returned: { + oID: string; + author: string; + issuedAt: number; + }[] = []; + + for (const result of proc) { + const director = this.guild.members.get(result.issuer); + const author = director ? `${director.username}#${director.discriminator}` : 'Deleted User#0000'; + + returned.push({ + oID: result.oID, + author, + issuedAt: result.at, + }); + } + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + results: returned, + }); + }); + + this.router.get('/resolution', async (req, res) => { + const page = !Number.isNaN(Number(req.query.page)) ? Number(req.query.page) : 0; + const skipped = page || page * 10; + + const resolution = await this.server.client.db.Resolution.find().skip(skipped); + const returned: { + oID: string; + author: string; + issuedAt: number; + }[] = []; + + for (const result of resolution) { + const director = this.guild.members.get(result.issuer); + const author = director ? `${director.username}#${director.discriminator}` : 'Deleted User#0000'; + + returned.push({ + oID: result.oID, + author, + issuedAt: result.at, + }); + } + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + results: returned, + }); + }); + + this.router.put('/motion/confirm/:id', async (req, res) => { + const authenticated = await this.authenticate(req); + if (!authenticated) { + return res.status(401).json({ + code: this.constants.codes.UNAUTHORIZED, + message: this.constants.messages.UNAUTHORIZED, + }); + } + + const motion = await this.server.client.db.Motion.findOne({ oID: req.params.id }); + + if (!motion) { + return res.status(404).json({ + code: this.constants.codes.NOT_FOUND, + message: this.constants.messages.NOT_FOUND, + }); + } + + if (Number.isNaN(Number(req.body.yea)) || Number.isNaN(Number(req.body.nay)) || Number.isNaN(Number(req.body.present))) { + return res.status(400).json({ + code: this.constants.codes.CLIENT_ERROR, + message: this.constants.messages.CLIENT_ERROR, + }); + } + + const yea = Number(req.body.yea); + const nay = Number(req.body.nay); + const present = Number(req.body.present); + const total = this.guild.members.filter((member) => member.roles.includes(this.directorRole)); + const absent = total.length - (yea + nay + present); + + await motion.updateOne({ + processed: true, + results: { + yea, + nay, + present, + absent, + }, + }); + + const confirmationEmbed = this.genEmbed( + motion.oID, + 'confirmMotion', + { + user: authenticated.director, + member: authenticated.member, + }, + { + subject: motion.subject, + body: motion.body, + }, + ); + confirmationEmbed.addField('Results', `**Total:** ${total.length}\n**Yea:** ${yea}\n**Nay:** ${nay}\n**Present:** ${present}\n**Absent:** ${absent}`); + await this.directorLogs.createMessage({ embed: confirmationEmbed }); + + const excludingYea = nay + present + absent; + if (yea > excludingYea) { + const resolutionEmbed = this.genEmbed( + motion.oID, + 'res', + { + user: authenticated.director, + member: authenticated.member, + }, + { + subject: motion.subject, + body: motion.body, + }, + ); + const resolutionMessage = await this.directorLogs.createMessage({ embed: resolutionEmbed }); + + await this.server.client.db.Resolution.create({ + issuer: motion.issuer, + subject: motion.subject, + body: motion.body, + at: Date.now(), + oID: motion.oID, + results: { + yea, + nay, + present, + absent, + }, + msg: resolutionMessage.id, + }); + } + + res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: 'Confirmed the results of the Motion.', + }); + }); + } +} diff --git a/src/api/comm.libraryofcode.org/main.ts b/src/api/comm.libraryofcode.org/main.ts new file mode 100644 index 0000000..f6c876e --- /dev/null +++ b/src/api/comm.libraryofcode.org/main.ts @@ -0,0 +1,3 @@ +import { Server, ServerManagement } from '../../class'; + +export default (management: ServerManagement) => new Server(management, 3895, `${__dirname}/routes`); diff --git a/src/api/comm.libraryofcode.org/routes/index.ts b/src/api/comm.libraryofcode.org/routes/index.ts new file mode 100644 index 0000000..06de8be --- /dev/null +++ b/src/api/comm.libraryofcode.org/routes/index.ts @@ -0,0 +1 @@ +export { default as report } from './report'; diff --git a/src/api/comm.libraryofcode.org/routes/report.ts b/src/api/comm.libraryofcode.org/routes/report.ts new file mode 100644 index 0000000..1165d77 --- /dev/null +++ b/src/api/comm.libraryofcode.org/routes/report.ts @@ -0,0 +1,605 @@ +/* eslint-disable no-bitwise */ +/* eslint-disable no-continue */ +import jwt from 'jsonwebtoken'; +import { TextChannel } from 'eris'; +import { LocalStorage, Route, Server } from '../../../class'; +import { ScoreHistoricalRaw } from '../../../models/ScoreHistorical'; +import { getTotalMessageCount } from '../../../intervals/score'; + +export default class Report extends Route { + public timeout: Map; + + public acceptedOffers: LocalStorage; + + constructor(server: Server) { + super(server); + this.timeout = new Map(); + this.acceptedOffers = new LocalStorage('accepted-offers'); + this.conf = { + path: '/report', + }; + } + + protected check(userID: string) { + if (this.timeout.has(userID)) { + if (this.timeout.get(userID) >= 3) { + return true; + } + this.timeout.set(userID, this.timeout.get(userID) + 1); + } else { + this.timeout.set(userID, 1); + } + setTimeout(() => { + if (this.timeout.has(userID)) { + this.timeout.set(userID, this.timeout.get(userID) - 1); + } else { + this.timeout.delete(userID); + } + }, 30000); + return false; + } + + public bind() { + this.router.all('*', (_req, res, next) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', '*'); + res.setHeader('Access-Control-Allow-Headers', '*'); + next(); + }); + + this.router.post('/hard', async (req, res) => { + try { + if (!req.headers.authorization) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + if (!req.body.pin || !req.body.userID || !req.body.reason) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR, message: this.constants.messages.CLIENT_ERROR }); + if (req.body.reason?.length < 1) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR, message: this.constants.codes.CLIENT_ERROR }); + + const merchant = await this.server.client.db.Merchant.findOne({ key: req.headers.authorization }).lean().exec(); + if (!merchant) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + + const member = await this.server.client.db.Score.findOne({ userID: req.body.userID, 'pin.2': req.body.pin }).lean().exec(); + if (!member) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + + const mem = this.server.client.util.resolveMember(member.userID, this.server.client.guilds.get(this.server.client.config.guildID)); + if (!mem) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.codes.NOT_FOUND }); + + if (member.locked) return res.status(403).json({ code: this.constants.codes.PERMISSION_DENIED, message: this.constants.messages.PERMISSION_DENIED }); + if (merchant?.type !== 1) return res.status(403).json({ code: this.constants.codes.PERMISSION_DENIED, message: this.constants.messages.PERMISSION_DENIED }); + + if (this.check(member.userID)) { + await this.server.client.db.Score.updateOne({ userID: member.userID }, { $set: { locked: true } }); + const chan = await this.server.client.getDMChannel(member.userID); + try { + await chan.createMessage(`__**Community Report Locked**__\nWe've detected suspicious activity on your Community Report, for the integrity of your report we have automatically locked it. To unlock your report, please run \`${this.server.client.config.prefix}score pref unlock\` in <#468759629334183956>.`); + } catch (err) { + this.server.client.util.signale.error(`Unable to DM user: ${member.userID} | ${err}`); + } + return res.status(403).json({ code: this.constants.codes.PERMISSION_DENIED, message: this.constants.messages.PERMISSION_DENIED }); + } + + const flags = []; + if (mem.user.publicFlags) { + if ((mem.user.publicFlags & (1 << 0)) === 1 << 0) flags.push('DISCORD_EMPLOYEE'); + if ((mem.user.publicFlags & (1 << 1)) === 1 << 1) flags.push('PARTNERED_SERVER_OWNER'); + if ((mem.user.publicFlags & (1 << 2)) === 1 << 2) flags.push('HYPESQUAD_EVENTS'); + if ((mem.user.publicFlags & (1 << 3)) === 1 << 3) flags.push('BUG_HUNTER_1'); + if ((mem.user.publicFlags & (1 << 6)) === 1 << 6) flags.push('HOUSE_BRAVERY'); + if ((mem.user.publicFlags & (1 << 7)) === 1 << 7) flags.push('HOUSE_BRILLIANCE'); + if ((mem.user.publicFlags & (1 << 8)) === 1 << 8) flags.push('HOUSE_BALANCE'); + if ((mem.user.publicFlags & (1 << 9)) === 1 << 9) flags.push('EARLY_SUPPORTER'); + if ((mem.user.publicFlags & (1 << 10)) === 1 << 10) flags.push('TEAM_USER'); + if ((mem.user.publicFlags & (1 << 12)) === 1 << 12) flags.push('SYSTEM'); + if ((mem.user.publicFlags & (1 << 14)) === 1 << 14) flags.push('BUG_HUNTER_2'); + if ((mem.user.publicFlags & (1 << 16)) === 1 << 16) flags.push('VERIFIED_BOT'); + if ((mem.user.publicFlags & (1 << 17)) === 1 << 17) flags.push('EARLY_VERIFIED_BOT_DEVELOPER'); + } + const set = []; + const accounts = await this.server.client.db.Score.find().lean().exec(); + for (const sc of accounts) { + if (sc.total < 200) { continue; } + if (sc.total > 800) { set.push(800); continue; } + set.push(sc.total); + } + + let totalScore: number; + let activityScore: number; + const moderationScore = Math.round(member.moderation); + let roleScore: number; + let cloudServicesScore: number; + const otherScore = Math.round(member.other); + let miscScore: number; + + if (member.total < 200) totalScore = 0; + else if (member.total > 800) totalScore = 800; + else totalScore = Math.round(member.total); + + if (member.activity < 10) activityScore = 0; + else if (member.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activityScore = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12)); + else activityScore = Math.round(member.activity); + + if (member.roles <= 0) roleScore = 0; + else if (member.roles > 54) roleScore = 54; + else roleScore = Math.round(member.roles); + + if (member.staff <= 0) miscScore = 0; + else miscScore = Math.round(member.staff); + + if (member.cloudServices === 0) cloudServicesScore = 0; + else if (member.cloudServices > 10) cloudServicesScore = 10; + else cloudServicesScore = Math.round(member.cloudServices); + + const inqs = await this.server.client.db.Inquiry.find({ userID: member.userID }).lean().exec(); + const inquiries: [{ id?: string, name: string, date: Date }?] = []; + if (inqs?.length > 0) { + for (const inq of inqs) { + const testDate = (new Date(new Date(inq.date).setHours(1460))); + if (testDate > new Date()) inquiries.push({ id: inq.iid, name: inq.name, date: inq.date }); + } + } + + const historicalData = await this.server.client.db.ScoreHistorical.find({ userID: member.userID }).lean().exec(); + const array: ScoreHistoricalRaw[] = []; + for (const data of historicalData) { + delete data.report?.softInquiries; + + let total: number; + let activity: number; + const moderation = Math.round(data.report.moderation); + let role: number; + let cloud: number; + const other = Math.round(data.report.other); + let misc: number; + + if (data.report.total < 200) total = 0; + else if (data.report.total > 800) total = 800; + else total = Math.round(data.report.total); + + if (data.report.activity < 10) activity = 0; + else if (data.report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activity = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12)); + else activity = Math.round(data.report.activity); + + if (data.report.roles <= 0) role = 0; + else if (data.report.roles > 54) role = 54; + else role = Math.round(data.report.roles); + + if (data.report.staff <= 0) role = 0; + else misc = Math.round(data.report.staff); + + if (data.report.cloudServices === 0) cloud = 0; + else if (data.report.cloudServices > 10) cloud = 10; + else cloud = Math.round(data.report.cloudServices); + + data.report.total = total; data.report.activity = activity; data.report.moderation = moderation; data.report.roles = role; data.report.cloudServices = cloud; data.report.other = other; data.report.staff = misc; + + array.push(data); + } + + const inq = await this.server.client.report.createInquiry(member.userID, merchant.name, 0, req.body.reason); + + await this.server.client.db.Merchant.updateOne({ key: req.headers.authorization }, { $addToSet: { pulls: { type: 0, reason: req.body.reason, date: new Date() } } }); + + return res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: { + id: inq.id, + userID: member.userID, + memberInformation: { + username: mem.user.username, + discriminator: mem.user.discriminator, + joinedServerAt: new Date(mem.joinedAt), + createdAt: new Date(mem.createdAt), + avatarURL: mem.avatarURL, + flags, + nitroBoost: mem.premiumSince === null, + }, + percentile: Math.round(this.server.client.util.percentile(set, member.total)), + totalScore, + activityScore, + roleScore, + moderationScore, + cloudServicesScore, + miscScore, + otherScore, + inquiries, + historical: array ?? [], + }, + }); + } catch (err) { + return this.handleError(err, res); + } + }); + + this.router.post('/v2/soft', async (req, res) => { + try { + if (!req.headers.authorization) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + if (!req.body.userID) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR, message: this.constants.messages.CLIENT_ERROR }); + + const merchant = await this.server.client.db.Merchant.findOne({ key: req.headers.authorization }).lean().exec(); + if (!merchant) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + + const report = await this.server.client.db.Score.findOne({ userID: req.body.userID }).lean().exec(); + if (!report) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + + let totalScore: number; + + if (report.total < 200) totalScore = 0; + else if (report.total > 800) totalScore = 800; + else totalScore = Math.round(report.total); + + await this.server.client.db.Merchant.updateOne({ key: req.headers.authorization }, { $addToSet: { pulls: { type: 1, reason: 'N/A', date: new Date() } } }); + const mem = this.server.client.util.resolveMember(report.userID, this.server.client.guilds.get(this.server.client.config.guildID)); + await this.server.client.report.createInquiry(report.userID, merchant.name.toUpperCase(), 1); + + if (!merchant.privileged) { + return res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: { + userID: report.userID, + totalScore, + }, + }); + } + const flags = []; + if (mem.user.publicFlags) { + if ((mem.user.publicFlags & (1 << 0)) === 1 << 0) flags.push('DISCORD_EMPLOYEE'); + if ((mem.user.publicFlags & (1 << 1)) === 1 << 1) flags.push('PARTNERED_SERVER_OWNER'); + if ((mem.user.publicFlags & (1 << 2)) === 1 << 2) flags.push('HYPESQUAD_EVENTS'); + if ((mem.user.publicFlags & (1 << 3)) === 1 << 3) flags.push('BUG_HUNTER_1'); + if ((mem.user.publicFlags & (1 << 6)) === 1 << 6) flags.push('HOUSE_BRAVERY'); + if ((mem.user.publicFlags & (1 << 7)) === 1 << 7) flags.push('HOUSE_BRILLIANCE'); + if ((mem.user.publicFlags & (1 << 8)) === 1 << 8) flags.push('HOUSE_BALANCE'); + if ((mem.user.publicFlags & (1 << 9)) === 1 << 9) flags.push('EARLY_SUPPORTER'); + if ((mem.user.publicFlags & (1 << 10)) === 1 << 10) flags.push('TEAM_USER'); + if ((mem.user.publicFlags & (1 << 12)) === 1 << 12) flags.push('SYSTEM'); + if ((mem.user.publicFlags & (1 << 14)) === 1 << 14) flags.push('BUG_HUNTER_2'); + if ((mem.user.publicFlags & (1 << 16)) === 1 << 16) flags.push('VERIFIED_BOT'); + if ((mem.user.publicFlags & (1 << 17)) === 1 << 17) flags.push('EARLY_VERIFIED_BOT_DEVELOPER'); + } + const set = []; + if (req.query.p?.toString() === 'true') { + const accounts = await this.server.client.db.Score.find().lean().exec(); + for (const sc of accounts) { + if (sc.total < 200) { continue; } + if (sc.total > 800) { set.push(800); continue; } + set.push(sc.total); + } + } + + let activityScore: number; + const moderationScore = Math.round(report.moderation); + let roleScore: number; + let cloudServicesScore: number; + const otherScore = Math.round(report.other); + let miscScore: number; + + if (report.activity < 10) activityScore = 0; + else if (report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activityScore = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12)); + else activityScore = Math.round(report.activity); + + if (report.roles <= 0) roleScore = 0; + else if (report.roles > 54) roleScore = 54; + else roleScore = Math.round(report.roles); + + if (report.staff <= 0) miscScore = 0; + else miscScore = Math.round(report.staff); + + if (report.cloudServices === 0) cloudServicesScore = 0; + else if (report.cloudServices > 10) cloudServicesScore = 10; + else cloudServicesScore = Math.round(report.cloudServices); + + const historicalData = await this.server.client.db.ScoreHistorical.find({ userID: report.userID }).lean().exec(); + const array: ScoreHistoricalRaw[] = []; + for (const data of historicalData) { + delete data.report?.softInquiries; + delete data.report?.inquiries; + + let total: number; + let activity: number; + const moderation = Math.round(data.report.moderation); + let role: number; + let cloud: number; + const other = Math.round(data.report.other); + let misc: number; + + if (data.report.total < 200) total = 0; + else if (data.report.total > 800) total = 800; + else total = Math.round(data.report.total); + + if (data.report.activity < 10) activity = 0; + else if (data.report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activity = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12)); + else activity = Math.round(data.report.activity); + + if (data.report.roles <= 0) role = 0; + else if (data.report.roles > 54) role = 54; + else role = Math.round(data.report.roles); + + if (data.report.staff <= 0) role = 0; + else misc = Math.round(data.report.staff); + + if (data.report.cloudServices === 0) cloud = 0; + else if (data.report.cloudServices > 10) cloud = 10; + else cloud = Math.round(data.report.cloudServices); + + data.report.total = total; data.report.activity = activity; data.report.moderation = moderation; data.report.roles = role; data.report.cloudServices = cloud; data.report.other = other; data.report.staff = misc; + + array.push(data); + } + + return res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: { + userID: report.userID, + memberInformation: { + username: mem.user.username, + discriminator: mem.user.discriminator, + joinedServerAt: new Date(mem.joinedAt), + createdAt: new Date(mem.createdAt), + avatarURL: mem.avatarURL, + flags, + nitroBoost: mem.premiumSince === null, + }, + totalScore, + percentile: Math.round(this.server.client.util.percentile(set, report.total)), + activityScore, + moderationScore, + roleScore, + cloudServicesScore, + otherScore, + miscScore, + historical: array ?? [], + }, + }); + } catch (err) { + return this.handleError(err, res); + } + }); + + this.router.post('/soft', async (req, res) => { + try { + if (!req.headers.authorization) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + if (!req.body.pin || !req.body.userID) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR, message: this.constants.messages.CLIENT_ERROR }); + + const merchant = await this.server.client.db.Merchant.findOne({ key: req.headers.authorization }).lean().exec(); + if (!merchant) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + + const member = await this.server.client.db.Score.findOne({ userID: req.body.userID, 'pin.2': req.body.pin }).lean().exec(); + if (!member) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + const mem = this.server.client.util.resolveMember(member.userID, this.server.client.guilds.get(this.server.client.config.guildID)); + if (!mem) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.codes.NOT_FOUND }); + + let totalScore: number; + + if (member.total < 200) totalScore = 0; + else if (member.total > 800) totalScore = 800; + else totalScore = Math.round(member.total); + + await this.server.client.db.Merchant.updateOne({ key: req.headers.authorization }, { $addToSet: { pulls: { type: 1, reason: 'N/A', date: new Date() } } }); + await this.server.client.report.createInquiry(member.userID, merchant.name.toUpperCase(), 1); + + if (!merchant.privileged) { + return res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: { + userID: member.userID, + totalScore, + }, + }); + } + let activityScore: number; + const moderationScore = Math.round(member.moderation); + let roleScore: number; + let cloudServicesScore: number; + const otherScore = Math.round(member.other); + let miscScore: number; + + if (member.activity < 10) activityScore = 0; + else if (member.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activityScore = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12)); + else activityScore = Math.round(member.activity); + + if (member.roles <= 0) roleScore = 0; + else if (member.roles > 54) roleScore = 54; + else roleScore = Math.round(member.roles); + + if (member.staff <= 0) miscScore = 0; + else miscScore = Math.round(member.staff); + + if (member.cloudServices === 0) cloudServicesScore = 0; + else if (member.cloudServices > 10) cloudServicesScore = 10; + else cloudServicesScore = Math.round(member.cloudServices); + + return res.status(200).json({ + code: this.constants.codes.SUCCESS, + message: { + userID: member.userID, + totalScore, + activityScore, + moderationScore, + roleScore, + cloudServicesScore, + otherScore, + miscScore, + }, + }); + } catch (err) { + return this.handleError(err, res); + } + }); + + this.router.get('/web', async (req, res) => { + try { + res.setHeader('Access-Control-Allow-Origin', '*'); + if (this.timeout.has(req.ip)) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + if (!req.query.pin) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + const args = req.query.pin.toString(); + this.timeout.set(req.ip, 1); + setTimeout(() => this.timeout.delete(req.ip), 1800000); + let score = await this.server.client.db.Score.findOne({ pin: [Number(args.split('-')[0]), Number(args.split('-')[1]), Number(args.split('-')[2])] }).lean().exec(); + if (!score) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + const member = await this.server.client.getRESTGuildMember(this.constants.discord.SERVER_ID, score.userID); + if (!member) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + let updated = false; + if (req.query.staff) { + // eslint-disable-next-line no-shadow + const args = req.query.staff.toString(); + const staffScore = await this.server.client.db.Score.findOne({ pin: [Number(args.split('-')[0]), Number(args.split('-')[1]), Number(args.split('-')[2])] }).lean().exec(); + if (!staffScore) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + if (!staffScore.staff) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + this.timeout.delete(req.ip); + if (staffScore.userID === score.userID) { + updated = true; + await this.server.client.report.createInquiry(member.user.id, `${member.username} via report.libraryofcode.org @ IP ${req.ip}`, 1); + } else { + await this.server.client.report.createInquiry(member.user.id, 'Library of Code sp-us | Staff Team via report.libraryofcode.org', 1); + } + } else if (!updated) { + await this.server.client.report.createInquiry(member.user.id, `${member.username} via report.libraryofcode.org @ IP ${req.ip}`, 1); + } + score = await this.server.client.db.Score.findOne({ pin: [Number(args.split('-')[0]), Number(args.split('-')[1]), Number(args.split('-')[2])] }).lean().exec(); + + let totalScore = '0'; + let activityScore = '0'; + let moderationScore = '0'; + let roleScore = '0'; + let cloudServicesScore = '0'; + let otherScore = '0'; + let miscScore = '0'; + + if (score.total < 200) totalScore = '---'; + else if (score.total > 800) totalScore = '800'; + else totalScore = `${score.total}`; + + if (score.activity < 10) activityScore = '---'; + else if (score.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activityScore = String(Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))); + else activityScore = `${score.activity}`; + + if (score.roles <= 0) roleScore = '---'; + else if (score.roles > 54) roleScore = '54'; + else roleScore = `${score.roles}`; + + moderationScore = `${score.moderation}`; + + if (score.other === 0) otherScore = '---'; + else otherScore = `${score.other}`; + + if (score.staff <= 0) miscScore = '---'; + else miscScore = `${score.staff}`; + + if (score.cloudServices === 0) cloudServicesScore = '---'; + else if (score.cloudServices > 10) cloudServicesScore = '10'; + else cloudServicesScore = `${score.cloudServices}`; + + const moderations = await this.server.client.db.Moderation.find({ userID: score.userID }).lean().exec(); + + const historical = await this.server.client.db.ScoreHistorical.find({ userID: score.userID }).lean().exec(); + + for (const data of historical) { + let total: number; + let activity: number; + const moderation = Math.round(data.report.moderation); + let role: number; + let cloud: number; + const other = Math.round(data.report.other); + let misc: number; + + if (data.report.total < 200) total = 0; + else if (data.report.total > 800) total = 800; + else total = Math.round(data.report.total); + + if (data.report.activity < 10) activity = 0; + else if (data.report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12))) activity = Math.floor((Math.log1p(getTotalMessageCount(this.server.client)) * 12)); + else activity = Math.round(data.report.activity); + + if (data.report.roles <= 0) role = 0; + else if (data.report.roles > 54) role = 54; + else role = Math.round(data.report.roles); + + if (data.report.staff <= 0) role = 0; + else misc = Math.round(data.report.staff); + + if (data.report.cloudServices === 0) cloud = 0; + else if (data.report.cloudServices > 10) cloud = 10; + else cloud = Math.round(data.report.cloudServices); + + data.report.total = total; data.report.activity = activity; data.report.moderation = moderation; data.report.roles = role; data.report.cloudServices = cloud; data.report.other = other; data.report.staff = misc; + } + + const inqs = await this.server.client.db.Inquiry.find({ userID: score.userID }).lean().exec(); + const hardInquiries: [{ id?: string, name: string, reason: string, date: Date }?] = []; + const softInquiries: [{ id?: string, name: string, date: Date }?] = []; + for (const inq of inqs) { + if (inq.type === 0) { + hardInquiries.push({ id: inq.iid, name: inq.name, reason: inq.reason, date: inq.date }); + } else if (inq.type === 1) { + softInquiries.push({ name: inq.name, date: inq.date }); + } + } + + return res.status(200).json({ + name: `${member.username}#${member.discriminator}`, + avatarURL: member.avatarURL, + userID: score.userID, + pin: score.pin?.join('-'), + score: totalScore, + activityScore, + cloudServicesScore, + moderationScore, + roleScore, + otherScore, + miscScore, + notify: score.notify, + locked: !!score.locked, + totalModerations: moderations?.length > 0 ? moderations.length : 0, + inquiries: hardInquiries?.length > 0 ? hardInquiries.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) : [], + softInquiries: softInquiries?.length > 0 ? softInquiries.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) : [], + historical: historical ?? [], + lastUpdated: score.lastUpdate, + }); + } catch (err) { + return this.handleError(err, res); + } + }); + + this.router.get('/offer', async (req, res) => { + try { + res.setHeader('Access-Control-Allow-Origin', '*'); + if (!req.query.code) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + if (await this.acceptedOffers.get(req.query.code.toString())) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + let offer: { + userID?: string, + staffID?: string, + channelID?: string, + messageID?: string, + pin?: string, + name?: string, + department?: string, + date?: Date, + }; + + try { + offer = <{ + userID?: string, + staffID?: string, + channelID?: string, + messageID?: string, + pin?: string, + name?: string, + department?: string, + date?: Date, + }>jwt.verify(req.query.code.toString(), this.server.client.config.internalKey); + } catch { + return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + } + const chan = this.server.client.guilds.get(this.constants.discord.SERVER_ID).channels.get(offer.channelID); + await chan.createMessage(`__**PRE-APPROVED OFFER ACCEPTED**__\n<@${offer.staffID}>`); + const message = await chan.getMessage(offer.messageID); + const args = []; + args.push(offer.userID, 'hard'); + `${offer.department}:${offer.name}`.split(' ').forEach((item) => args.push(item)); + await this.server.client.commands.get('score').run(message, args); + await this.acceptedOffers.set(req.query.code.toString(), true); + return res.sendStatus(200); + } catch (err) { + return this.handleError(err, res); + } + }); + } +} diff --git a/src/api/cr.ins/main.ts b/src/api/cr.ins/main.ts new file mode 100644 index 0000000..d27b262 --- /dev/null +++ b/src/api/cr.ins/main.ts @@ -0,0 +1,3 @@ +import { Server, ServerManagement } from '../../class'; + +export default (management: ServerManagement) => new Server(management, 3891, `${__dirname}/routes`); diff --git a/src/api/cr.ins/routes/index.ts b/src/api/cr.ins/routes/index.ts new file mode 100644 index 0000000..011b9db --- /dev/null +++ b/src/api/cr.ins/routes/index.ts @@ -0,0 +1 @@ +export { default as root } from './root'; diff --git a/src/api/cr.ins/routes/root.ts b/src/api/cr.ins/routes/root.ts new file mode 100644 index 0000000..ff413f0 --- /dev/null +++ b/src/api/cr.ins/routes/root.ts @@ -0,0 +1,32 @@ +import { LocalStorage, Route, Server } from '../../../class'; + +export default class Root extends Route { + constructor(server: Server) { + super(server); + this.conf = { + path: '/', + }; + } + + public bind() { + this.router.get('/m/:id', async (req, res) => { + try { + const id = req.params.id.split('.')[0]; + const file = await this.server.client.db.File.findOne({ identifier: id }); + if (!file) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.messages.NOT_FOUND }); + if (file.downloaded >= file.maxDownloads) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.messages.NOT_FOUND }); + if (req.query.d === '1') { + res.contentType('text/html'); + const decomp = await LocalStorage.decompress(file.data); + res.status(200).send(decomp); + } else { + res.contentType(file.mimeType); + res.status(200).send(file.data); + } + return await file.updateOne({ $inc: { downloaded: 1 } }); + } catch (err) { + return this.handleError(err, res); + } + }); + } +} diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..e263f70 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,11 @@ +import locsh from './loc.sh/main'; +import crins from './cr.ins/main'; +import commlibraryofcodeorg from './comm.libraryofcode.org/main'; +import boardins from './board.ins/main'; + +export default { + 'board.ins': boardins, + 'loc.sh': locsh, + 'cr.ins': crins, + 'comm.libraryofcode.org': commlibraryofcodeorg, +}; diff --git a/src/api/loc.sh/main.ts b/src/api/loc.sh/main.ts new file mode 100644 index 0000000..bcf6ce9 --- /dev/null +++ b/src/api/loc.sh/main.ts @@ -0,0 +1,6 @@ +import { Server, ServerManagement } from '../../class'; + +export default (management: ServerManagement) => { + const server = new Server(management, 3890, `${__dirname}/routes`, false); + return server; +}; diff --git a/src/api/loc.sh/routes/index.ts b/src/api/loc.sh/routes/index.ts new file mode 100644 index 0000000..ed96cd48 --- /dev/null +++ b/src/api/loc.sh/routes/index.ts @@ -0,0 +1,2 @@ +export { default as root } from './root'; +export { default as internal } from './internal'; diff --git a/src/api/loc.sh/routes/internal.ts b/src/api/loc.sh/routes/internal.ts new file mode 100644 index 0000000..be15084 --- /dev/null +++ b/src/api/loc.sh/routes/internal.ts @@ -0,0 +1,119 @@ +import axios from 'axios'; +import bodyParser from 'body-parser'; +import { Route, Server, LocalStorage } from '../../../class'; +// import acknowledgements from '../../../configs/acknowledgements.json'; + +export default class Internal extends Route { + public timeout: Set; + + public acceptedOffers: LocalStorage; + + constructor(server: Server) { + super(server); + this.timeout = new Set(); + this.conf = { + path: '/int', + }; + this.acceptedOffers = new LocalStorage('accepted-offers'); + } + + public bind() { + this.router.get('/directory', async (req, res) => { + try { + res.setHeader('Access-Control-Allow-Origin', '*'); + if (req.query.id) { + let member = this.server.client.guilds.get(this.server.client.config.guildID).members.get(req.query.id.toString()); + if (!member) member = await this.server.client.getRESTGuildMember(this.server.client.config.guildID, req.query.id.toString()); + if (!member) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.messages.NOT_FOUND }); + const pagerNumber = await this.server.client.db.PagerNumber.findOne({ individualAssignID: member.id }).lean().exec(); + let status = false; + if (member.roles.includes('446104438969466890') || member.roles.includes('701481967149121627')) status = true; + return res.status(200).json({ staff: status, username: member.user.username, discriminator: member.user.discriminator, nick: member.nick, avatarURL: member.user.avatarURL, pager: pagerNumber?.num }); + } + const acknowledgements = await this.server.client.db.Staff.find().lean().exec(); + return res.status(200).json(acknowledgements); + } catch (err) { + return this.handleError(err, res); + } + }); + + this.router.get('/offer', async (_req, res) => { + try { + return res.status(410).json({ code: this.constants.codes.DEPRECATED, message: this.constants.codes.DEPRECATED }); + } catch (err) { + return this.handleError(err, res); + } + }); + + this.router.get('/score', async (_req, res) => { + try { + return res.status(410).json({ code: this.constants.codes.DEPRECATED, message: this.constants.codes.DEPRECATED }); + } catch (err) { + return this.handleError(err, res); + } + }); + + this.router.get('/id', async (req, res) => { + try { + if (req.query?.auth?.toString() !== this.server.client.config.internalKey) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + if (!req.query.pin) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR }); + const args = req.query.pin.toString(); + const user = await this.server.client.db.Score.findOne({ pin: [Number(args.split('-')[0]), Number(args.split('-')[1]), Number(args.split('-')[2])] }).lean().exec(); + if (!user) return res.status(404).json({ code: this.constants.codes.ACCOUNT_NOT_FOUND, message: this.constants.messages.NOT_FOUND }); + return res.status(200).json({ userID: user.userID }); + } catch (err) { + return this.handleError(err, res); + } + }); + + this.router.get('/pin', async (req, res) => { + try { + if (req.query?.auth?.toString() !== this.server.client.config.internalKey) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + if (!req.query.id) return res.status(400).json({ code: this.constants.codes.CLIENT_ERROR }); + + const report = await this.server.client.db.Score.findOne({ userID: req.query.id.toString() }); + if (!report) return res.status(404).json({ code: this.constants.codes.ACCOUNT_NOT_FOUND, message: this.constants.messages.NOT_FOUND }); + return res.status(200).json({ pin: report.pin }); + } catch (err) { + return this.handleError(err, res); + } + }); + + this.router.post('/sub', bodyParser.raw({ type: 'application/json' }), async (req, res) => { + try { + const event = this.server.client.stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], this.server.client.config.stripeSubSigningSecret); + const data = event.data.object; + + switch (event.type) { + default: + return res.sendStatus(400); + case 'customer.subscription.created': + if (data.items.data[0].price.product === 'prod_Hi4EYmf2am5VZt') { + const customer = await this.server.client.db.Customer.findOne({ cusID: data.customer }).lean().exec(); + if (!customer) return res.sendStatus(404); + await axios({ + method: 'get', + url: `https://api.cloud.libraryofcode.org/wh/t3?userID=${customer.userID}&auth=${this.server.client.config.internalKey}`, + }); + res.sendStatus(200); + } + break; + case 'customer.subscription.deleted': + if (data.items.data[0].price.product === 'prod_Hi4EYmf2am5VZt') { + const customer = await this.server.client.db.Customer.findOne({ cusID: data.customer }).lean().exec(); + if (!customer) return res.sendStatus(404); + await axios({ + method: 'get', + url: `https://api.cloud.libraryofcode.org/wh/t3-rm?userID=${customer.userID}&auth=${this.server.client.config.internalKey}`, + }); + return res.sendStatus(200); + } + break; + } + return null; + } catch (err) { + return this.handleError(err, res); + } + }); + } +} diff --git a/src/api/loc.sh/routes/root.ts b/src/api/loc.sh/routes/root.ts new file mode 100644 index 0000000..9540c1b --- /dev/null +++ b/src/api/loc.sh/routes/root.ts @@ -0,0 +1,64 @@ +import { Route, Server } from '../../../class'; +import { RedirectRaw } from '../../../models'; + +export default class Root extends Route { + constructor(server: Server) { + super(server); + this.conf = { + path: '/', + }; + } + + public bind() { + this.router.get('/', (_req, res) => res.redirect('https://www.libraryofcode.org/')); + + this.router.get('/dash', async (req, res) => { + try { + const lookup = await this.server.client.db.CustomerPortal.findOne({ key: req.query.q?.toString() }); + if (!lookup) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.messages.NOT_FOUND }); + if (new Date(lookup.expiresOn) < new Date()) return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED }); + + const customer = await this.server.client.db.Customer.findOne({ userID: lookup.userID }); + if (!customer) { + const newCus = await this.server.client.stripe.customers.create({ + email: lookup.emailAddress, + metadata: { + userID: lookup.userID, + username: lookup.username, + }, + }); + await (new this.server.client.db.Customer({ + cusID: newCus.id, + userID: lookup.userID, + })).save(); + const billingURL = await this.server.client.stripe.billingPortal.sessions.create({ + customer: newCus.id, + return_url: 'https://www.libraryofcode.org', + }); + res.redirect(302, billingURL.url); + return await lookup.updateOne({ $set: { used: true } }); + } + const billingURL = await this.server.client.stripe.billingPortal.sessions.create({ + customer: customer.cusID, + return_url: 'https://www.libraryofcode.org', + }); + res.redirect(302, billingURL.url); + return await lookup.updateOne({ $set: { used: true } }); + } catch (err) { + return this.handleError(err, res); + } + }); + + this.router.get('/:key', async (req, res) => { + try { + const link: RedirectRaw = await this.server.client.db.Redirect.findOne({ key: req.params.key }).lean().exec(); + if (!link) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.messages.NOT_FOUND }); + res.redirect(link.to); + return await this.server.client.db.Redirect.updateOne({ key: req.params.key }, { $inc: { visitedCount: 1 } }); + } catch (err) { + this.server.client.util.handleError(err); + return res.status(500).json({ code: this.constants.codes.SERVER_ERROR, message: this.constants.messages.SERVER_ERROR }); + } + }); + } +} diff --git a/src/class/Client.ts b/src/class/Client.ts new file mode 100644 index 0000000..8375bf1 --- /dev/null +++ b/src/class/Client.ts @@ -0,0 +1,137 @@ +import Stripe from 'stripe'; +import eris from 'eris'; +import pluris from 'pluris'; +import mongoose from 'mongoose'; +import { promises as fs } from 'fs'; +import { Collection, Command, LocalStorage, Queue, Util, ServerManagement, Event } from '.'; +import { + Customer, CustomerInterface, + CustomerPortal, CustomerPortalInterface, + ExecutiveOrder, ExecutiveOrderInterface, + File, FileInterface, + Inquiry, InquiryInterface, + Member, MemberInterface, + Merchant, MerchantInterface, + Moderation, ModerationInterface, + Motion, MotionInterface, + NNTrainingData, NNTrainingDataInterface, + Note, NoteInterface, + PagerNumber, PagerNumberInterface, + Proclamation, ProclamationInterface, + Promo, PromoInterface, + Rank, RankInterface, + Redirect, RedirectInterface, + Resolution, ResolutionInterface, + Score, ScoreInterface, + ScoreHistorical, ScoreHistoricalInterface, + Staff, StaffInterface, + Stat, StatInterface, +} from '../models'; +import { Config } from '../../types'; // eslint-disable-line + +pluris(eris); + +export default class Client extends eris.Client { + public config: Config; + + public commands: Collection; + + public events: Collection; + + public intervals: Collection; + + public util: Util; + + public serverManagement: ServerManagement; + + public queue: Queue; + + public stripe: Stripe; + + public db: { Customer: mongoose.Model, CustomerPortal: mongoose.Model, ExecutiveOrder: mongoose.Model, File: mongoose.Model, Inquiry: mongoose.Model, Member: mongoose.Model, Merchant: mongoose.Model, Moderation: mongoose.Model, Motion: mongoose.Model, NNTrainingData: mongoose.Model, Note: mongoose.Model, PagerNumber: mongoose.Model, Proclamation: mongoose.Model, Promo: mongoose.Model, Rank: mongoose.Model, Redirect: mongoose.Model, Resolution: mongoose.Model, Score: mongoose.Model, ScoreHistorical: mongoose.Model, Staff: mongoose.Model, Stat: mongoose.Model, local: { muted: LocalStorage } }; + + constructor(token: string, options?: eris.ClientOptions) { + super(token, options); + this.commands = new Collection(); + this.events = new Collection(); + this.intervals = new Collection(); + this.queue = new Queue(this); + this.db = { Customer, CustomerPortal, ExecutiveOrder, File, Inquiry, Member, Merchant, Moderation, Motion, NNTrainingData, Note, PagerNumber, Promo, Proclamation, Rank, Redirect, Resolution, Score, ScoreHistorical, Staff, Stat, local: { muted: new LocalStorage('muted') } }; + } + + get report() { + return this.util.report; + } + + get pbx() { + return this.util.pbx; + } + + + public async loadDatabase() { + await mongoose.connect(this.config.mongoDB, { useNewUrlParser: true, useUnifiedTopology: true, poolSize: 50 }); + + const statMessages = await this.db.Stat.findOne({ name: 'messages' }); + const statCommands = await this.db.Stat.findOne({ name: 'commands' }); + const statPages = await this.db.Stat.findOne({ name: 'pages' }); + const statRequests = await this.db.Stat.findOne({ name: 'requests' }); + + if (!statMessages) { + await (new this.db.Stat({ name: 'messages', value: 0 }).save()); + } + if (!statCommands) { + await (new this.db.Stat({ name: 'commands', value: 0 }).save()); + } + if (!statPages) { + await (new this.db.Stat({ name: 'pages', value: 0 }).save()); + } + if (!statRequests) { + await (new this.db.Stat({ name: 'requests', value: 0 }).save()); + } + } + + public loadPlugins() { + this.util = new Util(this); + this.serverManagement = new ServerManagement(this); + this.stripe = new Stripe(this.config.stripeKey, { apiVersion: null, typescript: true }); + } + + public async loadIntervals() { + const intervalFiles = await fs.readdir(`${__dirname}/../intervals`); + intervalFiles.forEach((file) => { + const intervalName = file.split('.')[0]; + if (file === 'index.js') return; + const interval: NodeJS.Timeout = (require(`${__dirname}/../intervals/${file}`).default)(this); + this.intervals.add(intervalName, interval); + this.util.signale.success(`Successfully loaded interval: ${intervalName}`); + }); + } + + public async loadEvents(eventFiles: { [s: string]: typeof Event; } | ArrayLike) { + const evtFiles = Object.entries(eventFiles); + for (const [name, Ev] of evtFiles) { + const event = new Ev(this); + this.events.add(event.event, event); + this.on(event.event, event.run); + this.util.signale.success(`Successfully loaded event: ${name}`); + delete require.cache[require.resolve(`${__dirname}/../events/${name}`)]; + } + } + + public async loadCommands(commandFiles: { [s: string]: typeof Command; } | ArrayLike) { + const cmdFiles = Object.values(commandFiles); + for (const Cmd of cmdFiles) { + const command = new Cmd(this); + if (command.subcmds.length) { + command.subcmds.forEach((C) => { + const cmd: Command = new C(this); + command.subcommands.add(cmd.name, cmd); + this.util.signale.success(`Successfully loaded subcommand ${cmd.name} under ${command.name}`); + }); + } + delete command.subcmds; + this.commands.add(command.name, command); + this.util.signale.success(`Successfully loaded command: ${command.name}`); + } + } +} diff --git a/src/class/Collection.ts b/src/class/Collection.ts new file mode 100644 index 0000000..044e12c --- /dev/null +++ b/src/class/Collection.ts @@ -0,0 +1,153 @@ +/** + * Hold a bunch of something + */ +export default class Collection extends Map { + baseObject: new (...args: any[]) => V; + + /** + * Creates an instance of Collection + */ + constructor(iterable: Iterable<[string, V]>|object = null) { + if (iterable && iterable instanceof Array) { + super(iterable); + } else if (iterable && iterable instanceof Object) { + super(Object.entries(iterable)); + } else { + super(); + } + } + + /** + * Map to array + * ```js + * [value, value, value] + * ``` + */ + toArray(): V[] { + return [...this.values()]; + } + + /** + * Map to object + * ```js + * { key: value, key: value, key: value } + * ``` + */ + toObject(): { [key: string]: V } { + const obj: { [key: string]: V } = {}; + for (const [key, value] of this.entries()) { + obj[key] = value; + } + return obj; + } + + /** + * Add an object + * + * If baseObject, add only if instance of baseObject + * + * If no baseObject, add + * @param key The key of the object + * @param value The object data + * @param replace Whether to replace an existing object with the same key + * @return The existing or newly created object + */ + add(key: string, value: V, replace: boolean = false): V { + if (this.has(key) && !replace) { + return this.get(key); + } + if (this.baseObject && !(value instanceof this.baseObject)) return null; + + this.set(key, value); + return value; + } + + /** + * Return the first object to make the function evaluate true + * @param func A function that takes an object and returns something + * @return The first matching object, or `null` if no match + */ + find(func: Function): V { + for (const item of this.values()) { + if (func(item)) return item; + } + return null; + } + + /** + * Return an array with the results of applying the given function to each element + * @param callbackfn A function that takes an object and returns something + */ + map(callbackfn: (value?: V, index?: number, array?: V[]) => U): U[] { + const arr = []; + for (const item of this.values()) { + arr.push(callbackfn(item)); + } + return arr; + } + + /** + * Return all the objects that make the function evaluate true + * @param func A function that takes an object and returns true if it matches + */ + filter(func: (value: V) => boolean): V[] { + const arr = []; + for (const item of this.values()) { + if (func(item)) { + arr.push(item); + } + } + return arr; + } + + /** + * Test if at least one element passes the test implemented by the provided function. Returns true if yes, or false if not. + * @param func A function that takes an object and returns true if it matches + */ + some(func: (value: V) => boolean) { + for (const item of this.values()) { + if (func(item)) { + return true; + } + } + return false; + } + + /** + * Update an object + * @param key The key of the object + * @param value The updated object data + */ + update(key: string, value: V) { + return this.add(key, value, true); + } + + /** + * Remove an object + * @param key The key of the object + * @returns The removed object, or `null` if nothing was removed + */ + remove(key: string): V { + const item = this.get(key); + if (!item) { + return null; + } + this.delete(key); + return item; + } + + /** + * Get a random object from the Collection + * @returns The random object or `null` if empty + */ + random(): V { + if (!this.size) { + return null; + } + return Array.from(this.values())[Math.floor(Math.random() * this.size)]; + } + + toString() { + return `[Collection<${this.baseObject.name}>]`; + } +} diff --git a/src/class/Command.ts b/src/class/Command.ts new file mode 100644 index 0000000..d79e678 --- /dev/null +++ b/src/class/Command.ts @@ -0,0 +1,129 @@ +import { Member, Message, TextableChannel } from 'eris'; +import { Client, Collection } from '.'; + +export default class Command { + public client: Client; + + /** + * The name of the command + */ + public name: string; + /** + * The description for the command. + */ + + public description: string; + + /** + * Usage for the command. + */ + public usage: string; + /** + * The aliases for the command. + */ + + public aliases: string[]; + + /** + * - **0:** Everyone + * - **1:** Associates+ + * - **2:** Core Team+ + * - **3:** Moderators, Supervisor, & Board of Directors + * - **4:** Technicians, Supervisor, & Board of Directors + * - **5:** Moderators, Technicians, Supervisor, & Board of Directors + * - **6:** Supervisor+ + * - **7:** Board of Directors + */ + public permissions: number; + + /** + * Determines if the command is only available in server. + */ + public guildOnly: boolean; + + /** + * Determines if the command is enabled or not. + */ + + public subcommands?: Collection; + + public subcmds?: any[]; + + public enabled: boolean; + + public run(message: Message, args: string[]): Promise { return Promise.resolve(); } + + constructor(client: Client) { + this.client = client; + + this.aliases = []; + + this.subcommands = new Collection(); + + this.subcmds = []; + } + + get mainGuild() { + return this.client.guilds.get(this.client.config.guildID); + } + + public checkPermissions(member: Member): boolean { + if (member.id === '278620217221971968' || member.id === '253600545972027394') return true; + switch (this.permissions) { + case 0: + return true; + case 1: + return member.roles.some((r) => ['701481967149121627', '453689940140883988', '455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r)); + case 2: + return member.roles.some((r) => ['453689940140883988', '455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r)); + case 3: + return member.roles.some((r) => ['455972169449734144', '701454855952138300', '662163685439045632'].includes(r)); + case 4: + return member.roles.some((r) => ['701454780828221450', '701454855952138300', '662163685439045632'].includes(r)); + case 5: + return member.roles.some((r) => ['455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r)); + case 6: + return member.roles.some((r) => ['701454855952138300', '662163685439045632'].includes(r)); + case 7: + return member.roles.includes('662163685439045632'); + default: + return false; + } + } + + public checkCustomPermissions(member: Member, permission: number): boolean { + if (member.id === '278620217221971968' || member.id === '253600545972027394') return true; + switch (permission) { + case 0: + return true; + case 1: + return member.roles.some((r) => ['701481967149121627', '453689940140883988', '455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r)); + case 2: + return member.roles.some((r) => ['453689940140883988', '455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r)); + case 3: + return member.roles.some((r) => ['455972169449734144', '701454855952138300', '662163685439045632'].includes(r)); + case 4: + return member.roles.some((r) => ['701454780828221450', '701454855952138300', '662163685439045632'].includes(r)); + case 5: + return member.roles.some((r) => ['455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r)); + case 6: + return member.roles.some((r) => ['701454855952138300', '662163685439045632'].includes(r)); + case 7: + return member.roles.includes('662163685439045632'); + default: + return false; + } + } + + public error(channel: TextableChannel, text: string): Promise { + return channel.createMessage(`***${this.client.util.emojis.ERROR} ${text}***`); + } + + public success(channel: TextableChannel, text: string): Promise { + return channel.createMessage(`***${this.client.util.emojis.SUCCESS} ${text}***`); + } + + public loading(channel: TextableChannel, text: string): Promise { + return channel.createMessage(`***${this.client.util.emojis.LOADING} ${text}***`); + } +} diff --git a/src/class/Event.ts b/src/class/Event.ts new file mode 100644 index 0000000..658f18c --- /dev/null +++ b/src/class/Event.ts @@ -0,0 +1,15 @@ +import { Client } from '.'; + +export default class Event { + public client: Client + + public event: string; + + constructor(client: Client) { + this.client = client; + this.event = ''; + this.run = this.run.bind(this); + } + + public async run(...args: any[]): Promise { return Promise.resolve(); } +} diff --git a/src/class/Handler.ts b/src/class/Handler.ts new file mode 100644 index 0000000..9c406f7 --- /dev/null +++ b/src/class/Handler.ts @@ -0,0 +1,28 @@ +import ARI from 'ari-client'; +import { PBX } from '.'; + +export default class Handler { + public pbx: PBX; + + public app: string; + + public options: { + available?: boolean, + } + + constructor(pbx: PBX) { + this.pbx = pbx; + this.options = {}; + } + + get client() { return this.pbx.client; } + + public run(event: ARI.Event, channel: ARI.Channel): Promise { return Promise.resolve(); } + + public async unavailable(event: ARI.Event, channel: ARI.Channel) { + const playback = await channel.play({ + media: 'sound:all-outgoing-lines-unavailable', + }, undefined); + playback.once('PlaybackFinished', () => channel.hangup()); + } +} diff --git a/src/class/LocalStorage.ts b/src/class/LocalStorage.ts new file mode 100644 index 0000000..cc076f7 --- /dev/null +++ b/src/class/LocalStorage.ts @@ -0,0 +1,156 @@ +/* eslint-disable no-constant-condition */ +import { promises as fs, accessSync, constants, writeFileSync } from 'fs'; +import { promisify } from 'util'; +import { join } from 'path'; +import { gzip, gzipSync, unzip } from 'zlib'; + +type JSONData = [{ key: string, value: any }?]; + + +/** + * Persistant local JSON-based storage. + * - auto-locking system to prevent corrupted data + * - uses gzip compression to keep DB storage space utilization low + * @author Matthew + */ +export default class LocalStorage { + protected storagePath: string; + + private locked: boolean = false; + + constructor(dbName: string, dir = `${__dirname}/../../localstorage`) { + this.storagePath = join(__dirname, '../../localstorage') || dir; + this.init(); + } + + private init() { + try { + accessSync(this.storagePath, constants.F_OK); + } catch { + const setup = []; + const data = gzipSync(JSON.stringify(setup)); + writeFileSync(this.storagePath, data); + } + return this; + } + + /** + * Compresses data using gzip. + * @param data The data to be compressed. + * ```ts + * await LocalStorage.compress('hello!'); + * ``` + */ + static async compress(data: string): Promise { + const func = promisify(gzip); + const comp = await func(data); + return comp; + } + + /** + * Decompresses data using gzip. + * @param data The data to be decompressed. + * ```ts + * const compressed = await LocalStorage.compress('data'); + * const decompressed = await LocalStorage.decompress(compressed); + * console.log(decompressed); // logs 'data'; + * ``` + */ + static async decompress(data: Buffer): Promise { + const func = promisify(unzip); + const uncomp = await func(data); + return uncomp.toString(); + } + + /** + * Retrieves one data from the store. + * If the store has multiple entries for the same key, this function will only return the first entry. + * ```ts + * await LocalStorage.get('data-key'); + * ``` + * @param key The key for the data entry. + */ + public async get(key: string): Promise { + while (true) { + if (!this.locked) break; + } + this.locked = true; + + const file = await fs.readFile(this.storagePath); + const uncomp = await LocalStorage.decompress(file); + this.locked = false; + const json: JSONData = JSON.parse(uncomp); + const result = json.filter((data) => data.key === key); + if (!result[0]) return null; + return result[0].value; + } + + /** + * Retrieves multiple data keys/values from the store. + * This function will return all of the values matching the key you provided exactly. Use `LocalStorage.get();` if possible. + * ```ts + * await LocalStorage.get('data-key'); + * @param key The key for the data entry. + */ + public async getMany(key: string): Promise<{ key: string, value: T }[]> { + while (true) { + if (!this.locked) break; + } + this.locked = true; + + const file = await fs.readFile(this.storagePath); + const uncomp = await LocalStorage.decompress(file); + this.locked = false; + const json: JSONData = JSON.parse(uncomp); + const result = json.filter((data) => data.key === key); + if (result.length < 1) return null; + return result; + } + + /** + * Sets a key/value pair and creates a new data entry. + * @param key The key for the data entry. + * @param value The value for the data entry, can be anything that is valid JSON. + * @param options.override [DEPRECATED] By default, this function will error if the key you're trying to set already exists. Set this option to true to override that setting. + * ```ts + * await LocalStorage.set('data-key', 'test'); + * ``` + */ + public async set(key: string, value: any): Promise { + while (true) { + if (!this.locked) break; + } + this.locked = true; + + const file = await fs.readFile(this.storagePath); + const uncomp = await LocalStorage.decompress(file); + const json: JSONData = JSON.parse(uncomp); + json.push({ key, value }); + const comp = await LocalStorage.compress(JSON.stringify(json)); + await fs.writeFile(this.storagePath, comp); + this.locked = false; + } + + /** + * Deletes the data for the specified key. + * **Warning:** This function will delete ALL matching entries. + * ```ts + * await LocalStorage.del('data-key'); + * ``` + * @param key The key for the data entry. + */ + public async del(key: string): Promise { + while (true) { + if (!this.locked) break; + } + this.locked = true; + + const file = await fs.readFile(this.storagePath); + const uncomp = await LocalStorage.decompress(file); + const json: JSONData = JSON.parse(uncomp); + const filtered = json.filter((data) => data.key !== key); + const comp = await LocalStorage.compress(JSON.stringify(filtered)); + await fs.writeFile(this.storagePath, comp); + this.locked = false; + } +} diff --git a/src/class/Moderation.ts b/src/class/Moderation.ts new file mode 100644 index 0000000..89841d4 --- /dev/null +++ b/src/class/Moderation.ts @@ -0,0 +1,220 @@ +/* eslint-disable no-bitwise */ +import { Member, User } from 'eris'; +import { randomBytes } from 'crypto'; +import moment, { unitOfTime } from 'moment'; +import { Client, RichEmbed } from '.'; +import { Moderation as ModerationModel, ModerationInterface } from '../models'; +import { moderation as channels } from '../configs/channels.json'; + +export default class Moderation { + public client: Client; + + public logChannels: { + modlogs: string + }; + + constructor(client: Client) { + this.client = client; + this.logChannels = { + modlogs: channels.modlogs, + }; + } + + public checkPermissions(member: Member, moderator: Member): boolean { + if (member.id === moderator.id) return false; + if (member.roles.some((r) => ['662163685439045632', '455972169449734144', '453689940140883988'].includes(r))) return false; + const bit = member.permission.allow; + if ((bit | 8) === bit) return false; + if ((bit | 20) === bit) return false; + return true; + } + + /** + * Converts some sort of time based duration to milliseconds based on length. + * @param time The time, examples: 2h, 1m, 1w + */ + public convertTimeDurationToMilliseconds(time: string): number { + const lockLength = time.match(/[a-z]+|[^a-z]+/gi); + const length = Number(lockLength[0]); + const unit = lockLength[1] as unitOfTime.Base; + return moment.duration(length, unit).asMilliseconds(); + } + + public async ban(user: User, moderator: Member, duration: number, reason?: string): Promise { + if (reason && reason.length > 512) throw new Error('Ban reason cannot be longer than 512 characters'); + await this.client.guilds.get(this.client.config.guildID).banMember(user.id, 7, reason); + const logID = randomBytes(2).toString('hex'); + const mod = new ModerationModel({ + userID: user.id, + logID, + moderatorID: moderator.id, + reason: reason || null, + type: 5, + date: new Date(), + }); + const now: number = Date.now(); + let date: Date; + let processed = true; + if (duration > 0) { + date = new Date(now + duration); + processed = false; + } else date = null; + const expiration = { date, processed }; + mod.expiration = expiration; + + const embed = new RichEmbed(); + embed.setTitle(`Case ${logID} | Ban`); + embed.setColor('#e74c3c'); + embed.setAuthor(user.username, user.avatarURL); + embed.setThumbnail(user.avatarURL); + embed.addField('User', `<@${user.id}>`, true); + embed.addField('Moderator', `<@${moderator.id}>`, true); + if (reason) { + embed.addField('Reason', reason, true); + } + if (date) { + embed.addField('Expiration', moment(date).calendar(), true); + } + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + this.client.createMessage(this.logChannels.modlogs, { embed }); + return mod.save(); + } + + public async unban(userID: string, moderator: Member, reason?: string): Promise { + this.client.unbanGuildMember(this.client.config.guildID, userID, reason); + const user = await this.client.getRESTUser(userID); + if (!user) throw new Error('Cannot get user.'); + const logID = randomBytes(2).toString('hex'); + const mod = new ModerationModel({ + userID, + logID, + moderatorID: moderator.id, + reason: reason || null, + type: 4, + date: new Date(), + }); + + const embed = new RichEmbed(); + embed.setTitle(`Case ${logID} | Unban`); + embed.setColor('#1abc9c'); + embed.setAuthor(user.username, user.avatarURL); + embed.setThumbnail(user.avatarURL); + embed.addField('User', `<@${user.id}>`, true); + embed.addField('Moderator', `<@${moderator.id}>`, true); + if (reason) { + embed.addField('Reason', reason, true); + } + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + this.client.createMessage(this.logChannels.modlogs, { embed }); + return mod.save(); + } + + public async mute(user: User, moderator: Member, duration: number, reason?: string): Promise { + if (reason && reason.length > 512) throw new Error('Mute reason cannot be longer than 512 characters'); + const member = await this.client.getRESTGuildMember(this.client.config.guildID, user.id); + if (!member) throw new Error('Cannot find member.'); + await member.addRole('478373942638149643', `Muted by ${moderator.username}#${moderator.discriminator}`); + const logID = randomBytes(2).toString('hex'); + const mod = new ModerationModel({ + userID: user.id, + logID, + moderatorID: moderator.id, + reason: reason || null, + type: 2, + date: new Date(), + }); + const now: number = Date.now(); + let date: Date; + let processed = true; + if (duration > 0) { + date = new Date(now + duration); + processed = false; + } else date = null; + const expiration = { date, processed }; + mod.expiration = expiration; + await this.client.db.local.muted.set(`muted-${member.id}`, true); + + const embed = new RichEmbed(); + embed.setTitle(`Case ${logID} | Mute`); + embed.setColor('#ffff00'); + embed.setAuthor(user.username, user.avatarURL); + embed.setThumbnail(user.avatarURL); + embed.addField('User', `<@${user.id}>`, true); + embed.addField('Moderator', `<@${moderator.id}>`, true); + if (reason) { + embed.addField('Reason', reason, true); + } + if (date) { + embed.addField('Expiration', moment(date).calendar(), true); + } + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + this.client.createMessage(this.logChannels.modlogs, { embed }); + return mod.save(); + } + + public async unmute(userID: string, moderator: Member, reason?: string): Promise { + const member = await this.client.getRESTGuildMember(this.client.config.guildID, userID); + const user = await this.client.getRESTUser(userID); + if (member) { + await member.removeRole('478373942638149643'); + } + const logID = randomBytes(2).toString('hex'); + const mod = new ModerationModel({ + userID, + logID, + moderatorID: moderator.id, + reason: reason || null, + type: 1, + date: new Date(), + }); + + await this.client.db.local.muted.del(`muted-${member.id}`); + + const embed = new RichEmbed(); + embed.setTitle(`Case ${logID} | Unmute`); + embed.setColor('#1abc9c'); + embed.setAuthor(user.username, user.avatarURL); + embed.setThumbnail(user.avatarURL); + embed.addField('User', `<@${user.id}>`, true); + embed.addField('Moderator', `<@${moderator.id}>`, true); + if (reason) { + embed.addField('Reason', reason, true); + } + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + this.client.createMessage(this.logChannels.modlogs, { embed }); + return mod.save(); + } + + public async kick(user: Member | User, moderator: Member, reason?: string): Promise { + if (reason && reason.length > 512) throw new Error('Kick reason cannot be longer than 512 characters'); + await this.client.guilds.get(this.client.config.guildID).kickMember(user.id, reason); + const logID = randomBytes(2).toString('hex'); + const mod = new ModerationModel({ + userID: user.id, + logID, + moderatorID: moderator.id, + reason: reason || null, + type: 5, + date: new Date(), + }); + + const embed = new RichEmbed(); + embed.setTitle(`Case ${logID} | Kick`); + embed.setColor('#e74c3c'); + embed.setAuthor(user.username, user.avatarURL); + embed.setThumbnail(user.avatarURL); + embed.addField('User', `<@${user.id}>`, true); + embed.addField('Moderator', `<@${moderator.id}>`, true); + if (reason) { + embed.addField('Reason', reason, true); + } + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + this.client.createMessage(this.logChannels.modlogs, { embed }); + return mod.save(); + } +} diff --git a/src/class/PBX.ts b/src/class/PBX.ts new file mode 100644 index 0000000..6fe79e5 --- /dev/null +++ b/src/class/PBX.ts @@ -0,0 +1,61 @@ +/* eslint-disable no-continue */ +import ARIClient from 'ari-client'; +import AMIClient from 'asterisk-manager'; +import GoogleTTS, { TextToSpeechClient } from '@google-cloud/text-to-speech'; +import { Client, Collection, Handler } from '.'; + +export default class PBX { + public client: Client; + + public handlers: Collection; + + public ari: ARIClient.Client; + + public ami: any; + + public tts: TextToSpeechClient; + + constructor(client: Client) { + this.client = client; + + this.handlers = new Collection(); + + this.load(); + } + + private async load() { + this.ari = await ARIClient.connect('http://10.8.0.1:8088/ari', 'cr0', this.client.config.ariClientKey); + this.ari.start(['cr-zero', 'page-dtmf']); + + this.ami = new AMIClient(5038, '10.8.0.1', 'cr', this.client.config.amiClientKey); + + process.env.GOOGLE_APPLICATION_CREDENTIALS = `${__dirname}/../../google.json`; + this.tts = new GoogleTTS.TextToSpeechClient(); + this.start(); + } + + public start() { + const handlers = Object.values(require(`${__dirname}/../pbx`)); + for (const HandlerFile of handlers) { + const handler = new HandlerFile(this); + if (!handler.app) continue; + if (!handler.options?.available) { + this.ari.on('StasisStart', async (event, channel) => { + if (event.application !== handler.app) return; + await handler.unavailable(event, channel); + }); + } else { + this.ari.on('StasisStart', async (event, channel) => { + if (event.application !== handler.app) return; + try { + await handler.run(event, channel); + } catch (err) { + this.client.util.handleError(err); + } + }); + } + this.handlers.add(handler.app, handler); + this.client.util.signale.success(`Successfully loaded PBX Handler ${handler.app}`); + } + } +} diff --git a/src/class/Queue.ts b/src/class/Queue.ts new file mode 100644 index 0000000..df5fe5f --- /dev/null +++ b/src/class/Queue.ts @@ -0,0 +1,170 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-eval */ +import Bull from 'bull'; +import cron from 'cron'; +import { TextableChannel, TextChannel } from 'eris'; +import { Client, RichEmbed } from '.'; +import { ScoreInterface, InqType as InquiryType } from '../models'; + +import { apply as Apply } from '../commands'; + +export default class Queue { + public client: Client; + + public queues: { score: Bull.Queue }; + + constructor(client: Client) { + this.client = client; + this.queues = { + score: new Bull('score', { prefix: 'queue::score' }), + }; + this.setProcessors(); + this.setCronJobs(); + } + + protected setCronJobs() { + const historialCommunityReportJob = new cron.CronJob('0 20 * * *', async () => { + try { + const reports = await this.client.db.Score.find().lean().exec(); + const startDate = new Date(); + + for (const report of reports) { + const inqs = await this.client.db.Inquiry.find({ userID: report.userID }); + const data = new this.client.db.ScoreHistorical({ + userID: report.userID, + report, + inquiries: inqs.map((inq) => inq._id), + date: startDate, + }); + await data.save(); + } + } catch (err) { + this.client.util.handleError(err); + } + }); + + historialCommunityReportJob.start(); + } + + public async jobCounts() { + const data = { + waiting: 0, + active: 0, + completed: 0, + failed: 0, + delayed: 0, + }; + for (const entry of Object.entries(this.queues)) { + // eslint-disable-next-line no-await-in-loop + const counts = await entry[1].getJobCounts(); + data.waiting += counts.waiting; + data.active += counts.active; + data.completed += counts.completed; + data.failed += counts.failed; + data.delayed += counts.delayed; + } + return data; + } + + protected listeners() { + this.queues.score.on('active', (job) => { + this.client.util.signale.pending(`${job.id} has become active.`); + }); + this.queues.score.on('completed', (job) => { + this.client.util.signale.success(`Job with id ${job.id} has been completed`); + }); + this.queues.score.on('error', async (err) => { + this.client.util.handleError(err); + }); + } + + protected setProcessors() { + this.queues.score.process('score::inquiry', async (job: Bull.Job<{ inqID: string, userID: string, name: string, type: InquiryType, reason?: string }>) => { + const member = this.client.util.resolveMember(job.data.userID, this.client.guilds.get(this.client.config.guildID)); + const report = await this.client.db.Score.findOne({ userID: job.data.userID }).lean().exec(); + const embed = new RichEmbed(); + if (job.data.type === InquiryType.HARD) { + embed.setTitle('Inquiry Notification'); + embed.setDescription(job.data.inqID); + embed.setColor('#800080'); + embed.addField('Member', `${member.user.username}#${member.user.discriminator} | <@${job.data.userID}>`, true); + embed.addField('Type', 'HARD', true); + embed.addField('Department/Service', job.data.name.toUpperCase(), true); + embed.addField('Reason', job.data.reason ?? 'N/A', true); + embed.setTimestamp(); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + if (report.notify === true) { + await this.client.getDMChannel(job.data.userID).then((chan) => { + chan.createMessage(`__**Community Score - Hard Pull Notification**__\n*You have signed up to be notified whenever your hard score has been pulled. See \`?score\` for more information.*\n\n**Department/Service:** ${job.data.name.toUpperCase()}`); + }).catch(() => {}); + } + } else { + embed.setTitle('Inquiry Notification'); + embed.setColor('#00FFFF'); + embed.addField('Member', `${member.user.username}#${member.user.discriminator} | <@${job.data.userID}>`, true); + embed.addField('Type', 'SOFT', true); + embed.addField('Department/Service', job.data.name.toUpperCase(), true); + embed.setTimestamp(); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + } + const log = this.client.guilds.get(this.client.config.guildID).channels.get('611584771356622849'); + log.createMessage({ embed }).catch(() => {}); + }); + this.queues.score.process('score::update', async (job: Bull.Job<{ score: ScoreInterface, total: number, activity: number, roles: number, moderation: number, cloudServices: number, other: number, staff: number }>) => { + await this.client.db.Score.updateOne({ userID: job.data.score.userID }, { $set: { total: job.data.total, activity: job.data.activity, roles: job.data.roles, moderation: job.data.moderation, cloudServices: job.data.cloudServices, other: job.data.other, staff: job.data.staff, lastUpdate: new Date() } }); + if (!job.data.score.pin || job.data.score.pin?.length < 1) { + await this.client.db.Score.updateOne({ userID: job.data.score.userID }, { $set: { pin: [this.client.util.randomNumber(100, 999), this.client.util.randomNumber(10, 99), this.client.util.randomNumber(1000, 9999)] } }); + } + }); + this.queues.score.process('score::apply', async (job: Bull.Job<{ channelInformation: { messageID: string, guildID: string, channelID: string }, url: string, userID: string, func?: string }>) => { + const application = await Apply.apply(this.client, job.data.url, job.data.userID); + const guild = this.client.guilds.get(job.data.channelInformation.guildID); + const channel = guild.channels.get(job.data.channelInformation.channelID); + const message = await channel.getMessage(job.data.channelInformation.messageID); + const member = guild.members.get(job.data.userID); + await message.delete(); + const embed = new RichEmbed(); + embed.setTitle('Application Decision'); + if (member) { + embed.setAuthor(member.username, member.avatarURL); + } + if (application.decision === 'APPROVED') { + embed.setColor('#50c878'); + } else if (application.decision === 'DECLINED') { + embed.setColor('#fe0000'); + } else if (application.decision === 'PRE-DECLINE') { + embed.setColor('#ffa500'); + } else { + embed.setColor('#eeeeee'); + } + const chan = await this.client.getDMChannel(job.data.userID); + if (chan && application.token) { + chan.createMessage(`__**Application Decision Document**__\n*This document provides detailed information about the decision given to you by EDS.*\n\nhttps://eds.libraryofcode.org/dec/${application.token}`); + } + embed.setDescription(`This application was processed by __${application.processedBy}__ on behalf of the vendor, department, or service who operates this application. Please contact the vendor for further information about your application if needed.`); + embed.addField('Status', application.decision, true); + embed.addField('User ID', job.data.userID, true); + embed.addField('Application ID', application.id, true); + embed.addField('Job ID', job.id.toString(), true); + embed.setFooter(`${this.client.user.username} via Electronic Decision Service [EDS]`, this.client.user.avatarURL); + embed.setTimestamp(); + await channel.createMessage({ content: `<@${job.data.userID}>`, embed }); + if (job.data.func) { + const func = eval(job.data.func); + if (application.status === 'SUCCESS' && application.decision === 'APPROVED') await func(this.client, job.data.userID); + } + }); + } + + public addInquiry(inqID: string, userID: string, name: string, type: InquiryType, reason?: string) { + return this.queues.score.add('score::inquiry', { inqID, userID, name, type, reason }); + } + + public updateScore(score: ScoreInterface, total: number, activity: number, roles: number, moderation: number, cloudServices: number, other: number, staff: number) { + return this.queues.score.add('score::update', { score, total, activity, roles, moderation, cloudServices, other, staff }); + } + + public processApplication(channelInformation: { messageID: string, guildID: string, channelID: string }, url: string, userID: string, func?: string) { + return this.queues.score.add('score::apply', { channelInformation, url, userID, func }); + } +} diff --git a/src/class/Report.ts b/src/class/Report.ts new file mode 100644 index 0000000..6c4a987 --- /dev/null +++ b/src/class/Report.ts @@ -0,0 +1,97 @@ +import { v4 as uuid } from 'uuid'; +import { Client } from '.'; +import { InqType } from '../models'; + +export default class Report { + public client: Client; + + constructor(client: Client) { + this.client = client; + } + + public async createInquiry(userID: string, name: string, type: InqType, reason?: string) { + const report = await this.client.db.Score.findOne({ userID }).lean().exec(); + const member = this.client.util.resolveMember(userID, this.client.guilds.get(this.client.config.guildID)); + if (!report || !member) return null; + if (type === InqType.HARD && report.locked) return null; + const mod = await (new this.client.db.Inquiry({ + iid: uuid(), + userID, + name, + type, + reason, + date: new Date(), + report, + }).save()); + + this.client.queue.addInquiry(mod.iid, userID, name, type, reason); + + return mod; + } + + /* public async soft(userID: string) { + const report = await this.client.db.Score.findOne({ userID }); + if (!report) return null; + let totalScore: number; + let activityScore: number; + const moderationScore = Math.round(report.moderation); + let roleScore: number; + let cloudServicesScore: number; + const otherScore = Math.round(report.other); + let miscScore: number; + + if (report.total < 200) totalScore = 0; + else if (report.total > 800) totalScore = 800; + else totalScore = Math.round(report.total); + + if (report.activity < 10) activityScore = 0; + else if (report.activity > Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12))) activityScore = Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12)); + else activityScore = Math.round(report.activity); + + if (report.roles <= 0) roleScore = 0; + else if (report.roles > 54) roleScore = 54; + else roleScore = Math.round(report.roles); + + if (report.staff <= 0) miscScore = 0; + else miscScore = Math.round(report.staff); + + if (report.cloudServices === 0) cloudServicesScore = 0; + else if (report.cloudServices > 10) cloudServicesScore = 10; + else cloudServicesScore = Math.round(report.cloudServices); + + const historicalData = await this.client.db.ScoreHistorical.find({ userID: member.userID }).lean().exec(); + const array: ScoreHistoricalRaw[] = []; + for (const data of historicalData) { + let total: number; + let activity: number; + const moderation = Math.round(data.report.moderation); + let role: number; + let cloud: number; + const other = Math.round(data.report.other); + let misc: number; + + if (data.report.total < 200) total = 0; + else if (data.report.total > 800) total = 800; + else total = Math.round(data.report.total); + + if (data.report.activity < 10) activity = 0; + else if (data.report.activity > Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12))) activity = Math.floor((Math.log1p(3000 + 300 + 200 + 100) * 12)); + else activity = Math.round(data.report.activity); + + if (data.report.roles <= 0) role = 0; + else if (data.report.roles > 54) role = 54; + else role = Math.round(data.report.roles); + + if (data.report.staff <= 0) role = 0; + else misc = Math.round(data.report.staff); + + if (data.report.cloudServices === 0) cloud = 0; + else if (data.report.cloudServices > 10) cloud = 10; + else cloud = Math.round(data.report.cloudServices); + + data.report.total = total; data.report.activity = activity; data.report.moderation = moderation; data.report.roles = role; data.report.cloudServices = cloud; data.report.other = other; data.report.staff = misc; + + array.push(data); + } + } */ +} diff --git a/src/class/RichEmbed.ts b/src/class/RichEmbed.ts new file mode 100644 index 0000000..c9daea7 --- /dev/null +++ b/src/class/RichEmbed.ts @@ -0,0 +1,164 @@ +/* eslint-disable no-param-reassign */ + +import { EmbedOptions } from 'eris'; + +export default class RichEmbed implements EmbedOptions { + title?: string + + type?: string + + description?: string + + url?: string + + timestamp?: string | Date + + color?: number + + footer?: { text: string, icon_url?: string, proxy_icon_url?: string} + + image?: { url?: string, proxy_url?: string, height?: number, width?: number } + + thumbnail?: { url?: string, proxy_url?: string, height?: number, width?: number } + + video?: { url: string, height?: number, width?: number } + + provider?: { name: string, url?: string} + + author?: { name: string, url?: string, proxy_icon_url?: string, icon_url?: string} + + fields?: {name: string, value: string, inline?: boolean}[] + + constructor(data: EmbedOptions = {}) { + this.title = data.title; + this.description = data.description; + this.url = data.url; + this.color = data.color; + this.author = data.author; + this.timestamp = data.timestamp; + this.fields = data.fields || []; + this.thumbnail = data.thumbnail; + this.image = data.image; + this.footer = data.footer; + } + + /** + * Sets the title of this embed. + */ + public setTitle(title: string) { + if (typeof title !== 'string') throw new TypeError('RichEmbed titles must be a string.'); + if (title.length > 256) throw new RangeError('RichEmbed titles may not exceed 256 characters.'); + this.title = title; + return this; + } + + /** + * Sets the description of this embed. + */ + public setDescription(description: string) { + if (typeof description !== 'string') throw new TypeError('RichEmbed descriptions must be a string.'); + if (description.length > 2048) throw new RangeError('RichEmbed descriptions may not exceed 2048 characters.'); + this.description = description; + return this; + } + + /** + * Sets the URL of this embed. + */ + public setURL(url: string) { + if (typeof url !== 'string') throw new TypeError('RichEmbed URLs must be a string.'); + if (!url.startsWith('http://') && !url.startsWith('https://')) url = `https://${url}`; + this.url = encodeURI(url); + return this; + } + + /** + * Sets the color of this embed. + */ + public setColor(color: string | number) { + if (typeof color === 'string' || typeof color === 'number') { + if (typeof color === 'string') { + const regex = /[^a-f0-9]/gi; + color = color.replace(/#/g, ''); + if (regex.test(color)) throw new RangeError('Hexadecimal colours must not contain characters other than 0-9 and a-f.'); + color = parseInt(color, 16); + } else if (color < 0 || color > 16777215) throw new RangeError('Base 10 colours must not be less than 0 or greater than 16777215.'); + this.color = color; + return this; + } + throw new TypeError('RichEmbed colours must be hexadecimal as string or number.'); + } + + /** + * Sets the author of this embed. + */ + public setAuthor(name: string, icon_url?: string, url?: string) { + if (typeof name !== 'string') throw new TypeError('RichEmbed Author names must be a string.'); + if (url && typeof url !== 'string') throw new TypeError('RichEmbed Author URLs must be a string.'); + if (icon_url && typeof icon_url !== 'string') throw new TypeError('RichEmbed Author icons must be a string.'); + this.author = { name, icon_url, url }; + return this; + } + + /** + * Sets the timestamp of this embed. + */ + public setTimestamp(timestamp = new Date()) { + if (Number.isNaN(timestamp.getTime())) throw new TypeError('Expecting ISO8601 (Date constructor)'); + this.timestamp = timestamp; + return this; + } + + /** + * Adds a field to the embed (max 25). + */ + public addField(name: string, value: string, inline = false) { + if (typeof name !== 'string') throw new TypeError('RichEmbed Field names must be a string.'); + if (typeof value !== 'string') throw new TypeError('RichEmbed Field values must be a string.'); + if (typeof inline !== 'boolean') throw new TypeError('RichEmbed Field inlines must be a boolean.'); + if (this.fields.length >= 25) throw new RangeError('RichEmbeds may not exceed 25 fields.'); + if (name.length > 256) throw new RangeError('RichEmbed field names may not exceed 256 characters.'); + if (!/\S/.test(name)) throw new RangeError('RichEmbed field names may not be empty.'); + if (value.length > 1024) throw new RangeError('RichEmbed field values may not exceed 1024 characters.'); + if (!/\S/.test(value)) throw new RangeError('RichEmbed field values may not be empty.'); + this.fields.push({ name, value, inline }); + return this; + } + + /** + * Convenience function for `.addField('\u200B', '\u200B', inline)`. + */ + public addBlankField(inline = false) { + return this.addField('\u200B', '\u200B', inline); + } + + /** + * Set the thumbnail of this embed. + */ + public setThumbnail(url: string) { + if (typeof url !== 'string') throw new TypeError('RichEmbed Thumbnail URLs must be a string.'); + this.thumbnail = { url }; + return this; + } + + /** + * Set the image of this embed. + */ + public setImage(url: string) { + if (typeof url !== 'string') throw new TypeError('RichEmbed Image URLs must be a string.'); + if (!url.startsWith('http://') || !url.startsWith('https://')) url = `https://${url}`; + this.image = { url }; + return this; + } + + /** + * Sets the footer of this embed. + */ + public setFooter(text: string, icon_url?: string) { + if (typeof text !== 'string') throw new TypeError('RichEmbed Footers must be a string.'); + if (icon_url && typeof icon_url !== 'string') throw new TypeError('RichEmbed Footer icon URLs must be a string.'); + if (text.length > 2048) throw new RangeError('RichEmbed footer text may not exceed 2048 characters.'); + this.footer = { text, icon_url }; + return this; + } +} diff --git a/src/class/Route.ts b/src/class/Route.ts new file mode 100644 index 0000000..1bd8a1e --- /dev/null +++ b/src/class/Route.ts @@ -0,0 +1,77 @@ +/* eslint-disable consistent-return */ +import { Router, Request, Response } from 'express'; +import { Server } from '.'; + +export default class Route { + public server: Server; + + public conf: { path: string; deprecated?: boolean; maintenance?: boolean; }; + + public router: Router; + + constructor(server: Server) { + this.server = server; + this.conf = { path: '' }; + this.router = Router(); + } + + public bind() {} + + public init() { + this.router.all('*', (req, res, next) => { + this.server.client.util.signale.log(`'${req.method}' request from '${req.ip}' to '${req.hostname}${req.path}'.`); + this.server.client.db.Stat.updateOne({ name: 'requests' }, { $inc: { value: 1 } }).exec(); + if (this.conf.maintenance === true) res.status(503).json({ code: this.constants.codes.MAINTENANCE_OR_UNAVAILABLE, message: this.constants.messages.MAINTENANCE_OR_UNAVAILABLE }); + else if (this.conf.deprecated === true) res.status(501).json({ code: this.constants.codes.DEPRECATED, message: this.constants.messages.DEPRECATED }); + else next(); + }); + } + + public deprecated(): void { + this.router.all('*', (_req, res) => { + res.status(501).json({ code: this.constants.codes.DEPRECATED, message: this.constants.messages.DEPRECATED }); + }); + } + + public maintenance(): void { + this.router.all('*', (_req, res) => { + res.status(503).json({ code: this.constants.codes.MAINTENANCE_OR_UNAVAILABLE, message: this.constants.messages.MAINTENANCE_OR_UNAVAILABLE }); + }); + } + + public handleError(error: Error, res: Response) { + res.status(500).json({ code: this.constants.codes.SERVER_ERROR, message: this.constants.messages.SERVER_ERROR }); + this.server.parent.client.util.handleError(error); + } + + get constants() { + return { + codes: { + SUCCESS: 100, + UNAUTHORIZED: 101, + PERMISSION_DENIED: 104, + ENDPOINT_NOT_FOUND: 104, + NOT_FOUND: 1041, + ACCOUNT_NOT_FOUND: 1041, + CLIENT_ERROR: 1044, + SERVER_ERROR: 105, + DEPRECATED: 1051, + MAINTENANCE_OR_UNAVAILABLE: 1053, + }, + messages: { + UNAUTHORIZED: ['CREDENTIALS_INVALID', 'The credentials you supplied are invalid.'], + BEARER_TOKEN_INVALID: ['BEARER_TOKEN_INVALID', 'The Bearer token you supplied is invalid.'], + PERMISSION_DENIED: ['PERMISSION_DENIED', 'You do not have valid credentials to access this resource.'], + NOT_FOUND: ['NOT_FOUND', 'The resource you requested cannot be located.'], + ENDPOINT_NOT_FOUND: ['ENDPOINT_NOT_FOUND', 'The endpoint you requested does not exist or cannot be located.'], + CLIENT_ERROR: ['CLIENT_ERROR', 'The information provided to this endpoint via headers, body, query, or parameters are invalid.'], + SERVER_ERROR: ['INTERNAL_ERROR', 'An internal error has occurred, Engineers have been notified.'], + DEPRECATED: ['ENDPOINT_OR_RESOURCE_DEPRECATED', 'The endpoint or resource you\'re trying to access has been deprecated.'], + MAINTENANCE_OR_UNAVAILABLE: ['SERVICE_UNAVAILABLE', 'The endpoint or resource you\'re trying to access is either in maintenance or is not available.'], + }, + discord: { + SERVER_ID: '446067825673633794', + }, + }; + } +} diff --git a/src/class/Server.ts b/src/class/Server.ts new file mode 100644 index 0000000..4d7e590 --- /dev/null +++ b/src/class/Server.ts @@ -0,0 +1,76 @@ +import express from 'express'; +import bodyParser from 'body-parser'; +import helmet from 'helmet'; +import { Server as HTTPServer } from 'http'; +import { Collection, ServerManagement, Route } from '.'; + +export default class Server { + public app: express.Application; + + public routes: Collection; + + public parent: ServerManagement; + + public port: number; + + private root: string; + + protected parse: boolean; + + constructor(parent: ServerManagement, port: number, routeRoot: string, parse = true) { + this.parent = parent; + this.app = express(); + this.routes = new Collection(); + this.port = port; + this.root = routeRoot; + + this.parse = parse; + + this.init(); + this.loadRoutes(); + } + + get client() { + return this.parent.client; + } + + public async loadRoutes() { + const routes = Object.values(require(this.root)); + for (const RouteFile of routes) { + const route = new RouteFile(this); + if (route.conf.deprecated) { + route.deprecated(); + } else if (route.conf.maintenance) { + route.maintenance(); + } else { + route.init(); + route.bind(); + } + this.parent.client.util.signale.success(`Successfully loaded route 'http://localhost:${this.port}/${route.conf.path}'.`); + this.routes.add(route.conf.path, route); + this.app.use(route.conf.path, route.router); + } + this.app.listen(this.port); + } + + public init() { + if (this.parse) { + this.app.use(bodyParser.json()); + this.app.use(bodyParser.urlencoded({ extended: true })); + } + this.app.set('trust proxy', 'loopback'); + this.app.use(helmet({ + hsts: false, + hidePoweredBy: false, + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + }, + }, + })); + } + + public listen(port: number): HTTPServer { + return this.app.listen(port); + } +} diff --git a/src/class/ServerManagement.ts b/src/class/ServerManagement.ts new file mode 100644 index 0000000..efb9d3e --- /dev/null +++ b/src/class/ServerManagement.ts @@ -0,0 +1,22 @@ +import { Client, Collection, Server } from '.'; +import serverSetup from '../api'; + +export default class ServerManagement { + public client: Client; + + public servers: Collection; + + constructor(client: Client) { + this.client = client; + this.servers = new Collection(); + this.loadServers(); + } + + public async loadServers() { + const apiRoot = Object.entries<(management: ServerManagement) => Server>(serverSetup); + for (const [api, server] of apiRoot) { + this.servers.add(api, server(this)); + this.client.util.signale.success(`Successfully loaded server '${api}'.`); + } + } +} diff --git a/src/class/Util.ts b/src/class/Util.ts new file mode 100644 index 0000000..2b1b82e --- /dev/null +++ b/src/class/Util.ts @@ -0,0 +1,310 @@ +/* eslint-disable no-bitwise */ +import nodemailer from 'nodemailer'; +import childProcess from 'child_process'; +import { promisify } from 'util'; +import signale from 'signale'; +import { Member, Message, Guild, PrivateChannel, GroupChannel, Role, AnyGuildChannel, WebhookPayload } from 'eris'; +import { Client, Command, Moderation, PBX, Report, RichEmbed } from '.'; +import { statusMessages as emotes } from '../configs/emotes.json'; + +export default class Util { + public client: Client; + + public moderation: Moderation; + + public signale: signale.Signale; + + public transporter: nodemailer.Transporter; + + public pbx: PBX; + + public report: Report; + + constructor(client: Client) { + this.client = client; + this.moderation = new Moderation(this.client); + this.signale = signale; + this.signale.config({ + displayDate: true, + displayTimestamp: true, + displayFilename: true, + }); + this.transporter = nodemailer.createTransport({ + host: 'staff.libraryofcode.org', + port: 587, + auth: { user: 'internal', pass: this.client.config.emailPass }, + }); + this.pbx = new PBX(this.client); + this.report = new Report(this.client); + } + + get emojis() { + return { + SUCCESS: emotes.success, + LOADING: emotes.loading, + ERROR: emotes.error, + }; + } + + public hrn(number: any, fixed: number, formatter: any | any[]) { + const builtInFormatters = { + en: ['KMGTPEZY'.split(''), 1e3], + zh_CN: ['百千万亿兆京垓秭'.split(''), [100, 10, 10, 1e4, 1e4, 1e4, 1e4, 1e4]], + }; + number = Math.abs(number); + if (!fixed && fixed !== 0) fixed = 1; + + if (typeof formatter === 'object') { + const name = `${new Date().getTime()}`; + builtInFormatters[name] = formatter; + formatter = name; + } + + if (!builtInFormatters[formatter]) formatter = 'en'; + + formatter = builtInFormatters[formatter]; + let power = 1; + const texts = formatter[0]; + const powers = formatter[1]; + let loop = 0; + let is_array = false; + + if (typeof powers === 'object') is_array = true; + + // eslint-disable-next-line no-constant-condition + while (1) { + if (is_array) power = powers[loop]; + else power = powers; + + if (number >= power && loop < texts.length) number /= power; + else { + number = number.toFixed(fixed); + return loop === 0 ? number : `${number} ${texts[loop - 1]}`; + } + // eslint-disable-next-line no-plusplus + ++loop; + } + } + + /** + * Resolves a command + * @param query Command input + * @param message Only used to check for errors + */ + /* public resolveCommand(query: string | string[]): Promise<{cmd: Command, args: string[] }> { + try { + if (typeof query === 'string') query = query.split(' '); + const commands = this.client.commands.toArray(); + const resolvedCommand = commands.find((c) => c.name === query[0].toLowerCase() || c.aliases.includes(query[0].toLowerCase())); + + if (!resolvedCommand) return Promise.resolve(null); + query.shift(); + return Promise.resolve({ cmd: resolvedCommand, args: query }); + } catch (error) { + return Promise.reject(error); + } + } + */ + + public dataConversion(bytes: number): string { + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + if (bytes === 0) { + return '0 KB'; + } + return `${(bytes / 1024 ** i).toFixed(2)} ${sizes[i]}`; + } + + public async exec(command: string, _options: childProcess.ExecOptions = {}): Promise { + const ex = promisify(childProcess.exec); + try { + return (await ex(command)).stdout; + } catch (err) { + return err; + } + /* return new Promise((res, rej) => { + let output = ''; + const writeFunction = (data: string|Buffer|Error) => { + output += `${data}`; + }; + const cmd = childProcess.exec(command, options); + cmd.stdout.on('data', writeFunction); + cmd.stderr.on('data', writeFunction); + cmd.on('error', writeFunction); + cmd.once('close', (code, signal) => { + cmd.stdout.off('data', writeFunction); + cmd.stderr.off('data', writeFunction); + cmd.off('error', writeFunction); + setTimeout(() => {}, 1000); + if (code !== 0) rej(new Error(`Command failed: ${command}\n${output}`)); + res(output); + }); + }); */ + } + + /** + * Resolves a command + * @param query Command input + * @param message Only used to check for errors + */ + public resolveCommand(query: string | string[], message?: Message): Promise<{ cmd: Command, args: string[] }> { + try { + let resolvedCommand: Command; + if (typeof query === 'string') query = query.split(' '); + const commands = this.client.commands.toArray(); + resolvedCommand = commands.find((c) => c.name === query[0].toLowerCase() || c.aliases.includes(query[0].toLowerCase())); + + if (!resolvedCommand) return Promise.resolve(null); + query.shift(); + while (resolvedCommand.subcommands.size && query.length) { + const subCommands = resolvedCommand.subcommands.toArray(); + const found = subCommands.find((c) => c.name === query[0].toLowerCase() || c.aliases.includes(query[0].toLowerCase())); + if (!found) break; + resolvedCommand = found; + query.shift(); + } + return Promise.resolve({ cmd: resolvedCommand, args: query }); + } catch (error) { + if (message) this.handleError(error, message); + else this.handleError(error); + return Promise.reject(error); + } + } + + public resolveGuildChannel(query: string, { channels }: Guild, categories = false): AnyGuildChannel | undefined { + const ch: AnyGuildChannel[] = channels.filter((c) => (!categories ? c.type !== 4 : true)); + return ch.find((c) => c.id === query.replace(/[<#>]/g, '') || c.name === query) + || ch.find((c) => c.name.toLowerCase() === query.toLowerCase()) + || ch.find((c) => c.name.toLowerCase().startsWith(query.toLowerCase())); + } + + public resolveRole(query: string, { roles }: Guild): Role | undefined { + return roles.find((r) => r.id === query.replace(/[<@&>]/g, '') || r.name === query) + || roles.find((r) => r.name.toLowerCase() === query.toLowerCase()) + || roles.find((r) => r.name.toLowerCase().startsWith(query.toLowerCase())); + } + + public resolveMember(query: string, { members }: Guild): Member | undefined { + return members.find((m) => `${m.username}#${m.discriminator}` === query || m.username === query || m.id === query.replace(/[<@!>]/g, '') || m.nick === query) // Exact match for mention, username+discrim, username and user ID + || members.find((m) => `${m.username.toLowerCase()}#${m.discriminator}` === query.toLowerCase() || m.username.toLowerCase() === query.toLowerCase() || (m.nick && m.nick.toLowerCase() === query.toLowerCase())) // Case insensitive match for username+discrim, username + || members.find((m) => m.username.toLowerCase().startsWith(query.toLowerCase()) || (m.nick && m.nick.toLowerCase().startsWith(query.toLowerCase()))); + } + + public async handleError(error: Error, message?: Message, command?: Command, disable = true): Promise { + try { + this.signale.error(error); + const info: WebhookPayload = { content: `\`\`\`js\n${error.stack || error}\n\`\`\``, embeds: [] }; + if (message) { + const embed = new RichEmbed(); + embed.setColor('FF0000'); + embed.setAuthor(`Error caused by ${message.author.username}#${message.author.discriminator}`, message.author.avatarURL); + embed.setTitle('Message content'); + embed.setDescription(message.content); + embed.addField('User', `${message.author.mention} (\`${message.author.id}\`)`, true); + embed.addField('Channel', message.channel.mention, true); + let guild: string; + if (message.channel instanceof PrivateChannel || message.channel instanceof GroupChannel) guild = '@me'; + else guild = message.channel.guild.id; + embed.addField('Message link', `[Click here](https://discordapp.com/channels/${guild}/${message.channel.id}/${message.id})`, true); + embed.setTimestamp(new Date(message.timestamp)); + info.embeds.push(embed); + } + await this.client.executeWebhook(this.client.config.webhookID, this.client.config.webhookToken, info); + const msg = message ? message.content.slice(this.client.config.prefix.length).trim().split(/ +/g) : []; + if (command && disable) this.resolveCommand(msg).then((c) => { c.cmd.enabled = false; }); + if (message) message.channel.createMessage(`***${this.emojis.ERROR} An unexpected error has occured - please contact a Staff member.${command && disable ? ' This command has been disabled.' : ''}***`); + } catch (err) { + this.signale.error(err); + } + } + + public splitString(string: string, length: number): string[] { + if (!string) return []; + if (Array.isArray(string)) string = string.join('\n'); + if (string.length <= length) return [string]; + const arrayString: string[] = []; + let str: string = ''; + let pos: number; + while (string.length > 0) { + pos = string.length > length ? string.lastIndexOf('\n', length) : string.length; + if (pos > length) pos = length; + str = string.substr(0, pos); + string = string.substr(pos); + arrayString.push(str); + } + return arrayString; + } + + public splitFields(fields: { name: string, value: string, inline?: boolean }[]): { name: string, value: string, inline?: boolean }[][] { + let index = 0; + const array: { name: string, value: string, inline?: boolean }[][] = [[]]; + while (fields.length) { + if (array[index].length >= 25) { index += 1; array[index] = []; } + array[index].push(fields[0]); fields.shift(); + } + return array; + } + + public splitArray(array: T[], count: number) { + const finalArray: T[][] = []; + while (array.length) { + finalArray.push(array.splice(0, count)); + } + return finalArray; + } + + public decimalToHex(int: number): string { + const hex = int.toString(16); + return '#000000'.substring(0, 7 - hex.length) + hex; + } + + public randomNumber(min: number, max: number): number { + return Math.round(Math.random() * (max - min) + min); + } + + public encode(arg: string) { + return arg.split('').map((x) => x.charCodeAt(0) / 400); + } + + public normalize(string) { + const input = []; + // eslint-disable-next-line no-plusplus + for (let i = 0; i < string.length; i++) { + input.push(string.charCodeAt(i) / 1000); + } + return input; + } + + public convert_ascii(ascii: []) { + let string = ''; + // eslint-disable-next-line no-plusplus + for (let i = 0; i < ascii.length; i++) { + string += String.fromCharCode(ascii[i] * 4000); + } + return string; + } + + public percentile(arr: number[], val: number) { + return (100 * arr.reduce((acc, v) => acc + (v < val ? 1 : 0) + (v === val ? 0.5 : 0), 0)) / arr.length; + } + + public ordinal(i: number) { + const j = i % 10; + const k = i % 100; + if (j === 1 && k !== 11) { + return `${i}st`; + } + if (j === 2 && k !== 12) { + return `${i}nd`; + } + if (j === 3 && k !== 13) { + return `${i}rd`; + } + return `${i}th`; + } + + public capsFirstLetter(string?: string): string | void { + if (typeof string !== 'string') return undefined; + return string.substring(0, 1).toUpperCase() + string.substring(1); + } +} diff --git a/src/class/index.ts b/src/class/index.ts new file mode 100644 index 0000000..935672d --- /dev/null +++ b/src/class/index.ts @@ -0,0 +1,15 @@ +export { default as Client } from './Client'; +export { default as Collection } from './Collection'; +export { default as Command } from './Command'; +export { default as Event } from './Event'; +export { default as Handler } from './Handler'; +export { default as LocalStorage } from './LocalStorage'; +export { default as Moderation } from './Moderation'; +export { default as PBX } from './PBX'; +export { default as Queue } from './Queue'; +export { default as Report } from './Report'; +export { default as RichEmbed } from './RichEmbed'; +export { default as Route } from './Route'; +export { default as Server } from './Server'; +export { default as ServerManagement } from './ServerManagement'; +export { default as Util } from './Util'; diff --git a/src/commands/additem.ts b/src/commands/additem.ts new file mode 100644 index 0000000..d487a70 --- /dev/null +++ b/src/commands/additem.ts @@ -0,0 +1,60 @@ +import { Message } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class AddItem extends Command { + constructor(client: Client) { + super(client); + this.name = 'additem'; + this.description = 'Adds information to your whois embed.'; + this.usage = 'additem [code]'; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (args.length < 1) { + const embed = new RichEmbed(); + embed.setTitle('Whois Data Codes'); + embed.addField('Languages', '**Assembly Language:** lang-asm\n**C/C++:** lang-cfam\n**C#:** lang-csharp\n**Go:** lang-go\n**Java:** lang-java\n**JavaScript:** lang-js\n**Kotlin:** lang-kt\n**Python:** lang-py\n**Ruby:** lang-rb\n**Rust:** lang-rs\n**Swift:** lang-swift\n**TypeScript:** lang-ts'); + embed.addField('Operating Systems', '**Arch:** os-arch\n**Debian:** os-deb\n**CentOS:** os-cent\n**Fedora:** os-fedora\n**macOS:** os-mdarwin\n**Manjaro:** os-manjaro\n**RedHat:** os-redhat\n**Ubuntu:** os-ubuntu\n**Windows:** os-win'); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } + if (args[0].split('-')[0] === 'os' && ['arch', 'deb', 'cent', 'fedora', 'manjaro', 'mdarwin', 'redhat', 'ubuntu', 'win'].includes(args[0].split('-')[1])) { + const account = await this.client.db.Member.findOne({ userID: message.member.id }); + if (!account) { + const newAccount = new this.client.db.Member({ + userID: message.member.id, + additional: { + operatingSystems: [args[0].split('-')[1]], + }, + }); + await newAccount.save(); + } else { + await account.updateOne({ $addToSet: { 'additional.operatingSystems': args[0].split('-')[1] } }); + } + return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Added OS code ${args[0]} to profile.***`); + } + if (args[0].split('-')[0] === 'lang' && ['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'].includes(args[0].split('-')[1])) { + const account = await this.client.db.Member.findOne({ userID: message.member.id }); + if (!account) { + const newAccount = new this.client.db.Member({ + userID: message.member.id, + additional: { + langs: [args[0].split('-')[1]], + }, + }); + await newAccount.save(); + } else { + await account.updateOne({ $addToSet: { 'additional.langs': args[0].split('-')[1] } }); + } + return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Added language code ${args[0]} to profile.***`); + } + return message.channel.createMessage(`***${this.client.util.emojis.ERROR} Invalid data code.***`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/addmerchant.ts b/src/commands/addmerchant.ts new file mode 100644 index 0000000..091ad36 --- /dev/null +++ b/src/commands/addmerchant.ts @@ -0,0 +1,35 @@ +import { Message } from 'eris'; +import { randomBytes } from 'crypto'; +import { Client, Command } from '../class'; + +export default class AddMerchant extends Command { + constructor(client: Client) { + super(client); + this.name = 'addmerchant'; + this.description = 'Creates a new merchant.'; + this.usage = `${this.client.config.prefix}addmerchant `; + this.aliases = ['am']; + this.permissions = 6; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[1]) return this.client.commands.get('help').run(message, [this.name]); + if ((Number(args[0]) !== 0) && (Number(args[0]) !== 1)) return this.error(message.channel, 'Invalid permissions.'); + if ((Number(args[1]) !== 0) && (Number(args[1]) !== 1)) return this.error(message.channel, 'Invalid permissions.'); + const key = randomBytes(20).toString('hex'); + const merchant = await (new this.client.db.Merchant({ + name: args.slice(2).join(' '), + privileged: Number(args[0]), + type: Number(args[1]), + key, + pulls: [], + })).save(); + return this.success(message.channel, `Created merchant (${merchant._id}). \`${args.slice(2).join(' ')}\`\n\n\`${key}\``); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/addnote.ts b/src/commands/addnote.ts new file mode 100644 index 0000000..3e07453 --- /dev/null +++ b/src/commands/addnote.ts @@ -0,0 +1,40 @@ +import { Message } from 'eris'; +import { Command, Client } from '../class'; + +export default class AddNote extends Command { + constructor(client: Client) { + super(client); + this.name = 'addnote'; + this.description = 'Adds a note to a member.'; + this.usage = `${this.client.config.prefix}addnote [category: comm | cs | edu]`; + this.permissions = 1; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0] || args.length < 1) return this.client.commands.get('help').run(message, [this.name]); + let user = this.client.util.resolveMember(args[0], this.mainGuild)?.user; + if (!user) user = await this.client.getRESTUser(args[0]); + if (!user) return this.error(message.channel, 'The member you specified could not be found.'); + + const note: { userID?: string, staffID: string, date: Date, category?: string, text?: string } = { + userID: user.id, + date: new Date(), + staffID: message.author.id, + }; + if (args[args.length - 1] !== 'edu' && args[args.length - 1] !== 'comm' && args[args.length - 1] !== 'cs') { + note.category = ''; + note.text = args.slice(1).join(' '); + } else { + note.category = args[args.length - 1]; + note.text = args.slice(0, args.length - 1).join(' '); + } + const saved = await (new this.client.db.Note(note).save()); + return this.success(message.channel, `Successfully created Note # \`${saved._id}\`.`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/addrank.ts b/src/commands/addrank.ts new file mode 100644 index 0000000..ff7b64f --- /dev/null +++ b/src/commands/addrank.ts @@ -0,0 +1,45 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class AddRank extends Command { + constructor(client: Client) { + super(client); + this.name = 'addrank'; + this.description = 'Makes a role self-assignable.'; + this.usage = `${this.client.config.prefix}addrank `; + this.permissions = 6; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + if (!args[1]) return this.error(message.channel, 'Permissions are required.'); + if (!args[2]) return this.error(message.channel, 'A description is required'); + const role = this.client.util.resolveRole(args[0], this.mainGuild); + if (!role) return this.error(message.channel, 'The role you specified doesn\'t appear to exist.'); + + const check = await this.client.db.Rank.findOne({ roleID: role.id }); + if (check) return this.error(message.channel, 'This role is already self-assignable.'); + + let permissions: string[]; + if (args[1] === '0') { + permissions = ['0']; + } else { + permissions = args[1].split(':'); + } + + const entry = new this.client.db.Rank({ + name: role.name, + roleID: role.id, + permissions, + description: args.slice(2).join(' '), + }); + await entry.save(); + return this.success(message.channel, `Role ${role.name} is now self-assignable.`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/addredirect.ts b/src/commands/addredirect.ts new file mode 100644 index 0000000..ad5956e --- /dev/null +++ b/src/commands/addredirect.ts @@ -0,0 +1,38 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class AddRedirect extends Command { + constructor(client: Client) { + super(client); + this.name = 'addredirect'; + this.description = 'Adds a redirect link for \'loc.sh\''; + this.usage = 'addredirect '; + this.aliases = ['ar']; + this.permissions = 6; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const check = await this.client.db.Redirect.findOne({ key: args[1].toLowerCase() }); + if (check) return this.error(message.channel, `Redirect key ${args[1].toLowerCase()} already exists. Linked to: ${check.to}`); + try { + const test = new URL(args[0]); + if (test.protocol !== 'https:') return this.error(message.channel, 'Protocol must be HTTPS.'); + } catch { + return this.error(message.channel, 'This doesn\'t appear to be a valid URL.'); + } + if ((/^[a-zA-Z0-9]+$/gi.test(args[1].toLowerCase().replace('-', '').trim()) === false) || args[1].toLowerCase().length > 15) return this.error(message.channel, 'Invalid key. The key must be alphanumeric and less than 16 characters.'); + const redirect = new this.client.db.Redirect({ + key: args[1].toLowerCase(), + to: args[0], + visitedCount: 0, + }); + await redirect.save(); + return this.success(message.channel, `Redirect https://loc.sh/${args[1].toLowerCase()} -> ${args[0]} is now active.`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/apply.ts b/src/commands/apply.ts new file mode 100644 index 0000000..1b41afc --- /dev/null +++ b/src/commands/apply.ts @@ -0,0 +1,158 @@ +/* eslint-disable no-continue */ +import type { AxiosError, AxiosStatic } from 'axios'; +import axios from 'axios'; +import { Member, Message } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class Apply extends Command { + public services: Map Promise | boolean, func?: Function }>; + + constructor(client: Client) { + super(client); + this.name = 'apply'; + this.description = 'apply'; + this.usage = `${this.client.config.prefix}apply [serviceName]\n${this.client.config.prefix}apply full`; + this.permissions = 0; + this.guildOnly = true; + this.enabled = true; + this.setServices(); + } + + protected setServices() { + this.services = new Map(); + this.services.set('role::constants', { + description: 'Constants role assignment.', + type: 'HARD', + url: 'https://eds.libraryofcode.org/roles/constants', + validation: (member: Member) => !member.roles.includes('511771731891847168'), + func: async (client: Client, ...data: any[]) => { + const member = await client.guilds.get(client.config.guildID).getRESTMember(data[0]); + await member.addRole('511771731891847168', 'Constants Approval from EDS'); + }, + }); + + this.services.set('cs::t2', { + description: 'Tier 2 upgrade for Cloud Services account.', + type: 'HARD', + url: 'https://eds.libraryofcode.org/cs/t2', + validation: (member: Member) => member.roles.includes('546457886440685578'), + func: async (client: Client, ...data: any[]) => { + const member = await client.guilds.get(client.config.guildID).getRESTMember(data[0]); + const ax = require('axios'); + await ax({ + method: 'get', + url: `https://api.cloud.libraryofcode.org/wh/t2?userID=${member.id}&auth=${client.config.internalKey}`, + }); + }, + }); + + this.services.set('cs::promot3', { + description: 'Receive 25% off your first purchase of Tier 3.', + type: 'SOFT', + url: 'https://eds.libraryofcode.org/cs/t3-promo', + validation: async (member: Member) => { + if (!member.roles.includes('546457886440685578')) return false; + const customer = await this.client.db.Customer.findOne({ userID: member.user.id }).lean().exec(); + if (!customer) return false; + return true; + }, + func: async (client: Client, ...data: any[]) => { + const member = await client.guilds.get(client.config.guildID).getRESTMember(data[0]); + const customer = await client.db.Customer.findOne({ userID: member.user.id }).lean().exec(); + const coupon = await client.stripe.coupons.create({ + percent_off: 25, + duration: 'once', + max_redemptions: 1, + name: 'Tier 3 - EDS Discount', + metadata: { + userID: member.user.id, + }, + }); + const promo = await client.stripe.promotionCodes.create({ + coupon: coupon.id, + customer: customer.cusID, + max_redemptions: 1, + restrictions: { + first_time_transaction: true, + }, + }); + const doc = new client.db.Promo({ + code: promo.code, + pID: promo.id, + }); + await doc.save(); + const chan = await client.getDMChannel(customer.userID); + chan.createMessage(`__**Tier 3 Coupon Code**__\n\`${promo.code}\`\n\n*Do not share this promotional code with anyone else. This promo code is good for your first purchase of Tier 2, 25% off applied. Will apply to your first invoice only, for more questions contact support.*`); + }, + }); + + this.services.set('p::role::constants', { + description: 'Pre-approval for Constants role assignment.', + type: 'SOFT', + url: 'https://eds.libraryofcode.org/roles/preconstants', + validation: (member: Member) => !member.roles.includes('511771731891847168'), + }); + + this.services.set('p::cs::t2', { + description: 'Pre-approval for Tier 2.', + type: 'SOFT', + url: 'https://eds.libraryofcode.org/cs/t2pre', + validation: (member: Member) => member.roles.includes('546457886440685578'), + }); + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0] || args[0] === 'full') { + const embed = new RichEmbed(); + embed.setTitle('Instant Application Service [IAS]'); + embed.setColor('#556cd6'); + if (args[0] !== 'full') { + embed.setDescription(`*These applications are specifically targeted to you based on validation conditions. Run \`${this.client.config.prefix}apply full\` for a full list of all applications.*`); + embed.setThumbnail(message.member.avatarURL); + embed.setAuthor(message.member.username, message.member.avatarURL); + } + for (const service of this.services) { + // eslint-disable-next-line no-await-in-loop + const test = await service[1].validation(message.member); + if (!test && args[0] !== 'full') continue; + embed.addField(service[0], `**Description**: ${service[1].description}\n**Inquiry Type:** ${service[1].type}\n\n*Run \`${this.client.config.prefix}apply ${service[0]}\` to apply.*`); + } + if (embed.fields?.length <= 0) embed.setDescription(`*We have no offers for you at this time. To see a full list of offers, please run \`${this.client.config.prefix}apply full\`.*`); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } + + if (!this.services.has(args[0])) return this.error(message.channel, 'Invalid service/product name.'); + const service = this.services.get(args[0]); + const test = await this.services.get(args[0]).validation(message.member); + if (!test) return this.error(message.channel, 'A condition exists which prevents you from applying, please try again later.'); + const msg = await this.loading(message.channel, 'Thank you for submitting an application. We are currently processing it, you will be pinged here shortly with the decision.'); + return await this.client.queue.processApplication({ channelID: message.channel.id, guildID: this.mainGuild.id, messageID: msg.id }, service.url, message.author.id, service.func ? service.func.toString() : undefined); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } + + public static async apply(client: Client, url: string, userID: string) { + try { + const { data } = await axios({ + method: 'get', + url: `${url}?userID=${userID}&auth=${client.config.internalKey}`, + }); + + return { + status: 'SUCCESS', + decision: data.decision, + id: data.id, + processedBy: data.processedBy, + token: data.token, + }; + } catch (err) { + const error = err; + if (error.response?.status === 404 || error.response.status === 400 || error.response.status === 401) return { id: 'N/A', processedBy: 'N/A', status: 'CLIENT_ERROR', decision: 'PRE-DECLINED' }; + return { id: 'N/A', processedBy: 'N/A', status: 'SERVER_ERROR', decision: 'PRE-DECLINED' }; + } + } +} diff --git a/src/commands/ban.ts b/src/commands/ban.ts new file mode 100644 index 0000000..2f83979 --- /dev/null +++ b/src/commands/ban.ts @@ -0,0 +1,53 @@ +import moment, { unitOfTime } from 'moment'; +import { Message, User } from 'eris'; +import { Client, Command } from '../class'; + +export default class Ban extends Command { + constructor(client: Client) { + super(client); + this.name = 'ban'; + this.description = 'Bans a member from the guild.'; + this.usage = 'ban [time] [reason]'; + this.permissions = 3; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const member = this.client.util.resolveMember(args[0], this.mainGuild); + let user: User; + if (!member) { + try { + user = await this.client.getRESTUser(args[0]); + } catch { + return this.error(message.channel, 'Cannot find user.'); + } + } else { + user = member.user; + } + try { + await this.mainGuild.getBan(args[0]); + return this.error(message.channel, 'This user is already banned.'); + } catch {} // eslint-disable-line no-empty + if (member && !this.client.util.moderation.checkPermissions(member, message.member)) return this.error(message.channel, 'Permission Denied.'); + message.delete(); + + let momentMilliseconds: number; + let reason: string; + if (args.length > 1) { + const lockLength = args[1].match(/[a-z]+|[^a-z]+/gi); + const length = Number(lockLength[0]); + const unit = lockLength[1] as unitOfTime.Base; + momentMilliseconds = moment.duration(length, unit).asMilliseconds(); + reason = momentMilliseconds ? args.slice(2).join(' ') : args.slice(1).join(' '); + if (reason.length > 512) return this.error(message.channel, 'Ban reasons cannot be longer than 512 characters.'); + } + await this.client.util.moderation.ban(user, message.member, momentMilliseconds, reason); + return this.success(message.channel, `${user.username}#${user.discriminator} has been banned.`); + } catch (err) { + return this.client.util.handleError(err, message, this, false); + } + } +} diff --git a/src/commands/billing.ts b/src/commands/billing.ts new file mode 100644 index 0000000..de56760 --- /dev/null +++ b/src/commands/billing.ts @@ -0,0 +1,55 @@ +import axios from 'axios'; +import moment from 'moment'; +import { Message } from 'eris'; +import { randomBytes } from 'crypto'; +import { v4 as uuid } from 'uuid'; +import { Client, Command } from '../class'; +import Billing_T3 from './billing_t3'; + +export default class Billing extends Command { + constructor(client: Client) { + super(client); + this.name = 'billing'; + this.description = 'Pulls up your Billing Portal. You must have a CS Account to continue.'; + this.usage = `${this.client.config.prefix}billing`; + this.subcmds = [Billing_T3]; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message) { + try { + const response = <{ + found: boolean, + emailAddress?: string, + tier?: number, + supportKey?: string, + }> (await axios.get(`https://api.cloud.libraryofcode.org/wh/info?id=${message.author.id}&authorization=${this.client.config.internalKey}`)).data; + if (!response.found) return this.error(message.channel, 'CS Account not found.'); + + const portalKey = randomBytes(50).toString('hex'); + const uid = uuid(); + const redirect = new this.client.db.Redirect({ + key: uid, + to: `https://loc.sh/dash?q=${portalKey}`, + }); + const portal = new this.client.db.CustomerPortal({ + key: portalKey, + username: message.author.username, + userID: message.author.id, + emailAddress: response.emailAddress, + expiresOn: moment().add(5, 'minutes').toDate(), + used: false, + }); + await portal.save(); + await redirect.save(); + + const chan = await this.client.getDMChannel(message.author.id); + await chan.createMessage(`__***Billing Account Portal***__\nClick here: https://loc.sh/${uid}\n\nYou will be redirected to your billing portal, please note the link expires after 5 minutes.`); + return await this.success(message.channel, 'Your Billing Portal information has been DMed to you.'); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/billing_t3.ts b/src/commands/billing_t3.ts new file mode 100644 index 0000000..63568cb --- /dev/null +++ b/src/commands/billing_t3.ts @@ -0,0 +1,62 @@ +import axios from 'axios'; +import { Message } from 'eris'; +import type { Stripe } from 'stripe'; +import { Client, Command } from '../class'; +import type { PromoInterface } from '../models'; + +export default class Billing_T3 extends Command { + constructor(client: Client) { + super(client); + this.name = 't3'; + this.description = 'Subscription to CS Tier 3.'; + this.usage = `${this.client.config.prefix}billing t3 [promoCode]`; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + await message.delete(); + const response = <{ + found: boolean, + emailAddress?: string, + tier?: number, + supportKey?: string, + }>(await axios.get(`https://api.cloud.libraryofcode.org/wh/info?id=${message.author.id}&authorization=${this.client.config.internalKey}`)).data; + if (!response.found) return this.error(message.channel, 'CS Account not found.'); + + const customer = await this.client.db.Customer.findOne({ userID: message.author.id }); + if (!customer) return this.error(message.channel, `You do not have a Customer Account. Please run \`${this.client.config.prefix}billing\`, once you visit the Billing Portal via the URL given to you, please try again.`); + + let promoCode: PromoInterface; + if (args[0]) { + promoCode = await this.client.db.Promo.findOne({ code: args[0].toUpperCase() }); + } + + let subscription: Stripe.Response; + try { + subscription = await this.client.stripe.subscriptions.create({ + customer: customer.cusID, + payment_behavior: 'allow_incomplete', + items: [{ price: 'price_1H8e6ODatwI1hQ4WFVvX6Nda' }], + days_until_due: 1, + collection_method: 'send_invoice', + default_tax_rates: ['txr_1HlAadDatwI1hQ4WRHu14S2I'], + promotion_code: promoCode ? promoCode.id : undefined, + }); + } catch (err) { + return this.error(message.channel, `Error creating subscription.\n\n${err}`); + } + + await this.client.stripe.invoices.finalizeInvoice(subscription.latest_invoice.toString()); + const invoice = await this.client.stripe.invoices.retrieve(subscription.latest_invoice.toString()); + + const chan = await this.client.getDMChannel(message.author.id); + await chan.createMessage(`__**Invoice for New Subscription**__\n${invoice.hosted_invoice_url}\n\n*Please click on the link above to pay for your subscription.*`); + return this.success(message.channel, 'Transaction processed.'); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/callback.ts b/src/commands/callback.ts new file mode 100644 index 0000000..35f084d --- /dev/null +++ b/src/commands/callback.ts @@ -0,0 +1,52 @@ +import PhoneNumber from 'awesome-phonenumber'; +import axios from 'axios'; +import { Message, TextChannel } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class Callback extends Command { + constructor(client: Client) { + super(client); + this.name = 'callback'; + this.description = 'Requests a Callback from a Technican.\nPlease use `-` to separate the number if needed. E.x. 202-750-2585.\nDo note, we are unable to dial international numbers outside of the US and Canada.\n\n*We recommend you run this command in your DMs for privacy.*'; + this.usage = 'callback '; + this.aliases = ['cb']; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + if (message.channel.type === 0) await message.delete(); + const member = this.mainGuild.members.get(message.author.id); + if (!member) return this.error(message.channel, 'Unable to fetch member.'); + const phone = new PhoneNumber(args.join(' '), 'US'); + if (!phone.isValid()) return this.error(message.channel, 'The number you have entered is invalid.'); + const embed = new RichEmbed(); + embed.setTitle('Callback Request'); + embed.setDescription('Please dial `9` first to reach an the external trunk. For example, to dial 202-750-2585 you would dial 92027502585 on your device.\n\n*Please react with <:modSuccess:578750988907970567> on this message if you are taking the call.*'); + embed.addField('Member', `${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`, true); + embed.addField('Phone Number', phone.getNumber('national'), true); + embed.addField('Phone Number Type', phone.getType(), true); + const communityReport = await this.client.db.Score.findOne({ userID: message.author.id }).lean().exec(); + if (communityReport) { + await this.client.report.createInquiry(member.user.id, 'Library of Code sp-us | VOIP/PBX Member Support SVCS', 1); + embed.addField('PIN', `${communityReport.pin[0]}-${communityReport.pin[1]}-${communityReport.pin[2]}`, true); + } + try { + const d = await axios.get(`https://api.cloud.libraryofcode.org/wh/info/?id=${message.author.id}&authorization=${this.client.config.internalKey}`); + embed.addField('Email Address', d.data.emailAddress, true); + embed.addField('Support Key', d.data.supportKey, true); + } catch { + this.client.util.signale.warn('No CS Account found for user.'); + } + const chan = this.mainGuild.channels.get('780513128240382002'); + const msg = await chan.createMessage({ content: '<@&780519428873781298>', embed }); + await msg.addReaction('modSuccess:578750988907970567'); + return message.channel.createMessage('__**Callback Request**__\nYour callback request has been sent to our agents, you should receive a callback within 1-24 hours from the date of this request. The number you will be called from is listed below.\n\n**Callback Number:** +1 202-750-2585\n**Callback Region:** Washington, D.C., United States\n\n\n*Your number is never stored on our systems at any time, as soon as your call is taken by an agent your number is deleted from notification channels.*'); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/delitem.ts b/src/commands/delitem.ts new file mode 100644 index 0000000..164a3af --- /dev/null +++ b/src/commands/delitem.ts @@ -0,0 +1,46 @@ +import { Message } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class DelItem extends Command { + constructor(client: Client) { + super(client); + this.name = 'delitem'; + this.description = 'Removes information to your whois embed.'; + this.usage = 'delitem [code]'; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (args.length < 1) { + const embed = new RichEmbed(); + embed.setTitle('Whois Data Codes'); + embed.addField('Languages', '**Assembly Language:** lang-asm\n**C/C++:** lang-cfam\n**C#:** lang-csharp\n**Go:** lang-go\n**Java:** lang-java\n**JavaScript:** lang-js\n**Kotlin:** lang-kt\n**Python:** lang-py\n**Ruby:** lang-rb\n**Rust:** lang-rs\n**Swift:** lang-swift\n**TypeScript:** lang-ts'); + embed.addField('Operating Systems', '**Arch:** os-arch\n**Debian:** os-deb\n**CentOS:** os-cent\n**Fedora:** os-fedora\n**macOS:** os-mdarwin\n**Manjaro:** os-manjaro\n**RedHat:** os-redhat\n**Ubuntu:** os-ubuntu\n**Windows:** os-win'); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } + if (args[0].split('-')[0] === 'os' && ['arch', 'deb', 'cent', 'fedora', 'manjaro', 'mdarwin', 'redhat', 'ubuntu', 'win'].includes(args[0].split('-')[1])) { + const account = await this.client.db.Member.findOne({ userID: message.member.id }); + if (account?.additional.operatingSystems.length < 1) { + return message.channel.createMessage(`***${this.client.util.emojis.ERROR} You don't have any operating systems to remove.***`); + } + await account.updateOne({ $pull: { 'additional.operatingSystems': args[0].split('-')[1] } }); + return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Removed OS code ${args[0]} from profile.***`); + } + if (args[0].split('-')[0] === 'lang' && ['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'].includes(args[0].split('-')[1])) { + const account = await this.client.db.Member.findOne({ userID: message.member.id }); + if (account?.additional.langs.length < 1) { + return message.channel.createMessage(`***${this.client.util.emojis.ERROR} You don't have any languages to remove.***`); + } + await account.updateOne({ $pull: { 'additional.langs': args[0].split('-')[1] } }); + return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Removed language code ${args[0]} from profile.***`); + } + return message.channel.createMessage(`***${this.client.util.emojis.ERROR} Invalid data code.***`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/delmerchant.ts b/src/commands/delmerchant.ts new file mode 100644 index 0000000..8c9ef2d --- /dev/null +++ b/src/commands/delmerchant.ts @@ -0,0 +1,26 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class DelMerchant extends Command { + constructor(client: Client) { + super(client); + this.name = 'delmerchant'; + this.description = 'Deletes a merchant.'; + this.usage = `${this.client.config.prefix}delmerchant `; + this.aliases = ['dm']; + this.permissions = 6; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const merchant = await this.client.db.Merchant.findOne({ key: args[0] }); + if (!merchant) return this.error(message.channel, 'Merchant specified does not exist.'); + return this.success(message.channel, `Deleted merchant \`${merchant._id}\`.`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/delnote.ts b/src/commands/delnote.ts new file mode 100644 index 0000000..ad94795 --- /dev/null +++ b/src/commands/delnote.ts @@ -0,0 +1,26 @@ +import { Message } from 'eris'; +import { Command, Client } from '../class'; + +export default class DelNote extends Command { + constructor(client: Client) { + super(client); + this.name = 'delnote'; + this.description = 'Deletes a note.'; + this.usage = `${this.client.config.prefix}delnote `; + this.permissions = 1; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const note = await this.client.db.Note.findOne({ _id: args[0] }).lean().exec().catch(() => {}); + if (!note) return this.error(message.channel, 'Could not locate that note.'); + await this.client.db.Note.deleteOne({ _id: note._id }); + return this.success(message.channel, `Note # \`${note._id}\` has been deleted.`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/delrank.ts b/src/commands/delrank.ts new file mode 100644 index 0000000..524b7b1 --- /dev/null +++ b/src/commands/delrank.ts @@ -0,0 +1,30 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class DelRank extends Command { + constructor(client: Client) { + super(client); + this.name = 'delrank'; + this.description = 'Deletes an existing self-assignable role. This doesn\'t delete the role itself.'; + this.usage = `${this.client.config.prefix}delrank `; + this.permissions = 6; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const role = this.client.util.resolveRole(args[0], this.mainGuild); + if (!role) return this.error(message.channel, 'The role you specified doesn\'t appear to exist.'); + + const check = await this.client.db.Rank.findOne({ roleID: role.id }); + if (!check) return this.error(message.channel, 'The entry doesn\'t appear to exist.'); + + await this.client.db.Rank.deleteOne({ roleID: role.id }); + return this.success(message.channel, `Role ${role.name} is no longer self-assignable.`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/delredirect.ts b/src/commands/delredirect.ts new file mode 100644 index 0000000..1dd8ae9 --- /dev/null +++ b/src/commands/delredirect.ts @@ -0,0 +1,26 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class DelRedirect extends Command { + constructor(client: Client) { + super(client); + this.name = 'delredirect'; + this.description = 'Delete a redirect link for \'loc.sh\''; + this.usage = 'delredirect '; + this.aliases = ['dr']; + this.permissions = 6; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const check = await this.client.db.Redirect.findOne({ key: args[0].toLowerCase() }); + if (!check) return this.error(message.channel, `Redirect key ${args[0].toLowerCase()} doesn't exist.`); + await this.client.db.Redirect.deleteOne({ key: args[0].toLowerCase() }); + return this.success(message.channel, `Deleted redirect https://loc.sh/${args[0].toLowerCase()}.`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/djs.ts b/src/commands/djs.ts new file mode 100644 index 0000000..405e0ac --- /dev/null +++ b/src/commands/djs.ts @@ -0,0 +1,37 @@ +import { Message, EmbedOptions } from 'eris'; +import axios, { AxiosResponse } from 'axios'; +import { Client, Command, RichEmbed } from '../class'; + +export default class DJS extends Command { + constructor(client: Client) { + super(client); + this.name = 'djs'; + this.description = 'Get information about Discord.js.'; + this.usage = 'djs '; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + + let res: AxiosResponse; + try { + res = await axios.get(`https://djsdocs.sorta.moe/v2/embed?src=master&q=${args[0]}`); + } catch (err) { + return this.error(message.channel, 'Please try again later, something unexpected happened.'); + } + + if (!res.data) return this.error(message.channel, 'Could not find information. Try something else.'); + + const embed = new RichEmbed(res.data); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/eris.ts b/src/commands/eris.ts new file mode 100644 index 0000000..a80ea1f --- /dev/null +++ b/src/commands/eris.ts @@ -0,0 +1,33 @@ +import { Message, EmbedOptions } from 'eris'; +import axios, { AxiosResponse } from 'axios'; +import { Client, Command } from '../class'; + +export default class Eris extends Command { + constructor(client: Client) { + super(client); + this.name = 'eris'; + this.description = 'Get information about Eris.'; + this.usage = 'eris '; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + + let res: AxiosResponse<{embed: EmbedOptions}>; + try { + res = await axios.get('https://erisdocs.cloud.libraryofcode.org/docs', { params: { search: args[0] } }); + } catch (err) { + if (err.code === 404) return this.error(message.channel, 'Could not find information. Try something else.'); + return this.error(message.channel, 'Please try again later, something unexpected happened.'); + } + + return message.channel.createMessage(res.data); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/eval.ts b/src/commands/eval.ts new file mode 100644 index 0000000..e749d4d --- /dev/null +++ b/src/commands/eval.ts @@ -0,0 +1,68 @@ +import axios from 'axios'; +import { inspect } from 'util'; +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Eval extends Command { + constructor(client: Client) { + super(client); + this.name = 'eval'; + this.description = 'Evaluates native JS code'; + this.aliases = ['e']; + this.permissions = 7; + this.enabled = true; + this.guildOnly = false; + } + + public async run(message: Message, args: string[]) { + try { + const evalMessage = message.content.slice(this.client.config.prefix.length).trim().split(' ').slice(1); + let evalString = evalMessage.join(' ').trim(); + let evaled: any; + let depth = 0; + + if (args[0] && args[0].startsWith('-d')) { + depth = Number(args[0].replace('-d', '')); + if (!depth || depth < 0) depth = 0; + const index = evalMessage.findIndex((v) => v.startsWith('-d')) + 1; + evalString = evalMessage.slice(index).join(' ').trim(); + } + if (args[0] === '-a') { + const index = evalMessage.findIndex((v) => v === '-a') + 1; + evalString = `(async () => { ${evalMessage.slice(index).join(' ').trim()} })()`; + } + + try { + // eslint-disable-next-line no-eval + evaled = await eval(evalString); + if (typeof evaled !== 'string') { + evaled = inspect(evaled, { depth }); + } + if (evaled === undefined) { + evaled = 'undefined'; + } + } catch (error) { + evaled = error.stack; + } + + evaled = evaled.replace(new RegExp(this.client.config.token, 'gi'), 'juul'); + // evaled = evaled.replace(new RegExp(this.client.config.emailPass, 'gi'), 'juul'); + // evaled = evaled.replace(new RegExp(this.client.config.cloudflare, 'gi'), 'juul'); + + + const display = this.client.util.splitString(evaled, 1975); + if (display[5]) { + try { + const { data } = await axios.post('https://snippets.cloud.libraryofcode.org/documents', display.join('')); + return this.success(message.channel, `Your evaluation evaled can be found on https://snippets.cloud.libraryofcode.org/${data.key}`); + } catch (error) { + return this.error(message.channel, `${error}`); + } + } + + return display.forEach((m) => message.channel.createMessage(`\`\`\`js\n${m}\n\`\`\``)); + } catch (err) { + return this.client.util.handleError(err, message, this, false); + } + } +} diff --git a/src/commands/game.ts b/src/commands/game.ts new file mode 100644 index 0000000..325b6c2 --- /dev/null +++ b/src/commands/game.ts @@ -0,0 +1,64 @@ +/* eslint-disable prefer-destructuring */ +import { Activity, Member, Message } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +enum ActivityType { + PLAYING, + STREAMING, + LISTENING, + WATCHING, + CUSTOM_STATUS +} + +export default class Game extends Command { + constructor(client: Client) { + super(client); + this.name = 'game'; + this.description = 'Displays information about the member\'s game.'; + this.usage = 'game [member]'; + this.permissions = 0; + this.aliases = ['activity']; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + let member: Member; + if (!args[0]) member = message.member; + else { + member = this.client.util.resolveMember(args.join(' '), this.mainGuild); + if (!member) { + return this.error(message.channel, 'Member not found.'); + } + } + if (!member.activities || member.activities.length <= 0) return this.error(message.channel, 'Cannot find a game for this member.'); + const embed = new RichEmbed(); + let mainStatus: Activity; + if (member.activities[0]?.type === ActivityType.CUSTOM_STATUS) { + mainStatus = member.activities[1]; + embed.setDescription(`*${member.activities[0].state}*`); + } else { + mainStatus = member.activities[0]; + } + embed.setAuthor(member.user.username, member.user.avatarURL); + if (mainStatus?.type === ActivityType.LISTENING) { + embed.setTitle('Spotify'); + embed.setColor('#1ed760'); + embed.addField('Song', mainStatus.details, true); + embed.addField('Artist', mainStatus.state, true); + embed.addField('Album', mainStatus.assets.large_text); + embed.addField('Start', `${new Date(mainStatus.timestamps.start).toLocaleTimeString('en-us')} ET`, true); + embed.addField('End', `${new Date(mainStatus.timestamps.end).toLocaleTimeString('en-us')} ET`, true); + embed.setThumbnail(`https://i.scdn.co/image/${mainStatus.assets.large_image.split(':')[1]}`); + embed.setFooter(`Listening to Spotify | ${this.client.user.username}`, 'https://media.discordapp.net/attachments/358674161566220288/496894273304920064/2000px-Spotify_logo_without_text.png'); + embed.setTimestamp(); + } else { + return this.error(message.channel, 'Only Spotify games are supported at this time.'); + } + return message.channel.createMessage({ embed }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/help.ts b/src/commands/help.ts new file mode 100644 index 0000000..c76906a --- /dev/null +++ b/src/commands/help.ts @@ -0,0 +1,124 @@ +import { createPaginationEmbed } from 'eris-pagination'; +import { Message } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class Help extends Command { + constructor(client: Client) { + super(client); + this.name = 'help'; + this.description = 'Information about commands.'; + this.usage = 'help [command]'; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (args.length > 0) { + const resolved = await this.client.util.resolveCommand(args, message); + if (!resolved) return this.error(message.channel, 'The command you provided doesn\'t exist.'); + const { cmd } = resolved; + const embed = new RichEmbed(); + const subcommands = cmd.subcommands.size ? `\n**Subcommands:** ${cmd.subcommands.map((s) => `${cmd.name} ${s.name}`).join(', ')}` : ''; + embed.setTitle(`${this.client.config.prefix}${cmd.name}`); + embed.addField('Description', cmd.description ?? '-'); + embed.addField('Usage', cmd.usage ?? '-'); + if (subcommands) embed.addField('Sub-commands', subcommands); + if (cmd.aliases.length > 0) { + embed.addField('Aliases', cmd.aliases.map((alias) => `${this.client.config.prefix}${alias}`).join(', ')); + } + let description: string = ''; + if (!cmd.enabled) { + description += 'This command is disabled.'; + } + if (cmd.guildOnly) { + description += 'This command can only be ran in a guild.'; + } + embed.setDescription(description); + switch (cmd.permissions) { + case 0: + break; + case 1: + embed.addField('Permissions', 'Associates+'); + break; + case 2: + embed.addField('Permissions', 'Core Team+'); + break; + case 3: + embed.addField('Permissions', 'Moderators, Supervisor, & Board of Directors'); + break; + case 4: + embed.addField('Permissions', 'Technicians, Supervisor, & Board of Directors'); + break; + case 5: + embed.addField('Permissions', 'Moderators, Technicians, Supervisor, & Board of Directors'); + break; + case 6: + embed.addField('Permissions', 'Supervisor+'); + break; + case 7: + embed.addField('Permissions', 'Board of Directors'); + break; + default: + break; + } + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } + const cmdList: Command[] = []; + this.client.commands.forEach((c) => { + if (c.permissions !== 0 && c.guildOnly) { + const check = c.checkCustomPermissions(message.member, c.permissions); + if (!check) return; + } + cmdList.push(c); + }); + const commands = this.client.commands.map((c) => { + const aliases = c.aliases.map((alias) => `${this.client.config.prefix}${alias}`).join(', '); + let perm: string; + switch (c.permissions) { + case 0: + break; + case 1: + perm = 'Associates+'; + break; + case 2: + perm = 'Core Team+'; + break; + case 3: + perm = 'Moderators, Supervisor, & Board of Directors'; + break; + case 4: + perm = 'Technicians, Supervisor, & Board of Directors'; + break; + case 5: + perm = 'Moderators, Technicians, Supervisor, & Board of Directors'; + break; + case 6: + perm = 'Supervisor+'; + break; + case 7: + perm = 'Board of Directors'; + break; + default: + break; + } + return { name: `${this.client.config.prefix}${c.name}`, value: `**Description:** ${c.description}\n**Aliases:** ${aliases}\n**Usage:** ${c.usage}\n**Permissions:** ${perm ?? ''}`, inline: false }; + }); + const splitCommands = this.client.util.splitFields(commands); + const cmdPages: RichEmbed[] = []; + splitCommands.forEach((splitCmd) => { + const embed = new RichEmbed(); + embed.setTimestamp(); embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setDescription(`Command list for ${this.client.user.username}`); + splitCmd.forEach((c) => embed.addField(c.name, c.value, c.inline)); + return cmdPages.push(embed); + }); + if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] }); + return createPaginationEmbed(message, cmdPages); + } catch (err) { + return this.client.util.handleError(err, message, this, false); + } + } +} diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..f060ed7 --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,48 @@ +export { default as additem } from './additem'; +export { default as addmerchant } from './addmerchant'; +export { default as addnote } from './addnote'; +export { default as addrank } from './addrank'; +export { default as addredirect } from './addredirect'; +export { default as apply } from './apply'; +export { default as ban } from './ban'; +export { default as billing } from './billing'; +export { default as callback } from './callback'; +export { default as delitem } from './delitem'; +export { default as delmerchant } from './delmerchant'; +export { default as delnote } from './delnote'; +export { default as delrank } from './delrank'; +export { default as delredirect } from './delredirect'; +export { default as djs } from './djs'; +export { default as eris } from './eris'; +export { default as eval } from './eval'; +export { default as game } from './game'; +export { default as help } from './help'; +export { default as info } from './info'; +export { default as intercom } from './intercom'; +export { default as kick } from './kick'; +export { default as listredirects } from './listredirects'; +export { default as market } from './market'; +export { default as members } from './members'; +export { default as mute } from './mute'; +export { default as notes } from './notes'; +export { default as npm } from './npm'; +export { default as offer } from './offer'; +export { default as page } from './page'; +export { default as ping } from './ping'; +export { default as profile } from './profile'; +export { default as pulldata } from './pulldata'; +export { default as rank } from './rank'; +export { default as role } from './role'; +export { default as roleinfo } from './roleinfo'; +export { default as score } from './score'; +export { default as sip } from './sip'; +export { default as site } from './site'; +export { default as slowmode } from './slowmode'; +export { default as stats } from './stats'; +export { default as storemessages } from './storemessages'; +export { default as sysinfo } from './sysinfo'; +export { default as train } from './train'; +export { default as tts } from './tts'; +export { default as unban } from './unban'; +export { default as unmute } from './unmute'; +export { default as whois } from './whois'; diff --git a/src/commands/info.ts b/src/commands/info.ts new file mode 100644 index 0000000..6f8a7b1 --- /dev/null +++ b/src/commands/info.ts @@ -0,0 +1,37 @@ +import { Message } from 'eris'; +import { totalmem } from 'os'; +import { Client, Command, RichEmbed } from '../class'; +import { version as tsVersion } from '../../node_modules/typescript/package.json'; + +export default class Info extends Command { + constructor(client: Client) { + super(client); + this.name = 'info'; + this.description = 'Information about this application.'; + this.usage = 'info'; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message) { + try { + const embed = new RichEmbed(); + embed.setTitle('Information'); + embed.setThumbnail(this.client.user.avatarURL); + embed.setDescription(`*See \`${this.client.config.prefix}sysinfo\` for more information on libraries used by this application.*`); + embed.addField('Version', 'Rolling Release', true); + embed.addField('Language(s)', '<:TypeScript:703451285789343774> TypeScript', true); + embed.addField('Runtime', `Node (${process.version})`, true); + embed.addField('Compilers/Transpilers', `TypeScript [tsc] (${tsVersion})`, true); + embed.addField('Memory Usage', `${Math.round(process.memoryUsage().rss / 1024 / 1024)} MB / ${Math.round(totalmem() / 1024 / 1024 / 1024)} GB`, true); + embed.addField('Repository', 'https://loc.sh/crgit | Licensed under GNU Affero General Public License V3', true); + embed.addField('Branch', await this.client.util.exec('git rev-parse --abbrev-ref HEAD'), true); + embed.addField('Commit', `[${await this.client.util.exec('git rev-parse --short HEAD')}](${await this.client.util.exec('git rev-parse HEAD')})`, true); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + message.channel.createMessage({ embed }); + } catch (err) { + this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/intercom.ts b/src/commands/intercom.ts new file mode 100644 index 0000000..071ec0a --- /dev/null +++ b/src/commands/intercom.ts @@ -0,0 +1,45 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; +import { Misc as MiscPBXActions } from '../pbx'; + +export default class Intercom extends Command { + constructor(client: Client) { + super(client); + this.name = 'intercom'; + this.description = 'Will synthesize inputted text to a recording and dial an intercom to the extension specified, then play the recording.'; + this.usage = `${this.client.config.prefix}intercom `; + this.permissions = 1; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const loading = await this.loading(message.channel, 'Synthesizing text...'); + + const recordingLocation = await MiscPBXActions.TTS(this.client.util.pbx, `Hello, this is the Library of Code Private Branch Exchange dialing you at the request of ${message.author.username} to deliver you a message. Playing message: ${args.slice(1).join(' ')}`, 'MALE'); + await loading.edit(`***${this.client.util.emojis.LOADING} Preparing to dial...***`); + this.client.util.pbx.ami.action({ + action: 'originate', + channel: `PJSIP/${args[0]}`, + exten: args[0], + context: 'from-internal', + CallerID: `TTS INTC FRM ${message.author.username} <000>`, + application: 'PlayBack', + priority: '1', + data: `beep&${recordingLocation.split(':')[1]}`, + variable: { + 'PJSIP_HEADER(add,Call-Info)': ';answer-after=0', + 'PJSIP_HEADER(add,Alert-Info)': 'Ring Answer', + }, + }, async (err: Error) => { + if (err) return loading.edit(`***${this.client.util.emojis.ERROR} Failed to dial extension.***`); + return loading.edit(`***${this.client.util.emojis.SUCCESS} Successfully queued intercom message to EXT \`${args[0]}\`.***`); + }); + return undefined; + } catch (err) { + return this.client.util.handleError(err, message, this, false); + } + } +} diff --git a/src/commands/kick.ts b/src/commands/kick.ts new file mode 100644 index 0000000..b0787c2 --- /dev/null +++ b/src/commands/kick.ts @@ -0,0 +1,37 @@ +import { Member, Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Kick extends Command { + constructor(client: Client) { + super(client); + this.name = 'kick'; + this.description = 'Kicks a member from the guild.'; + this.usage = 'kick [reason]'; + this.permissions = 3; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + let user: Member = this.client.util.resolveMember(args[0], this.mainGuild); + if (!user) { + try { + user = await this.client.getRESTGuildMember(this.mainGuild.id, args[0]); + } catch { + return this.error(message.channel, 'Cannot find user.'); + } + } + if (user && !this.client.util.moderation.checkPermissions(user, message.member)) return this.error(message.channel, 'Permission Denied.'); + message.delete(); + + const reason: string = args.slice(1).join(' '); + if (reason.length > 512) return this.error(message.channel, 'Kick reasons cannot be longer than 512 characters.'); + await this.client.util.moderation.kick(user, message.member, reason); + return this.success(message.channel, `${user.username}#${user.discriminator} has been kicked.`); + } catch (err) { + return this.client.util.handleError(err, message, this, false); + } + } +} diff --git a/src/commands/listredirects.ts b/src/commands/listredirects.ts new file mode 100644 index 0000000..ae9a11e --- /dev/null +++ b/src/commands/listredirects.ts @@ -0,0 +1,52 @@ +import { Message } from 'eris'; +import { createPaginationEmbed } from 'eris-pagination'; +import { Client, Command, RichEmbed } from '../class'; + +export default class DelRedirect extends Command { + constructor(client: Client) { + super(client); + this.name = 'listredirects'; + this.description = 'Delete a redirect link for \'loc.sh\''; + this.usage = 'listredirects [key || redirect to]'; + this.aliases = ['getredirect', 'lsredirects', 'listlinks', 'lsr', 'gr']; + this.permissions = 6; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (args[0]) { + const redirects = await this.client.db.Redirect.find({ $or: [{ key: args[0].toLowerCase() }, { to: args[0].toLowerCase() }] }); + if (redirects.length <= 0) return this.error(message.channel, 'Could not find an entry matching that query.'); + const embed = new RichEmbed(); + embed.setTitle('Redirect Information'); + for (const redirect of redirects) { + embed.addField(`${redirect.key} | visited ${redirect.visitedCount} times`, redirect.to); + } + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } + const redirects = await this.client.db.Redirect.find(); + if (!redirects) return this.error(message.channel, 'No redirect links found.'); + const redirectArray: [{ name: string, value: string }?] = []; + for (const redirect of redirects) { + redirectArray.push({ name: `${redirect.key} | visited ${redirect.visitedCount} times`, value: redirect.to }); + } + const splitRedirects = this.client.util.splitFields(redirectArray); + const cmdPages: RichEmbed[] = []; + splitRedirects.forEach((split) => { + const embed = new RichEmbed(); + embed.setTitle('Redirect Information'); + embed.setTimestamp(); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + split.forEach((c) => embed.addField(c.name, c.value)); + return cmdPages.push(embed); + }); + if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] }); + return createPaginationEmbed(message, cmdPages); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/market.ts b/src/commands/market.ts new file mode 100644 index 0000000..132edd2 --- /dev/null +++ b/src/commands/market.ts @@ -0,0 +1,59 @@ +import { Message } from 'eris'; +import stockInfo from 'stock-info'; +import { Client, Command, RichEmbed } from '../class'; + +export default class Market extends Command { + constructor(client: Client) { + super(client); + this.name = 'market'; + this.description = 'Fetches information from a ticker for a stock or fund.'; + this.usage = `${this.client.config.prefix}market `; + this.permissions = 0; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + let stock: stockInfo.Stock; + try { + stock = await stockInfo.getSingleStockInfo(args[0]); + } catch (err) { + return this.error(message.channel, `Unable to fetch information for that ticker. | ${err}`); + } + + const embed = new RichEmbed(); + embed.setTitle(stock.longName ?? stock.symbol); + let type: string; + switch (stock.quoteType) { + case 'EQUITY': + type = 'Common/Preferred Stock'; + break; + case 'ETF': + type = 'Exchange Traded Fund (ETF)'; + break; + case 'MUTUALFUND': + type = 'Mutual Fund'; + break; + default: + type = 'N/A or Unknown'; + break; + } + embed.addField('Type', type, true); + embed.addField('Market Cap', `${stock.marketCap ? this.client.util.hrn(stock.marketCap, undefined, undefined) : 'N/A'}`, true); + embed.addField('Day Quote Price', stock.regularMarketPrice ? `$${Number(stock.regularMarketPrice).toFixed(2)}` : 'N/A', true); + embed.addField('Day G/L', stock.regularMarketChange ? `$${Number(stock.regularMarketChange.toFixed(2))}` : 'N/A', true); + embed.addField('Day Range', stock.regularMarketDayRange ?? 'N/A', true); + embed.addField('Bid/Ask', `Bid: $${stock.bid?.toFixed(2) ?? 'N/A'} | Ask: $${stock.ask?.toFixed(2) ?? 'N/A'}`, true); + embed.addField('Forward P/E (Price/Earnings)', `${stock.forwardPE?.toFixed(2) ?? 'N/A'}`, true); + embed.addField('Forward EPS (Earnings Per Share)', `${stock.epsForward?.toFixed(2) ?? 'N/A'}`, true); + embed.addField('Exchange', `${stock.fullExchangeName?.toUpperCase() ?? 'N/A'}`, true); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } catch (err) { + return this.client.util.handleError(err, message, this, false); + } + } +} diff --git a/src/commands/members.ts b/src/commands/members.ts new file mode 100644 index 0000000..678ca09 --- /dev/null +++ b/src/commands/members.ts @@ -0,0 +1,85 @@ +import { Message } from 'eris'; +import { createPaginationEmbed } from 'eris-pagination'; +import { Client, Command, RichEmbed } from '../class'; + +export default class Members extends Command { + constructor(client: Client) { + super(client); + this.name = 'members'; + this.description = 'Gets a list of members in the server or members in a specific role.'; + this.usage = `${this.client.config.prefix}members [role name]`; + this.permissions = 0; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + await this.mainGuild.fetchAllMembers(); + if (!args[0]) { + const embed = new RichEmbed(); + const membersOnline = this.mainGuild.members.filter((member) => member.status === 'online'); + const membersIdle = this.mainGuild.members.filter((member) => member.status === 'idle'); + const membersDnd = this.mainGuild.members.filter((member) => member.status === 'dnd'); + const membersOffline = this.mainGuild.members.filter((member) => member.status === 'offline' || member.status === undefined); + const membersBots = this.mainGuild.members.filter((member) => member.user.bot === true); + const membersHuman = this.mainGuild.members.filter((member) => member.user.bot === false); + + embed.setTitle('Members'); + embed.setDescription(`**Total:** ${this.mainGuild.members.size}\n**Humans:** ${membersHuman.length}\n**Bots:** ${membersBots.length}\n\n**<:online:732025023547834369> Online:** ${membersOnline.length}\n**<:idle:732025087896715344> Idle:** ${membersIdle.length}\n**<:dnd:732024861853089933> Do Not Disturb:** ${membersDnd.length}\n**<:offline:732024920518688849> Offline:** ${membersOffline.length}`); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + + return message.channel.createMessage({ embed }); + } + + const role = this.client.util.resolveRole(args.join(' '), this.mainGuild); + if (!role) return this.error(message.channel, 'The role you specified doesn\'t exist.'); + const statusArray: string[] = []; + const membersOnline: string[] = []; + const membersIdle: string[] = []; + const membersDnd: string[] = []; + const membersOffline: string[] = []; + for (const member of this.mainGuild.members.filter((m) => m.roles.includes(role.id)).sort((a, b) => a.username.localeCompare(b.username))) { + switch (member.status) { + case 'online': + membersOnline.push(`<:online:732025023547834369> ${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`); + break; + case 'idle': + membersIdle.push(`<:idle:732025087896715344> ${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`); + break; + case 'dnd': + membersDnd.push(`<:dnd:732024861853089933> ${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`); + break; + case 'offline': + membersOffline.push(`<:offline:732024920518688849> ${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`); + break; + case undefined: + membersOffline.push(`<:offline:732024920518688849> ${member.user.username}#${member.user.discriminator} | <@${member.user.id}>`); + break; + default: + break; + } + } + if (membersOnline.length > 0) statusArray.push(membersOnline.join('\n')); + if (membersIdle.length > 0) statusArray.push(membersIdle.join('\n')); + if (membersDnd.length > 0) statusArray.push(membersDnd.join('\n')); + if (membersOffline.length > 0) statusArray.push(membersOffline.join('\n')); + const statusSplit = this.client.util.splitString(statusArray.join('\n'), 2000); + const cmdPages: RichEmbed[] = []; + statusSplit.forEach((split) => { + const embed = new RichEmbed(); + embed.setTitle(`Members in ${role.name}`); + embed.setDescription(`Members in Role: ${membersOnline.length + membersIdle.length + membersDnd.length + membersOffline.length}\n\n${split}`); + embed.setColor(role.color); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return cmdPages.push(embed); + }); + if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] }); + return createPaginationEmbed(message, cmdPages); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/mute.ts b/src/commands/mute.ts new file mode 100644 index 0000000..65d47a8 --- /dev/null +++ b/src/commands/mute.ts @@ -0,0 +1,45 @@ +import moment, { unitOfTime } from 'moment'; +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Mute extends Command { + constructor(client: Client) { + super(client); + this.name = 'mute'; + this.description = 'Mutes a member.'; + this.usage = 'mute [time] [reason]'; + this.permissions = 2; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const member = this.client.util.resolveMember(args[0], this.mainGuild); + if (!member) return this.error(message.channel, 'Cannot find user.'); + + try { + const res1 = await this.client.db.local.muted.get(`muted-${member.id}`); + if (res1 || this.mainGuild.members.get(member.id).roles.includes('478373942638149643')) return this.error(message.channel, 'This user is already muted.'); + } catch {} // eslint-disable-line no-empty + if (member && !this.client.util.moderation.checkPermissions(member, message.member)) return this.error(message.channel, 'Permission Denied.'); + message.delete(); + + let momentMilliseconds: number; + let reason: string; + if (args.length > 1) { + const lockLength = args[1].match(/[a-z]+|[^a-z]+/gi); + const length = Number(lockLength[0]); + const unit = lockLength[1] as unitOfTime.Base; + momentMilliseconds = moment.duration(length, unit).asMilliseconds(); + reason = momentMilliseconds ? args.slice(2).join(' ') : args.slice(1).join(' '); + if (reason.length > 512) return this.error(message.channel, 'Mute reasons cannot be longer than 512 characters.'); + } + await this.client.util.moderation.mute(member.user, message.member, momentMilliseconds, reason); + return this.success(message.channel, `${member.user.username}#${member.user.discriminator} has been muted.`); + } catch (err) { + return this.client.util.handleError(err, message, this, false); + } + } +} diff --git a/src/commands/notes.ts b/src/commands/notes.ts new file mode 100644 index 0000000..b711a8a --- /dev/null +++ b/src/commands/notes.ts @@ -0,0 +1,91 @@ +import { Message, Member, User } from 'eris'; +import { createPaginationEmbed } from 'eris-pagination'; +import { Command, Client, RichEmbed } from '../class'; + +export default class Notes extends Command { + constructor(client: Client) { + super(client); + this.name = 'notes'; + this.description = 'Pulls up notes for a member, with an optional categorical filter.'; + this.usage = `${this.client.config.prefix}notes [filter: comm | cs | edu]`; + this.permissions = 1; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + let member: Member | User = this.client.util.resolveMember(args[0], this.mainGuild); + if (!member) { + try { + member = await this.client.getRESTUser(args[0]); + } catch { + member = undefined; + } + } + if (!member) return this.error(message.channel, 'User specified could not be found.'); + + const notes = await this.client.db.Note.find({ userID: member.id }); + if (!notes || notes?.length < 1) return this.error(message.channel, 'No notes exist for this user.'); + + const noteArray: [{ name: string, value: string, inline: boolean }?] = []; + if (args[1] === 'comm' || args[1] === 'cs' || args[1] === 'edu') { + switch (args[1]) { + case 'comm': + for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime()).filter((r) => r.category === 'comm')) { + noteArray.push({ + name: `${note._id}${note.category === '' ? '' : `, ${note.category.toUpperCase()}`} | ${note.date.toLocaleString('en-us')} ET | Staff: ${this.client.users.get(note.staffID) ? `${this.client.users.get(note.staffID).username}#${this.client.users.get(note.staffID).discriminator}` : 'N/A'}`, + value: note.text, + inline: true, + }); + } + break; + case 'cs': + for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime()).filter((r) => r.category === 'cs')) { + noteArray.push({ + name: `${note._id}${note.category === '' ? '' : `, ${note.category.toUpperCase()}`} | ${note.date.toLocaleString('en-us')} ET | Staff: ${this.client.users.get(note.staffID) ? `${this.client.users.get(note.staffID).username}#${this.client.users.get(note.staffID).discriminator}` : 'N/A'}`, + value: note.text, + inline: true, + }); + } + break; + case 'edu': + for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime()).filter((r) => r.category === 'edu')) { + noteArray.push({ + name: `${note._id}${note.category === '' ? '' : `, ${note.category.toUpperCase()}`}} | ${note.date.toLocaleString('en-us')} ET | Staff: Staff: ${this.client.users.get(note.staffID) ? `${this.client.users.get(note.staffID).username}#${this.client.users.get(note.staffID).discriminator}` : 'N/A'}`, + value: note.text, + inline: true, + }); + } + break; + default: + break; + } + } else { + for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime())) { + noteArray.push({ + name: `${note._id}${note.category === '' ? '' : `, ${note.category}`} | ${note.date.toLocaleString('en-us')} ET | Staff: ${this.client.users.get(note.staffID) ? `${this.client.users.get(note.staffID).username}#${this.client.users.get(note.staffID).discriminator}` : 'N/A'}`, + value: note.text, + inline: true, + }); + } + } + const noteSplit = this.client.util.splitFields(noteArray); + const cmdPages: RichEmbed[] = []; + noteSplit.forEach((split) => { + const embed = new RichEmbed(); + embed.setColor('#0000FF'); + embed.setAuthor(`${member.username}#${member.discriminator}`, member.avatarURL); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + split.forEach((c) => embed.addField(c.name, c.value, c.inline)); + return cmdPages.push(embed); + }); + if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] }); + return createPaginationEmbed(message, cmdPages); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/npm.ts b/src/commands/npm.ts new file mode 100644 index 0000000..c8c4998 --- /dev/null +++ b/src/commands/npm.ts @@ -0,0 +1,65 @@ +import { Message } from 'eris'; +import axios from 'axios'; +import { Client, Command, RichEmbed } from '../class'; + +export default class NPM extends Command { + constructor(client: Client) { + super(client); + this.name = 'npm'; + this.description = 'Get information about npm modules.'; + this.usage = 'npm '; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + + const res = await axios.get(`https://registry.npmjs.com/${args[0]}`, { validateStatus: (_) => true }); + + if (res.status === 404) return this.error(message.channel, 'Could not find the library, try something else.'); + + const { data } = res; + + const bugs: string = data.bugs?.url || ''; + const description: string = data.description || 'None'; + const version: string = data['dist-tags']?.latest || 'Unknown'; + const homepage: string = data.homepage || ''; + let license: string = 'None'; + if (typeof data.license === 'object') { + license = data.license.type; + } else if (typeof data.license === 'string') { + license = data.license; + } + let dependencies: string = 'None'; + if (version !== 'Unknown' && data.versions[version].dependencies !== undefined && Object.keys(data.versions[version].dependencies).length > 0) { + dependencies = Object.keys(data.versions[version].dependencies).join(', '); + if (dependencies.length > 1024) dependencies = `${dependencies.substr(0, 1021)}...`; + } + const name: string = data.name || 'None'; + const repository: string = bugs.replace('/issues', '') || ''; + const creation: string = data?.time.created ? new Date(data.time.created).toLocaleString('en') : 'None'; + const modification: string = data?.time.modified ? new Date(data.time.modified).toLocaleString('en') : 'None'; + + const embed = new RichEmbed(); + embed.setColor(0xCC3534); + embed.setTimestamp(); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setAuthor('NPM', 'https://i.imgur.com/ErKf5Y0.png', 'https://www.npmjs.com/'); + embed.setDescription(`[NPM](https://www.npmjs.com/package/${args[0]}) | [Homepage](${homepage}) | [Repository](${repository}) | [Bugs](${bugs})`); + embed.addField('Name', name, true); + embed.addField('Latest version', version, true); + embed.addField('License', license, true); + embed.addField('Description', description, false); + embed.addField('Dependencies', dependencies, false); + embed.addField('Creation Date', creation, true); + embed.addField('Modification Date', modification, true); + + return message.channel.createMessage({ embed }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/offer.ts b/src/commands/offer.ts new file mode 100644 index 0000000..7f9b423 --- /dev/null +++ b/src/commands/offer.ts @@ -0,0 +1,50 @@ +/* eslint-disable default-case */ +import jwt from 'jsonwebtoken'; +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Offer extends Command { + constructor(client: Client) { + super(client); + this.name = 'offer'; + this.description = 'Pre-qualifies a member for an offer. Will run a hard-pull automatically on acceptance.'; + this.usage = `${this.client.config.prefix}offer :`; + this.permissions = 4; + this.aliases = ['qualify']; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const member = this.client.util.resolveMember(args[0], this.mainGuild); + if (!member) return this.error(message.channel, 'Could not find member.'); + const score = await this.client.db.Score.findOne({ userID: member.user.id }).lean().exec(); + if (!score) return this.error(message.channel, 'Could not find score report for this user.'); + + if (score.locked) return this.error(message.channel, 'This user\'s score report is locked.'); + + const name = args.slice(1).join(' ').split(':')[0]; + const dept = args.slice(1).join(' ').split(':')[1]; + if (!name || !dept) return this.error(message.channel, 'Invalid arguments.'); + + const token = jwt.sign({ + userID: member.user.id, + staffID: message.author.id, + channelID: message.channel.id, + messageID: message.id, + pin: score.pin.join('-'), + name, + department: dept, + date: new Date(), + }, this.client.config.internalKey, { expiresIn: '12h' }); + this.client.getDMChannel(member.user.id).then((chan) => { + chan.createMessage(`__**Offer Pre-Qualified**__\nYou have been pre-approved for an offer! If you wish to accept this offer, please enter the offer code at https://report.libraryofcode.org/. Do not share this code with anyone else. This offer automatically expires in 12 hours. Your report will be Hard Inquiried immediately after accepting this offer.\n\n**Department:** ${dept.toUpperCase()}\n**Offer for:** ${name}\n\n\`${token}\``); + }).catch(() => this.error(message.channel, 'Could not DM member.')); + return this.success(message.channel, `Offer sent.\n\n\`${token}\``); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/page.ts b/src/commands/page.ts new file mode 100644 index 0000000..56fc4ac --- /dev/null +++ b/src/commands/page.ts @@ -0,0 +1,240 @@ +/* eslint-disable no-case-declarations */ +/* eslint-disable no-continue */ +/* eslint-disable no-await-in-loop */ +import { promises as fs } from 'fs'; +import { randomBytes } from 'crypto'; +import { Message, TextableChannel } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class Page extends Command { + public local: { emergencyNumbers: string[], departmentNumbers: string[], validPagerCodes: string[], codeDict: Map, }; + + constructor(client: Client) { + super(client); + this.name = 'page'; + this.description = 'Pages the specified emergency number, department number, or individual number with the specified pager code.'; + this.usage = `${this.client.config.prefix}page [optional message]\n${this.client.config.prefix}page settings `; + this.aliases = ['p']; + this.permissions = 1; + this.enabled = true; + this.guildOnly = true; + this.local = { + emergencyNumbers: ['#0', '#1', '#2', '#3'], + departmentNumbers: ['00', '01', '10', '20', '21', '22'], + validPagerCodes: ['911', '811', '210', '265', '411', '419', '555', '556', '557', '558'], + codeDict: new Map(), + }; + this.init(); + } + + public init() { + this.local.codeDict.set('911', 'Sender is requesting EMERGENCY assistance.'); + this.local.codeDict.set('811', 'Sender is requesting immediate/ASAP assistance.'); + this.local.codeDict.set('210', 'Sender is informing you they acknowledged your request, usually sent in response to OK the initial page. (10-4)'); + this.local.codeDict.set('265', 'Sender is requesting that you check your email.'); + this.local.codeDict.set('411', 'Sender is requesting information/counsel from you.'); + this.local.codeDict.set('419', 'Sender didn\'t recognize your request.'); + this.local.codeDict.set('555', 'Sender is requesting that you contact them.'); + this.local.codeDict.set('556', 'Sender is requesting that you contact them via DMs.'); + this.local.codeDict.set('557', 'Sender is requesting that you contact them via PBX/their extension.'); + this.local.codeDict.set('558', 'Sender is requesting if they are able to call you via PBX. If so, please send the sender back a 210 page.'); + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) { + this.client.commands.get('help').run(message, [this.name]); + const embed = new RichEmbed(); + embed.setTitle('Special Emergency/Department Numbers & Pager Codes'); + embed.addField('Special Emergency Numbers', '`#0` | Broadcast - all Staff/Associates\n`#1` | Authoritative Broadcast - all Directors, Supervisors, Technicians, and Moderators\n`#2` | Systems Administrators/Technicians Broadcast - Matthew, Bsian, NightRaven, and all Technicians\n`#3` | Community/Moderation Team Broadcast - all Directors, Supervisors, Moderators, and Core Team'); + embed.addField('Department Numbers', '`00` | Board of Directors\n`01` | Supervisors\n`10` | Technicians\n`20` | Moderators\n`21` | Core Team\n`22` | Associates'); + embed.addField('Pager Codes', '"Pager" term in this field refers to the Staff member that initially paged. This is a list of valid codes you can send via a page.\n\n`911` - Pager is requesting EMERGENCY assistance\n`811` - Pager is requesting immediate/ASAP assistance\n`210` - Pager is informing you they acknowledged your request, usually sent in response to OK the initial page.\n`265` - Pager is requesting that you check your email\n`411` - Pager is requesting information/counsel from you\n`419` - Pager didn\'t recognize your request\n`555` - Pager is requesting that you contact them\n`556` - Pager is requesting that you contact them via DMs\n`557` - Pager is requesting that you contact them via PBX/their extension.\n`558` - Pager is requesting if they are able to call you via PBX. If so, please send the pager back a 210 page.'); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } + if (args[0] === 'settings') { + const pager = await this.client.db.PagerNumber.findOne({ individualAssignID: message.author.id }); + if (!pager) return this.error(message.channel, 'You do not have a Pager Number.'); + switch (args[1]) { + case 'email': + if (args[2] === 'off') { + if (pager.receiveEmail === false) return this.error(message.channel, 'You are already set to not receive email notifications.'); + await pager.updateOne({ $set: { receiveEmail: false } }); + return this.success(message.channel, 'You will no longer receive notifications by email for pages.'); + } + if (args[2] === 'on') { + if (pager.receiveEmail === true) return this.error(message.channel, 'You are already set to receive email notifications.'); + await pager.updateOne({ $set: { receiveEmail: true } }); + return this.success(message.channel, 'You will now receive notifications by email for pages.'); + } + break; + case 'phone': + if (args[2] === 'off') { + if (pager.receivePhone === false) return this.error(message.channel, 'You are already set to not receive PBX calls.'); + await pager.updateOne({ $set: { receivePhone: false } }); + return this.success(message.channel, 'You will no longer receive PBX calls for pages.'); + } + if (args[2] === 'on') { + if (pager.receivePhone === true) return this.error(message.channel, 'You are already set to receive PBX calls.'); + await pager.updateOne({ $set: { receivePhone: true } }); + return this.success(message.channel, 'You will now receive PBX calls for pages.'); + } + break; + default: + this.error(message.channel, 'Invalid response provided.'); + break; + } + } + message.delete(); + const loading = await this.loading(message.channel, 'Paging...'); + const sender = await this.client.db.PagerNumber.findOne({ individualAssignID: message.author.id }); + if (!sender) return this.error(message.channel, 'You do not have a Pager Number.'); + const page = await this.page(args[0], sender.num, args[1], message, args[2] ? args.slice(2).join(' ') : undefined); + if (page.status === true) { + loading.delete(); + return this.success(message.channel, page.message); + } + loading.delete(); + return this.error(message.channel, page.message); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } + + public logPage(sender: { number: string, user?: string }, recipient: { number: string, user?: string }, type: 'discord' | 'email' | 'phone', code: string): void { + const chan = this.mainGuild.channels.get('722636436716781619'); + chan.createMessage(`***[${type.toUpperCase()}] \`${sender.number} (${sender.user ? sender.user : ''})\` sent a page to \`${recipient.number} (${recipient.user ? recipient.user : ''})\` with code \`${code}\`.***`); + this.client.util.signale.log(`PAGE (${type.toUpperCase()})| TO: ${recipient.number}, FROM: ${sender.number}, CODE: ${code}`); + } + + public async page(recipientNumber: string, senderNumber: string, code: string, message: Message, txt?: string, options?: { emergencyNumber: string }): Promise<{status: boolean, message: string}> { + try { + if (txt?.length >= 140) return { status: false, message: 'Your message must be less than 141 characters.' }; + const senderEntry = await this.client.db.PagerNumber.findOne({ num: senderNumber }); + if (!senderEntry) { + return { status: false, message: 'You do not have a Pager Number.' }; + } + if (this.local.emergencyNumbers.includes(recipientNumber)) { + switch (recipientNumber) { + case '#0': + this.local.departmentNumbers.forEach(async (num) => { + await this.page(num, senderNumber, code, message, txt, { emergencyNumber: '0' }); + }); + break; + case '#1': + await this.page('00', senderNumber, code, message, txt, { emergencyNumber: '1' }); + await this.page('01', senderNumber, code, message, txt, { emergencyNumber: '1' }); + await this.page('10', senderNumber, code, message, txt, { emergencyNumber: '1' }); + await this.page('20', senderNumber, code, message, txt, { emergencyNumber: '1' }); + break; + case '#2': + const matthew = await this.client.db.PagerNumber.findOne({ individualAssignID: '278620217221971968' }); + const bsian = await this.client.db.PagerNumber.findOne({ individualAssignID: '253600545972027394' }); + const nightraven = await this.client.db.PagerNumber.findOne({ individualAssignID: '239261547959025665' }); + await this.page(matthew?.num, senderNumber, code, message, txt, { emergencyNumber: '2' }); + await this.page(bsian?.num, senderNumber, code, message, txt, { emergencyNumber: '2' }); + await this.page(nightraven?.num, senderNumber, code, message, txt, { emergencyNumber: '2' }); + await this.page('10', senderNumber, code, message, txt, { emergencyNumber: '2' }); + break; + case '#3': + await this.page('00', senderNumber, code, message, txt, { emergencyNumber: '3' }); + await this.page('01', senderNumber, code, message, txt, { emergencyNumber: '3' }); + await this.page('20', senderNumber, code, message, txt, { emergencyNumber: '3' }); + await this.page('21', senderNumber, code, message, txt, { emergencyNumber: '3' }); + break; + default: + break; + } + return { status: true, message: `Page to \`${recipientNumber}\` sent.` }; + } + const recipientEntry = await this.client.db.PagerNumber.findOne({ num: recipientNumber }); + if (!recipientEntry) { + return { status: false, message: `Pager Number \`${recipientNumber}\` does not exist.` }; + } + if (!this.local.validPagerCodes.includes(code)) { + return { status: false, message: 'The Pager Code you provided is invalid.' }; + } + + for (const id of recipientEntry.discordIDs) { + const recipient = this.mainGuild.members.get(recipientEntry.individualAssignID); + const sender = this.mainGuild.members.get(senderEntry.individualAssignID); + const chan = await this.client.getDMChannel(id); + if (!chan) continue; + if (!recipient || !sender) { + this.logPage({ number: senderNumber, user: 'N/A' }, { number: recipientNumber, user: 'N/A' }, 'discord', code); + } else { + this.logPage({ number: senderNumber, user: `${sender.username}#${sender.discriminator}` }, { number: recipientNumber, user: `${recipient.username}#${recipient.discriminator}` }, 'discord', code); + } + chan.createMessage(`${options?.emergencyNumber ? `[SEN#${options.emergencyNumber}] ` : ''}__**Page**__\n**Recipient PN:** ${recipientNumber}\n**Sender PN:** ${senderNumber} (${sender ? `${sender.username}#${sender.discriminator}` : ''})\n**Initial Command:** https://discordapp.com/channels/${this.mainGuild.id}/${message.channel.id}/${message.id} (<#${message.channel.id}>)\n\n**Pager Code:** ${code} (${this.local.codeDict.get(code)})${txt ? `\n**Message:** ${txt}` : ''}`); + } + for (const email of recipientEntry.emailAddresses) { + const recipient = this.mainGuild.members.get(recipientEntry.individualAssignID); + const sender = this.mainGuild.members.get(senderEntry.individualAssignID); + if (recipientEntry.receiveEmail === false) continue; + if (!recipient || !sender) { + this.logPage({ number: senderNumber, user: 'N/A' }, { number: recipientNumber, user: 'N/A' }, 'email', code); + } else { + this.logPage({ number: senderNumber, user: `${sender.username}#${sender.discriminator}` }, { number: recipientNumber, user: `${recipient.username}#${recipient.discriminator}` }, 'email', code); + } + await this.client.util.transporter.sendMail({ + from: '"LOC Paging System" ', + to: email, + subject: `PAGE FROM ${senderNumber}`, + html: `

Page

${options?.emergencyNumber ? `

[SEN#${options.emergencyNumber}]` : ''}Recipient PN: ${recipientNumber}
Sender PN: ${senderNumber} (${sender ? `${sender.username}#${sender.discriminator}` : ''})
Initial Command: https://discordapp.com/channels/${this.mainGuild.id}/${message.channel.id}/${message.id} (<#${message.channel.id}>)

Pager Code: ${code} (${this.local.codeDict.get(code)})${txt ? `
Message: ${txt}` : ''}`, + }); + } + + for (const id of recipientEntry.discordIDs) { + const pager = await this.client.db.PagerNumber.findOne({ individualAssignID: id }); + if (!pager || !pager.receivePhone) continue; + const member = await this.client.db.Staff.findOne({ userID: pager.individualAssignID }); + if (!member || !member.extension) continue; + + const fileExtension = `${randomBytes(10).toString('hex')}`; + + const [response] = await this.client.util.pbx.tts.synthesizeSpeech({ + input: { text: `Hello, this call was automatically dialed by the Library of Code Private Branch Exchange. You have received a page from Pager Number, ${senderNumber}. The Pager Code is ${code}. Please check your Direct Messages on Discord for further information.` }, + voice: { languageCode: 'en-US', ssmlGender: 'MALE' }, + audioConfig: { audioEncoding: 'OGG_OPUS' }, + }); + await fs.writeFile(`/tmp/${fileExtension}.ogg`, response.audioContent, 'binary'); + await this.client.util.exec(`ffmpeg -i /tmp/${fileExtension}.ogg -af "highpass=f=300, lowpass=f=3400" -ar 8000 -ac 1 -ab 64k -f mulaw /tmp/${fileExtension}.ulaw`); + + try { + const chan = await this.client.util.pbx.ari.channels.originate({ + endpoint: `PJSIP/${member.extension}`, + extension: `${member.extension}`, + callerId: `PAGE FRM ${senderNumber} <000>`, + context: 'from-internal', + priority: 1, + app: 'cr-zero', + }); + chan.on('StasisStart', async (_event, channel) => { + const playback = await channel.play({ + media: `sound:/tmp/${fileExtension}`, + }, undefined); + playback.on('PlaybackFinished', () => { + channel.hangup(); + }); + }); + const recipient = this.mainGuild.members.get(recipientEntry.individualAssignID); + const sender = this.mainGuild.members.get(senderEntry.individualAssignID); + + if (!recipient || !sender) { + this.logPage({ number: senderNumber, user: 'N/A' }, { number: recipientNumber, user: 'N/A' }, 'phone', code); + } else { + this.logPage({ number: senderNumber, user: `${sender.username}#${sender.discriminator}` }, { number: recipientNumber, user: `${recipient.username}#${recipient.discriminator}` }, 'phone', code); + } + } catch (err) { + this.client.util.signale.log(`Unable to Dial ${member.extension} | ${err}`); + } + } + this.client.db.Stat.updateOne({ name: 'pages' }, { $inc: { value: 1 } }).exec(); + return { status: true, message: `Page to \`${recipientNumber}\` sent.` }; + } catch (err) { + this.client.util.signale.error(err); + return { status: false, message: `Error during Processing: ${err}` }; + } + } +} diff --git a/src/commands/ping.ts b/src/commands/ping.ts new file mode 100644 index 0000000..6f20184 --- /dev/null +++ b/src/commands/ping.ts @@ -0,0 +1,23 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Ping extends Command { + constructor(client: Client) { + super(client); + this.name = 'ping'; + this.description = 'Pings the bot'; + this.usage = 'ping'; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message) { + try { + const clientStart: number = Date.now(); + const msg: Message = await message.channel.createMessage('🏓 Pong!'); + msg.edit(`🏓 Pong!\nClient: \`${Date.now() - clientStart}ms\`\nResponse: \`${msg.createdAt - message.createdAt}ms\``); + } catch (err) { + this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/profile.ts b/src/commands/profile.ts new file mode 100644 index 0000000..20978a4 --- /dev/null +++ b/src/commands/profile.ts @@ -0,0 +1,26 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +import Profile_Bio from './profile_bio'; +import Profile_GitHub from './profile_github'; +import Profile_Gitlab from './profile_gitlab'; + +export default class Profile extends Command { + constructor(client: Client) { + super(client); + this.name = 'profile'; + this.description = 'Manages your profile on CR.'; + this.usage = 'profile \n*Provide no value in subcommand to clear data.*'; + this.permissions = 0; + this.enabled = true; + this.subcmds = [Profile_Bio, Profile_GitHub, Profile_Gitlab]; + } + + public async run(message: Message) { + if (!await this.client.db.Member.exists({ userID: message.author.id })) { + await this.client.db.Member.create({ userID: message.author.id }); + } + + this.error(message.channel, `Please specify a valid option to change. Choose from \`github\`, \`bio\` and \`gitlab\`. You can view your profile with \`${this.client.config.prefix}whois\`.`); + } +} diff --git a/src/commands/profile_bio.ts b/src/commands/profile_bio.ts new file mode 100644 index 0000000..f26cb76 --- /dev/null +++ b/src/commands/profile_bio.ts @@ -0,0 +1,39 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Profile_Bio extends Command { + constructor(client: Client) { + super(client); + this.name = 'bio'; + this.description = 'Updates your bio on your profile.'; + this.usage = `${this.client.config.prefix}bio `; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + if (!await this.client.db.Member.exists({ userID: message.author.id })) { + await this.client.db.Member.create({ userID: message.author.id }); + } + const member = await this.client.db.Member.findOne({ userID: message.author.id }); + + if (!args[0]) { + await member.updateOne({ + additional: { + ...member.additional, + bio: null, + }, + }); + return message.addReaction('modSuccess:578750988907970567'); + } + const bio = args.join(' '); + if (bio.length >= 256) return this.error(message.channel, 'Bio too long. It must be less than or equal to 256 characters.'); + await member.updateOne({ + additional: { + ...member.additional, + bio, + }, + }); + return message.addReaction('modSuccess:578750988907970567'); + } +} diff --git a/src/commands/profile_github.ts b/src/commands/profile_github.ts new file mode 100644 index 0000000..7989140 --- /dev/null +++ b/src/commands/profile_github.ts @@ -0,0 +1,47 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Profile_GitHub extends Command { + constructor(client: Client) { + super(client); + this.name = 'github'; + this.description = 'Updates your GitHub information on your profile.'; + this.usage = `${this.client.config.prefix}github `; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + if (!await this.client.db.Member.exists({ userID: message.author.id })) { + await this.client.db.Member.create({ userID: message.author.id }); + } + const member = await this.client.db.Member.findOne({ userID: message.author.id }); + if (!args[0]) { + await member.updateOne({ + additional: { + ...member.additional, + github: null, + }, + }); + return message.addReaction('modSuccess:578750988907970567'); + } + const urlRegex = new RegExp( + '^(https?:\\/\\/)?' + + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + + '((\\d{1,3}\\.){3}\\d{1,3}))' + + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + + '(\\?[;&a-z\\d%_.~+=-]*)?' + + '(\\#[-a-z\\d_]*)?$', + 'i', + ); + if (!urlRegex.test(args[0]) || !args[0].startsWith('https://github.com/')) return this.error(message.channel, 'Invalid GitHub profile URL.'); + + await member.updateOne({ + additional: { + ...member.additional, + github: args[0], + }, + }); + return message.addReaction('modSuccess:578750988907970567'); + } +} diff --git a/src/commands/profile_gitlab.ts b/src/commands/profile_gitlab.ts new file mode 100644 index 0000000..7cb84eb --- /dev/null +++ b/src/commands/profile_gitlab.ts @@ -0,0 +1,48 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Profile_GitLab extends Command { + constructor(client: Client) { + super(client); + this.name = 'gitlab'; + this.description = 'Updates your GitLab information on your profile.'; + this.usage = `${this.client.config.prefix}gitlab `; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + if (!await this.client.db.Member.exists({ userID: message.author.id })) { + await this.client.db.Member.create({ userID: message.author.id }); + } + if (!args[0]) return this.error(message.channel, 'No GitLab profile URL was provided.'); + const member = await this.client.db.Member.findOne({ userID: message.author.id }); + if (!args[0]) { + await member.updateOne({ + additional: { + ...member.additional, + gitlab: null, + }, + }); + return message.addReaction('modSuccess:578750988907970567'); + } + const urlRegex = new RegExp( + '^(https?:\\/\\/)?' + + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + + '((\\d{1,3}\\.){3}\\d{1,3}))' + + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + + '(\\?[;&a-z\\d%_.~+=-]*)?' + + '(\\#[-a-z\\d_]*)?$', + 'i', + ); + if (!urlRegex.test(args[0])) return this.error(message.channel, 'Invalid GitLab profile URL.'); + + await member.updateOne({ + additional: { + ...member.additional, + gitlab: args[0], + }, + }); + return message.addReaction('modSuccess:578750988907970567'); + } +} diff --git a/src/commands/pulldata.ts b/src/commands/pulldata.ts new file mode 100644 index 0000000..a0abc3f --- /dev/null +++ b/src/commands/pulldata.ts @@ -0,0 +1,97 @@ +/* eslint-disable no-continue */ +/* eslint-disable default-case */ +import { Message, TextChannel } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; +import { getTotalMessageCount } from '../intervals/score'; + +export default class Score extends Command { + constructor(client: Client) { + super(client); + this.name = 'pulldata'; + this.description = 'Retrieves information about a hard inquiry that was performed on a member.'; + this.usage = `${this.client.config.prefix}pulldata `; + this.aliases = ['inq']; + this.permissions = 5; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[1]) return this.client.commands.get('help').run(message, [this.name]); + + const member = this.client.util.resolveMember(args[0], this.mainGuild); + if (!member) return this.error(message.channel, 'Could not locate member.'); + const score = await this.client.db.Score.findOne({ userID: member.id }); + if (!score) return this.error(message.channel, 'Could not find Community Report for this user.'); + const report = score.inquiries.find((inq) => inq.id === args[1]); + if (!report) return this.error(message.channel, 'Could not find inquiry information.'); + + await this.client.report.createInquiry(member.id, 'Library of Code sp-us | Bureau of Community Reports', 1); + + const embed = new RichEmbed(); + embed.setTitle(`Hard Inquiry Information - ${report.id}`); + embed.setAuthor(member.username, member.user.avatarURL); + let currentScore = '0'; + if (score.total < 200) currentScore = '---'; + else if (score.total > 800) currentScore = '800'; + else currentScore = `${score.total}`; + embed.setDescription(`**Current Community Score:** ${currentScore}\n\n**Department/Service:** ${report.name || 'N/A'}\n**Reason:** ${report.reason || 'N/A'}`); + + let totalScore = '0'; + let activityScore = '0'; + let moderationScore = '0'; + let roleScore = '0'; + let cloudServicesScore = '0'; + let otherScore = '0'; + let miscScore = '0'; + + if (score.total < 200) totalScore = '---'; + else if (score.total > 800) totalScore = '800'; + else totalScore = `${score.total}`; + + if (score.activity < 10) activityScore = '---'; + else if (score.activity > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityScore = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))); + else activityScore = `${score.activity}`; + + if (score.roles <= 0) roleScore = '---'; + else if (score.roles > 54) roleScore = '54'; + else roleScore = `${score.roles}`; + + moderationScore = `${score.moderation}`; + + if (score.other === 0) otherScore = '---'; + else otherScore = `${score.other}`; + + if (score.staff <= 0) miscScore = '---'; + else miscScore = `${score.staff}`; + + if (score.cloudServices === 0) cloudServicesScore = '---'; + else if (score.cloudServices > 10) cloudServicesScore = '10'; + else cloudServicesScore = `${score.cloudServices}`; + + let color = '🔴'; + let additionalText = 'POOR'; + embed.setColor('FF0000'); + if (score.total >= 550) { color = '🟠'; additionalText = 'FAIR'; embed.setColor('FFA500'); } + if (score.total >= 630) { color = '🟡'; additionalText = 'GOOD'; embed.setColor('FFFF00'); } + if (score.total >= 700) { color = '🟢'; additionalText = 'EXCELLENT'; embed.setColor('66FF66'); } + if (score.total >= 770) { color = '✨'; additionalText = 'EXCEPTIONAL'; embed.setColor('#99FFFF'); } + embed.addField('Total | 200 to 800', score ? `${color} ${totalScore} | ${additionalText}` : 'N/C', true); + embed.addField(`Activity | 10 to ${Math.floor(Math.log1p(getTotalMessageCount(this.client)) * 12)}`, activityScore || 'N/C', true); + embed.addField('Roles | 1 to N/A', roleScore || 'N/C', true); + embed.addField('Moderation | N/A to 2' || 'N/C', moderationScore, true); + embed.addField('Cloud Services | N/A to 10+', cloudServicesScore || 'N/C', true); + embed.addField('Other', otherScore || 'N/C', true); + embed.addField('Misc', miscScore || 'N/C', true); + if (score.pin?.length > 0) { + embed.addField('PIN', score.pin.join('-'), true); + } + embed.setTimestamp(report.date); + embed.setFooter('Inquiry performed on', this.client.user.avatarURL); + return message.channel.createMessage({ embed }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/rank.ts b/src/commands/rank.ts new file mode 100644 index 0000000..74ca6cd --- /dev/null +++ b/src/commands/rank.ts @@ -0,0 +1,73 @@ +import { Message, Role } from 'eris'; +import { createPaginationEmbed } from 'eris-pagination'; +import { Client, Command, RichEmbed } from '../class'; + +export default class Rank extends Command { + constructor(client: Client) { + super(client); + this.name = 'rank'; + this.description = 'Joins/leaves a self-assignable rank. Run this command without arguments to get a list of self-assignable roles.'; + this.usage = `${this.client.config.prefix}rank `; + this.permissions = 0; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) { + const roles = await this.client.db.Rank.find(); + const rankArray: [{ name: string, value: string }?] = []; + for (const rank of roles.sort((a, b) => a.name.localeCompare(b.name))) { + let perms: string; + if (rank.permissions.includes('0')) { + perms = 'Everyone'; + } else { + const rolesArray: Role[] = []; + rank.permissions.forEach((r) => { + rolesArray.push(this.mainGuild.roles.get(r)); + }); + perms = rolesArray.map((r) => this.mainGuild.roles.get(r.id)).sort((a, b) => b.position - a.position).map((r) => `<@&${r.id}>`).join(', '); + } + let hasRank = false; + if (message.member.roles.includes(rank.roleID)) hasRank = true; + rankArray.push({ name: rank.name, value: `${hasRank ? '*You have this role.*\n' : ''}__Description:__ ${rank.description}\n__Permissions:__ ${perms}` }); + } + const ranksSplit = this.client.util.splitFields(rankArray); + const cmdPages: RichEmbed[] = []; + ranksSplit.forEach((split) => { + const embed = new RichEmbed(); + embed.setTitle('Ranks'); + embed.setDescription(`Use \`${this.client.config.prefix}rank \` to join/leave the rank.`); + embed.setFooter(`Requested by: ${message.author.username}#${message.author.discriminator} | ${this.client.user.username}`, message.author.avatarURL); + embed.setTimestamp(); + split.forEach((c) => embed.addField(c.name, c.value)); + return cmdPages.push(embed); + }); + if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] }); + return createPaginationEmbed(message, cmdPages); + } + const role = this.client.util.resolveRole(args.join(' '), this.client.guilds.get(this.client.config.guildID)); + if (!role) return this.error(message.channel, 'The role you specified doesn\'t exist.'); + const entry = await this.client.db.Rank.findOne({ roleID: role.id }).lean().exec(); + if (!entry) return this.error(message.channel, 'The rank you specified doesn\'t exist.'); + if (!message.member.roles.includes(entry.roleID)) { + let permCheck: boolean; + if (entry.permissions.includes('0')) { + permCheck = true; + } else { + permCheck = entry.permissions.some((item) => message.member.roles.includes(item)); + } + if (!permCheck) return this.error(message.channel, 'Permission denied.'); + await message.member.addRole(entry.roleID, 'User self-assigned this role.'); + this.success(message.channel, `You have self-assigned ${entry.name}.`); + } else if (message.member.roles.includes(entry.roleID)) { + await message.member.removeRole(entry.roleID, 'User has removed a self-assignable role.'); + this.success(message.channel, `You have removed ${entry.name}.`); + } + return null; + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/role.ts b/src/commands/role.ts new file mode 100644 index 0000000..f8031b6 --- /dev/null +++ b/src/commands/role.ts @@ -0,0 +1,73 @@ +import { Message, Role as DRole } from 'eris'; +import { Client, Command } from '../class'; + +export default class Role extends Command { + constructor(client: Client) { + super(client); + this.name = 'role'; + this.description = 'Manage the roles of a member.'; + this.usage = `${this.client.config.prefix}role `; + this.permissions = 6; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (args.length < 2) return this.client.commands.get('help').run(message, [this.name]); + const member = this.client.util.resolveMember(args[0], this.mainGuild); + if (!member) return this.error(message.channel, 'Member not found'); + // if (!this.client.util.moderation.checkPermissions(member, message.member)) return this.error(message.channel, 'Permission denied.'); + const rolesList = args.slice(1).join(' ').split(', '); + const rolesToAdd = []; + const rolesToRemove = []; + let stop = false; + for (const arg of rolesList) { + const action = arg[0]; + let role: DRole; + if (action !== '+' && action !== '-') { + role = this.client.util.resolveRole(arg, this.mainGuild); + if (!role) { + stop = true; + return this.error(message.channel, `Role \`${arg}\` not found.`); + } + if (member.roles.includes(role.id)) return rolesToRemove.push(role); + rolesToAdd.push(role); + continue; + } + if (action === '+') { + role = this.client.util.resolveRole(arg.slice(1), this.mainGuild); + if (!role) { + stop = true; + return this.error(message.channel, `Role \`${arg.slice(1)}\` not found.`); + } + if (member.roles.includes(role.id)) { + stop = true; + return this.error(message.channel, `You already have the role \`${role.name}\`.`); + } + rolesToAdd.push(role); + continue; + } + if (action === '-') { + role = this.client.util.resolveRole(arg.slice(1), this.mainGuild); + if (!role) { + stop = true; + return this.error(message.channel, `Role \`${arg.slice(1)}\` not found.`); + } + if (!member.roles.includes(role.id)) { + stop = true; + return this.error(message.channel, `You don't have the role \`${role.name}\``); + } + rolesToRemove.push(role); + continue; + } + } + // eslint-disable-next-line + // if (stop) return; + rolesToAdd.forEach((role) => member.addRole(role.id)); + rolesToRemove.forEach((role) => member.removeRole(role.id)); + return this.success(message.channel, `Changed the roles for ${member.username}#${member.discriminator}${rolesToAdd.length > 0 ? `, added \`${rolesToAdd.map((r) => r.name).join('`, `')}\`` : ''}${rolesToRemove.length > 0 ? `, removed \`${rolesToRemove.map((r) => r.name).join('`, `')}\`` : ''}`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/roleinfo.ts b/src/commands/roleinfo.ts new file mode 100644 index 0000000..8b39e46 --- /dev/null +++ b/src/commands/roleinfo.ts @@ -0,0 +1,57 @@ +import moment from 'moment'; +import { Message, Role } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class Roleinfo extends Command { + constructor(client: Client) { + super(client); + this.name = 'roleinfo'; + this.description = 'Get information about a role.'; + this.usage = 'roleinfo [role ID or role name]'; + this.permissions = 0; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + + const role = this.client.util.resolveRole(args.join(' '), this.mainGuild); + if (!role) return this.error(message.channel, 'Could not find role.'); + + const perms = role.permissions; + const permsArray: string[] = []; + if (perms.has('administrator')) permsArray.push('Administrator'); + if (perms.has('manageGuild')) permsArray.push('Manage Server'); + if (perms.has('manageChannels')) permsArray.push('Manage Channels'); + if (perms.has('manageRoles')) permsArray.push('Manage Roles'); + if (perms.has('manageMessages')) permsArray.push('Manage Messages'); + if (perms.has('manageNicknames')) permsArray.push('Manage Nicknames'); + if (perms.has('manageEmojis')) permsArray.push('Manage Emojis'); + if (perms.has('banMembers')) permsArray.push('Ban Members'); + if (perms.has('kickMembers')) permsArray.push('Kick Members'); + + const embed = new RichEmbed(); + embed.setTitle('Role Information'); + embed.setDescription(`<@&${role.id}> ID: \`${role.id}\``); + embed.setColor(role.color); + embed.addField('Name', role.name, true); + embed.addField('Color', role.color ? this.client.util.decimalToHex(role.color) : 'None', true); + embed.addField('Members', String(this.client.guilds.get(this.client.config.guildID).members.filter((m) => m.roles.includes(role.id)).length), true); + embed.addField('Hoisted', role.hoist ? 'Yes' : 'No', true); + embed.addField('Position', String(role.position), true); + embed.addField('Creation Date', `${moment(new Date(role.createdAt)).format('dddd, MMMM Do YYYY, h:mm:ss A')} ET`, true); + embed.addField('Mentionable', role.mentionable ? 'Yes' : 'No', true); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + + if (permsArray.length > 0) { + embed.addField('Permissions', permsArray.join(', '), true); + } + return message.channel.createMessage({ embed }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/score.ts b/src/commands/score.ts new file mode 100644 index 0000000..e91c7b8 --- /dev/null +++ b/src/commands/score.ts @@ -0,0 +1,183 @@ +/* eslint-disable no-plusplus */ +/* eslint-disable no-continue */ +/* eslint-disable default-case */ +import moment from 'moment'; +import { Message, User } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; +import { getTotalMessageCount } from '../intervals/score'; +import Score_Hist from './score_hist'; +import Score_Notify from './score_notify'; +import Score_Pref from './score_pref'; + +export default class Score extends Command { + constructor(client: Client) { + super(client); + this.name = 'score'; + this.description = 'Retrieves your Community Report'; + this.usage = `${this.client.config.prefix}score\n${this.client.config.prefix}score :`; + this.aliases = ['report']; + this.subcmds = [Score_Hist, Score_Notify, Score_Pref]; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + let check = false; + let user: User; + if (!args[0] || !this.checkCustomPermissions(this.client.util.resolveMember(message.author.id, this.mainGuild), 4)) { + user = message.author; + if (!user) return this.error(message.channel, 'Member not found.'); + await this.client.report.createInquiry(user.id, `${user.username} via Discord`, 1); + check = true; + } else { + user = this.client.util.resolveMember(args[0], this.mainGuild)?.user; + if (!user) { + const sc = await this.client.db.Score.findOne({ pin: [Number(args[0].split('-')[0]), Number(args[0].split('-')[1]), Number(args[0].split('-')[2])] }); + if (!sc) return this.error(message.channel, 'Member not found.'); + user = this.client.util.resolveMember(sc.userID, this.mainGuild)?.user; + } + if (!user) return this.error(message.channel, 'Member not found.'); + if (message.channel.type !== 0) return this.error(message.channel, 'Hard Inquiries must be initiated in a guild.'); + if (args[1] === 'hard') { + if (args.length < 3) return this.client.commands.get('help').run(message, [this.name]); + const name = args.slice(2).join(' ').split(':')[0]; + const reason = args.slice(2).join(' ').split(':')[1]; + const score = await this.client.db.Score.findOne({ userID: user.id }); + if (!score) return this.error(message.channel, 'Score not calculated yet.'); + if (score.locked) return this.error(message.channel, 'The score report you have requested has been locked.'); + await this.client.report.createInquiry(score.userID, name, 0, reason); + } + } + if (!user) return this.error(message.channel, 'Member not found.'); + const score = await this.client.db.Score.findOne({ userID: user.id }); + const inqs = await this.client.db.Inquiry.find({ userID: user.id, type: 0 }); + if (!score) return this.error(message.channel, 'Community Report has not been generated yet.'); + let totalScore = '0'; + let activityScore = '0'; + let moderationScore = '0'; + let roleScore = '0'; + let cloudServicesScore = '0'; + let otherScore = '0'; + let miscScore = '0'; + + if (score) { + if (score.total < 200) totalScore = '---'; + else if (score.total > 800) totalScore = '800'; + else totalScore = `${score.total}`; + + if (score.activity < 10) activityScore = '---'; + else if (score.activity > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityScore = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))); + else activityScore = `${score.activity}`; + + if (score.roles <= 0) roleScore = '---'; + else if (score.roles > 54) roleScore = '54'; + else roleScore = `${score.roles}`; + + moderationScore = `${score.moderation}`; + + if (score.other === 0) otherScore = '---'; + else otherScore = `${score.other}`; + + if (score.staff <= 0) miscScore = '---'; + else miscScore = `${score.staff}`; + + if (score.cloudServices === 0) cloudServicesScore = '---'; + else if (score.cloudServices > 10) cloudServicesScore = '10'; + else cloudServicesScore = `${score.cloudServices}`; + } // else return this.error(message.channel, 'Community Score has not been calculated yet.'); + + const set = []; + const accounts = await this.client.db.Score.find().lean().exec(); + for (const sc of accounts) { + if (sc.total < 200) { continue; } + if (sc.total > 800) { set.push(800); continue; } + set.push(sc.total); + } + const percentile = this.client.util.percentile(set, score.total); + const embed = new RichEmbed(); + embed.setTitle('Community Report'); + embed.setAuthor(user.username, user.avatarURL); + embed.setThumbnail(user.avatarURL); + /* for (const role of member.roles.map((r) => this.mainGuild.roles.get(r)).sort((a, b) => b.position - a.position)) { + if (role?.color !== 0) { + embed.setColor(role.color); + break; + } + } */ + let inqAdded = 0; + if (inqs?.length > 0) { + let desc = '__**Hard Inquiries**__\n*These inquiries will fall off your report within 2 months, try to keep your hard inquiries to a minimum. If you want to file a dispute, please DM Ramirez.*\n\n'; + inqs.forEach((inq) => { + const testDate = (new Date(new Date(inq.date).setHours(1460))); + // eslint-disable-next-line no-useless-escape + if (testDate > new Date()) { + desc += `${inq.iid ? `__[${inq.iid}]__\n` : ''}**Department/Service:** ${inq.name.replace(/\*/gmi, '')}\n**Reason:** ${inq.reason}\n**Date:** ${moment(inq.date).format('MMMM Do YYYY, h:mm:ss')}\n**Expires:** ${moment(testDate).calendar()}\n\n`; + inqAdded++; + } + }); + if (inqAdded <= 0) desc = ''; + if (desc.length >= 1900) { + embed.setDescription('__***Too many Hard Inquiries to show, please see https://report.libraryofcode.org***__'); + } else { + embed.setDescription(desc); + } + } + let color = '🔴'; + let additionalText = 'POOR'; + embed.setColor('FF0000'); + if (score.total >= 550) { color = '🟠'; additionalText = 'FAIR'; embed.setColor('FFA500'); } + if (score.total >= 630) { color = '🟡'; additionalText = 'GOOD'; embed.setColor('FFFF00'); } + if (score.total >= 700) { color = '🟢'; additionalText = 'EXCELLENT'; embed.setColor('66FF66'); } + if (score.total >= 770) { color = '✨'; additionalText = 'EXCEPTIONAL'; embed.setColor('#99FFFF'); } + embed.addField('CommScore™ | 200 to 800', score ? `${color} ${totalScore} | ${additionalText} | ${this.client.util.ordinal(Math.round(percentile))} Percentile` : 'N/C', true); + embed.addField(`Activity | 10 to ${Math.floor(Math.log1p(getTotalMessageCount(this.client)) * 12)}`, activityScore || 'N/C', true); + embed.addField('Roles | 1 to N/A', roleScore || 'N/C', true); + embed.addField('Moderation | N/A to 2' || 'N/C', moderationScore, true); + embed.addField('Cloud Services | N/A to 10+', cloudServicesScore || 'N/C', true); + embed.addField('Other', otherScore || 'N/C', true); + embed.addField('Misc', miscScore || 'N/C', true); + if (score.locked) { + embed.addField('Status', 'Score Report Locked'); + } + if (score.pin?.length > 0 && message.channel.type === 1) { + embed.addField('PIN', score.pin.join('-'), true); + } + if (score.lastUpdate) { + embed.setFooter('Report last updated', this.client.user.avatarURL); + embed.setTimestamp(score.lastUpdate); + } else { + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + } + // eslint-disable-next-line no-mixed-operators + if ((args[1] === 'hard' || args[1] === 'soft') && this.checkCustomPermissions(this.client.util.resolveMember(message.author.id, this.mainGuild), 4)) { + if (score.pin?.length > 0) embed.addField('PIN', score.pin.join('-'), true); + + if (args[1] === 'soft' && !check) { + let name = ''; + for (const role of this.client.util.resolveMember(message.author.id, this.mainGuild).roles.map((r) => this.mainGuild.roles.get(r)).sort((a, b) => b.position - a.position)) { + name = `Library of Code sp-us | ${role.name}`; + break; + } + await this.client.report.createInquiry(user.id, name, 1); + } + } + if (args[1] === 'hard' && this.checkCustomPermissions(this.client.util.resolveMember(message.author.id, this.mainGuild), 6)) { + await message.channel.createMessage({ embed }); + await message.channel.createMessage('***===BEGIN ADDITIONAL INFORMATION===***'); + await this.client.commands.get('score').subcommands.get('hist').run(message, [user.id]); + await this.client.commands.get('whois').run(message, [user.id]); + await this.client.commands.get('notes').run(message, [user.id]); + const whoisMessage = await message.channel.createMessage(`=whois ${user.id} --full`); + await whoisMessage.delete(); + const modlogsMessage = await message.channel.createMessage(`=modlogs ${user.id}`); + await modlogsMessage.delete(); + return await message.channel.createMessage('***===END ADDITIONAL INFORMATION===***'); + } + return message.channel.createMessage({ embed }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/score_hist.ts b/src/commands/score_hist.ts new file mode 100644 index 0000000..f14afe8 --- /dev/null +++ b/src/commands/score_hist.ts @@ -0,0 +1,334 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-continue */ +/* eslint-disable default-case */ +import moment from 'moment'; +import { median, mode, mean } from 'mathjs'; +import { createPaginationEmbed } from 'eris-pagination'; +import { Message, User, TextChannel } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; +import { getTotalMessageCount } from '../intervals/score'; +import { InquiryInterface } from '../models'; + +export default class Score_Hist extends Command { + constructor(client: Client) { + super(client); + this.name = 'hist'; + this.description = 'Pulls your Community Report history.'; + this.usage = `${this.client.config.prefix}score hist \n${this.client.config.prefix}score hist`; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + let user: User; + + if (!args[0] || !this.checkCustomPermissions(this.client.util.resolveMember(message.author.id, this.mainGuild), 4)) { + user = message.author; + if (!user) return this.error(message.channel, 'Member not found.'); + const hists = await this.client.db.ScoreHistorical.find({ userID: user.id }).lean().exec(); + if (!hists) return this.error(message.channel, 'No history found.'); + if (hists.length < 1) return this.error(message.channel, 'No history found.'); + const histArray: [{ name: string, value: string }?] = []; + const totalArray: number[] = []; + const activityArray: number[] = []; + const moderationArray: number[] = []; + const roleArray: number[] = []; + const cloudServicesArray: number[] = []; + const otherArray: number[] = []; + const miscArray: number[] = []; + for (const hist of hists.reverse()) { + totalArray.push(hist.report.total); + activityArray.push(hist.report.activity); + moderationArray.push(hist.report.moderation); + roleArray.push(hist.report.roles); + cloudServicesArray.push(hist.report.cloudServices); + otherArray.push(hist.report.other); + miscArray.push(hist.report.staff); + let totalScore = '0'; + let activityScore = '0'; + let moderationScore = '0'; + let roleScore = '0'; + let cloudServicesScore = '0'; + let otherScore = '0'; + let miscScore = '0'; + + if (hist.report.total < 200) totalScore = '---'; + else if (hist.report.total > 800) totalScore = '800'; + else totalScore = `${hist.report.total}`; + + if (hist.report.activity < 10) activityScore = '---'; + else if (hist.report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityScore = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))); + else activityScore = `${hist.report.activity}`; + + if (hist.report.roles <= 0) roleScore = '---'; + else if (hist.report.roles > 54) roleScore = '54'; + else roleScore = `${hist.report.roles}`; + + moderationScore = `${hist.report.moderation}`; + + if (hist.report.other === 0) otherScore = '---'; + else otherScore = `${hist.report.other}`; + + if (hist.report.staff <= 0) miscScore = '---'; + else miscScore = `${hist.report.staff}`; + + if (hist.report.cloudServices === 0) cloudServicesScore = '---'; + else if (hist.report.cloudServices > 10) cloudServicesScore = '10'; + else cloudServicesScore = `${hist.report.cloudServices}`; + + let data = ''; + let hardInquiries = 0; + const inquiries: InquiryInterface[] = []; + if (hist.inquiries?.length > 0) { + for (const h of hist.inquiries) { + const inq = await this.client.db.Inquiry.findOne({ _id: h }); + inquiries.push(inq); + } + } + if (inquiries?.length > 0) { + inquiries.forEach((inq) => { + const testDate = (new Date(new Date(inq.date).setHours(1460))); + // eslint-disable-next-line no-plusplus + if (testDate > new Date()) hardInquiries++; + }); + data += `__CommScore™:__ ${totalScore}\n__Activity:__ ${activityScore}\n__Roles:__ ${roleScore}\n__Moderation:__ ${moderationScore}\n__Cloud Services:__ ${cloudServicesScore}\n__Other:__ ${otherScore}\n__Misc:__ ${miscScore}\n\n__Hard Inquiries:__ ${hardInquiries}\n__Soft Inquiries:__ ${hist.report.softInquiries?.length ?? '0'}`; + histArray.push({ name: moment(hist.date).calendar(), value: data }); + } + } + + const stat = { + totalMean: mean(totalArray), + totalMode: mode(totalArray), + totalMedian: median(totalArray), + activityMean: mean(activityArray), + rolesMean: mean(roleArray), + moderationMean: mean(moderationArray), + cloudServicesMean: mean(cloudServicesArray), + otherMean: mean(otherArray), + miscMean: mean(miscArray), + }; + const splitHist = this.client.util.splitFields(histArray); + const cmdPages: RichEmbed[] = []; + splitHist.forEach((split) => { + const embed = new RichEmbed(); + embed.setTitle('Historical Community Report'); + let totalMean = '0'; + let totalMedian = '0'; + let totalMode = '0'; + let activityMean = '0'; + let moderationMean = '0'; + let roleMean = '0'; + let cloudServicesMean = '0'; + let otherMean = '0'; + let miscMean = '0'; + + if (stat.totalMean < 200) totalMean = '---'; + else if (stat.totalMean > 800) totalMean = '800'; + else totalMean = `${stat.totalMean}`; + + if (stat.totalMedian < 200) totalMedian = '---'; + else if (stat.totalMedian > 800) totalMedian = '800'; + else totalMedian = `${stat.totalMedian}`; + + if (stat.totalMode < 200) totalMode = '---'; + else if (stat.totalMode > 800) totalMode = '800'; + else totalMode = `${stat.totalMode}`; + + if (stat.activityMean < 10) activityMean = '---'; + else if (stat.activityMean > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityMean = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))); + else activityMean = `${stat.activityMean}`; + + if (stat.rolesMean <= 0) roleMean = '---'; + else if (stat.rolesMean > 54) roleMean = '54'; + else roleMean = `${stat.rolesMean}`; + + moderationMean = `${stat.moderationMean}`; + + if (stat.otherMean === 0) otherMean = '---'; + else otherMean = `${stat.otherMean}`; + + if (stat.miscMean <= 0) miscMean = '---'; + else miscMean = `${stat.miscMean}`; + + if (stat.cloudServicesMean === 0) cloudServicesMean = '---'; + else if (stat.cloudServicesMean > 10) cloudServicesMean = '10'; + else cloudServicesMean = `${stat.cloudServicesMean}`; + + embed.setDescription(`__**Statistical Averages**__\n**CommScore™ Mean:** ${totalMean} | **CommScore™ Mode:** ${totalMode} | **CommScore™ Median:** ${totalMedian}\n\n**Activity Mean:** ${activityMean}\n**Roles Mean:** ${roleMean}\n**Moderation Mean:** ${moderationMean}\n**Cloud Services Mean:** ${cloudServicesMean}\n**Other Mean:** ${otherMean}\n**Misc Mean:** ${miscMean}`); + embed.setAuthor(user.username, user.avatarURL); + embed.setThumbnail(user.avatarURL); + embed.setTimestamp(); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + split.forEach((c) => embed.addField(c.name, c.value)); + return cmdPages.push(embed); + }); + const name = `${user.username} VIA DISCORD - [HISTORICAL]`; + await this.client.report.createInquiry(user.id, name, 1); + + + if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] }); + return createPaginationEmbed(message, cmdPages); + } if (args[0] && this.checkCustomPermissions(this.client.util.resolveMember(message.author.id, this.mainGuild), 4)) { + user = this.client.util.resolveMember(args[0], this.mainGuild)?.user; + if (!user) { + const sc = await this.client.db.Score.findOne({ pin: [Number(args[0].split('-')[0]), Number(args[0].split('-')[1]), Number(args[0].split('-')[2])] }); + user = this.client.util.resolveMember(sc.userID, this.mainGuild)?.user; + } + if (!user) return this.error(message.channel, 'Member not found.'); + const hists = await this.client.db.ScoreHistorical.find({ userID: user.id }).lean().exec(); + if (!hists) return this.error(message.channel, 'No history found.'); + if (hists.length < 1) return this.error(message.channel, 'No history found.'); + const histArray: [{ name: string, value: string }?] = []; + const totalArray: number[] = []; + const activityArray: number[] = []; + const moderationArray: number[] = []; + const roleArray: number[] = []; + const cloudServicesArray: number[] = []; + const otherArray: number[] = []; + const miscArray: number[] = []; + for (const hist of hists.reverse()) { + totalArray.push(hist.report.total); + activityArray.push(hist.report.activity); + moderationArray.push(hist.report.moderation); + roleArray.push(hist.report.roles); + cloudServicesArray.push(hist.report.cloudServices); + otherArray.push(hist.report.other); + miscArray.push(hist.report.staff); + let totalScore = '0'; + let activityScore = '0'; + let moderationScore = '0'; + let roleScore = '0'; + let cloudServicesScore = '0'; + let otherScore = '0'; + let miscScore = '0'; + + if (hist.report.total < 200) totalScore = '---'; + else if (hist.report.total > 800) totalScore = '800'; + else totalScore = `${hist.report.total}`; + + if (hist.report.activity < 10) activityScore = '---'; + else if (hist.report.activity > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityScore = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))); + else activityScore = `${hist.report.activity}`; + + if (hist.report.roles <= 0) roleScore = '---'; + else if (hist.report.roles > 54) roleScore = '54'; + else roleScore = `${hist.report.roles}`; + + moderationScore = `${hist.report.moderation}`; + + if (hist.report.other === 0) otherScore = '---'; + else otherScore = `${hist.report.other}`; + + if (hist.report.staff <= 0) miscScore = '---'; + else miscScore = `${hist.report.staff}`; + + if (hist.report.cloudServices === 0) cloudServicesScore = '---'; + else if (hist.report.cloudServices > 10) cloudServicesScore = '10'; + else cloudServicesScore = `${hist.report.cloudServices}`; + + let data = ''; + let hardInquiries = 0; + const inquiries: InquiryInterface[] = []; + if (hist.inquiries?.length > 0) { + for (const h of hist.inquiries) { + const inq = await this.client.db.Inquiry.findOne({ _id: h }); + inquiries.push(inq); + } + } + if (inquiries?.length > 0) { + inquiries.forEach((inq) => { + const testDate = (new Date(new Date(inq.date).setHours(1460))); + // eslint-disable-next-line no-plusplus + if (testDate > new Date()) hardInquiries++; + }); + data += `__CommScore™:__ ${totalScore}\n__Activity:__ ${activityScore}\n__Roles:__ ${roleScore}\n__Moderation:__ ${moderationScore}\n__Cloud Services:__ ${cloudServicesScore}\n__Other:__ ${otherScore}\n__Misc:__ ${miscScore}\n\n__Hard Inquiries:__ ${hardInquiries}\n__Soft Inquiries:__ ${hist.report.softInquiries?.length ?? '0'}`; + histArray.push({ name: moment(hist.date).calendar(), value: data }); + } + } + + const stat = { + totalMean: mean(totalArray), + totalMode: mode(totalArray), + totalMedian: median(totalArray), + activityMean: mean(activityArray), + rolesMean: mean(roleArray), + moderationMean: mean(moderationArray), + cloudServicesMean: mean(cloudServicesArray), + otherMean: mean(otherArray), + miscMean: mean(miscArray), + }; + const splitHist = this.client.util.splitFields(histArray); + const cmdPages: RichEmbed[] = []; + splitHist.forEach((split) => { + const embed = new RichEmbed(); + embed.setTitle('Historical Community Report'); + let totalMean = '0'; + let totalMedian = '0'; + let totalMode = '0'; + let activityMean = '0'; + let moderationMean = '0'; + let roleMean = '0'; + let cloudServicesMean = '0'; + let otherMean = '0'; + let miscMean = '0'; + + if (stat.totalMean < 200) totalMean = '---'; + else if (stat.totalMean > 800) totalMean = '800'; + else totalMean = `${stat.totalMean}`; + + if (stat.totalMedian < 200) totalMedian = '---'; + else if (stat.totalMedian > 800) totalMedian = '800'; + else totalMedian = `${stat.totalMedian}`; + + if (stat.totalMode < 200) totalMode = '---'; + else if (stat.totalMode > 800) totalMode = '800'; + else totalMode = `${stat.totalMode}`; + + if (stat.activityMean < 10) activityMean = '---'; + else if (stat.activityMean > Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))) activityMean = String(Math.floor((Math.log1p(getTotalMessageCount(this.client)) * 12))); + else activityMean = `${stat.activityMean}`; + + if (stat.rolesMean <= 0) roleMean = '---'; + else if (stat.rolesMean > 54) roleMean = '54'; + else roleMean = `${stat.rolesMean}`; + + moderationMean = `${stat.moderationMean}`; + + if (stat.otherMean === 0) otherMean = '---'; + else otherMean = `${stat.otherMean}`; + + if (stat.miscMean <= 0) miscMean = '---'; + else miscMean = `${stat.miscMean}`; + + if (stat.cloudServicesMean === 0) cloudServicesMean = '---'; + else if (stat.cloudServicesMean > 10) cloudServicesMean = '10'; + else cloudServicesMean = `${stat.cloudServicesMean}`; + + embed.setDescription(`__**Statistical Averages**__\n**CommScore™ Mean:** ${totalMean} | **CommScore™ Mode:** ${totalMode} | **CommScore™ Median:** ${totalMedian}\n\n**Activity Mean:** ${activityMean}\n**Roles Mean:** ${roleMean}\n**Moderation Mean:** ${moderationMean}\n**Cloud Services Mean:** ${cloudServicesMean}\n**Other Mean:** ${otherMean}\n**Misc Mean:** ${miscMean}`); + embed.setAuthor(user.username, user.avatarURL); + embed.setThumbnail(user.avatarURL); + embed.setTimestamp(); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + split.forEach((c) => embed.addField(c.name, c.value)); + return cmdPages.push(embed); + }); + + let name = ''; + for (const role of this.client.util.resolveMember(message.author.id, this.mainGuild).roles.map((r) => this.mainGuild.roles.get(r)).sort((a, b) => b.position - a.position)) { + name = `Library of Code sp-us | ${role.name} - [HISTORICAL]`; + break; + } + await this.client.report.createInquiry(user.id, name, 1); + + + if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] }); + return createPaginationEmbed(message, cmdPages); + } + return null; + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/score_notify.ts b/src/commands/score_notify.ts new file mode 100644 index 0000000..4538e7f --- /dev/null +++ b/src/commands/score_notify.ts @@ -0,0 +1,36 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Score_Notify extends Command { + constructor(client: Client) { + super(client); + this.name = 'notify'; + this.description = 'Edits your notification preferences for Hard Inquiries.'; + this.usage = `${this.client.config.prefix}score notify `; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + const user = message.author; + if (!user) return this.error(message.channel, 'Member not found.'); + const score = await this.client.db.Score.findOne({ userID: message.author.id }); + if (!score) return this.error(message.channel, 'Score not calculated yet.'); + if (!score.notify) await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { notify: false } }); + switch (args[0]) { + case 'on': + await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { notify: true } }); + return this.success(message.channel, 'You will now be sent notifications whenever your score is hard-pulled.'); + case 'off': + await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { notify: false } }); + return this.success(message.channel, 'You will no longer be sent notifications when your score is hard-pulled.'); + default: + return this.error(message.channel, 'Invalid option. Valid options are `on` and `off`.'); + } + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/score_pref.ts b/src/commands/score_pref.ts new file mode 100644 index 0000000..a0c2e3d --- /dev/null +++ b/src/commands/score_pref.ts @@ -0,0 +1,35 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Score_Pref extends Command { + constructor(client: Client) { + super(client); + this.name = 'pref'; + this.description = 'Locks or unlocks your Community Report.'; + this.usage = `${this.client.config.prefix}score pref `; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!message.author) return this.error(message.channel, 'Member not found.'); + const score = await this.client.db.Score.findOne({ userID: message.author.id }); + if (!score) return this.error(message.channel, 'Score not calculated yet.'); + if (!score.locked) await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { locked: false } }); + switch (args[0]) { + case 'lock': + await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { locked: true } }); + return this.success(message.channel, 'Your report is now locked.'); + case 'unlock': + await this.client.db.Score.updateOne({ userID: message.author.id }, { $set: { locked: false } }); + return this.success(message.channel, 'Your report is now unlocked.'); + default: + return this.error(message.channel, 'Invalid input'); + } + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/setnick.ts b/src/commands/setnick.ts new file mode 100644 index 0000000..0515127 --- /dev/null +++ b/src/commands/setnick.ts @@ -0,0 +1,29 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Setnick extends Command { + constructor(client: Client) { + super(client); + this.name = 'setnick'; + this.description = 'Changes the nickname of a member'; + this.usage = 'setnick [new nickname]'; + this.permissions = 2; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const member = this.client.util.resolveMember(args[0], this.mainGuild); + if (!member) return this.error(message.channel, 'Cannot find user.'); + let nickname = args.slice(1).join(' '); + if (args.length === 1) nickname = null; + if (nickname?.length > 32) return this.error(message.channel, 'New nickname may not be more than 32 characters long.'); + await member.edit({ nick: nickname }); + return this.success(message.channel, `Updated the nickname of ${member.user.username}#${member.user.discriminator}.`); + } catch (err) { + return this.client.util.handleError(err, message, this, false); + } + } +} diff --git a/src/commands/sip.ts b/src/commands/sip.ts new file mode 100644 index 0000000..9933940 --- /dev/null +++ b/src/commands/sip.ts @@ -0,0 +1,87 @@ +/* eslint-disable consistent-return */ +import { Message } from 'eris'; +import type { Channel } from 'ari-client'; +import { Client, Command } from '../class'; +import { Misc } from '../pbx'; + +export default class SIP extends Command { + constructor(client: Client) { + super(client); + this.name = 'sip'; + this.description = 'Dials a SIP URI.'; + this.usage = `${this.client.config.prefix}sip `; + this.permissions = 1; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + const staff = await this.client.db.Staff.findOne({ userID: message.author.id }); + if (!staff || !staff?.extension) return this.error(message.channel, 'You must have an extension to complete this action.'); + this.success(message.channel, 'Queued call.'); + + const bridge = await this.client.pbx.ari.Bridge().create(); + let receiver: Channel = this.client.pbx.ari.Channel(); + let sender: Channel = this.client.pbx.ari.Channel(); + + try { + sender = await this.client.pbx.ari.channels.originate({ + endpoint: `PJSIP/${staff.extension}`, + extension: staff.extension, + callerId: 'LOC PBX OPERATOR ', + context: 'from-internal', + priority: 1, + app: 'cr-zero', + }); + } catch { + return this.error(message.channel, 'Unable to dial your extension.'); + } + + sender.once('StasisStart', async () => { + await Misc.play(this.client.pbx, sender, 'sound:pls-hold-while-try'); + await sender.ring(); + + try { + receiver = await receiver.originate({ + endpoint: `SIP/${args.join(' ')}`, + callerId: 'LOC PBX OPERATOR ', + context: 'from-internal', + priority: 1, + app: 'cr-zero', + }); + } catch { + await Misc.play(this.client.pbx, sender, 'sound:discon-or-out-of-service'); + await sender.hangup().catch(() => {}); + return false; + } + + // receiver.once('StasisStart', ) + }); + + receiver.once('StasisStart', async () => { + await sender.ringStop(); + await bridge.addChannel({ channel: [receiver.id, sender.id] }); + await bridge.play({ media: 'sound:beep' }); + }); + + receiver.once('ChannelDestroyed', async () => { + if (!sender.connected) return; + await Misc.play(this.client.pbx, sender, ['sound:the-number-u-dialed', 'sound:T-is-not-available', 'sound:please-try-again-later']); + await sender.hangup().catch(() => {}); + await bridge.destroy().catch(() => {}); + }); + + receiver.once('StasisEnd', async () => { + await sender.hangup().catch(() => {}); + await bridge.destroy().catch(() => {}); + }); + sender.once('StasisEnd', async () => { + await receiver.hangup().catch(() => {}); + await bridge.destroy().catch(() => {}); + }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/site.ts b/src/commands/site.ts new file mode 100644 index 0000000..4e7384f --- /dev/null +++ b/src/commands/site.ts @@ -0,0 +1,302 @@ +/* eslint-disable dot-notation */ +/* eslint-disable no-plusplus */ +import cheerio from 'cheerio'; +import https from 'https'; +import dns from 'dns'; +import puppeteer from 'puppeteer'; +import { createPaginationEmbed } from 'eris-pagination'; +import { promisify } from 'util'; +import axios, { AxiosError, AxiosResponse } from 'axios'; +import { Message } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +interface TLSResponse { + status: boolean, + message?: string, + subject: { + commonName: string, + organization: string[], + organizationalUnit: string[], + locality: string[], + country: string[], + }, + issuer: { + commonName: string, + organization: string[], + organizationalUnit: string[], + locality: string[], + country: string[], + }, + root: { + commonName: string, + organization: string[], + organizationalUnit: string[], + locality: string[], + country: string[], + }, + notBefore: Date, + notAfter: Date, + validationType: 'DV' | 'OV' | 'EV', + signatureAlgorithm: string, + publicKeyAlgorithm: string, + serialNumber: string, + keyUsage: number[], + keyUsageAsText: ['CRL Signing'?, 'Certificate Signing'?, 'Content Commitment'?, 'Data Encipherment'?, 'Decipher Only'?, 'Digital Signature'?, 'Encipher Only'?, 'Key Agreement'?, 'Key Encipherment'?], + extendedKeyUsage: number[], + extendedKeyUsageAsText: ['All/Any Usages'?, 'TLS Web Server Authentication'?, 'TLS Web Client Authentication'?, 'Code Signing'?, 'E-mail Protection (S/MIME)'?], + san: string[], + fingerprint: string, + connection: { + cipherSuite: string, + tlsVersion: 'SSLv3' | 'TLSv1' | 'TLSv1.1' | 'TLSv1.2' | 'TLSv1.3', + }, +} + + +export default class SiteInfo extends Command { + constructor(client: Client) { + super(client); + this.name = 'site'; + this.description = 'Retrieves various information about a site. Includes web, host, and TLS information.'; + this.usage = `${this.client.config.prefix}tls \n*Only raw domain, no protocols or URLs.*`; + this.aliases = ['ssl', 'cert', 'certinfo', 'ci', 'tls', 'si', 'siteinfo']; + this.permissions = 0; + this.guildOnly = true; + this.enabled = false; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + + const loading = await this.loading(message.channel, 'Loading...'); + + let author: { name?: string, icon?: string, url?: string } = {}; + let s: AxiosResponse; + try { + s = await axios.get(`https://${args[0]}`, { + httpsAgent: new https.Agent({ + rejectUnauthorized: false, + }), + }); + } catch (err) { + loading.delete().catch(() => {}); + return this.error(message.channel, `Unable to retrieve information from site. | ${err}`); + } + try { + const site = cheerio.load(s.data); + + let iconURI: string; + + try { + await axios.get(`https://${args[0]}/favicon.ico`, { + httpsAgent: new https.Agent({ + rejectUnauthorized: false, + }), + }); + iconURI = `https://${args[0]}/favicon.ico`; + } catch { + const att = site('link').toArray().filter((a) => a.attribs.rel === 'icon'); + if (att?.length > 0) { + if (att[0].attribs.href.startsWith('/') || !att[0].attribs.href.startsWith('http')) { + iconURI = `https://${args[0]}${att[0].attribs.href}`; + } else { + iconURI = att[0].attribs.href; + } + } + } + author = { + name: site('title').text(), + icon: iconURI, + url: `https://${args[0]}`, + }; + } catch { + author = { + name: `https://${args[0]}`, + icon: `https://${args[0]}/favicon.ico`, + url: `https://${args[0]}`, + }; + } + + const embeds: RichEmbed[] = []; + try { + const server = await this.getServerInformation(args[0]); + if (server) { + const em = new RichEmbed(); + em.setTitle('Web Information'); + server.forEach((f) => em.addField(f.name, f.value, f.inline)); + embeds.push(em); + } + const host = await this.getHostInformation(args[0]); + if (host) { + const em = new RichEmbed(); + em.setTitle('Host Information'); + host.forEach((f) => em.addField(f.name, f.value, f.inline)); + embeds.push(em); + } + const tls = await this.getTLSInformation(args[0]); + if (tls) { + const em = new RichEmbed(); + em.setTitle('TLS Information'); + tls.forEach((f) => em.addField(f.name, f.value, f.inline)); + embeds.push(em); + } + } catch { + loading.delete().catch(() => {}); + return this.error(message.channel, 'Unable to receive information.'); + } + let screenshotData: string; + try { + const browser = await puppeteer.launch({ + ignoreHTTPSErrors: true, + args: [ + '--ignore-certificate-errors', + '--ignore-certificate-errors-spki-list', + ], + }); + const page = await browser.newPage(); + await page.goto(`https://${args[0]}`); + screenshotData = await page.screenshot(); + browser.close(); + } catch (e) { + this.client.util.signale.error(e); + } + embeds.forEach((embed) => { + embed.setAuthor(author.name, author.icon, author.url); + embed.setColor('#4870fe'); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + }); + + if (screenshotData) { + if (embeds.length === 1) return message.channel.createMessage({ embed: embeds[0] }, { name: 'img.png', file: screenshotData }); + } else { + await message.channel.createMessage('', { name: 'img.png', file: screenshotData }); + } + loading.delete().catch(() => {}); + return await createPaginationEmbed(message, embeds, { + cycling: true, + extendedButtons: true, + }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } + + public async getHostInformation(domain: string) { + const getDNS = promisify(dns.resolve4); + + try { + const dnsd = await getDNS(domain); + if (dnsd?.length < 1) return null; + + const r = await axios.get(`http://ip-api.com/json/${dnsd[0]}`); + + const embed = new RichEmbed(); + embed.addField('IPv4 Address', dnsd[0], true); + embed.addBlankField(); + embed.addField('Organization/Customer', r.data.org, true); + embed.addField('Internet Service Provider', r.data.isp, true); + embed.addField('Location', `${r.data.city}, ${r.data.regionName}, ${r.data.country}`, true); + return embed.fields; + } catch (err) { + this.client.util.signale.error(err); + return null; + } + } + + public async getServerInformation(domain: string) { + let r: AxiosResponse; + try { + r = await axios.get(`https://${domain}`, { + httpsAgent: new https.Agent({ + rejectUnauthorized: false, + }), + }); + } catch (err) { + this.client.util.signale.error(err); + return null; + } + + const embed = new RichEmbed(); + embed.addField('Web Software', r.headers['server'] ?? 'N/A', true); + embed.addField('Content Type', r.headers['content-type'] ? r.headers['content-type'].split(';')[0] : 'N/A', true); + embed.addField('Content Length', r.headers['content-length'] ? this.client.util.dataConversion(r.headers['content-length']) : 'N/A', true); + return embed.fields; + } + + public async getTLSInformation(domain: string) { + https.globalAgent.options.rejectUnauthorized = false; + + let r: AxiosResponse; + try { + r = await axios.get(`https://certapi.libraryofcode.org?q=${domain}`, { + httpsAgent: new https.Agent({ + rejectUnauthorized: false, + }), + }); + } catch (err) { + const error = err; + return null; + } + const resp: TLSResponse = r.data; + + const embed = new RichEmbed(); + + let subjectString = `**Common Name:** ${resp.subject.commonName}\n`; + if (resp.subject.organization?.length > 0) subjectString += `**Organization:** ${resp.subject.organization[0]}\n`; + if (resp.subject.organizationalUnit?.length > 0) subjectString += `**Organizational Unit:** ${resp.subject.organizationalUnit[0]}\n`; + if (resp.subject.locality?.length > 0) subjectString += `**Locality:** ${resp.subject.locality[0]}\n`; + if (resp.subject.country?.length > 0) subjectString += `**Country:** ${resp.subject.country[0]}`; + embed.addField('Subject', subjectString, true); + + let issuerString = `**Common Name:** ${resp.subject.commonName}\n`; + if (resp.issuer.organization?.length > 0) issuerString += `**Organization:** ${resp.issuer.organization[0]}\n`; + if (resp.issuer.organizationalUnit?.length > 0) issuerString += `**Organizational Unit:** ${resp.issuer.organizationalUnit[0]}\n`; + if (resp.issuer.locality?.length > 0) issuerString += `**Locality:** ${resp.issuer.locality[0]}\n`; + if (resp.issuer.country?.length > 0) issuerString += `**Country:** ${resp.issuer.country[0]}`; + const rootString = `**Root Certificate:** ${resp.root.organization?.length > 0 ? resp.root.organization[0] : ''} (${resp.root.commonName ?? ''} : ${resp.root.organizationalUnit?.length > 0 ? resp.root.organizationalUnit[0] : ''})`; + if (rootString?.length > 0) issuerString += `\n\n${rootString}`; + embed.addField('Issuer', issuerString, true); + + embed.addBlankField(); + + embed.addField('Issued On', new Date(resp.notBefore).toLocaleString('en-us'), true); + embed.addField('Expiry', new Date(resp.notAfter).toLocaleString('en-us'), true); + + embed.addBlankField(); + + let sanString = ''; + + for (let i = 0; i < resp.san.length; i++) { + if (i >= 10) { + sanString += '...'; + break; + } + sanString += `${resp.san[i]}\n`; + } + + embed.addField('Subject Alternative Names', sanString, true); + + embed.addBlankField(); + + embed.addField('Key Usage', resp.keyUsageAsText.join(', '), true); + embed.addField('Extended Key Usage', resp.extendedKeyUsageAsText.join(', '), true); + embed.addField('Validation Type', resp.validationType, true); + + embed.addBlankField(); + + embed.addField('Serial Number', String(resp.serialNumber), true); + embed.addField('Fingerprint (SHA1)', resp.fingerprint, true); + embed.addField('Signature Algorithm', resp.signatureAlgorithm, true); + embed.addField('Public Key Algorithm', resp.publicKeyAlgorithm, true); + + embed.addBlankField(); + + embed.addField('TLS Cipher Suite', resp.connection.cipherSuite, true); + embed.addField('TLS Version', resp.connection.tlsVersion, true); + + https.globalAgent.options.rejectUnauthorized = true; + return embed.fields; + } +} diff --git a/src/commands/slowmode.ts b/src/commands/slowmode.ts new file mode 100644 index 0000000..729ff89 --- /dev/null +++ b/src/commands/slowmode.ts @@ -0,0 +1,34 @@ +import { Message, GuildTextableChannel } from 'eris'; +import moment, { unitOfTime } from 'moment'; +import { Client, Command } from '../class'; + +export default class Slowmode extends Command { + regex: RegExp; + + constructor(client: Client) { + super(client); + this.name = 'slowmode'; + this.description = 'Set slowmode to a channel.'; + this.usage = 'slowmode '; + this.permissions = 1; + this.guildOnly = true; + this.enabled = true; + this.regex = /[a-z]+|[^a-z]+/gi; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + + const [length, unit] = args[0].match(this.regex); + if (Number.isNaN(Number(length))) return this.error(message.channel, 'Could not determine the slowmode time.'); + const momentSeconds: number = Math.round(moment.duration(length, unit as unitOfTime.Base || 's').asSeconds()); + + if (momentSeconds > 21600 || momentSeconds < 0) return this.error(message.channel, 'Slowmode must be between 0 seconds and 6 hours.'); + + return message.channel.edit({ rateLimitPerUser: momentSeconds }).then((c) => message.addReaction(':success:477618704155410452')); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/stats.ts b/src/commands/stats.ts new file mode 100644 index 0000000..72f59ea --- /dev/null +++ b/src/commands/stats.ts @@ -0,0 +1,37 @@ +import { Message } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class Stats extends Command { + constructor(client: Client) { + super(client); + this.name = 'stats'; + this.description = 'Provides system statistics.'; + this.usage = `${this.client.config.prefix}stats`; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message) { + try { + const messages = await this.client.db.Stat.findOne({ name: 'messages' }); + const commands = await this.client.db.Stat.findOne({ name: 'commands' }); + const pages = await this.client.db.Stat.findOne({ name: 'pages' }); + const requests = await this.client.db.Stat.findOne({ name: 'requests' }); + + const embed = new RichEmbed(); + embed.setTitle('Statistics'); + embed.setThumbnail(this.client.user.avatarURL); + embed.addField('Messages Seen', `${messages.value}`, true); + embed.addField('Commands Executed', `${commands.value}`, true); + embed.addField('HTTP Requests Served', `${requests.value}`, true); + embed.addField('Pages Sent', `${pages.value}`, true); + embed.addField('Jobs Processed', `${(await this.client.queue.jobCounts()).completed}`, true); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/storemessages.ts b/src/commands/storemessages.ts new file mode 100644 index 0000000..fa3bc72 --- /dev/null +++ b/src/commands/storemessages.ts @@ -0,0 +1,52 @@ +import { randomBytes } from 'crypto'; +import { Message, TextChannel } from 'eris'; +import { Client, Command, LocalStorage } from '../class'; + +export default class StoreMessages extends Command { + constructor(client: Client) { + super(client); + this.name = 'storemessages'; + this.description = 'Fetches 1000 messages from the specified channel and stores them in a HTML file.'; + this.usage = `${this.client.config.prefix}storemessages [member ID]`; + this.aliases = ['sm']; + this.permissions = 7; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const check = this.client.util.resolveGuildChannel(args[0], this.mainGuild, false); + if (!check || check.type !== 0) return this.error(message.channel, 'The channel you specified either doesn\'t exist or isn\'t a textable guild channel.'); + const chan = this.mainGuild.channels.get(check.id); + const loadingMessage = await this.loading(message.channel, 'Fetching messages...'); + let messages = await chan.getMessages(10000); + if (args[1]) { + messages = messages.filter((m) => m.author.id === args[1]); + } + let html = `CLASSIFIED RESOURCE: CL-GEN/AD

Library of Code sp-us

Channel: ${chan.name} (${chan.id})
Generated by: ${message.author.username}#${message.author.discriminator}
Generated at: ${new Date().toLocaleString('en-us')}

`; + for (const msg of messages) { + html += `(${new Date(msg.timestamp).toLocaleString('en-us')}) [${msg.author.username}#${msg.author.discriminator} - ${msg.author.id}]: ${msg.cleanContent}
`; + } + message.delete(); + const identifier = randomBytes(20).toString('hex'); + + const comp = await LocalStorage.compress(html); + const file = new this.client.db.File({ + name: `${chan.name}-${new Date().toLocaleString('en-us')}.html.gz`, + identifier, + mimeType: 'application/gzip', + data: comp, + downloaded: 0, + maxDownloads: 20, + }); + await file.save(); + loadingMessage.delete(); + this.client.getDMChannel(message.author.id).then((c) => c.createMessage(`https://cr.ins/m/${identifier}.html.gz || https://cr.ins/m/${identifier}.html?d=1`)).catch(() => this.error(message.channel, 'Could not send a DM to you.')); + return this.success(message.channel, `Fetched messages for <#${chan.id}>. Check your DMs for link to access.`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/sysinfo.ts b/src/commands/sysinfo.ts new file mode 100644 index 0000000..042f086 --- /dev/null +++ b/src/commands/sysinfo.ts @@ -0,0 +1,54 @@ +// ?e require('mongoose').connections[0].db.command({ buildInfo: 1 }) + +import { Message } from 'eris'; +import mongoose from 'mongoose'; +import { Client, Command, RichEmbed } from '../class'; +import { version as erisVersion } from '../../node_modules/eris/package.json'; +import { version as mongooseVersion } from '../../node_modules/mongoose/package.json'; +import { version as ariVersion } from '../../node_modules/ari-client/package.json'; +import { version as amiVersion } from '../../node_modules/asterisk-manager/package.json'; +import { version as nodeMailerVersion } from '../../node_modules/nodemailer/package.json'; +import { version as ioredisVersion } from '../../node_modules/ioredis/package.json'; +import { version as stripeVersion } from '../../node_modules/stripe/package.json'; +import { version as cronVersion } from '../../node_modules/cron/package.json'; +import { version as ttsVersion } from '../../node_modules/@google-cloud/text-to-speech/package.json'; +import { version as bullVersion } from '../../node_modules/bull/package.json'; + +export default class SysInfo extends Command { + constructor(client: Client) { + super(client); + this.name = 'sysinfo'; + this.description = 'Information about the various services/system we use to run.'; + this.usage = 'sysinfo'; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message) { + try { + const embed = new RichEmbed(); + embed.setTitle('System & Service Information'); + embed.setDescription('__Format of Services__\n- {Server/Service Name} + {various server stats} | {Node Library Used for Communication w/ Server or Service}'); + const mongoBuild = await mongoose.connections[0].db.command({ buildInfo: 1 }); + const mongoDatabase: { + collections: number, + objects: number, + dataSize: number, + } = await mongoose.connections[0].db.command({ dbStats: 1 }); + const asteriskInformation = await this.client.util.pbx.ari.asterisk.getInfo(); + const redisVersion = await this.client.util.exec('redis-cli info | grep "redis_version"'); + embed.addField('Database', `- [MongoDB v${mongoBuild.version}](https://www.mongodb.com/) + Documents: ${mongoDatabase.objects} | [Mongoose ODM v${mongooseVersion}](https://github.com/Automattic/mongoose)\n- CR Local Storage w/ GZIP Compression | Internal\n- [Redis v${redisVersion.split(':')[1]}](https://redis.io/) | [IORedis v${ioredisVersion}](https://github.com/luin/ioredis)`, true); + embed.addField('Telephony/PBX', `- [Asterisk v${asteriskInformation.system.version}](https://www.asterisk.org/) | [Asterisk ARI Node.js Client v${ariVersion}](https://github.com/asterisk/node-ari-client) & [Asterisk AMI Node.js Client v${amiVersion}](https://github.com/danjenkins/node-asterisk-ami)`, true); + embed.addField('Email', `- [Postfix SMTP v3.1.12](http://www.postfix.org/) | [Nodemailer v${nodeMailerVersion}](https://github.com/nodemailer/nodemailer)`, true); + embed.addField('Discord', `- N/A | [Eris v${erisVersion}](https://github.com/abalabahaha/eris)`, true); + embed.addField('Payments', `- [Stripe API](https://stripe.com/) | [Stripe Node.js Client v${stripeVersion}](https://github.com/stripe/stripe-node)`, true); + embed.addField('Scheduling', `- [Cron](https://systemd.io/) | [Cron Scheduling Node.js v${cronVersion}](https://github.com/node-cron/node-cron)\n- Internal Queue | [Bull v${bullVersion}](https://github.com/OptimalBits/bull) w/ IORedis`, true); + embed.addField('Audio & Voice', `- [Google Cloud ML](https://cloud.google.com/text-to-speech) | [Google Cloud Node.js Text to Speech Synthesizer v${ttsVersion}](https://github.com/googleapis/nodejs-text-to-speech)\n - [FFMPEG](https://ffmpeg.org/) | Internal`, true); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + message.channel.createMessage({ embed }); + } catch (err) { + this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/train.ts b/src/commands/train.ts new file mode 100644 index 0000000..bd68c06 --- /dev/null +++ b/src/commands/train.ts @@ -0,0 +1,40 @@ +import { Message, TextChannel } from 'eris'; +import { Client, Command } from '../class'; + +export default class Train extends Command { + constructor(client: Client) { + super(client); + this.name = 'train'; + this.description = 'Trains a neural network.'; + this.usage = `${this.client.config.prefix}train <1: good | 0: bad>`; + this.permissions = 1; + this.guildOnly = false; + this.enabled = false; + } + + public async run(message: Message, args: string[]) { + try { + if (args?.length < 3) return this.client.commands.get('help').run(message, [this.name]); + if (args[2] !== '0' && args[2] !== '1') return this.error(message.channel, 'Result must be either 0 or 1.'); + const channel = this.client.util.resolveGuildChannel(args[0], this.mainGuild); + if (!channel) return this.error(message.channel, 'Channel could not be found.'); + if (channel.type !== 0) return this.error(message.channel, 'Invalid channel type.'); + let msg: Message; + try { + msg = await channel.getMessage(args[1]); + } catch { + return this.error(message.channel, 'Could not find message.'); + } + if (!msg) return this.error(message.channel, 'Message could not be found.'); + + await this.client.db.NNTrainingData.updateOne({ name: 'tc' }, { $addToSet: { data: { input: this.client.util.encode(msg.content), output: { res: Number(args[2]) } } } }); + await message.delete(); + const done = await this.success(message.channel, 'Neural Network trained successfully.'); + return setTimeout(async () => { + await done.delete(); + }, 3000); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/tts.ts b/src/commands/tts.ts new file mode 100644 index 0000000..940fde7 --- /dev/null +++ b/src/commands/tts.ts @@ -0,0 +1,36 @@ +import axios from 'axios'; +import { v4 as uuid } from 'uuid'; +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class TTS extends Command { + constructor(client: Client) { + super(client); + this.name = 'tts'; + this.description = 'Uses Google Text to Speech engines to synthesize input to a MP3 file. Only supports English at this time.'; + this.usage = `${this.client.config.prefix}tts `; + this.permissions = 0; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + if (args.length > 200) return this.error(message.channel, 'Cannot synthesize more than 200 characters.'); + const msg = await this.loading(message.channel, 'Synthesizing...'); + const d = await axios({ + method: 'GET', + url: `https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=${encodeURIComponent(args.join(' '))}&tl=en`, + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0', + }, + responseType: 'arraybuffer', + }); + msg.delete(); + return message.channel.createMessage(undefined, { name: `${uuid()}.mp3`, file: d.data }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/unban.ts b/src/commands/unban.ts new file mode 100644 index 0000000..b86233d --- /dev/null +++ b/src/commands/unban.ts @@ -0,0 +1,37 @@ +import { Message, User } from 'eris'; +import { Client, Command } from '../class'; + +export default class Unban extends Command { + constructor(client: Client) { + super(client); + this.name = 'unban'; + this.description = 'Unbans a member from the guild.'; + this.usage = 'unban [reason]'; + this.permissions = 3; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + let user: User; + try { + user = await this.client.getRESTUser(args[0]); + } catch { + return this.error(message.channel, 'Could find find user.'); + } + try { + await this.mainGuild.getBan(args[0]); + } catch { + return this.error(message.channel, 'This user is not banned.'); + } + message.delete(); + + await this.client.util.moderation.unban(user.id, message.member, args.slice(1).join(' ')); + return this.success(message.channel, `${user.username}#${user.discriminator} has been unbanned.`); + } catch (err) { + return this.client.util.handleError(err, message, this, false); + } + } +} diff --git a/src/commands/unmute.ts b/src/commands/unmute.ts new file mode 100644 index 0000000..170b245 --- /dev/null +++ b/src/commands/unmute.ts @@ -0,0 +1,34 @@ +import { Message } from 'eris'; +import { Client, Command } from '../class'; + +export default class Unmute extends Command { + constructor(client: Client) { + super(client); + this.name = 'unmute'; + this.description = 'Unmutes a member.'; + this.usage = 'unmute [reason]'; + this.permissions = 2; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const member = this.client.util.resolveMember(args[0], this.mainGuild); + if (!member) return this.error(message.channel, 'Cannot find user.'); + + try { + const res1 = await this.client.db.local.muted.get(`muted-${member.id}`); + if (!res1 || !this.mainGuild.members.get(member.id).roles.includes('478373942638149643')) return this.error(message.channel, 'This user is already unmuted.'); + } catch {} // eslint-disable-line no-empty + if (member && !this.client.util.moderation.checkPermissions(member, message.member)) return this.error(message.channel, 'Permission Denied.'); + message.delete(); + + await this.client.util.moderation.unmute(member.user.id, message.member, args.slice(1).join(' ')); + return this.success(message.channel, `${member.user.username}#${member.user.discriminator} has been unmuted.`); + } catch (err) { + return this.client.util.handleError(err, message, this, false); + } + } +} diff --git a/src/commands/whois.ts b/src/commands/whois.ts new file mode 100644 index 0000000..0b3024f --- /dev/null +++ b/src/commands/whois.ts @@ -0,0 +1,227 @@ +/* eslint-disable no-bitwise */ +import moment from 'moment'; +import { Message, Member } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; +import { whois as emotes } from '../configs/emotes.json'; + +export default class Whois extends Command { + constructor(client: Client) { + super(client); + this.name = 'whois'; + this.description = 'Provides information on a member.'; + this.usage = 'whois [member]'; + this.permissions = 0; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + let member: Member; + if (!args[0]) member = message.member; + else { + member = this.client.util.resolveMember(args.join(' '), this.mainGuild); + try { + if (!member) member = await this.mainGuild.getRESTMember(args[0]); + } catch { + return this.error(message.channel, 'Member not found.'); + } + } + + if (!member) { + return this.error(message.channel, 'Member not found.'); + } + const embed = new RichEmbed(); + embed.setThumbnail(member.avatarURL); + const ackResolve = await this.client.db.Staff.findOne({ userID: member.id }).lean().exec(); + let title = `${member.user.username}#${member.user.discriminator}`; + if (ackResolve?.pn?.length > 0) title += `, ${ackResolve.pn.join(', ')}`; + embed.setAuthor(title, member.user.avatarURL); + + let description = ''; + let titleAndDepartment = ''; + if (ackResolve?.title && ackResolve?.dept) { + titleAndDepartment += `${emotes.titleAndDepartment} __**${ackResolve.title}**__, __${ackResolve.dept}__\n\n`; + } else if (ackResolve?.dept) { + titleAndDepartment += `${emotes.titleAndDepartment} __${ackResolve.dept}__\n\n`; + } + if (titleAndDepartment.length > 0) description += titleAndDepartment; + if (ackResolve?.emailAddress) { + description += `${emotes.email} ${ackResolve.emailAddress}\n`; + } + const pager = await this.client.db.PagerNumber.findOne({ individualAssignID: member.user.id }).lean().exec(); + if (pager?.num) { + description += `📟 ${pager.num}\n`; + } + if (ackResolve?.extension) { + description += `☎️ ${ackResolve.extension}\n`; + } + const memberProfile = await this.client.db.Member.findOne({ userID: member.id }).lean().exec(); + if (memberProfile?.additional?.gitlab) { + description += `${emotes.gitlab} ${memberProfile?.additional.gitlab}\n`; + } + if (memberProfile?.additional?.github) { + description += `${emotes.github} ${memberProfile?.additional.github}\n`; + } + if (memberProfile?.additional?.bio) { + description += `${emotes.bio} *${memberProfile?.additional.bio}*\n`; + } + description += `\n<@${member.id}>`; + embed.setDescription(description); + + for (const role of member.roles.map((r) => this.mainGuild.roles.get(r)).sort((a, b) => b.position - a.position)) { + if (role?.color !== 0) { + embed.setColor(role.color); + break; + } + } + embed.addField('Status', member.status === 'dnd' ? 'Do Not Disturb' : this.client.util.capsFirstLetter(member.status) || 'Offline', true); + embed.addField('Joined At', `${moment(new Date(member.joinedAt)).format('dddd, MMMM Do YYYY, h:mm:ss A')} ET`, true); + embed.addField('Created At', `${moment(new Date(member.user.createdAt)).format('dddd, MMMM Do YYYY, h:mm:ss A')} ET`, true); + const score = await this.client.db.Score.findOne({ userID: member.id }).lean().exec(); + if (score) { + await this.client.report.createInquiry(member.id, 'Library of Code sp-us | Bureau of Community Reports', 1); + let totalScore = '0'; + if (score.total < 200) totalScore = '---'; + else if (score.total > 800) totalScore = '800'; + else totalScore = `${score.total}`; + embed.addField('CommScore™', totalScore, true); + } else embed.addField('CommScore™', 'N/C', true); + + if (member.roles.length > 0) { + embed.addField(`Roles [${member.roles.length}]`, member.roles.map((r) => this.mainGuild.roles.get(r)).sort((a, b) => b.position - a.position).map((r) => `<@&${r.id}>`).join(', ')); + } + + const flags: string[] = []; + if (member.user.publicFlags) { + if ((member.user.publicFlags & (1 << 12)) === 1 << 12) flags.push('<:System:768370601265201152>'); + if ((member.user.publicFlags & (1 << 0)) === 1 << 0) flags.push('<:DiscordStaff:768370601882025985>'); + if ((member.user.publicFlags & (1 << 1)) === 1 << 1) flags.push('<:Partnered:768370601395879936>'); + if ((member.user.publicFlags & (1 << 3)) === 1 << 3) flags.push('<:BugHunter:768370601105555467>'); + if ((member.user.publicFlags & (1 << 14)) === 1 << 14) flags.push('<:BugHunter:768370601105555467>'); + if ((member.user.publicFlags & (1 << 2)) === 1 << 2) flags.push('<:HypeSquadEvents:768370600745762846>'); + if ((member.user.publicFlags & (1 << 6)) === 1 << 6) flags.push('<:HypeSquadBravery:768370601328640011> '); + if ((member.user.publicFlags & (1 << 7)) === 1 << 7) flags.push('<:HypeSquadBrilliance:768370600842362900>'); + if ((member.user.publicFlags & (1 << 8)) === 1 << 8) flags.push('<:HypeSquadBalance:768370599584071751> '); + if ((member.user.publicFlags & (1 << 9)) === 1 << 9) flags.push('<:EarlySupporter:768370601873768468>'); + if ((member.user.publicFlags & (1 << 16)) === 1 << 16) flags.push('<:VerifiedBot:768370599252197396>'); + if ((member.user.publicFlags & (1 << 17)) === 1 << 17) flags.push('<:VerifiedBotDeveloper:768370601701933077>'); + } + if (flags.length > 0) embed.addField('Flags', flags.join(' ')); + + const permissions: string[] = []; + const serverAcknowledgements: string[] = []; + const bit = member.permission.allow; + if (this.mainGuild.ownerID === member.id) serverAcknowledgements.push('Server Owner'); + if (bit & 8) { permissions.push('Administrator'); serverAcknowledgements.push('Server Admin'); } + if (bit & 32) { permissions.push('Manage Server'); serverAcknowledgements.push('Server Manager'); } + if (bit & 16) permissions.push('Manage Channels'); + if (bit & 268435456) permissions.push('Manage Roles'); + if (bit & 8192) { permissions.push('Manage Messages'); serverAcknowledgements.push('Server Moderator'); } + if (bit & 134217728) permissions.push('Manage Nicknames'); + if (bit & 1073741824) permissions.push('Manage Emojis'); + if (bit & 4) permissions.push('Ban Members'); + if (bit & 2) permissions.push('Kick Members'); + + + const account = await this.client.db.Member.findOne({ userID: member.id }).lean().exec(); + if (account?.additional?.langs?.length > 0) { + const langs: string[] = []; + for (const lang of account.additional.langs.sort((a, b) => a.localeCompare(b))) { + switch (lang) { + case 'asm': + langs.push('<:AssemblyLanguage:703448714248716442> Assembly Language'); + break; + case 'cfam': + langs.push('<:clang:553684262193332278> C/C++'); + break; + case 'csharp': + langs.push('<:csharp:553684277280112660> C#'); + break; + case 'go': + langs.push('<:Go:703449475405971466> Go'); + break; + case 'java': + langs.push('<:Java:703449725181100135> Java'); + break; + case 'js': + langs.push('<:JavaScriptECMA:703449987916496946> JavaScript'); + break; + case 'kt': + langs.push('<:Kotlin:703450201838321684> Kotlin'); + break; + case 'py': + langs.push('<:python:553682965482176513> Python'); + break; + case 'rb': + langs.push('<:ruby:604812470451699712> Ruby'); + break; + case 'rs': + langs.push('<:Rust:703450901960196206> Rust'); + break; + case 'swift': + langs.push('<:Swift:703451096093294672> Swift'); + break; + case 'ts': + langs.push('<:TypeScript:703451285789343774> TypeScript'); + break; + default: + break; + } + } + embed.addField('Known Languages', langs.join(', ')); + } + if (account?.additional?.operatingSystems?.length > 0) { + const operatingSystems: string[] = []; + for (const os of account.additional.operatingSystems.sort((a, b) => a.localeCompare(b))) { + switch (os) { + case 'arch': + operatingSystems.push('<:arch:707694976523304960> Arch'); + break; + case 'deb': + operatingSystems.push('<:debian:707695042617147589> Debian'); + break; + case 'cent': + operatingSystems.push('<:centos:707702165816213525> CentOS'); + break; + case 'fedora': + operatingSystems.push('<:fedora:707695073151680543> Fedora'); + break; + case 'manjaro': + operatingSystems.push('<:manjaro:707701473680556062> Manjaro'); + break; + case 'mdarwin': + operatingSystems.push('<:mac:707695427754917919> macOS'); + break; + case 'redhat': + operatingSystems.push('<:redhat:707695102159749271> RedHat Enterprise Linux'); + break; + case 'ubuntu': + operatingSystems.push('<:ubuntu:707695136888586300> Ubuntu'); + break; + case 'win': + operatingSystems.push('<:windows10:707695160259248208> Windows'); + break; + default: + break; + } + } + embed.addField('Used Operating Systems', operatingSystems.join(', ')); + } + if (permissions.length > 0) { + embed.addField('Permissions', permissions.join(', ')); + } + if (serverAcknowledgements.length > 0) { + embed.addField('Acknowledgements', serverAcknowledgements[0]); + } + if (ackResolve?.additionalRoles?.length > 0) { + embed.addField('Additional Acknowledgements', ackResolve.additionalRoles.join(', ')); + } + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/configs/channels.json b/src/configs/channels.json new file mode 100644 index 0000000..f7ac350 --- /dev/null +++ b/src/configs/channels.json @@ -0,0 +1,6 @@ +{ + "moderation": { + "modlogs": "446080867065135115", + "automod": "" + } +} diff --git a/src/configs/emotes.json b/src/configs/emotes.json new file mode 100644 index 0000000..3f0a0ab --- /dev/null +++ b/src/configs/emotes.json @@ -0,0 +1,14 @@ +{ + "whois": { + "titleAndDepartment": "<:loc:607695848612167700>", + "email": "<:email:699786452267040878>", + "gitlab": "<:gitlab:699788655748841492>", + "github": "<:github:699786469404835939>", + "bio": "<:bio:699786408193294416>" + }, + "statusMessages": { + "success": "<:modSuccess:578750988907970567>", + "loading": "", + "error": "<:modError:578750737920688128>" + } +} diff --git a/src/events/CallBackHandler.ts b/src/events/CallBackHandler.ts new file mode 100644 index 0000000..2069fff --- /dev/null +++ b/src/events/CallBackHandler.ts @@ -0,0 +1,23 @@ +import { Emoji, Message, TextChannel } from 'eris'; +import { Client, Event } from '../class'; + +export default class CallBackHandler extends Event { + public client: Client; + + constructor(client: Client) { + super(client); + this.event = 'messageReactionAdd'; + } + + public async run(message: Message, emoji: Emoji, member: string) { + try { + if (emoji.id !== '578750988907970567' || message.channel.id !== '780513128240382002') return; + if (member === this.client.user.id) return; + const chan = this.client.guilds.get(this.client.config.guildID).channels.get('780513128240382002'); + const msg = await chan.getMessage(message.id); + await msg.delete(); + } catch (err) { + this.client.util.handleError(err); + } + } +} diff --git a/src/events/CommandHandler.ts b/src/events/CommandHandler.ts new file mode 100644 index 0000000..06f1b59 --- /dev/null +++ b/src/events/CommandHandler.ts @@ -0,0 +1,34 @@ +/* eslint-disable no-useless-return */ +import { Message, TextChannel, NewsChannel } from 'eris'; +import { Client, Event } from '../class'; + +export default class CommandHandler extends Event { + public client: Client; + + constructor(client: Client) { + super(client); + this.event = 'messageCreate'; + } + + public async run(message: Message) { + try { + this.client.db.Stat.updateOne({ name: 'messages' }, { $inc: { value: 1 } }).exec(); + if (message.author.bot) return; + if (message.content.indexOf(this.client.config.prefix) !== 0) return; + const noPrefix: string[] = message.content.slice(this.client.config.prefix.length).trim().split(/ +/g); + const resolved = await this.client.util.resolveCommand(noPrefix); + if (!resolved) return; + if (resolved.cmd.guildOnly && !(message.channel instanceof TextChannel || message.channel instanceof NewsChannel)) return; + if (!resolved.cmd.enabled) { message.channel.createMessage(`***${this.client.util.emojis.ERROR} This command has been disabled***`); return; } + if (!resolved.cmd.checkPermissions(this.client.util.resolveMember(message.author.id, this.client.guilds.get('446067825673633794')))) return; + if ((message.channel.type === 0) && !message.channel.guild.members.get(message.author.id)) { + message.channel.guild.members.add(await message.channel.guild.getRESTMember(message.author.id)); + } + this.client.util.signale.log(`User '${message.author.username}#${message.author.discriminator}' ran command '${resolved.cmd.name}' in '${message.channel.id}'.`); + await resolved.cmd.run(message, resolved.args); + this.client.db.Stat.updateOne({ name: 'commands' }, { $inc: { value: 1 } }).exec(); + } catch (err) { + this.client.util.handleError(err, message); + } + } +} diff --git a/src/events/Training.ts b/src/events/Training.ts new file mode 100644 index 0000000..f178a3c --- /dev/null +++ b/src/events/Training.ts @@ -0,0 +1,29 @@ +/* eslint-disable no-unreachable */ +import { Message } from 'eris'; +import { Client, Event, LocalStorage } from '../class'; + +export default class Training extends Event { + public client: Client; + + public storage: LocalStorage; + + constructor(client: Client) { + super(client); + this.event = 'messageCreate'; + this.storage = new LocalStorage('training'); + } + + public async run(message: Message) { + return; + try { + if (message.channel.id !== '485680288123584525') return; + await this.storage.set(message.id, { + content: message.content, + date: new Date(), + score: -1, + }); + } catch (err) { + this.client.util.handleError(err, message); + } + } +} diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts new file mode 100644 index 0000000..2517eb5 --- /dev/null +++ b/src/events/guildMemberAdd.ts @@ -0,0 +1,22 @@ +import { Member } from 'eris'; +import { Client, Event } from '../class'; + +export default class GuildMemberAdd extends Event { + public client: Client; + + constructor(client: Client) { + super(client); + this.event = 'guildMemberAdd'; + } + + public async run(_, member: Member) { + try { + const search = await this.client.db.local.muted.get(`muted-${member.user.id}`); + if (search === true) { + member.addRole('478373942638149643', 'muted user left server and joined back'); + } + } catch (err) { + this.client.util.handleError(err); + } + } +} diff --git a/src/events/index.ts b/src/events/index.ts new file mode 100644 index 0000000..02d7ba0 --- /dev/null +++ b/src/events/index.ts @@ -0,0 +1,6 @@ +export { default as CallBackHandler } from './CallBackHandler'; +export { default as CommandHandler } from './CommandHandler'; +export { default as guildMemberAdd } from './guildMemberAdd'; +export { default as messageReactionAdd } from './messageReactionAdd'; +export { default as ready } from './ready'; +export { default as Training } from './Training'; diff --git a/src/events/messageReactionAdd.ts b/src/events/messageReactionAdd.ts new file mode 100644 index 0000000..9b6fac6 --- /dev/null +++ b/src/events/messageReactionAdd.ts @@ -0,0 +1,73 @@ +import { Emoji, Guild, GuildTextableChannel, Member, Message } from 'eris'; +import { Client, Event } from '../class'; + +export default class MessageReactionAdd extends Event { + public client: Client; + + constructor(client: Client) { + super(client); + this.event = 'messageReactionAdd'; + + this.directorLogs = '807444198969835550'; + this.directorRole = '662163685439045632'; + } + + private directorLogs: string; + + private directorRole: string; + + + public async run(message: Message, _emoji: Emoji, reactor: Member) { + if (message.channel.id !== this.directorLogs) return; + if (message.author.id !== this.client.user.id) return; + + if (!reactor.roles[0]) { + reactor = await message.channel.guild.getRESTMember(reactor.id); + } + + if (!reactor.roles.includes(this.directorRole)) return; + + const proc = await this.client.db.Proclamation.findOne({ msg: message.id, processed: false }); + if (proc) { + if (proc.votedDirectors?.includes(message.author.id)) return; + + let yea = await message.getReaction('modSuccess:578750988907970567'); + yea = yea.filter((val) => !val.bot); + let nay = await message.getReaction('modError:578750737920688128'); + nay = nay.filter((val) => !val.bot); + let present = await message.getReaction('🙋'); + present = present.filter((val) => !val.bot); + + const totalDirectors = message.channel.guild.members.filter((member) => member.roles.includes(this.directorRole)).length; + + const processed = totalDirectors === (yea.length + nay.length + present.length) || Date.now() - proc.at > 604800000; + const absent = totalDirectors - (yea.length + nay.length + present.length); + + await proc.updateOne({ + results: { + yea: yea.length, + nay: nay.length, + present: present.length, + absent, + }, + processed, + votedDirectors: [...(proc.votedDirectors || []), message.author.id], + }); + const inTheMajority = yea.length > nay.length + present.length; + + if (processed) { + const author = this.client.users.get(proc.issuer) || await this.client.getRESTUser(proc.issuer); + + if (inTheMajority) { + await author.createMessage(`__**Proclamation Majority Vote Received**__\nThe Proclamation you created at Library of Code sp-us, titled **${proc.subject}** (\`${proc.oID}\`) received the majority vote.`); + await message.channel.createMessage(`__**Proclamation Results**__\nProclamation issued by ${author.mention} **received** the majority vote. Proclamation ID: ${proc.oID}\n\n__Results:__\n**Yea:** ${yea.length}\n**Nay:** ${nay.length}\n**Present:** ${present.length}\n**Absent:** ${absent}`); + } else { + await author.createMessage(`__**Proclamation Majority Vote Lost**__\nThe Proclamation you created at Library of Code sp-us, titled **${proc.subject}** (\`${proc.oID}\`) lost the majority vote.`); + await message.channel.createMessage(`__**Proclamation Results**__\nProclamation issued by ${author.mention} **lost** the majority vote. Proclamation ID: ${proc.oID}\n\n__Results:__\n**Yea:** ${yea.length}\n**Nay:** ${nay.length}\n**Present:** ${present.length}\n**Absent:** ${absent}`); + } + } + + reactor.user.createMessage(`__**Vote Recorded**__\nYour vote on the proclamation with ID \`${proc.id}\` at Library of Code sp-us was successfully recorded.`); + } + } +} diff --git a/src/events/ready.ts b/src/events/ready.ts new file mode 100644 index 0000000..d108648 --- /dev/null +++ b/src/events/ready.ts @@ -0,0 +1,25 @@ +import { Client, Event } from '../class'; + +export default class Ready extends Event { + public client: Client; + + constructor(client: Client) { + super(client); + this.event = 'ready'; + } + + public async run() { + await this.client.loadIntervals(); + this.client.util.signale.start(`${this.client.user.username} is now ready!\nServers: ${this.client.guilds.size}\nUsers: ${this.client.users.size}\n\nhttps://gitlab.libraryofcode.org/engineering/communityrelations`); + this.client.on('error', (err) => { + this.client.util.handleError(err); + }); + process.on('uncaughtException', (err) => { + this.client.util.handleError(err); + process.exit(1); + }); + process.on('unhandledRejection', (err: Error) => { + this.client.util.handleError(err); + }); + } +} diff --git a/src/intervals/autoRelease.ts b/src/intervals/autoRelease.ts new file mode 100644 index 0000000..07dadc1 --- /dev/null +++ b/src/intervals/autoRelease.ts @@ -0,0 +1,36 @@ +import { Client } from '../class'; + +let interval: NodeJS.Timeout; + +export default function checkLock(client: Client): NodeJS.Timeout { + interval = setInterval(async () => { + try { + const moderations = await client.db.Moderation.find(); + moderations.forEach(async (moderation) => { + if (!moderation.expiration) return; + if (moderation.expiration.processed) return; + if (new Date() > moderation.expiration.date) { + await moderation.updateOne({ 'expiration.processed': true }); + + const system = client.guilds.get(client.config.guildID).members.get(client.user.id); + switch (moderation.type) { + case 5: + await client.util.moderation.unban(moderation.userID, system); + break; + case 2: + if (await client.db.local.muted.get(`muted-${moderation.userID}`) === true) { + await client.util.moderation.unmute(moderation.userID, system); + } + break; + default: + break; + } + client.util.signale.complete(`Released member ${moderation.userID} | Queue date at ${moderation.expiration.date.toLocaleString('en-us')}`); + } + }); + } catch (error) { + await client.util.handleError(error); + } + }, 10000); + return interval; +} diff --git a/src/intervals/departmentPager.ts b/src/intervals/departmentPager.ts new file mode 100644 index 0000000..003ed4b --- /dev/null +++ b/src/intervals/departmentPager.ts @@ -0,0 +1,370 @@ +/* eslint-disable no-continue */ +/* eslint-disable no-await-in-loop */ +import { Member, TextableChannel } from 'eris'; +import { Client } from '../class'; + +let interval: NodeJS.Timeout; + +async function setupDepartmentCodes(client: Client): Promise { + const directorPagers = await client.db.PagerNumber.findOne({ num: '00' }).lean().exec(); + const supervisorPagers = await client.db.PagerNumber.findOne({ num: '01' }).lean().exec(); + const technicianPagers = await client.db.PagerNumber.findOne({ num: '10' }).lean().exec(); + const moderatorPagers = await client.db.PagerNumber.findOne({ num: '20' }).lean().exec(); + const coreTeamPagers = await client.db.PagerNumber.findOne({ num: '21' }).lean().exec(); + const associatePagers = await client.db.PagerNumber.findOne({ num: '22' }).lean().exec(); + + if (!directorPagers) { + const setup = new client.db.PagerNumber({ + num: '00', + individualAssignID: '', + emailAddresses: [], + discordIDs: [], + }); + await setup.save(); + } + if (!supervisorPagers) { + const setup = new client.db.PagerNumber({ + num: '01', + individualAssignID: '', + emailAddresses: [], + discordIDs: [], + }); + await setup.save(); + } + if (!technicianPagers) { + const setup = new client.db.PagerNumber({ + num: '10', + individualAssignID: '', + emailAddresses: [], + discordIDs: [], + }); + setup.save(); + } + if (!moderatorPagers) { + const setup = new client.db.PagerNumber({ + num: '20', + individualAssignID: '', + emailAddresses: [], + discordIDs: [], + }); + await setup.save(); + } + if (!coreTeamPagers) { + const setup = new client.db.PagerNumber({ + num: '21', + individualAssignID: '', + emailAddresses: [], + discordIDs: [], + }); + await setup.save(); + } + if (!associatePagers) { + const setup = new client.db.PagerNumber({ + num: '22', + individualAssignID: '', + emailAddresses: [], + discordIDs: [], + }); + await setup.save(); + } +} + +function logNewPager(client: Client, num: string, member: Member): void { + client.util.signale.log(`Pager Number '${num}' created for '${member.user.username}#${member.user.discriminator}'.`); + const channel = client.guilds.get(client.config.guildID).channels.get('722636436716781619'); + channel.createMessage(`__**'${member.user.username}#${member.user.discriminator}' assigned to pager number '${num}'.**__`); +} + +export default async function departmentPager(client: Client): Promise { + setupDepartmentCodes(client); + // eslint-disable-next-line no-shadow + async function start(client: Client) { + async function resolveStaffInformation(id: string) { + return client.db.Staff.findOne({ userID: id }); // acknowledgements.find((m) => m.id === id); + } + await client.guilds.get(client.config.guildID).fetchAllMembers(); + const members = client.guilds.get(client.config.guildID).members.map((m) => m); + + for (const member of members) { + const pager = await client.db.PagerNumber.findOne({ individualAssignID: member.id }).lean().exec(); + if (!pager) continue; + if (pager.num.startsWith('00') && !member.roles.includes('662163685439045632')) { + await client.db.PagerNumber.deleteOne({ num: pager.num }); + client.util.signale.log(`Pager Number '${pager.num}' has been deleted.`); + } + if (pager.num.startsWith('01') && !member.roles.includes('701454855952138300')) { + await client.db.PagerNumber.deleteOne({ num: pager.num }); + client.util.signale.log(`Pager Number '${pager.num}' has been deleted.`); + } + if (pager.num.startsWith('10') && !member.roles.includes('701454780828221450')) { + await client.db.PagerNumber.deleteOne({ num: pager.num }); + client.util.signale.log(`Pager Number '${pager.num}' has been deleted.`); + } + if (pager.num.startsWith('20') && !member.roles.includes('455972169449734144')) { + await client.db.PagerNumber.deleteOne({ num: pager.num }); + client.util.signale.log(`Pager Number '${pager.num}' has been deleted.`); + } + if (pager.num.startsWith('21') && !member.roles.includes('453689940140883988')) { + await client.db.PagerNumber.deleteOne({ num: pager.num }); + client.util.signale.log(`Pager Number '${pager.num}' has been deleted.`); + } + if (pager.num.startsWith('22') && !member.roles.includes('701481967149121627')) { + await client.db.PagerNumber.deleteOne({ num: pager.num }); + client.util.signale.log(`Pager Number '${pager.num}' has been deleted.`); + } + } + + for (const member of members) { + let pager = await client.db.PagerNumber.findOne({ individualAssignID: member.id }).lean().exec(); + // Directors + if (!pager && member.roles.includes('662163685439045632')) { + let randomPagerNumber: string; + let status = true; + // eslint-disable-next-line no-constant-condition + while (status) { + randomPagerNumber = `00${String(Math.floor(Math.random() * 9) + 1)}`; + const check = await client.db.PagerNumber.findOne({ num: randomPagerNumber }); + if (check) status = false; + if (check?.num !== randomPagerNumber) status = false; + } + const acknowledgement = await resolveStaffInformation(member.id); + if (!acknowledgement || !acknowledgement.emailAddress) continue; + const newNumber = new client.db.PagerNumber({ + num: randomPagerNumber, + individualAssignID: member.id, + emailAddresses: [acknowledgement.emailAddress], + discordIDs: [member.id], + }); + if (await client.db.PagerNumber.findOne({ num: randomPagerNumber })) continue; + pager = await newNumber.save(); + logNewPager(client, randomPagerNumber, member); + client.getDMChannel(member.id).then((chan) => { + chan.createMessage(`__**Pager Number Creation**__\nYour individual pager number has been automatically created. Your number (PN) is ${randomPagerNumber}.`); + }); + } else if (!pager && member.roles.includes('701454855952138300')) { + // Supervisors + let randomPagerNumber: string; + let status = true; + // eslint-disable-next-line no-constant-condition + while (status) { + randomPagerNumber = `01${String(Math.floor(Math.random() * 9) + 1)}`; + const check = await client.db.PagerNumber.findOne({ num: randomPagerNumber }); + if (check) status = false; + if (check?.num !== randomPagerNumber) status = false; + } + const acknowledgement = await resolveStaffInformation(member.id); + if (!acknowledgement || !acknowledgement.emailAddress) continue; + const newNumber = new client.db.PagerNumber({ + num: randomPagerNumber, + individualAssignID: member.id, + emailAddresses: [acknowledgement.emailAddress], + discordIDs: [member.id], + }); + if (await client.db.PagerNumber.findOne({ num: randomPagerNumber })) continue; + logNewPager(client, randomPagerNumber, member); + pager = await newNumber.save(); + client.getDMChannel(member.id).then((chan) => { + chan.createMessage(`__**Pager Number Creation**__\nYour individual pager number has been automatically created. Your number (PN) is ${randomPagerNumber}.`); + }); + } else if (!pager && member.roles.includes('701454780828221450')) { + // Technicians + let randomPagerNumber: string; + let status = true; + // eslint-disable-next-line no-constant-condition + while (status) { + randomPagerNumber = `10${String(Math.floor(Math.random() * 99) + 1)}`; + if (randomPagerNumber.length === 3) randomPagerNumber = `${randomPagerNumber}0`; + const check = await client.db.PagerNumber.findOne({ num: randomPagerNumber }); + if (check) status = false; + if (check?.num !== randomPagerNumber) status = false; + } + const acknowledgement = await resolveStaffInformation(member.id); + if (!acknowledgement || !acknowledgement.emailAddress) continue; + const newNumber = new client.db.PagerNumber({ + num: randomPagerNumber, + individualAssignID: member.id, + emailAddresses: [acknowledgement.emailAddress], + discordIDs: [member.id], + }); + if (await client.db.PagerNumber.findOne({ num: randomPagerNumber })) continue; + logNewPager(client, randomPagerNumber, member); + pager = await newNumber.save(); + client.getDMChannel(member.id).then((chan) => { + chan.createMessage(`__**Pager Number Creation**__\nYour individual pager number has been automatically created. Your number (PN) is ${randomPagerNumber}.`); + }); + } else if (!pager && member.roles.includes('455972169449734144')) { + // Moderators + let randomPagerNumber: string; + let status = true; + // eslint-disable-next-line no-constant-condition + while (status) { + randomPagerNumber = `20${String(Math.floor(Math.random() * 99) + 1)}`; + if (randomPagerNumber.length === 3) randomPagerNumber = `${randomPagerNumber}0`; + const check = await client.db.PagerNumber.findOne({ num: randomPagerNumber }); + if (check) status = false; + if (check?.num !== randomPagerNumber) status = false; + } + const acknowledgement = await resolveStaffInformation(member.id); + if (!acknowledgement || !acknowledgement.emailAddress) continue; + const newNumber = new client.db.PagerNumber({ + num: randomPagerNumber, + individualAssignID: member.id, + emailAddresses: [acknowledgement.emailAddress], + discordIDs: [member.id], + }); + if (await client.db.PagerNumber.findOne({ num: randomPagerNumber })) continue; + logNewPager(client, randomPagerNumber, member); + pager = await newNumber.save(); + client.getDMChannel(member.id).then((chan) => { + chan.createMessage(`__**Pager Number Creation**__\nYour individual pager number has been automatically created. Your number (PN) is ${randomPagerNumber}.`); + }); + } else if (!pager && member.roles.includes('453689940140883988')) { + // Core Team + let randomPagerNumber: string; + let status = true; + // eslint-disable-next-line no-constant-condition + while (status) { + randomPagerNumber = `21${String(Math.floor(Math.random() * 999) + 1)}`; + if (randomPagerNumber.length === 4) randomPagerNumber = `${randomPagerNumber}0`; + const check = await client.db.PagerNumber.findOne({ num: randomPagerNumber }); + if (check) status = false; + if (check?.num !== randomPagerNumber) status = false; + } + const acknowledgement = await resolveStaffInformation(member.id); + if (!acknowledgement || !acknowledgement.emailAddress) continue; + const newNumber = new client.db.PagerNumber({ + num: randomPagerNumber, + individualAssignID: member.id, + emailAddresses: [acknowledgement.emailAddress], + discordIDs: [member.id], + }); + if (await client.db.PagerNumber.findOne({ num: randomPagerNumber })) continue; + logNewPager(client, randomPagerNumber, member); + pager = await newNumber.save(); + client.getDMChannel(member.id).then((chan) => { + chan.createMessage(`__**Pager Number Creation**__\nYour individual pager number has been automatically created. Your number (PN) is ${randomPagerNumber}.`); + }); + } else if (!pager && member.roles.includes('701481967149121627')) { + // Associates + let randomPagerNumber: string; + let status = true; + // eslint-disable-next-line no-constant-condition + while (status) { + randomPagerNumber = `22${String(Math.floor(Math.random() * 999) + 1)}`; + if (randomPagerNumber.length === 4) randomPagerNumber = `${randomPagerNumber}0`; + const check = await client.db.PagerNumber.findOne({ num: randomPagerNumber }); + if (check) status = false; + if (check?.num !== randomPagerNumber) status = false; + } + const acknowledgement = await resolveStaffInformation(member.id); + if (!acknowledgement || !acknowledgement.emailAddress) continue; + const newNumber = new client.db.PagerNumber({ + num: randomPagerNumber, + individualAssignID: member.id, + emailAddresses: [acknowledgement.emailAddress], + discordIDs: [member.id], + }); + if (await client.db.PagerNumber.findOne({ num: randomPagerNumber })) continue; + logNewPager(client, randomPagerNumber, member); + pager = await newNumber.save(); + client.getDMChannel(member.id).then((chan) => { + chan.createMessage(`__**Pager Number Creation**__\nYour individual pager number has been automatically created. Your number (PN) is ${randomPagerNumber}.`); + }); + } + } + + // Associates + const associatePagers = await client.db.PagerNumber.findOne({ num: '22' }); + for (const member of members) { + if (member.roles.includes('701481967149121627') && !associatePagers.discordIDs.includes(member.id)) { + await associatePagers.updateOne({ $addToSet: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await associatePagers.updateOne({ $addToSet: { emailAddresses: acknowledgement.emailAddress } }); + } + if (!member.roles.includes('701481967149121627') && associatePagers.discordIDs.includes(member.id)) { + await associatePagers.updateOne({ $pull: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await associatePagers.updateOne({ $pull: { emailAddresses: acknowledgement.emailAddress } }); + } + } + // Core Team + const coreTeamPagers = await client.db.PagerNumber.findOne({ num: '21' }); + for (const member of members) { + if (member.roles.includes('453689940140883988') && !coreTeamPagers.discordIDs.includes(member.id)) { + await coreTeamPagers.updateOne({ $addToSet: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await coreTeamPagers.updateOne({ $addToSet: { emailAddresses: acknowledgement.emailAddress } }); + } + if (!member.roles.includes('453689940140883988') && coreTeamPagers.discordIDs.includes(member.id)) { + await coreTeamPagers.updateOne({ $pull: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await coreTeamPagers.updateOne({ $pull: { emailAddresses: acknowledgement.emailAddress } }); + } + } + // Moderator + const moderatorPagers = await client.db.PagerNumber.findOne({ num: '20' }); + for (const member of members) { + if (member.roles.includes('455972169449734144') && !moderatorPagers.discordIDs.includes(member.id)) { + await moderatorPagers.updateOne({ $addToSet: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await moderatorPagers.updateOne({ $addToSet: { emailAddresses: acknowledgement.emailAddress } }); + } + if (!member.roles.includes('455972169449734144') && moderatorPagers.discordIDs.includes(member.id)) { + await moderatorPagers.updateOne({ $pull: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await moderatorPagers.updateOne({ $pull: { emailAddresses: acknowledgement.emailAddress } }); + } + } + // Technician + const technicianPagers = await client.db.PagerNumber.findOne({ num: '10' }); + for (const member of members) { + if (member.roles.includes('701454780828221450') && !technicianPagers.discordIDs.includes(member.id)) { + await technicianPagers.updateOne({ $addToSet: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await technicianPagers.updateOne({ $addToSet: { emailAddresses: acknowledgement.emailAddress } }); + } + if (!member.roles.includes('701454780828221450') && technicianPagers.discordIDs.includes(member.id)) { + await technicianPagers.updateOne({ $pull: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await technicianPagers.updateOne({ $pull: { emailAddresses: acknowledgement.emailAddress } }); + } + } + // Supervisor + const supervisorPagers = await client.db.PagerNumber.findOne({ num: '01' }); + for (const member of members) { + if (member.roles.includes('701454855952138300') && !supervisorPagers.discordIDs.includes(member.id)) { + await supervisorPagers.updateOne({ $addToSet: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await supervisorPagers.updateOne({ $addToSet: { emailAddresses: acknowledgement.emailAddress } }); + } + if (!member.roles.includes('701454855952138300') && supervisorPagers.discordIDs.includes(member.id)) { + await supervisorPagers.updateOne({ $pull: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await supervisorPagers.updateOne({ $pull: { emailAddresses: acknowledgement.emailAddress } }); + } + } + // Board of Directors + const directorPagers = await client.db.PagerNumber.findOne({ num: '00' }); + for (const member of members) { + if (member.roles.includes('662163685439045632') && !directorPagers.discordIDs.includes(member.id)) { + await directorPagers.updateOne({ $addToSet: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await directorPagers.updateOne({ $addToSet: { emailAddresses: acknowledgement.emailAddress } }); + } + if (!member.roles.includes('662163685439045632') && directorPagers.discordIDs.includes(member.id)) { + await directorPagers.updateOne({ $pull: { discordIDs: member.id } }); + const acknowledgement = await resolveStaffInformation(member.id); + if (acknowledgement?.emailAddress) await directorPagers.updateOne({ $pull: { emailAddresses: acknowledgement.emailAddress } }); + } + } + } + client.util.signale.time('pagerint'); + await start(client); + client.util.signale.timeEnd('pagerint'); + interval = setInterval(async () => { + client.util.signale.time('pagerint'); + await start(client); + client.util.signale.timeEnd('pagerint'); + }, 300000); + return interval; +} diff --git a/src/intervals/fetchMembers.ts b/src/intervals/fetchMembers.ts new file mode 100644 index 0000000..0068b95 --- /dev/null +++ b/src/intervals/fetchMembers.ts @@ -0,0 +1,11 @@ +import { Client } from '../class'; + +let interval: NodeJS.Timeout; + +export default async function fetchMembers(client: Client): Promise { + // await client.guilds.get(client.config.guildID)?.fetchAllMembers(); + interval = setInterval(async () => { + // await client.guilds.get(client.config.guildID).fetchAllMembers(); + }, 1800000); + return interval; +} diff --git a/src/intervals/score.ts b/src/intervals/score.ts new file mode 100644 index 0000000..61bbb7a --- /dev/null +++ b/src/intervals/score.ts @@ -0,0 +1,202 @@ +/* eslint-disable no-plusplus */ +/* eslint-disable no-continue */ +/* eslint-disable one-var-declaration-per-line */ +/* eslint-disable no-await-in-loop */ +import axios from 'axios'; +import { Message, TextChannel } from 'eris'; +import { Client } from '../class'; + +interface CSResponse { + found: boolean, + tier: number, + totalReferrals?: number, + createdAt?: Date, + warns?: Date[], + locks?: Date[], + deletes?: Date[], +} + +let interval: NodeJS.Timeout; + +export function getTotalMessageCount(client: Client): number { + const projectChannels: TextChannel[] = []; + for (const c of client.guilds.get(client.config.guildID).channels.filter((ch) => ch.type === 0)) { + const chan = c; + if (chan.parentID !== '701591772186411108') continue; + projectChannels.push(chan); + } + return (projectChannels.length * 200) + 2200 + 300 + 100 + 200; +} + +export default async function calculateScore(client: Client): Promise { + const start = async () => { + const { members } = client.guilds.get(client.config.guildID); + + const general = await ( client.guilds.get(client.config.guildID).channels.get('485680288123584525')).getMessages(2200); + const programmingSupport = await ( client.guilds.get(client.config.guildID).channels.get('506970598631538708')).getMessages(300); + const programmingSupport2 = await ( client.guilds.get(client.config.guildID).channels.get('554892820553531422')).getMessages(100); + const cloudSupport = await ( client.guilds.get(client.config.guildID).channels.get('546457788184789013')).getMessages(200); + + const projectChannels: TextChannel[] = []; + for (const c of client.guilds.get(client.config.guildID).channels.filter((ch) => ch.type === 0)) { + const chan = c; + if (chan.parentID !== '701591772186411108') continue; + projectChannels.push(chan); + } + const projectMessages: Message[] = []; + for (const chan of projectChannels) { + const msg = await chan.getMessages(200); + projectMessages.push(...msg); + } + for (const member of members.values()) { + if (member.bot) continue; + let score = await client.db.Score.findOne({ userID: member.user.id }); + if (!score) { + const data: { + userID: string, + total: number, + activity: number, + roles: number, + moderation: number, + cloudServices: number, + other: number, + staff: boolean, + locked: boolean, + notify: boolean, + // inquiries: [{ name: string, reason: string, date: Date }?], + // softInquiries: [{name: string, date: Date }?] + lastUpdated: Date, + pin: number[], + /* generalMessagesRatio: number, + supportMessagesRatio: number, + totalModerations: number, + notes: number, */ + } = { + userID: member.user.id, + total: 0, + activity: 0, + roles: 0, + moderation: 0, + cloudServices: 0, + other: 0, + staff: false, + locked: false, + notify: false, + // inquiries: [], + // softInquiries: [], + lastUpdated: new Date(), + pin: [client.util.randomNumber(100, 999), client.util.randomNumber(10, 99), client.util.randomNumber(1000, 9999)], + }; + score = await (new client.db.Score(data)).save(); + client.util.signale.debug(`SCORE INIT - ${member.username}`); + } + + // eslint-disable-next-line prefer-const + // eslint-disable-next-line one-var-declaration-per-line + // eslint-disable-next-line one-var + let total = 0, activity = 0, roles = 0, moderation = 0, cloudServices = 0, other = 0, staff = 0; + cloudServices = 0; + + roles = Math.floor(member.roles.length * 0.50); + if (roles > 54) roles = 54; + + const moderations = await client.db.Moderation.find({ userID: member.user.id }); + let activeMods = 0; + for (const mod of moderations) { + if (mod.type === 1 || mod.type === 4) continue; + const testDate = (new Date(new Date(mod.date).setHours(2190))); + if (testDate > new Date()) { moderation -= 15; activeMods++; } + } + if (activeMods <= 0) moderation = 2; + + let messageCountTotal = 0; + const allMessagesArray = [...general, ...programmingSupport, ...programmingSupport2, ...cloudSupport, ...projectMessages]; + + for (const msg of allMessagesArray.filter((m) => m.member?.id === member.id)) { + if (!msg.content) continue; + if (msg.content.length <= 5) continue; + // eslint-disable-next-line no-plusplus + messageCountTotal++; + } + + const activityTotal = messageCountTotal; + activity = Math.floor(Math.log1p(activityTotal) * 12); + if (activity > (Math.log1p(getTotalMessageCount(client)) * 12)) activity = Math.floor((Math.log1p(getTotalMessageCount(client)) * 12)); + if (member.roles.includes('446104438969466890') || member.roles.includes('701481967149121627')) staff = 20; + + const response = (await axios.get(`https://api.cloud.libraryofcode.org/wh/score?id=${member.user.id}&authorization=${client.config.internalKey}`)).data; + if (response.found === true) { + let negatives = 0; + let positives = 0; + if (response.createdAt) { + const csCreatedTestDate = (new Date(new Date(response.createdAt).setHours(730))); + if (csCreatedTestDate > new Date()) { + negatives -= 10; + } + } + let warns = 0; + for (const warn of response.warns) { + const date = (new Date(new Date(warn).setHours(730))); + if (date > new Date()) { negatives -= 2; warns++; } + } + if (warns <= 0) positives += 2; + if (response.warns.length === 0) positives += 3; + + let locks = 0; + for (const lock of response.locks) { + const date = (new Date(new Date(lock).setHours(1460))); + if (date > new Date()) { negatives -= 5; locks++; } + } + if (locks <= 0) positives += 3; + if (response.locks.length === 0) positives += 7; + + if (response.deletes.length > 0) { + for (const del of response.deletes) { + const date = (new Date(new Date(del).setHours(3650))); + if (date > new Date()) negatives -= 20; + } + } + positives += Math.floor(response.totalReferrals * 1.17); + if (response.tier === 2) positives += 2; + else if (response.tier === 3) positives += 3; + + if (negatives < 0) cloudServices = Math.floor((negatives * 1.2) + (positives * 0.06)); + else cloudServices = Math.floor(positives * 0.61); + } + + if (score.inquiries?.length > 0) { + for (const inq of score.inquiries) { + const testDate = (new Date(new Date(inq.date).setHours(1460))); + if (testDate > new Date()) other -= 1.65; + } + other = Math.floor(other * 1.15); + } + + const discordAccountCreatedAtTestDate = (new Date(new Date(new Date(member.user.createdAt)).setHours(730))); + if (discordAccountCreatedAtTestDate > new Date()) { + other -= 10; + } + + const joinedAtTestDate = (new Date(new Date(new Date(member.joinedAt)).setHours(420))); + if (joinedAtTestDate > new Date()) { + other -= 30; + } + + total = Math.floor(((total + activity + roles + moderation + cloudServices + staff + other) * 5.13) * 1.87); + + /* await score.updateOne({ $set: { total, activity, roles, moderation, cloudServices, other, staff, lastUpdate: new Date() } }); + if (!score.pin || score.pin?.length < 1) { + await client.db.Score.updateOne({ userID: member.id }, { $set: { pin: [client.util.randomNumber(100, 999), client.util.randomNumber(10, 99), client.util.randomNumber(1000, 9999)] } }); + } + */ + + client.queue.updateScore(score, total, activity, roles, moderation, cloudServices, other, staff); + // client.util.signale.debug(`SCORE SET - ${member.username}\nTotal: ${total}\nActivity: ${activity}\nRoles: ${roles}\nModeration: ${moderation}\nCloud Services: ${cloudServices}\nStaff: ${staff}`); + } + }; + await start(); + interval = setInterval(async () => { + await start(); + }, 1800000); + return interval; +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..3f4131d --- /dev/null +++ b/src/main.ts @@ -0,0 +1,29 @@ +/* DM Ramirez with the code below to claim 500 free Rubies! + d2c3d8e14b +*/ +import sdNotify from 'sd-notify'; +import { parse } from 'yaml'; +import { promises as fs } from 'fs'; +import { join } from 'path'; +import { Client } from './class'; +import * as eventFiles from './events'; +import * as commandFiles from './commands'; +import { Config } from '../types'; // eslint-disable-line + +async function main(): Promise { + if (process.env.PRODUCTION) { + sdNotify.ready(); + sdNotify.startWatchdogMode(2500); + } + const read = await fs.readFile(join(__dirname, '../config.yaml'), 'utf8'); + const config: Config = parse(read); + const client = new Client(config.token, { defaultImageFormat: 'png', restMode: true, intents: ['guildBans', 'guildEmojis', 'guildInvites', 'guildMembers', 'guildMessageReactions', 'guildMessages', 'guildPresences', 'guildWebhooks', 'guilds', 'directMessages'] }); + client.config = config; + await client.loadDatabase(); + client.loadPlugins(); + await client.loadEvents(eventFiles); + await client.loadCommands(commandFiles); + client.connect(); +} + +main(); diff --git a/src/models/Customer.ts b/src/models/Customer.ts new file mode 100644 index 0000000..b7525b2 --- /dev/null +++ b/src/models/Customer.ts @@ -0,0 +1,13 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface CustomerInterface extends Document { + cusID: string, + userID: string, +} + +const Customer: Schema = new Schema({ + cusID: String, + userID: String, +}); + +export default model('Customer', Customer); diff --git a/src/models/CustomerPortal.ts b/src/models/CustomerPortal.ts new file mode 100644 index 0000000..ca96881 --- /dev/null +++ b/src/models/CustomerPortal.ts @@ -0,0 +1,21 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface CustomerPortalInterface extends Document { + key: string, + username: string, + userID: string, + emailAddress: string, + expiresOn: Date, + used?: boolean, +} + +const CustomerPortal: Schema = new Schema({ + key: String, + username: String, + userID: String, + emailAddress: String, + expiresOn: Date, + used: Boolean, +}); + +export default model('CustomerPortal', CustomerPortal); diff --git a/src/models/ExecutiveOrder.ts b/src/models/ExecutiveOrder.ts new file mode 100644 index 0000000..39f7b39 --- /dev/null +++ b/src/models/ExecutiveOrder.ts @@ -0,0 +1,21 @@ +import { Document, model, Schema } from 'mongoose'; + +export interface ExecutiveOrderInterface extends Document { + issuer: string; + subject: string; + body: string; + at: number; + oID: string; + msg: string; +} + +const ExecutiveOrder = new Schema({ + issuer: { type: String, required: true }, + subject: { type: String, required: true }, + body: { type: String, required: true }, + at: { type: Number, required: true }, + oID: { type: String, required: true, unique: true }, + msg: { type: String, required: true, unique: true }, +}); + +export default model('ExecutiveOrders', ExecutiveOrder); diff --git a/src/models/File.ts b/src/models/File.ts new file mode 100644 index 0000000..f2fa685 --- /dev/null +++ b/src/models/File.ts @@ -0,0 +1,21 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface FileInterface extends Document { + name: string, + identifier: string, + mimeType: string, + data: Buffer, + downloaded: number, + maxDownloads: number, +} + +const File: Schema = new Schema({ + name: String, + identifier: String, + mimeType: String, + data: Buffer, + downloaded: Number, + maxDownloads: Number, +}); + +export default model('File', File); diff --git a/src/models/Inquiry.ts b/src/models/Inquiry.ts new file mode 100644 index 0000000..318bcec --- /dev/null +++ b/src/models/Inquiry.ts @@ -0,0 +1,33 @@ +import { Document, Schema, model } from 'mongoose'; +import { ScoreInterfaceRaw } from '.'; + +export enum InqType { + HARD, + SOFT, +} + +export interface InquiryInterface extends Document { + iid?: string, + userID: string, + /** + * - 0: Hard + * - 1: Soft + */ + type: InqType, + name: string, + reason?: string, + date: Date, + report?: ScoreInterfaceRaw, +} + +const Inquiry: Schema = new Schema({ + iid: String, + userID: String, + type: Number, + name: String, + reason: String, + date: String, + report: Object, +}); + +export default model('Inquiry', Inquiry); diff --git a/src/models/Member.ts b/src/models/Member.ts new file mode 100644 index 0000000..6c41590 --- /dev/null +++ b/src/models/Member.ts @@ -0,0 +1,25 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface MemberInterface extends Document { + userID: string, + additional: { + langs: ['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'], + operatingSystems: ['arch', 'deb', 'cent', 'fedora', 'manjaro', 'mdarwin', 'redhat', 'ubuntu', 'win'], + github: string, + gitlab: string, + bio: string, + }, +} + +const Member: Schema = new Schema({ + userID: String, + additional: { + langs: Array, + operatingSystems: Array, + github: String, + gitlab: String, + bio: String, + }, +}); + +export default model('Member', Member); diff --git a/src/models/Merchant.ts b/src/models/Merchant.ts new file mode 100644 index 0000000..c78274a --- /dev/null +++ b/src/models/Merchant.ts @@ -0,0 +1,24 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface MerchantInterface extends Document { + name: string, + key: string, + privileged: boolean, + /** + * type + * - 0: soft + * - 1: hard + */ + type: 0 | 1; + pulls: [{ type: 0 | 1, reason: string, date: Date }], +} + +const Merchant: Schema = new Schema({ + name: String, + key: String, + privileged: Boolean, + type: Number, + pulls: Array, +}); + +export default model('Merchant', Merchant); diff --git a/src/models/Moderation.ts b/src/models/Moderation.ts new file mode 100644 index 0000000..83399d5 --- /dev/null +++ b/src/models/Moderation.ts @@ -0,0 +1,37 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface ModerationInterface extends Document { + userID: string, + logID: string, + moderatorID: string, + reason: string, + /** + * @field 0 - Warn + * @field 1 - Unmute + * @field 2 - Mute + * @field 3 - Kick + * @field 4 - Unban + * @field 5 - Ban + */ + type: 0 | 1 | 2 | 3 | 4 | 5 + date: Date, + expiration?: { + date: Date, + processed: boolean + } +} + +const Moderation: Schema = new Schema({ + userID: String, + logID: String, + moderatorID: String, + reason: String, + type: Number, + date: Date, + expiration: { + date: Date, + processed: Boolean, + }, +}); + +export default model('Moderation', Moderation); diff --git a/src/models/Motion.ts b/src/models/Motion.ts new file mode 100644 index 0000000..e60685a --- /dev/null +++ b/src/models/Motion.ts @@ -0,0 +1,35 @@ +import { Document, model, Schema } from 'mongoose'; + +export interface MotionInterface extends Document { + issuer: string; + subject: string; + body: string; + at: number; + oID: string; + results?: { + yea: number; + nay: number; + present: number; + absent: number; + }; + processed: boolean; + msg: string; +} + +const Motion = new Schema({ + issuer: { type: String, required: true }, + subject: { type: String, required: true }, + body: { type: String, required: true }, + at: { type: Number, required: true }, + oID: { type: String, required: true, unique: true }, + results: { + yea: Number, + nay: Number, + present: Number, + absent: Number, + }, + processed: Boolean, + msg: { required: true, unique: true, type: String }, +}); + +export default model('Motions', Motion); diff --git a/src/models/NNTrainingData.ts b/src/models/NNTrainingData.ts new file mode 100644 index 0000000..fbff45b --- /dev/null +++ b/src/models/NNTrainingData.ts @@ -0,0 +1,13 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface NNTrainingDataInterface extends Document { + name: string, + data: [{ input: any, output: object }]; +} + +const NNTrainingData: Schema = new Schema({ + name: String, + data: Array, +}); + +export default model('NNTrainingData', NNTrainingData); diff --git a/src/models/Note.ts b/src/models/Note.ts new file mode 100644 index 0000000..2793b32 --- /dev/null +++ b/src/models/Note.ts @@ -0,0 +1,19 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface NoteInterface extends Document { + userID: string, + staffID: string, + date: Date, + category: 'comm' | 'cs' | 'edu' | '', + text: string, +} + +const Note: Schema = new Schema({ + userID: String, + staffID: String, + date: Date, + category: String, + text: String, +}); + +export default model('Note', Note); diff --git a/src/models/PagerNumber.ts b/src/models/PagerNumber.ts new file mode 100644 index 0000000..0d0cc16 --- /dev/null +++ b/src/models/PagerNumber.ts @@ -0,0 +1,32 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface PagerNumberRaw { + num: string, + // This field will be "" if the pager number doesn't belong to an individual user + individualAssignID: string, + emailAddresses: string[], + discordIDs: string[], + receiveEmail: boolean, + receivePhone: boolean, +} + +export interface PagerNumberInterface extends Document { + num: string, + // This field will be "" if the pager number doesn't belong to an individual user + individualAssignID: string, + emailAddresses: string[], + discordIDs: string[], + receiveEmail: boolean, + receivePhone: boolean, +} + +const PagerNumber: Schema = new Schema({ + num: String, + individualAssignID: String, + emailAddresses: Array, + discordIDs: Array, + receiveEmail: Boolean, + receivePhone: Boolean, +}); + +export default model('PagerNumber', PagerNumber); diff --git a/src/models/Proclamation.ts b/src/models/Proclamation.ts new file mode 100644 index 0000000..fbc0fdf --- /dev/null +++ b/src/models/Proclamation.ts @@ -0,0 +1,39 @@ +import { Document, model, Schema } from 'mongoose'; + +export interface ProclamationInterface extends Document { + issuer: string; + subject: string; + body: string; + at: number; + oID: string; + results?: { + yea: number; + nay: number; + present: number; + absent: number; + }; + acceptedAt: number; + msg: string; + processed?: boolean; + votedDirectors: string[]; +} + +const Proclamation = new Schema({ + issuer: { type: String, required: true }, + subject: { type: String, required: true }, + body: { type: String, required: true }, + at: { type: Number, required: true }, + oID: { type: String, required: true, unique: true }, + results: { + yea: Number, + nay: Number, + present: Number, + absent: Number, + }, + acceptedAt: Number, + msg: { type: String, required: true, unique: true }, + processed: Boolean, + votedDirectors: Array, +}); + +export default model('Proclamations', Proclamation); diff --git a/src/models/Promo.ts b/src/models/Promo.ts new file mode 100644 index 0000000..adbe2b0 --- /dev/null +++ b/src/models/Promo.ts @@ -0,0 +1,13 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface PromoInterface extends Document { + code: string, + pID: string, +} + +const Promo: Schema = new Schema({ + code: String, + pID: String, +}); + +export default model('Promo', Promo); diff --git a/src/models/Rank.ts b/src/models/Rank.ts new file mode 100644 index 0000000..ffdf443 --- /dev/null +++ b/src/models/Rank.ts @@ -0,0 +1,17 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface RankInterface extends Document { + name: string, + roleID: string, + permissions: string[], + description: string, +} + +const Rank: Schema = new Schema({ + name: String, + roleID: String, + permissions: Array, + description: String, +}); + +export default model('Rank', Rank); diff --git a/src/models/Redirect.ts b/src/models/Redirect.ts new file mode 100644 index 0000000..f17b77f --- /dev/null +++ b/src/models/Redirect.ts @@ -0,0 +1,21 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface RedirectInterface extends Document { + key: string, + to: string, + visitedCount: number, +} + +export interface RedirectRaw { + key: string, + to: string, + visitedCount: number, +} + +const Redirect: Schema = new Schema({ + key: String, + to: String, + visitedCount: Number, +}); + +export default model('Redirect', Redirect); diff --git a/src/models/Resolution.ts b/src/models/Resolution.ts new file mode 100644 index 0000000..b9f472c --- /dev/null +++ b/src/models/Resolution.ts @@ -0,0 +1,33 @@ +import { Document, model, Schema } from 'mongoose'; + +export interface ResolutionInterface extends Document { + issuer: string; + subject: string; + body: string; + at: number; + oID: string; + results: { + yea: number; + nay: number; + present: number; + absent: number; + }; + msg: string; +} + +const Resolution = new Schema({ + issuer: { type: String, required: true }, + subject: { type: String, required: true }, + body: { type: String, required: true }, + at: { type: Number, required: true }, + oID: { type: String, required: true, unique: true }, + results: { + yea: Number, + Nay: Number, + present: Number, + absent: Number, + }, + msg: { type: String, required: true, unique: true }, +}); + +export default model('Resolutions', Resolution); diff --git a/src/models/Score.ts b/src/models/Score.ts new file mode 100644 index 0000000..96820d2 --- /dev/null +++ b/src/models/Score.ts @@ -0,0 +1,95 @@ +// Community Score + +import { Document, Schema, model } from 'mongoose'; + +export interface Inquiry { + id?: string, + name: string, + reason: string, + date: Date, + report: ScoreInterfaceRaw, +} + +export interface ScoreInterfaceRaw { + userID: string + /** + * total will be between 800-200 - 0 signfies "No Score", too little information is available or other variable are too low + * - CALCULATION: `(COMBINED SUBSCORES x 5) * 5.13; Math.floor()` + */ + total: number, + /** + * 10 - 55 + */ + activity: number, + /** + * 0 - 54 + */ + roles: number, + /** + * -50 - 2 + * all users start out with 2 moderation points, the number of points decreases for each moderation. + */ + moderation: number, + /** + * -20 - 50 + * processed by CSD + */ + cloudServices: number, + // 0 or 20, 20 points are added if the user is a staff member + staff: number, + other: number, + notify: boolean, + locked: boolean, + inquiries: [ Inquiry ], + softInquiries: [{ name: string, date: Date }], + lastUpdate: Date, + pin: number[], +} + + +export interface ScoreInterface extends Document { + userID: string + total: number, + activity: number, + roles: number, + moderation: number, + cloudServices: number, + staff: number, + other: number, + notify: boolean, + locked: boolean, + inquiries: [ Inquiry ], + softInquiries: [{ name: string, date: Date }], + lastUpdate: Date, + pin: number[], + + // general & media + /* generalMessagesRatio: number, + // programming-support channels and cloud-support + supportMessagesRatio: number, + totalModerations: number, + notes: number, */ +} + +const Score: Schema = new Schema({ + userID: String, + total: Number, + activity: Number, + roles: Number, + moderation: Number, + cloudServices: Number, + staff: Number, + other: Number, + notify: Boolean, + locked: Boolean, + inquiries: Array, + softInquiries: Array, + lastUpdate: Date, + pin: Array, + /* generalMessagesRatio: Number, + supportMessagesRatio: Number, + totalModerations: Number, + notes: Number, */ +}); + +export default model('Score', Score); diff --git a/src/models/ScoreHistorical.ts b/src/models/ScoreHistorical.ts new file mode 100644 index 0000000..70b5a6a --- /dev/null +++ b/src/models/ScoreHistorical.ts @@ -0,0 +1,25 @@ +import { Document, Schema, model, Types } from 'mongoose'; +import { ScoreInterfaceRaw } from '.'; + +export interface ScoreHistoricalRaw { + userID: string, + report: ScoreInterfaceRaw, + inquiries: [ Types.ObjectId ], + date: Date, +} + +export interface ScoreHistoricalInterface extends Document { + userID: string, + report: ScoreInterfaceRaw, + inquiries: [ Types.ObjectId ], + date: Date +} + +const ScoreHistorical: Schema = new Schema({ + userID: String, + report: Object, + inquiries: Array, + date: Date, +}); + +export default model('ScoreHistorical', ScoreHistorical); diff --git a/src/models/Staff.ts b/src/models/Staff.ts new file mode 100644 index 0000000..83c8c2e --- /dev/null +++ b/src/models/Staff.ts @@ -0,0 +1,27 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface StaffInterface extends Document { + name: string, + userID: string, + title: string, + dept: string, + pn: string[], + emailAddress: string, + extension: string, + acknowledgements: string[], + additionalRoles: string[] +} + +const Staff: Schema = new Schema({ + name: String, + userID: String, + title: String, + dept: String, + pn: Array, + emailAddress: String, + extension: String, + acknowledgements: Array, + additionalRoles: Array, +}); + +export default model('Staff', Staff); diff --git a/src/models/Stat.ts b/src/models/Stat.ts new file mode 100644 index 0000000..7109699 --- /dev/null +++ b/src/models/Stat.ts @@ -0,0 +1,13 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface StatInterface extends Document { + name: 'messages' | 'commands' | 'pages' | 'requests', + value: number, +} + +const Stat: Schema = new Schema({ + name: String, + value: Number, +}); + +export default model('Stat', Stat); diff --git a/src/models/index.ts b/src/models/index.ts new file mode 100644 index 0000000..4ff29cf --- /dev/null +++ b/src/models/index.ts @@ -0,0 +1,21 @@ +export { default as Customer, CustomerInterface } from './Customer'; +export { default as CustomerPortal, CustomerPortalInterface } from './CustomerPortal'; +export { default as File, FileInterface } from './File'; +export { default as ExecutiveOrder, ExecutiveOrderInterface } from './ExecutiveOrder'; +export { default as Inquiry, InquiryInterface, InqType } from './Inquiry'; +export { default as Member, MemberInterface } from './Member'; +export { default as Merchant, MerchantInterface } from './Merchant'; +export { default as Moderation, ModerationInterface } from './Moderation'; +export { default as Motion, MotionInterface } from './Motion'; +export { default as NNTrainingData, NNTrainingDataInterface } from './NNTrainingData'; +export { default as Note, NoteInterface } from './Note'; +export { default as PagerNumber, PagerNumberInterface, PagerNumberRaw } from './PagerNumber'; +export { default as Promo, PromoInterface } from './Promo'; +export { default as Proclamation, ProclamationInterface } from './Proclamation'; +export { default as Rank, RankInterface } from './Rank'; +export { default as Redirect, RedirectInterface, RedirectRaw } from './Redirect'; +export { default as Resolution, ResolutionInterface } from './Resolution'; +export { default as Score, ScoreInterface, ScoreInterfaceRaw } from './Score'; +export { default as ScoreHistorical, ScoreHistoricalInterface } from './ScoreHistorical'; +export { default as Staff, StaffInterface } from './Staff'; +export { default as Stat, StatInterface } from './Stat'; diff --git a/src/pbx/actions/Misc.ts b/src/pbx/actions/Misc.ts new file mode 100644 index 0000000..4193287 --- /dev/null +++ b/src/pbx/actions/Misc.ts @@ -0,0 +1,44 @@ +import ARI from 'ari-client'; +import { randomBytes } from 'crypto'; +import { promises as fs } from 'fs'; +import { PBX } from '../../class'; + +export default class Misc { + public static accessDenied(channel: ARI.Channel) { + return new Promise((resolve, reject) => { + channel.play({ + media: 'sound:access-denied', + }, undefined).then((playback) => { + playback.on('PlaybackFinished', () => { + channel.hangup().catch((err) => reject(err)); + resolve(); + }); + }).catch((err) => reject(err)); + }); + } + + public static async TTS(pbx: PBX, text: string, gender: string | any): Promise { + const fileExtension = `${randomBytes(10).toString('hex')}`; + + const [response] = await pbx.tts.synthesizeSpeech({ + input: { text }, + voice: { languageCode: 'en-US', ssmlGender: gender }, + audioConfig: { audioEncoding: 'OGG_OPUS' }, + }); + await fs.writeFile(`/tmp/${fileExtension}.ogg`, response.audioContent, 'binary'); + await pbx.client.util.exec(`ffmpeg -i /tmp/${fileExtension}.ogg -af "highpass=f=300, lowpass=f=3400" -ar 8000 -ac 1 -ab 64k -f mulaw /tmp/${fileExtension}.ulaw`); + return `sound:/tmp/${fileExtension}`; + } + + public static play(pbx: PBX, channel: ARI.Channel, sound: string | string[]): Promise { + const playback = pbx.ari.Playback(); + + return new Promise((resolve, reject) => { + playback.once('PlaybackFinished', (_, pl: ARI.Playback) => { + resolve(pl); + }); + + channel.play({ media: sound }, playback).catch((err) => reject(err)); + }); + } +} diff --git a/src/pbx/handlers/PageDTMF.ts b/src/pbx/handlers/PageDTMF.ts new file mode 100644 index 0000000..464de4f --- /dev/null +++ b/src/pbx/handlers/PageDTMF.ts @@ -0,0 +1,75 @@ +/* eslint-disable consistent-return */ +import ARI from 'ari-client'; +import { TextableChannel } from 'eris'; +import PageCommand from '../../commands/page'; +import { Handler, PBX } from '../../class'; +import { Misc } from '..'; + +export default class PageDTMF extends Handler { + constructor(pbx: PBX) { + super(pbx); + this.app = 'page-dtmf'; + this.options.available = true; + } + + public async run(event: ARI.Event, channel: ARI.Channel) { + if (event.application !== 'page-dtmf') return; + const message = await ( this.client.guilds.get(this.client.config.guildID).channels.get('501089664040697858')).getMessage('775604192013320203'); + if (!message) return Misc.accessDenied(channel); + const member = await this.client.db.Staff.findOne({ extension: channel.caller.number }).lean().exec(); + if (!member) return Misc.accessDenied(channel); + const pager = await this.client.db.PagerNumber.findOne({ individualAssignID: member.userID }).lean().exec(); + if (!pager) return Misc.accessDenied(channel); + let status = 0; + const pagerNumber: string[] = []; + const pagerCode: string[] = []; + channel.answer(); + const pnPlaybackC = this.pbx.ari.Playback(); + channel.play({ + media: 'sound:please-enter-the-pn', + }, pnPlaybackC); + channel.on('ChannelDtmfReceived', async (ev) => { + if (!channel.connected) return; + if (status === 0) { + if (ev.digit === '#') { + pnPlaybackC.stop().catch(() => {}); + await channel.play({ + media: 'sound:please-enter-the-pc', + }, undefined); + status = 1; + return null; + } + pagerNumber.push(ev.digit); + } else if (status === 1) { + const processingPlayback = this.pbx.ari.Playback(); + if (ev.digit === '#') { + await channel.play({ + media: 'sound:pls-hold-process-tx', + }, processingPlayback); + + const Page = this.client.commands.get('page'); + const page = await Page.page(pagerNumber.join(''), pager.num, pagerCode.join(''), message); + if (page.status === true) { + processingPlayback.stop(); + await Misc.play(this.pbx, channel, 'sound:page-delivered'); + channel.hangup(); + } else if (page.status === false) { + try { + const ch = await this.client.getDMChannel(member.userID); + if (ch) { + ch.createMessage(`***An error has occurred while trying to process your page request over the PBX.***\n*${page.message}*\n\nPlease check your parameters and try again.`); + } + } catch { + this.client.util.handleError(new Error(page.message)); + } + processingPlayback.stop(); + await Misc.play(this.pbx, channel, 'sound:request-error'); + channel.hangup(); + } + return null; + } + pagerCode.push(ev.digit); + } + }); + } +} diff --git a/src/pbx/index.ts b/src/pbx/index.ts new file mode 100644 index 0000000..646977b --- /dev/null +++ b/src/pbx/index.ts @@ -0,0 +1,5 @@ +// Actions +export { default as Misc } from './actions/Misc'; + +// Handlers +export { default as PageDTMF } from './handlers/PageDTMF'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..041dad5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,64 @@ +{ + "compilerOptions": { + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": false, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./build", /* Redirect output structure to the directory. */ + "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": false, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "resolveJsonModule": true, + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + } +} diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000..eff940e --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,14 @@ +export declare interface Config { + token: string; + prefix: string; + guildID: string; + mongoDB: string; + emailPass: string; + webhookID: string; + webhookToken: string; + internalKey: string; + stripeKey: string; + stripeSubSigningSecret: string; + ariClientKey: string; + amiClientKey: string; +} diff --git a/types/sdNotify.d.ts b/types/sdNotify.d.ts new file mode 100644 index 0000000..82258c3 --- /dev/null +++ b/types/sdNotify.d.ts @@ -0,0 +1,5 @@ +declare module 'sd-notify' { + function ready(): void; + function startWatchdogMode(ms: number): void; + function sendStatus(txt: string): void; +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..ce507cd --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4052 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/helper-validator-identifier@^7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" + integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== + +"@babel/highlight@^7.10.4": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1" + integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@eslint/eslintrc@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547" + integrity sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^12.1.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" + +"@google-cloud/text-to-speech@^3.1.2": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@google-cloud/text-to-speech/-/text-to-speech-3.1.3.tgz#20bbdb3635f647e2d386e4819be9ad448062daf9" + integrity sha512-Dhv3kT8L3vuVa0X/uOi/okbNC3fNtq0vcJsUEHFR8fd275Y0rFLNo28j566xy+VJ1g31uwXN6eT8Ubq8yZGgOA== + dependencies: + google-gax "^2.9.2" + +"@grpc/grpc-js@~1.2.0": + version "1.2.11" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.2.11.tgz#68faa56bded64844294dc6429185503376f05ff1" + integrity sha512-DZqx3nHBm2OGY7NKq4sppDEfx4nBAsQH/d/H/yxo/+BwpVLWLGs+OorpwQ+Fqd6EgpDEoi4MhqndjGUeLl/5GA== + dependencies: + "@types/node" ">=12.12.47" + google-auth-library "^6.1.1" + semver "^6.2.0" + +"@grpc/proto-loader@^0.5.1": + version "0.5.6" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.5.6.tgz#1dea4b8a6412b05e2d58514d507137b63a52a98d" + integrity sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ== + dependencies: + lodash.camelcase "^4.3.0" + protobufjs "^6.8.6" + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + +"@types/ari-client@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@types/ari-client/-/ari-client-2.2.2.tgz#da47c48cbd3b57331a13adb7681732cc4741bcb9" + integrity sha512-ix9S/Gi6kCNuCm5SzrWgVaA7Fctkl1HwN8Y6VLX0c3Es4i+RvLW6LnC6vwKEHnp2jjdN1mP5SRAR8vW2qs9axQ== + dependencies: + "@types/node" "*" + +"@types/body-parser@*": + version "1.19.0" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" + integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bson@*": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.0.3.tgz#30889d2ffde6262abbe38659364c631454999fbf" + integrity sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw== + dependencies: + "@types/node" "*" + +"@types/bull@^3.14.4": + version "3.15.0" + resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.15.0.tgz#69c518d4e7a53056f287cebcc4ef4ffe91aaf201" + integrity sha512-54Y1RYkJt6i+4dH45w4gZOP6fyhksTvOImfgBYAxgq/nt5ZrES4xFWwOzt2bxAgSR7FMH9fwvaiJN/pripPzag== + dependencies: + "@types/ioredis" "*" + +"@types/connect@*": + version "3.4.34" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz#170a40223a6d666006d93ca128af2beb1d9b1901" + integrity sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ== + dependencies: + "@types/node" "*" + +"@types/cron@^1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@types/cron/-/cron-1.7.2.tgz#e9fb420da616920dae82d13adfca53282ffaab6e" + integrity sha512-AEpNLRcsVSc5AdseJKNHpz0d4e8+ow+abTaC0fKDbAU86rF1evoFF0oC2fV9FdqtfVXkG2LKshpLTJCFOpyvTg== + dependencies: + "@types/node" "*" + moment ">=2.14.0" + +"@types/eslint-visitor-keys@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" + integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== + +"@types/express-serve-static-core@^4.17.18": + version "4.17.19" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d" + integrity sha512-DJOSHzX7pCiSElWaGR8kCprwibCB/3yW6vcT8VG3P0SJjnv19gnWG/AZMfM60Xj/YJIp/YCaDHyvzsFVeniARA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@^4.17.6": + version "4.17.11" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545" + integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/helmet@^0.0.47": + version "0.0.47" + resolved "https://registry.yarnpkg.com/@types/helmet/-/helmet-0.0.47.tgz#ec5161541b649142205b7c558bca14801c5ce129" + integrity sha512-TcHA/djjdUtrMtq/QAayVLrsgjNNZ1Uhtz0KhfH01mrmjH44E54DA1A0HNbwW0H/NBFqV+tGMo85ACuEhMXcdg== + dependencies: + "@types/express" "*" + +"@types/ioredis@*": + version "4.22.0" + resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.22.0.tgz#b07cc3405d98bae50a642b20790471b4c3c390a9" + integrity sha512-BhgyAqt+CIFj/ejdYpWSGYUQzoQr7sFOBYLL8yEExa1tSTi2cy2D3a952zF8Tm4Q1cY3srn8xXZfb2riX6hWjw== + dependencies: + "@types/node" "*" + +"@types/json-schema@^7.0.3": + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" + integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/jsonwebtoken@^8.5.0": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#56958cb2d80f6d74352bd2e501a018e2506a8a84" + integrity sha512-rNAPdomlIUX0i0cg2+I+Q1wOUr531zHBQ+cV/28PJ39bSPKjahatZZ2LMuhiguETkCgLVzfruw/ZvNMNkKoSzw== + dependencies: + "@types/node" "*" + +"@types/long@^4.0.0", "@types/long@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" + integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== + +"@types/mathjs@^6.0.7": + version "6.0.11" + resolved "https://registry.yarnpkg.com/@types/mathjs/-/mathjs-6.0.11.tgz#bf24c1ed875252274008d2c373bad07e0348adfb" + integrity sha512-q9B8ZreO41L38iTY76bCZEtAqzeRs4mNIOZpZ1sLSlcYgvgfFrnf8y8qfmas0tVWrsODjmQbQJFD6RJJJCqJbQ== + dependencies: + decimal.js "^10.0.0" + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + +"@types/mongodb@*", "@types/mongodb@^3.5.27": + version "3.6.10" + resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.6.10.tgz#80cceaabeec9f460e5b46844e938e8eba74f9266" + integrity sha512-BkwAHFiZSSWdTIqbUVGmgvIsiXXjqAketeK7Izy7oSs6G3N8Bn993tK9eq6QEovQDx6OQ2FGP2KWDDxBzdlJ6Q== + dependencies: + "@types/bson" "*" + "@types/node" "*" + +"@types/mongoose@^5.7.19": + version "5.10.3" + resolved "https://registry.yarnpkg.com/@types/mongoose/-/mongoose-5.10.3.tgz#3bc6787245aa8ebbff4ed61da18f4775e0ec52cd" + integrity sha512-VfdnaFImXEJZZiuL2ID/ysZs4inOIjxwrAnUgkr5eum2O2BLhFkiSI0i87AwignVva1qWTJ3H3DyM0Rf4USJ4A== + dependencies: + "@types/mongodb" "*" + "@types/node" "*" + +"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=8.1.0", "@types/node@^14.14.25": + version "14.14.35" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313" + integrity sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag== + +"@types/node@^13.7.0": + version "13.13.47" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.47.tgz#6eca42c3462821309b26edbc2eff0db1e37ab9bc" + integrity sha512-R6851wTjN1YJza8ZIeX6puNBSi/ZULHVh4WVleA7q256l+cP2EtXnKbO455fTs2ytQk3dL9qkU+Wh8l/uROdKg== + +"@types/nodemailer@^6.4.0": + version "6.4.1" + resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.1.tgz#31f96f4410632f781d3613bd1f4293649e423f75" + integrity sha512-8081UY/0XTTDpuGqCnDc8IY+Q3DSg604wB3dBH0CaZlj4nZWHWuxtZ3NRZ9c9WUrz1Vfm6wioAUnqL3bsh49uQ== + dependencies: + "@types/node" "*" + +"@types/puppeteer@^5.4.3": + version "5.4.3" + resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.3.tgz#cdca84aa7751d77448d8a477dbfa0af1f11485f2" + integrity sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg== + dependencies: + "@types/node" "*" + +"@types/qs@*": + version "6.9.6" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" + integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA== + +"@types/range-parser@*": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" + integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== + +"@types/serve-static@*": + version "1.13.9" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.9.tgz#aacf28a85a05ee29a11fb7c3ead935ac56f33e4e" + integrity sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/signale@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@types/signale/-/signale-1.4.1.tgz#6137a6fd7960b48703dd2793c5b795480368b246" + integrity sha512-05d9fUDqRnt36rizLgo38SbPTrkMzdhXpvSHSAhxzokgIUPGNUoXHV0zYjPpTd4IryDADJ0mGHpfJ/Yhjyh9JQ== + dependencies: + "@types/node" "*" + +"@types/uuid@^7.0.3": + version "7.0.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-7.0.4.tgz#00a5749810b4ad80bff73a61f9cc9d0d521feb3c" + integrity sha512-WGZCqBZZ0mXN2RxvLHL6/7RCu+OWs28jgQMP04LWfpyJlQUMTR6YU9CNJAKDgbw+EV/u687INXuLUc7FuML/4g== + +"@types/yauzl@^2.9.1": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" + integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== + dependencies: + "@types/node" "*" + +"@typescript-eslint/eslint-plugin@^2.33.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" + integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== + dependencies: + "@typescript-eslint/experimental-utils" "2.34.0" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" + integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^2.33.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" + integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "2.34.0" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-visitor-keys "^1.1.0" + +"@typescript-eslint/typescript-estree@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" + integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +acorn-jsx@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" + integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== + +acorn@^7.1.1, acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +agent-base@5: + version "5.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" + integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + 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" + +ajv@^7.0.2: + version "7.2.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.2.1.tgz#a5ac226171912447683524fa2f1248fcf8bac83d" + integrity sha512-+nu0HDv7kNSOua9apAVc979qd932rrZeb3WOvoiD31A/p1mIE5/9bN2027pE2rOPYEdS3UHzsvof4hY+lM9/WQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +ari-client@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ari-client/-/ari-client-2.2.0.tgz#a4b51d1f119d8323a83537fd172b6e6ea907c958" + integrity sha512-DPz+vC/dZyvy5HqBrEzYpNH6X2hDb+AIyRith6f8IVLHyveRWaHPO0S7rF1Q91qry/U8G+504KUZfeaBPwzIVQ== + dependencies: + backoff-func "^0.1.2" + bluebird "^3.5.2" + lodash "^4.17.10" + request "^2.34.0" + swagger-client "2.0.26" + uuid "^3.0.0" + ws "^6.0.0" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +array-includes@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" + integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + get-intrinsic "^1.1.1" + is-string "^1.0.5" + +array.prototype.flat@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" + integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + +arrify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +asterisk-manager@^0.1.16: + version "0.1.16" + resolved "https://registry.yarnpkg.com/asterisk-manager/-/asterisk-manager-0.1.16.tgz#4afc7d669e0ae86f24d2d18b7d91c9fa7044fc36" + integrity sha512-JxcgSutGhz6B2XtYkqkOI/sRH3hCGHmeNXuqNBw1JMtDMrRzpfuXB0keH4ouRtQPxdb2LIj83RWdR5uVnFim9A== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +awesome-phonenumber@^2.45.0: + version "2.48.0" + resolved "https://registry.yarnpkg.com/awesome-phonenumber/-/awesome-phonenumber-2.48.0.tgz#0f56fc99061a55ddf62ea02e386dfd093a852709" + integrity sha512-SNAAYLOTkH76v5akcz8gEeo5x4O8ytF1SSWSZ7lPCdAwxncCtrjSZZ3Ja3tbjmpwlH+6C9g0RbUpxLIvKMFAlw== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" + integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== + +ax@0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/ax/-/ax-0.1.8.tgz#27ca9a73fa4c78a478d62d827cad860a24fdd497" + integrity sha1-J8qac/pMeKR41i2CfK2GCiT91Jc= + +axios@^0.18.1: + version "0.18.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3" + integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g== + dependencies: + follow-redirects "1.5.10" + is-buffer "^2.0.2" + +axios@^0.19.2: + version "0.19.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" + integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== + dependencies: + follow-redirects "1.5.10" + +backoff-func@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/backoff-func/-/backoff-func-0.1.2.tgz#54c3fae2bade587234bad5db87e3412c9fa98783" + integrity sha1-VMP64rreWHI0utXbh+NBLJ+ph4M= + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.3.0, base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +bignumber.js@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" + integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== + +bindings@1.5.0, bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bit-twiddle@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bit-twiddle/-/bit-twiddle-1.0.2.tgz#0c6c1fabe2b23d17173d9a61b7b7093eb9e1769e" + integrity sha1-DGwfq+KyPRcXPZpht7cJPrnhdp4= + +bl@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" + integrity sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +bluebird@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA== + +bluebird@^3.5.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +body-parser@1.19.0, body-parser@^1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + +bowser@2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.9.0.tgz#3bed854233b419b9a7422d9ee3e85504373821c9" + integrity sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brain.js@^2.0.0-beta.2: + version "2.0.0-beta.2" + resolved "https://registry.yarnpkg.com/brain.js/-/brain.js-2.0.0-beta.2.tgz#eefe0457b8da0e02e568601df19384487cc1603d" + integrity sha512-Cg8iUGrEz1kRqWos8mJddabMJxdiAEF24OX6Z0EWX/+9gu7BIjg5GL+IyST/vl4kbfwdvKHqeBr/N9eWEDzGdQ== + dependencies: + gpu.js "^2.9.5" + thaw.js "^2.1.0" + +bson@^1.1.4: + version "1.1.6" + resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.6.tgz#fb819be9a60cd677e0853aee4ca712a785d6618a" + integrity sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg== + +btoa@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/btoa/-/btoa-1.1.1.tgz#27c8106263108e9dd41ff2faaad2a17045e35eba" + integrity sha1-J8gQYmMQjp3UH/L6qtKhcEXjXro= + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= + +buffer@^5.2.1, buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +bull@^3.20.1: + version "3.21.1" + resolved "https://registry.yarnpkg.com/bull/-/bull-3.21.1.tgz#f0ff54b73c6a5ec54c5e12bb9d3ceb54229d055d" + integrity sha512-7IjQBz+mm2GYwlNOf5Pbq8F+EwqkBv0RjwWpG+5Kfk/QDWylQ6eL3FzJbvFQQ7uHjTVPsuzsQocArjhm/SUsqA== + dependencies: + cron-parser "^2.13.0" + debuglog "^1.0.0" + get-port "^5.1.1" + ioredis "^4.22.0" + lodash "^4.17.21" + p-timeout "^3.2.0" + promise.prototype.finally "^3.1.2" + semver "^7.3.2" + util.promisify "^1.0.1" + uuid "^8.3.0" + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelize@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" + integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^2.0.0, chalk@^2.3.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +cheerio-select-tmp@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz#55bbef02a4771710195ad736d5e346763ca4e646" + integrity sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ== + dependencies: + css-select "^3.1.2" + css-what "^4.0.0" + domelementtype "^2.1.0" + domhandler "^4.0.0" + domutils "^2.4.4" + +cheerio@^1.0.0-rc.5: + version "1.0.0-rc.5" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.5.tgz#88907e1828674e8f9fee375188b27dadd4f0fa2f" + integrity sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw== + dependencies: + cheerio-select-tmp "^0.1.0" + dom-serializer "~1.2.0" + domhandler "^4.0.0" + entities "~2.1.0" + htmlparser2 "^6.0.0" + parse5 "^6.0.0" + parse5-htmlparser2-tree-adapter "^6.0.0" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +cluster-key-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" + integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +complex.js@^2.0.11: + version "2.0.12" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.12.tgz#fa4df97d8928e5f7b6a86b35bdeecc3a3eda8a22" + integrity sha512-oQX99fwL6LrTVg82gDY1dIWXy6qZRnRL35N+YhIX0N7tSwsa0KFy6IEMHTNuCW4mP7FS7MEqZ/2I/afzYwPldw== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +confusing-browser-globals@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" + integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA== + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-security-policy-builder@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz#0a2364d769a3d7014eec79ff7699804deb8cfcbb" + integrity sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ== + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +cookiejar@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-1.3.1.tgz#c04b048f688f801623acd90cd5848c2bf8891a17" + integrity sha1-wEsEj2iPgBYjrNkM1YSMK/iJGhc= + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cron-parser@^2.13.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.18.0.tgz#de1bb0ad528c815548371993f81a54e5a089edcf" + integrity sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg== + dependencies: + is-nan "^1.3.0" + moment-timezone "^0.5.31" + +cron@^1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/cron/-/cron-1.8.2.tgz#4ac5e3c55ba8c163d84f3407bde94632da8370ce" + integrity sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg== + dependencies: + moment-timezone "^0.5.x" + +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-select@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-3.1.2.tgz#d52cbdc6fee379fba97fb0d3925abbd18af2d9d8" + integrity sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA== + dependencies: + boolbase "^1.0.0" + css-what "^4.0.0" + domhandler "^4.0.0" + domutils "^2.4.3" + nth-check "^2.0.0" + +css-what@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-4.0.0.tgz#35e73761cab2eeb3d3661126b23d7aa0e8432233" + integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A== + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +dasherize@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308" + integrity sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg= + +debug@2.6.9, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@3.1.0, debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +debuglog@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= + +decimal.js@^10.0.0, decimal.js@^10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" + integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== + +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +denque@^1.1.0, denque@^1.4.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de" + integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +devtools-protocol@0.0.818844: + version "0.0.818844" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.818844.tgz#d1947278ec85b53e4c8ca598f607a28fa785ba9e" + integrity sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg== + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-serializer@^1.0.1, dom-serializer@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1" + integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + entities "^2.0.0" + +domelementtype@^2.0.1, domelementtype@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" + integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== + +domhandler@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.0.0.tgz#01ea7821de996d85f69029e81fa873c21833098e" + integrity sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA== + dependencies: + domelementtype "^2.1.0" + +domutils@^2.4.3, domutils@^2.4.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.5.0.tgz#42f49cffdabb92ad243278b331fd761c1c2d3039" + integrity sha512-Ho16rzNMOFk2fPwChGh3D2D9OEHAfG19HgmRR2l+WLSsIstNsAYBzePH412bL0y5T44ejABIVfTHQ8nqi/tBCg== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.0.1" + domhandler "^4.0.0" + +dont-sniff-mimetype@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz#c7d0427f8bcb095762751252af59d148b0a623b2" + integrity sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug== + +duplexify@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.1.tgz#7027dc374f157b122a8ae08c2d3ea4d2d953aa61" + integrity sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA== + dependencies: + end-of-stream "^1.4.1" + inherits "^2.0.3" + readable-stream "^3.1.1" + stream-shift "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +"eris-pagination@github:bsian03/eris-pagination": + version "0.5.0" + resolved "https://codeload.github.com/bsian03/eris-pagination/tar.gz/c0f77b118e98309e89e6522ef545f9d121601f21" + dependencies: + eris-reactions "^0.1.2" + +eris-reactions@^0.1.2: + version "0.1.4" + resolved "https://registry.yarnpkg.com/eris-reactions/-/eris-reactions-0.1.4.tgz#4b642f7411144f164b6097138c262eac6b03def1" + integrity sha512-U2rMtfywnhh4TmqFcF7uo/ciyQN8vb9ptwSJMQs/uaY1a401f+t3sei1R3IR2gYqaFejJzgjTmzBMT6jIBPYYQ== + +eris@^0.13.3: + version "0.13.4" + resolved "https://registry.yarnpkg.com/eris/-/eris-0.13.4.tgz#06159ff619446046e392e28a70667d73c27aa631" + integrity sha512-IFA14nasCig8xp8cVCULvzBuJ0qpYqJ3XyEtm9OLdC177DYDCJ9QM2Aq+KOpejIVl7838n9AyRlLI6w9Eu3PiQ== + dependencies: + ws "^7.2.1" + optionalDependencies: + opusscript "^0.0.7" + tweetnacl "^1.0.1" + +eris@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/eris/-/eris-0.14.0.tgz#0d13391c0fc1a7b678b2f8d1fc3ec6aef05700d3" + integrity sha512-/W6X0SFR2swtA9oc4ga5Wh1TQcZtPgbUaDDdwYc67fvFUAtwC+V1xzWUZq2yDeJnTfB8Uot9SJWA8Lthe2sDtQ== + dependencies: + ws "^7.2.1" + optionalDependencies: + opusscript "^0.0.7" + tweetnacl "^1.0.1" + +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0-next.0, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: + version "1.18.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" + integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.2" + is-string "^1.0.5" + object-inspect "^1.9.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-latex@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" + integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-config-airbnb-base@^14.1.0: + version "14.2.1" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e" + integrity sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA== + dependencies: + confusing-browser-globals "^1.0.10" + object.assign "^4.1.2" + object.entries "^1.1.2" + +eslint-import-resolver-node@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + +eslint-module-utils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + +eslint-plugin-import@^2.20.2: + version "2.22.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" + integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw== + dependencies: + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.4" + eslint-module-utils "^2.6.0" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.1" + read-pkg-up "^2.0.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" + +eslint-scope@^5.0.0, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.0.0, eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + +eslint@^7.19.0: + version "7.22.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.22.0.tgz#07ecc61052fec63661a2cab6bd507127c07adc6f" + integrity sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg== + dependencies: + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.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" + 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" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + 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 "^4.17.21" + 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.4" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend@^3.0.2, extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extract-zip@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" + integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + +feature-policy@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069" + integrity sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ== + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" + integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== + +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fraction.js@^4.0.12: + version "4.0.13" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe" + integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +gaxios@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-4.2.0.tgz#33bdc4fc241fc33b8915a4b8c07cfb368b932e46" + integrity sha512-Ms7fNifGv0XVU+6eIyL9LB7RVESeML9+cMvkwGS70xyD6w2Z80wl6RiqiJ9k1KFlJCUTQqFFc8tXmPQfSKUe8g== + 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" + +gcp-metadata@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-4.2.1.tgz#31849fbcf9025ef34c2297c32a89a1e7e9f2cd62" + integrity sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw== + dependencies: + gaxios "^4.0.0" + json-bigint "^1.0.0" + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-port@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" + integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== + +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= + +gl-wiretap@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/gl-wiretap/-/gl-wiretap-0.6.2.tgz#e4aa19622831088fbaa7e5a18d01768f7a3fb07c" + integrity sha512-fxy1XGiPkfzK+T3XKDbY7yaqMBmozCGvAFyTwaZA3imeZH83w7Hr3r3bYlMRWIyzMI/lDUvUMM/92LE2OwqFyQ== + +gl@^4.5.2: + version "4.9.0" + resolved "https://registry.yarnpkg.com/gl/-/gl-4.9.0.tgz#0695cb0c0a5a4f38aa67e92643a4f0c54dee795a" + integrity sha512-5Qz8fM4kO4xTo/Ofv80hq/iXEGNlMxOCSo1+9cvT9CX/j84tIFBsbFLXkBVFZiKulA3H2VPQGSs0qZIMwv3KEA== + dependencies: + bindings "^1.5.0" + bit-twiddle "^1.0.2" + glsl-tokenizer "^2.0.2" + nan "^2.14.1" + node-abi "^2.18.0" + node-gyp "^7.1.0" + prebuild-install "^5.3.5" + +glob-parent@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + 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" + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globals@^13.6.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.7.0.tgz#aed3bcefd80ad3ec0f0be2cf0c895110c0591795" + integrity sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA== + dependencies: + type-fest "^0.20.2" + +glsl-tokenizer@^2.0.2: + version "2.1.5" + resolved "https://registry.yarnpkg.com/glsl-tokenizer/-/glsl-tokenizer-2.1.5.tgz#1c2e78c16589933c274ba278d0a63b370c5fee1a" + integrity sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA== + dependencies: + through2 "^0.6.3" + +google-auth-library@^6.1.1: + version "6.1.6" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-6.1.6.tgz#deacdcdb883d9ed6bac78bb5d79a078877fdf572" + integrity sha512-Q+ZjUEvLQj/lrVHF/IQwRo6p3s8Nc44Zk/DALsN+ac3T4HY/g/3rrufkgtl+nZ1TW7DNAw5cTChdVp4apUXVgQ== + 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" + +google-auth-library@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-7.0.2.tgz#cab6fc7f94ebecc97be6133d6519d9946ccf3e9d" + integrity sha512-vjyNZR3pDLC0u7GHLfj+Hw9tGprrJwoMwkYGqURCXYITjCrP9HprOyxVV+KekdLgATtWGuDkQG2MTh0qpUPUgg== + 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" + +google-gax@^2.9.2: + version "2.11.2" + resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-2.11.2.tgz#9ef7773b94aaa61c4588fb2408d62e8444995026" + integrity sha512-PNqXv7Oi5XBMgoMWVxLZHUidfMv7cPHrDSDXqLyEd6kY6pqFnVKC8jt2T1df4JPSc2+VLPdeo6L7X9mbdQG8Xw== + dependencies: + "@grpc/grpc-js" "~1.2.0" + "@grpc/proto-loader" "^0.5.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.0.2" + is-stream-ended "^0.1.4" + node-fetch "^2.6.1" + protobufjs "^6.10.2" + retry-request "^4.0.0" + +google-p12-pem@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-3.0.3.tgz#673ac3a75d3903a87f05878f3c75e06fc151669e" + integrity sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA== + dependencies: + node-forge "^0.10.0" + +gpu-mock.js@^1.3.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/gpu-mock.js/-/gpu-mock.js-1.3.1.tgz#f7deaa09da3f672762eda944ecf948fd2c4b1490" + integrity sha512-+lbp8rQ0p1nTa6Gk6HoLiw4yM6JTpql82U+nCF3sZbX4FJWP9PzzF1018dW8K+pbmqRmhLHbn6Bjc6i6tgUpbA== + +gpu.js@^2.9.5: + version "2.11.2" + resolved "https://registry.yarnpkg.com/gpu.js/-/gpu.js-2.11.2.tgz#2365e68fb1fcc86c3f5d94192c2f2ffe8a997489" + integrity sha512-9vTGWo/hpYTRX4Q+WO0lmug/8/eKjJ0CI8LXPFxKWfu0kN2sOLnZtlnrXcDIhLF/crA+/JV9rM+0zHi4x9wjPA== + dependencies: + acorn "^7.1.1" + gl "^4.5.2" + gl-wiretap "^0.6.2" + gpu-mock.js "^1.3.0" + +graceful-fs@^4.1.2, graceful-fs@^4.2.3: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + +gtoken@^5.0.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-5.2.1.tgz#4dae1fea17270f457954b4a45234bba5fc796d16" + integrity sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw== + dependencies: + gaxios "^4.0.0" + google-p12-pem "^3.0.3" + jws "^4.0.0" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +has-bigints@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +helmet-crossdomain@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz#5f1fe5a836d0325f1da0a78eaa5fd8429078894e" + integrity sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA== + +helmet-csp@2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.10.0.tgz#685dde1747bc16c5e28ad9d91e229a69f0a85e84" + integrity sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w== + dependencies: + bowser "2.9.0" + camelize "1.0.0" + content-security-policy-builder "2.1.0" + dasherize "2.0.0" + +helmet@^3.22.0: + version "3.23.3" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.23.3.tgz#5ba30209c5f73ded4ab65746a3a11bedd4579ab7" + integrity sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA== + dependencies: + depd "2.0.0" + dont-sniff-mimetype "1.1.0" + feature-policy "0.3.0" + helmet-crossdomain "0.4.0" + helmet-csp "2.10.0" + hide-powered-by "1.1.0" + hpkp "2.0.0" + hsts "2.2.0" + nocache "2.1.0" + referrer-policy "1.2.0" + x-xss-protection "1.3.0" + +hide-powered-by@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.1.0.tgz#be3ea9cab4bdb16f8744be873755ca663383fa7a" + integrity sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg== + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +hpkp@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hpkp/-/hpkp-2.0.0.tgz#10e142264e76215a5d30c44ec43de64dee6d1672" + integrity sha1-EOFCJk52IVpdMMROxD3mTe5tFnI= + +hsts@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/hsts/-/hsts-2.2.0.tgz#09119d42f7a8587035d027dda4522366fe75d964" + integrity sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ== + dependencies: + depd "2.0.0" + +htmlparser2@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.0.1.tgz#422521231ef6d42e56bd411da8ba40aa36e91446" + integrity sha512-GDKPd+vk4jvSuvCbyuzx/unmXkk090Azec7LovXP8as1Hn8q9p3hbjmDGbUqqhknw0ajwit6LiiWqfiTUPMK7w== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.4.4" + entities "^2.0.0" + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-proxy-agent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b" + integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg== + dependencies: + agent-base "5" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +"iconv-lite@>= 0.1.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" + integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +ioredis@^4.22.0: + version "4.24.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.24.2.tgz#ca827cb22b579827ac2999c8d5950d7605f286ed" + integrity sha512-SSuVXwoG747sZetxxs9gyAno5kfUfvo4s5mSZp4dh8vzuTnrtA5mTf2OjL6sPfIfNbVTROg2c+VbXceGlpucPQ== + dependencies: + cluster-key-slot "^1.1.0" + debug "^4.3.1" + denque "^1.1.0" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + p-map "^2.1.0" + redis-commands "1.7.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-bigint@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2" + integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg== + +is-boolean-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0" + integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA== + dependencies: + call-bind "^1.0.0" + +is-buffer@^2.0.2: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== + +is-core-module@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-nan@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-number-object@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" + integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== + +is-regex@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" + integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== + dependencies: + call-bind "^1.0.2" + has-symbols "^1.0.1" + +is-stream-ended@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-stream-ended/-/is-stream-ended-0.1.4.tgz#f50224e95e06bce0e356d440a4827cd35b267eda" + integrity sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw== + +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +javascript-natural-sort@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +json-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + dependencies: + bignumber.js "^9.0.0" + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +jsonwebtoken@^8.5.1: + version "8.5.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" + 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" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + 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" + +jwa@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" + integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + +jws@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" + integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== + dependencies: + jwa "^2.0.0" + safe-buffer "^5.0.1" + +kareem@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.2.tgz#78c4508894985b8d38a0dc15e1a8e11078f2ca93" + integrity sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= + +lodash@^4.17.10, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +mathjs@^7.6.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-7.6.0.tgz#f0b7579e0756b13422995d0c4f29bd17d65d4dcc" + integrity sha512-abywR28hUpKF4at5jE8Ys+Kigk40eKMT5mcBLD0/dtsqjfOLbtzd3WjlRqIopNo7oQ6FME51qph6lb8h/bhpUg== + dependencies: + complex.js "^2.0.11" + decimal.js "^10.2.1" + escape-latex "^1.2.0" + fraction.js "^4.0.12" + javascript-natural-sort "^0.7.1" + seed-random "^2.2.0" + tiny-emitter "^2.1.0" + typed-function "^2.0.0" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +mime-db@1.46.0: + version "1.46.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" + integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== + +mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: + version "2.1.29" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" + integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== + dependencies: + mime-db "1.46.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-response@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" + integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minipass@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +moment-timezone@^0.5.31, moment-timezone@^0.5.x: + version "0.5.33" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c" + integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w== + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0", moment@>=2.14.0, moment@^2.25.3: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + +mongodb@3.6.5: + version "3.6.5" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.6.5.tgz#c27d786fd4d3c83dc19302483707d12a9d2aee5f" + integrity sha512-mQlYKw1iGbvJJejcPuyTaytq0xxlYbIoVDm2FODR+OHxyEiMR021vc32bTvamgBjCswsD54XIRwhg3yBaWqJjg== + dependencies: + bl "^2.2.1" + bson "^1.1.4" + denque "^1.4.1" + require_optional "^1.0.1" + safe-buffer "^5.1.2" + optionalDependencies: + saslprep "^1.0.0" + +mongoose-legacy-pluralize@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4" + integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ== + +mongoose@^5.11.15: + version "5.12.1" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.12.1.tgz#9aace8ac783b239906253396a98f2a56c0964b8b" + integrity sha512-g/oIEvQQrK1XcICS/PfzU1Gu1s6Uw1rgJP7/SfC3Ru6pTLa1dH2Lb+iJipNWqChbrykE78j/wwBVSsbyCZRl5Q== + dependencies: + "@types/mongodb" "^3.5.27" + bson "^1.1.4" + kareem "2.3.2" + mongodb "3.6.5" + mongoose-legacy-pluralize "1.0.2" + mpath "0.8.3" + mquery "3.2.4" + ms "2.1.2" + regexp-clone "1.0.0" + safe-buffer "5.2.1" + sift "7.0.1" + sliced "1.0.1" + +mpath@0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.8.3.tgz#828ac0d187f7f42674839d74921970979abbdd8f" + integrity sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA== + +mquery@3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.4.tgz#9c5c2e285ea6c6f20673f3528973c99ee1aaa1a0" + integrity sha512-uOLpp7iRX0BV1Uu6YpsqJ5b42LwYnmu0WeF/f8qgD/On3g0XDaQM6pfn0m6UxO6SM8DioZ9Bk6xxbWIGHm2zHg== + dependencies: + bluebird "3.5.1" + debug "3.1.0" + regexp-clone "^1.0.0" + safe-buffer "5.1.2" + sliced "1.0.1" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nan@^2.14.1: + version "2.14.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" + integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== + +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +nocache@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f" + integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q== + +node-abi@^2.18.0, node-abi@^2.7.0: + version "2.21.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.21.0.tgz#c2dc9ebad6f4f53d6ea9b531e7b8faad81041d48" + integrity sha512-smhrivuPqEM3H5LmnY3KU6HfYv0u4QklgAxfFyRNujKUzbUcYZ+Jc2EhukB9SRcD2VpqhxM7n/MIcp1Ua1/JMg== + dependencies: + semver "^5.4.1" + +node-fetch@^2.3.0, node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + +node-forge@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== + +node-gyp@^7.1.0: + version "7.1.2" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-7.1.2.tgz#21a810aebb187120251c3bcec979af1587b188ae" + integrity sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.3" + nopt "^5.0.0" + npmlog "^4.1.2" + request "^2.88.2" + rimraf "^3.0.2" + semver "^7.3.2" + tar "^6.0.2" + which "^2.0.2" + +nodemailer@^6.4.8: + version "6.5.0" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.5.0.tgz#d12c28d8d48778918e25f1999d97910231b175d9" + integrity sha512-Tm4RPrrIZbnqDKAvX+/4M+zovEReiKlEXWDzG4iwtpL9X34MJY+D5LnQPH/+eghe8DLlAVshHAJZAZWBGhkguw== + +noop-logger@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" + integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +npmlog@^4.0.1, npmlog@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +nth-check@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" + integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== + dependencies: + boolbase "^1.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-inspect@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" + integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.3.tgz#c601c7f168b62374541a07ddbd3e2d5e4f7711a6" + integrity sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + has "^1.0.3" + +object.getownpropertydescriptors@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz#1bd63aeacf0d5d2d2f31b5e393b03a7c601a23f7" + integrity sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + +object.values@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" + integrity sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + has "^1.0.3" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + 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" + +opusscript@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/opusscript/-/opusscript-0.0.7.tgz#7dd7ec55b302d26bf588e6fc3feb090b8c7da856" + integrity sha512-DcBadTdYTUuH9zQtepsLjQn4Ll6rs3dmeFvN+SD0ThPnxRBRm/WC1zXWPg+wgAJimB784gdZvUMA57gDP7FdVg== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-map@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + +p-timeout@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= + dependencies: + error-ex "^1.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^6.0.0, parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pkg-conf@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.1.0.tgz#2126514ca6f2abfebd168596df18ba57867f0058" + integrity sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg= + dependencies: + find-up "^2.0.0" + load-json-file "^4.0.0" + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pluris@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/pluris/-/pluris-0.2.5.tgz#e98d8e0e7a49432b8bfe168b46e919e55405259e" + integrity sha512-K7UsbUn7uKflcnLRwN9g4sSlFsGYtM+sYhrO+nFrNTay/Gn6wTnhZlMbiIg1dXIy6+gnpVQZqVGghmBJb9t1Pg== + dependencies: + eris "^0.13.3" + optionalDependencies: + eris-reactions "^0.1.2" + +prebuild-install@^5.3.5: + version "5.3.6" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.6.tgz#7c225568d864c71d89d07f8796042733a3f54291" + integrity sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg== + dependencies: + detect-libc "^1.0.3" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^2.7.0" + noop-logger "^0.1.1" + npmlog "^4.0.1" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^3.0.3" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + which-pm-runs "^1.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +progress@^2.0.0, progress@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise.prototype.finally@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067" + integrity sha512-A2HuJWl2opDH0EafgdjwEw7HysI8ff/n4lW4QEVBCUXFk9QeGecBWv0Deph0UmLe3tTNYegz8MOjsVuE6SMoJA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.0" + function-bind "^1.1.1" + +protobufjs@^6.10.2, protobufjs@^6.8.6: + version "6.10.2" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.10.2.tgz#b9cb6bd8ec8f87514592ba3fdfd28e93f33a469b" + integrity sha512-27yj+04uF6ya9l+qfpH187aqEzfCF4+Uit0I9ZBQVqK09hk/SQzKa2MUqUpXaVa7LOFRg1TSSr3lVxGOk6c0SQ== + 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" + +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +proxy-from-env@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +puppeteer@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-5.5.0.tgz#331a7edd212ca06b4a556156435f58cbae08af00" + integrity sha512-OM8ZvTXAhfgFA7wBIIGlPQzvyEETzDjeRa4mZRCRHxYL+GNH5WAuYUQdja3rpWZvkX/JKqmuVgbsxDNsDFjMEg== + dependencies: + debug "^4.1.0" + devtools-protocol "0.0.818844" + extract-zip "^2.0.0" + https-proxy-agent "^4.0.0" + node-fetch "^2.6.1" + pkg-dir "^4.2.0" + progress "^2.0.1" + proxy-from-env "^1.0.0" + rimraf "^3.0.2" + tar-fs "^2.0.0" + unbzip2-stream "^1.3.3" + ws "^7.2.3" + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +qs@^6.6.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.0.tgz#8b6519121ab291c316a3e4d49cecf6d13d8c7fe5" + integrity sha512-yjACOWijC6L/kmPZZAsVBNY2zfHSIbpdpL977quseu56/8BZ2LoF5axK2bGhbzhVKt7V9xgWTtpyLbxwIoER0Q== + dependencies: + side-channel "^1.0.4" + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +"readable-stream@>=1.0.33-1 <1.1.0-0": + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^2.0.6, readable-stream@^2.3.5: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + 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" + +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +redis-commands@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" + integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + +referrer-policy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e" + integrity sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA== + +regexp-clone@1.0.0, regexp-clone@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63" + integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw== + +regexpp@^3.0.0, regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + +request@^2.34.0, request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +require_optional@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require_optional/-/require_optional-1.0.1.tgz#4cf35a4247f64ca3df8c2ef208cc494b1ca8fc2e" + integrity sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g== + dependencies: + resolve-from "^2.0.0" + semver "^5.1.0" + +resolve-from@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" + integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c= + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +retry-request@^4.0.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-4.1.3.tgz#d5f74daf261372cff58d08b0a1979b4d7cab0fde" + integrity sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ== + dependencies: + debug "^4.1.1" + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +saslprep@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" + +sd-notify@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/sd-notify/-/sd-notify-2.8.0.tgz#a8e4477efac8426084e235d5d6a89f2af75571fd" + integrity sha512-e+D1v0Y6UzmqXcPlaTkHk1QMdqk36mF/jIYv5gwry/N2Tb8/UNnpfG6ktGLpeBOR6TCC5hPKgqA+0hTl9sm2tA== + dependencies: + bindings "1.5.0" + +seed-random@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54" + integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ= + +"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.4.1, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.2.1, semver@^7.3.2: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shred@0.8.10: + version "0.8.10" + resolved "https://registry.yarnpkg.com/shred/-/shred-0.8.10.tgz#cf1cfe80f79bf5313d2edc3b912278fd107a8717" + integrity sha1-zxz+gPeb9TE9Ltw7kSJ4/RB6hxc= + dependencies: + ax "0.1.8" + cookiejar "1.3.1" + iconv-lite ">= 0.1.2" + sprintf "0.1.1" + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +sift@7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/sift/-/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08" + integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g== + +signal-exit@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +signale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/signale/-/signale-1.4.0.tgz#c4be58302fb0262ac00fc3d886a7c113759042f1" + integrity sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w== + dependencies: + chalk "^2.3.2" + figures "^2.0.0" + pkg-conf "^2.1.0" + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" + integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +sliced@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" + integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E= + +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE= + dependencies: + memory-pager "^1.0.2" + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.7" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" + integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sprintf@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/sprintf/-/sprintf-0.1.1.tgz#e8925fc9894e1aa6899e9091c7f2a12130b70de5" + integrity sha1-6JJfyYlOGqaJnpCRx/KhITC3DeU= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +stock-info@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/stock-info/-/stock-info-1.2.0.tgz#a29f54c5416deec5a08a9f3c19736a91c913c20a" + integrity sha512-GxhFrCWlibRdS/9KC1aRMBD0p4IyGj/rKD5InK/ZUOFAapJ76QhVY/ShsNT0WRkOh12Bh6NfZkWU/Ipj90NstQ== + dependencies: + axios "^0.18.1" + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^4.2.0: + version "4.2.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +stripe@^8.120.0: + version "8.138.0" + resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.138.0.tgz#032ad239cda2058ab0cc391874b4d40e449cafdb" + integrity sha512-Cr+jzcacXOlL1Wrd7xxcE9nk9OBF0l73Z/oCAlBHXldtRr2FRflg/2h4g4Na3LTlcLRYtx+jnKs64/WPOqmpsA== + dependencies: + "@types/node" ">=8.1.0" + qs "^6.6.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +swagger-client@2.0.26: + version "2.0.26" + resolved "https://registry.yarnpkg.com/swagger-client/-/swagger-client-2.0.26.tgz#73f15093f6def369f31b9670c6d9645b33163e4d" + integrity sha1-c/FQk/be82nzG5ZwxtlkWzMWPk0= + dependencies: + btoa "1.1.1" + shred "0.8.10" + +table@^6.0.4: + version "6.0.7" + resolved "https://registry.yarnpkg.com/table/-/table-6.0.7.tgz#e45897ffbcc1bcf9e8a87bf420f2c9e5a7a52a34" + integrity sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g== + dependencies: + ajv "^7.0.2" + lodash "^4.17.20" + slice-ansi "^4.0.0" + string-width "^4.2.0" + +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@^6.0.2: + version "6.1.0" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" + integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +thaw.js@^2.1.0: + version "2.1.4" + resolved "https://registry.yarnpkg.com/thaw.js/-/thaw.js-2.1.4.tgz#d00fbb20b9b6c6bc2aef541199cc0717deedfde9" + integrity sha512-si9DSzEzOBUpbE41wE4Q6NXzXDYZfrniYocrduNtNDwLWj1Auueq8UzgePMQa5gfcQtVCG2OmlCTaHldtno3Vw== + +through2@^0.6.3: + version "0.6.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" + integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg= + dependencies: + readable-stream ">=1.0.33-1 <1.1.0-0" + xtend ">=4.0.0 <4.1.0-0" + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tiny-emitter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== + +tsutils@^3.17.1: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +tweetnacl@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typed-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.0.0.tgz#15ab3825845138a8b1113bd89e60cd6a435739e8" + integrity sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA== + +typescript@^3.9.2: + version "3.9.9" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674" + integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w== + +unbox-primitive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.0.tgz#eeacbc4affa28e9b3d36b5eaeccc50b3251b1d3f" + integrity sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.0" + has-symbols "^1.0.0" + which-boxed-primitive "^1.0.1" + +unbzip2-stream@^1.3.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" + integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.1.1.tgz#77832f57ced2c9478174149cae9b96e9918cd54b" + integrity sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + for-each "^0.3.3" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.1" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@^3.0.0, uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +uuid@^8.0.0, uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +which-boxed-primitive@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +ws@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +ws@^7.2.1, ws@^7.2.3: + version "7.4.4" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59" + integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw== + +x-xss-protection@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/x-xss-protection/-/x-xss-protection-1.3.0.tgz#3e3a8dd638da80421b0e9fff11a2dbe168f6d52c" + integrity sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg== + +"xtend@>=4.0.0 <4.1.0-0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.9.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0"