From 74a822a5b96a7642a13a066a9c9b55238b35cfcd Mon Sep 17 00:00:00 2001 From: WubzyGD Date: Mon, 19 Apr 2021 22:19:14 -0600 Subject: [PATCH] wew first commit --- .gitignore | 4 + LICENSE | 619 ++++++ README.md | 77 + bot.js | 80 + commands/anime/anime.js | 173 ++ commands/dev/admin.js | 42 + commands/dev/blacklist.js | 97 + commands/dev/developer.js | 44 + commands/dev/eval.js | 48 + commands/dev/logger.js | 20 + commands/dev/pull.js | 36 + commands/dev/reload.js | 80 + commands/dev/setstatus.js | 42 + commands/dev/staff.js | 42 + commands/dev/support.js | 42 + commands/dev/vip.js | 54 + commands/fun/8ball.js | 35 + commands/fun/bite.js | 47 + commands/fun/deathnote.js | 144 ++ commands/fun/kiss.js | 52 + commands/fun/secretsanta.js | 182 ++ commands/fun/slap.js | 55 + commands/leveling/levelchannel.js | 49 + commands/leveling/levelrole.js | 98 + commands/leveling/setupleveling.js | 48 + commands/leveling/stats.js | 41 + commands/misc/ar.js | 159 ++ commands/misc/avatar.js | 32 + commands/misc/commands.js | 25 + commands/misc/help.js | 72 + commands/misc/info.js | 34 + commands/misc/ingorear.js | 48 + commands/misc/invite.js | 23 + commands/misc/mem.js | 21 + commands/misc/prefix.js | 48 + commands/misc/serverinfo.js | 31 + commands/misc/supportserver.js | 23 + commands/misc/uptime.js | 28 + commands/misc/userinfo.js | 45 + commands/moderation/autorole.js | 39 + commands/moderation/ban.js | 73 + commands/moderation/checkwarnings.js | 50 + commands/moderation/clearwarnings.js | 58 + commands/moderation/kick.js | 68 + commands/moderation/leave.js | 72 + commands/moderation/logs.js | 85 + commands/moderation/response.js | 85 + commands/moderation/softban.js | 72 + commands/moderation/staffrole.js | 55 + commands/moderation/togglestatuses.js | 27 + commands/moderation/unban.js | 33 + commands/moderation/warn.js | 140 ++ commands/moderation/welcome.js | 72 + commands/social/afk.js | 42 + commands/social/bio.js | 55 + commands/social/clearstatus.js | 26 + commands/social/cry.js | 38 + commands/social/dnd.js | 40 + commands/social/hug.js | 62 + commands/social/pat.js | 63 + commands/social/sip.js | 37 + commands/social/starboard.js | 65 + commands/utility/randnum.js | 51 + commands/utility/todo.js | 100 + events/guildCreate.js | 28 + events/guildDelete.js | 27 + events/guildMemberAdd.js | 19 + events/guildMemberRemove.js | 16 + events/message.js | 93 + events/messageDelete.js | 27 + events/messageReactionAdd.js | 41 + events/messageUpdate.js | 22 + events/ready.js | 98 + handle/command.js | 31 + handle/event.js | 17 + handle/response.js | 16 + loggers/cmds.js | 13 + models/anime.js | 27 + models/ar.js | 10 + models/bot.js | 14 + models/guild.js | 25 + models/levelroles.js | 8 + models/localxp.js | 10 + models/log.js | 30 + models/mod.js | 27 + models/responses.js | 9 + models/saves.js | 8 + models/secretsanta.js | 17 + models/starboard.js | 10 + models/status.js | 9 + models/statuses.js | 8 + models/todo.js | 9 + models/user.js | 23 + models/vscount.js | 10 + models/xp.js | 6 + package-lock.json | 2893 +++++++++++++++++++++++++ package.json | 26 + pull.bat | 1 + responses/decide.js | 19 + responses/wubzy.js | 98 + run.bat | 4 + sync.bat | 10 + template.js | 38 + util/ask.js | 12 + util/cache.js | 26 + util/cache/ar.js | 11 + util/cache/bl.js | 17 + util/cache/lr.js | 9 + util/cache/lxp.js | 9 + util/cachestatus.js | 11 + util/lxp/cacheloop.js | 19 + util/lxp/gainxp.js | 39 + util/makeid.js | 10 + util/mention.js | 20 + util/oncommand.js | 25 + util/pagination.d.ts | 36 + util/pagination.js | 134 ++ util/response/filterresponse.js | 11 + util/response/getresponse.js | 9 + util/response/parseresponse.js | 63 + util/response/saveresponse.js | 15 + util/response/sendresponse.js | 22 + util/siftstatuses.js | 27 + util/tag.d.ts | 9 + util/tag.js | 21 + util/tagfilter.d.ts | 10 + util/tagfilter.js | 75 + util/ts/pagination.ts | 162 ++ util/ts/tag.ts | 25 + util/ts/tagfilter.ts | 62 + util/ts/tsconfig.json | 11 + util/wait.js | 7 + 132 files changed, 9052 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bot.js create mode 100644 commands/anime/anime.js create mode 100644 commands/dev/admin.js create mode 100644 commands/dev/blacklist.js create mode 100644 commands/dev/developer.js create mode 100644 commands/dev/eval.js create mode 100644 commands/dev/logger.js create mode 100644 commands/dev/pull.js create mode 100644 commands/dev/reload.js create mode 100644 commands/dev/setstatus.js create mode 100644 commands/dev/staff.js create mode 100644 commands/dev/support.js create mode 100644 commands/dev/vip.js create mode 100644 commands/fun/8ball.js create mode 100644 commands/fun/bite.js create mode 100644 commands/fun/deathnote.js create mode 100644 commands/fun/kiss.js create mode 100644 commands/fun/secretsanta.js create mode 100644 commands/fun/slap.js create mode 100644 commands/leveling/levelchannel.js create mode 100644 commands/leveling/levelrole.js create mode 100644 commands/leveling/setupleveling.js create mode 100644 commands/leveling/stats.js create mode 100644 commands/misc/ar.js create mode 100644 commands/misc/avatar.js create mode 100644 commands/misc/commands.js create mode 100644 commands/misc/help.js create mode 100644 commands/misc/info.js create mode 100644 commands/misc/ingorear.js create mode 100644 commands/misc/invite.js create mode 100644 commands/misc/mem.js create mode 100644 commands/misc/prefix.js create mode 100644 commands/misc/serverinfo.js create mode 100644 commands/misc/supportserver.js create mode 100644 commands/misc/uptime.js create mode 100644 commands/misc/userinfo.js create mode 100644 commands/moderation/autorole.js create mode 100644 commands/moderation/ban.js create mode 100644 commands/moderation/checkwarnings.js create mode 100644 commands/moderation/clearwarnings.js create mode 100644 commands/moderation/kick.js create mode 100644 commands/moderation/leave.js create mode 100644 commands/moderation/logs.js create mode 100644 commands/moderation/response.js create mode 100644 commands/moderation/softban.js create mode 100644 commands/moderation/staffrole.js create mode 100644 commands/moderation/togglestatuses.js create mode 100644 commands/moderation/unban.js create mode 100644 commands/moderation/warn.js create mode 100644 commands/moderation/welcome.js create mode 100644 commands/social/afk.js create mode 100644 commands/social/bio.js create mode 100644 commands/social/clearstatus.js create mode 100644 commands/social/cry.js create mode 100644 commands/social/dnd.js create mode 100644 commands/social/hug.js create mode 100644 commands/social/pat.js create mode 100644 commands/social/sip.js create mode 100644 commands/social/starboard.js create mode 100644 commands/utility/randnum.js create mode 100644 commands/utility/todo.js create mode 100644 events/guildCreate.js create mode 100644 events/guildDelete.js create mode 100644 events/guildMemberAdd.js create mode 100644 events/guildMemberRemove.js create mode 100644 events/message.js create mode 100644 events/messageDelete.js create mode 100644 events/messageReactionAdd.js create mode 100644 events/messageUpdate.js create mode 100644 events/ready.js create mode 100644 handle/command.js create mode 100644 handle/event.js create mode 100644 handle/response.js create mode 100644 loggers/cmds.js create mode 100644 models/anime.js create mode 100644 models/ar.js create mode 100644 models/bot.js create mode 100644 models/guild.js create mode 100644 models/levelroles.js create mode 100644 models/localxp.js create mode 100644 models/log.js create mode 100644 models/mod.js create mode 100644 models/responses.js create mode 100644 models/saves.js create mode 100644 models/secretsanta.js create mode 100644 models/starboard.js create mode 100644 models/status.js create mode 100644 models/statuses.js create mode 100644 models/todo.js create mode 100644 models/user.js create mode 100644 models/vscount.js create mode 100644 models/xp.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 pull.bat create mode 100644 responses/decide.js create mode 100644 responses/wubzy.js create mode 100644 run.bat create mode 100644 sync.bat create mode 100644 template.js create mode 100644 util/ask.js create mode 100644 util/cache.js create mode 100644 util/cache/ar.js create mode 100644 util/cache/bl.js create mode 100644 util/cache/lr.js create mode 100644 util/cache/lxp.js create mode 100644 util/cachestatus.js create mode 100644 util/lxp/cacheloop.js create mode 100644 util/lxp/gainxp.js create mode 100644 util/makeid.js create mode 100644 util/mention.js create mode 100644 util/oncommand.js create mode 100644 util/pagination.d.ts create mode 100644 util/pagination.js create mode 100644 util/response/filterresponse.js create mode 100644 util/response/getresponse.js create mode 100644 util/response/parseresponse.js create mode 100644 util/response/saveresponse.js create mode 100644 util/response/sendresponse.js create mode 100644 util/siftstatuses.js create mode 100644 util/tag.d.ts create mode 100644 util/tag.js create mode 100644 util/tagfilter.d.ts create mode 100644 util/tagfilter.js create mode 100644 util/ts/pagination.ts create mode 100644 util/ts/tag.ts create mode 100644 util/ts/tagfilter.ts create mode 100644 util/ts/tsconfig.json create mode 100644 util/wait.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12a2baa --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +config.json +auth.json +test.js \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ca9b055 --- /dev/null +++ b/LICENSE @@ -0,0 +1,619 @@ + 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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..c03534e --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# Luno +The official repository of Luno the Discord Bot. Currently in the Early Development phase, and is expected to release to a few servers and eventually bot lists relatively soon. + +## Utils +*Some useful stuff we have in our bot that you can make use of!* + +Just about anything in the utils folder could be useful for a number of reasons, but here's a few big ones: + +### Tags +> util/tagfilter.js and util/tag.js + +Pass in a list of Tags. Each Tag has a list of triggers, its name, then its mode. + +- `toggle` = takes no options. If the tag is present, it will view as `true` in `options`. +- `append` = forms a string from the text after the tag. `-title Something Cool` will return the string `Something Cool`. Will append from all tags that trigger the alias, so if multiple `title` tags are present, all of them will be present in the same string. +- `listAppend` = Behaves like `append`, with the exception that it will return a list where each member is a string for every time the tag is used; see example below + +```js +let options = new TagFilter([ + new Tag(['t', '-title'], 'title', 'append'), + new Tag(['a', 'aliases', 'alts'], 'aliases', 'listAppend'), + new Tag(['f', 'force'], 'force', 'toggle') +]).test(args.join(" ")); + +// -title Example -a AnotherName -a Some other name -f + +// options will look like this +{ + title: "Example", + aliases: ["AnotherName", "Some other name"], + force: true +} +``` + +### Quick awaitMessages +> util/ask.js + +Ask a question and wait for an answer. Returns the string of the user's response, or nothing if there was no response. **Function is asynchronous!** + +*Please make sure you account for the chance of timeout.* + +Pass in your `message` object, the question to ask, the time - in ms - the user has to respond, and an optional boolean of whether or not to disable the filter, which would make it so that any user can answer the question. + +```js +let name = await ask(message, "What is your name?", 30000); +if (!name) {return;} // Function already sends a timeout message, just return here to stop the command from continuing. +return message.channel.send(`Hiya there, ${name}!`); + +new Pagination() +``` + +### Pagination +> util/pagination.ts + +Create a pagination based on a list of Discord MessageEmbeds. + +Paginations work based off of reactions, and the pages are cycled with the click of the reaction. Pass in the message channel object, a list of embeds, the original message, and your Discord.Client object. + +```js +let pages = [/*List of Discord.MessageEmbeds*/]; +let help = new Pagination(message.channel, pages, message, client); + +await help.setPage(1); //Pages start at 1 +await help.setControllers(); //Set the reaction controllers + +// OR you can call .start() to do all of this for you. + +await help.start({ + endTime: 60000 /*Time in ms before the pagination times out to save memory*/, + startPage: 3 /*Page num to start on*/, + user: 'discord_member_id' /*ID of a member that the Pagination will only listen to*/ +}); //All of these are optional. +``` + +_Please note that the Pagination class is still in the works. Only one bug is currently known, and it's that the Pagination will error when a user tries to end the pagination in a DM channel, but this error is not extremely high-level and shouldn't have any major effects on your node process._ + +_Another note: you'll want to go into the pagination.js file and search for .setFooter() and change the name Luno to whatever name your bot is_ \ No newline at end of file diff --git a/bot.js b/bot.js new file mode 100644 index 0000000..475061c --- /dev/null +++ b/bot.js @@ -0,0 +1,80 @@ +const Discord = require('discord.js'); +const client = new Discord.Client(); + +const chalk = require('chalk'); +const ora = require('ora'); +const mongoose = require('mongoose'); + +client.misc = { + savers: ['497598953206841375', '480535078150340609', '468903364533420074'], + activeDMs: new Discord.Collection(), + statusPings: new Discord.Collection(), + startup: new Date(), + startupNoConnect: null, + cache: { + ar: new Map(), + arIgnore: new Map(), + bl: { + guild: [], + user: [] + }, + lxp: { + enabled: [], + xp: {}, + hasLevelRoles: [] + } + }, + loggers: {} +}; + +//const config = require('./config.js'); +const auth = require('./auth.json'); + +//client.config = config; + +async function init() { + let cloginsp = ora(chalk.magentaBright('Connecting Discord client...')).start(); + let pclc = new Date().getTime(); + await client.login(auth.token); + cloginsp.stop(); cloginsp.clear(); + console.log(`${chalk.green('[BOOT]')} >> ${chalk.greenBright(`Connected to Discord in `)}${chalk.white(`${new Date().getTime() - pclc}ms`)}`); + + client.misc.startupNoConnect = new Date(); + client.config = auth; + + let mloginsp = ora(chalk.magentaBright('Connecting to Mongo client...')).start(); + let pmcc = new Date().getTime(); + const config = client.config; + try { + await mongoose.connect(`mongodb+srv://${config.database.user}:${config.database.password}@${config.database.cluster}.uqyvv.mongodb.net/test`, { + useFindAndModify: false, useNewUrlParser: true, dbName: 'Luno', useUnifiedTopology: true, useCreateIndex: true + }).catch(e => { + let date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); + console.error(`\n${chalk.red('[ERROR]')} >> ${chalk.yellow(`At [${date}] | Occurred while trying to connect to Mongo Cluster`)}`, e); + mloginsp.stop(); mloginsp.clear(); + }); + mloginsp.stop(); mloginsp.clear(); + console.log(`${chalk.green('[BOOT]')} >> ${chalk.greenBright(`Connected to Mongo Database in `)}${chalk.white(`${new Date().getTime() - pmcc}ms`)}`); + } catch (e) { + let date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); + console.error(`\n${chalk.red('[ERROR]')} >> ${chalk.yellow(`At [${date}] | Occurred while trying to connect to Mongo Cluster`)}`, e); + mloginsp.stop(); mloginsp.clear(); + } + + ['commands', 'aliases'].forEach(x => client[x] = new Discord.Collection()); + client.responses = {triggers: [], commands: new Discord.Collection()}; + + ['command', 'event', 'response'].forEach(x => require(`./handle/${x}`)(client)); + + client.developers = ["330547934951112705", "673477059904929802"]; + client.utils = {}; + + client.utils.logch = async () => {return client.guilds.cache.get('762707532417335296').channels.cache.get('762732961753595915');}; + client.guildconfig = {}; + client.guildconfig.prefixes = new Map(); + + client.guildconfig.logs = new Map(); + + await require('./events/ready')(client); +} +init().then(() => {}); \ No newline at end of file diff --git a/commands/anime/anime.js b/commands/anime/anime.js new file mode 100644 index 0000000..2f361e6 --- /dev/null +++ b/commands/anime/anime.js @@ -0,0 +1,173 @@ +const Discord = require('discord.js'); + +const UserData = require('../../models/user'); +const AniData = require('../../models/anime'); + +const {TagFilter} = require("../../util/tagfilter"); +const {Tag} = require ("../../util/tag"); +const ask = require('../../util/ask'); + +module.exports = { + name: "anime", + aliases: ['ani', 'an'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Anime") + .setDescription("View and find anime in our huge list of anime!") + .addField("Syntax", "`anime <>`"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}anime <>\``);} + let queue = false; + let options = {}; + if (['a', 'add', 'n', 'new'].includes(args[0])) { + let tu = await UserData.findOne({uid: message.author.id}); + if (!tu || !tu.staff) { + await message.channel.send("Since you aren't a Luno Staff member, this anime will be __submitted__ for reviewal!"); + queue = true; + } + options = new TagFilter([ + new Tag(['ask', 'question'], 'ask', 'toggle'), + new Tag(['title', 't', 'name', 'n'], 'name', 'append'), + new Tag(['japname', 'japanesename', 'jn'], 'japname', 'listAppend'), + new Tag(['description', 'desc', 'd', 'plot', 'p'], 'plot', 'append'), + new Tag(['pub', 'pubs', 'publishers', 'publisher', 'pb'], 'publishers', 'listAppend'), + new Tag(['stud', 's', 'studio', 'studs', 'studios'], 'studios', 'listAppend'), + new Tag(['began', 'airstart', 'as'], 'airStartDate', 'append'), + new Tag(['ended', 'airend', 'ae'], 'airEndDate', 'append'), + new Tag(['iscomplete', 'completed', 'ic'], 'isComplete', 'toggle'), + new Tag(['seasons', 'sns'], 'seasons', 'append'), + new Tag(['episodes', 'es'], 'episodes', 'append'), + new Tag(['genres', 'g'], 'genres', 'listAppend'), + new Tag(['tags', 'ta', 'tgs', 'tg', 'tag'], 'tags', 'listAppend'), + new Tag(['cs', 'characters', 'chars', 'chs'], 'characters', 'listAppend'), + new Tag(['streams', 'streamat', 'sa'], 'streamAt', 'listAppend'), + new Tag(['img', 'thumb', 'thumbnail', 'image']) + ]).test(args.join(' ')); + + if (Object.keys(options).length) { + let foptions = {}; + let option; for (option of Object.keys(options)) { + if (Array.isArray(options[option])) { + let s = ''; + let data; + for (data of options[option]) { + s += data; + s += options[option].indexOf(data) < (options[option].length - 1) ? ', ' : ''; + } + foptions[option] = s; + } + } + } else { + if (client.misc.activeDMs.has(message.author.id)) {return message.channel.send("I'm already asking you questions in a DM! Finish that first, then try this command again.");} + client.misc.activeDMs.set(message.author.id, 'anime-add'); + + let mesg = await message.author.send("I'm going to ask you some questions about the anime's info. Just reply with the answer and use good grammar and spelling and be as accurate as possible. To cancel the process, just leave the question unanswered for a few minutes and I'll let you know that the question timed out and is not longer answerable.") + .catch(() => {return message.reply("Something went wrong there! Most likely, your DMs are closed.");}); + await mesg.channel.send("Check your DMs!"); + + function clearDM() {client.misc.activeDMs.delete(message.author.id);} + client.misc.activeDMs.set(message.author.id, 'anime-make'); + let dmch = mesg.channel; + + options.name = await ask(mesg, "What is the anime's name?", 60000, true); if (!options.name) {return;} + if (options.name.length > 75) {clearDM(); return dmch.send("The anime name can't be more than 75 characters!");} + + options.plot = await ask(mesg, "How would you describe the anime? Give a very brief description of things like its plot, main characters, and setting.", 240000, true); if (!options.plot) {return clearDM();;} + if (options.plot.length > 500) {clearDM(); return dmch.send("Oi! I said give a \"very brief\" description of the anime!");} + + options.japname = await ask(mesg, "What is the anime's japanese name? (The romanization, not the Japanese characters.)", 120000, true); if (!options.japname) {return clearDM();} + if (options.japname.length > 75) {clearDM(); return dmch.send("The japanese name can't be more than 75 characters!");} + + options.studios = await ask(mesg, "What studio created the anime? If there are multiple studios, please separate them with a comma.", 120000, true); if (!options.studios) {return clearDM();} + if (options.studios.length > 150) {clearDM(); return dmch.send("No way there were actually that many studios...");} + options.studios = options.studios.split(/,\s+/gm); + if (options.studios.length > 5) {clearDM(); return dmch.send("No way there were actually that many studios...");} + + options.publishers = await ask(mesg, "What company published the anime? If there are multiple publishers, please separate them with a comma.", 120000, true); if (!options.publishers) {return clearDM();} + if (options.publishers.length > 150) {clearDM(); return dmch.send("No way there were actually that many publishers...");} + options.publishers = options.publishers.split(/,\s+/gm); + if (options.publishers.length > 5) {clearDM(); return dmch.send("No way there were actually that many publishers...");} + + options.airStartDate = await ask(mesg, "When did the anime start?", 120000, true); if (!options.airStartDate) {return clearDM();} + options.airEndDate = await ask(mesg, "When did the anime end?", 120000, true); if (!options.airStartDate) {return clearDM();} + options.lastUpdate = await ask(mesg, "When was the last time a new episode was released for the anime?", 120000, true); if (!options.lastUpdate) {return clearDM();} + + options.isComplete = await ask(mesg, "Is the anime completed? (If the most recent season has finished, you may only say \"no\" if the next season is *confirmed* by the *studio or publishers* or the next season is in the works.", 60000, true); if (!options.isComplete) {return clearDM();} + if (!['y', 'yes', 'ye', 'n', 'no'].includes(options.isComplete.trim().toLowerCase())) {clearDM(); return dmch.send("You must specify yes or no! Please try again.");} + options.isComplete = ['y', 'yes', 'ye'].includes(options.isComplete.trim().toLowerCase()); + + options.seasons = await ask(mesg, "How many seasons does the anime have?", 120000, true); if (!options.seasons) {return clearDM();} + if (isNaN(options.seasons) || Number(options.seasons < 1)) {clearDM(); return dmch.send("You either didn't give a number, or it was < 1.");} + options.seasons = Number(options.seasons); + + options.episodes = await ask(mesg, "How many episodes does the anime have?", 120000, true); if (!options.episodes) {return clearDM();} + if (isNaN(options.episodes) || Number(options.episodes < 1)) {clearDM(); return dmch.send("You either didn't give a number, or it was < 1.");} + options.episodes = Number(options.episodes); + + options.genres = await ask(mesg, "What genre(s) describe the anime? If there are multiple genres, please separate them with a comma.", 120000, true); if (!options.genres) {return clearDM();} + if (options.genres.length > 150) {clearDM(); return dmch.send("That's too many genres!");} + options.genres = options.genres.split(/,\s+/gm); + if (options.genres.length > 7) {clearDM(); return dmch.send("That's too many genres!");} + + options.tags = await ask(mesg, "What tags describe the anime? Please separate tags with a comma, and *do not put the # in the tag!*.", 120000, true); if (!options.tags) {return clearDM();} + if (options.tags.length > 200) {clearDM(); return dmch.send("That's too many tags!");} + options.tags = options.tags.split(/,\s+/gm); + if (options.tags.length > 25) {clearDM(); return dmch.send("That's too many tags!");} + + options.streamAt = await ask(mesg, "What streaming services can you use to watch the anime? If there are multiple stream sites, please separate them with a comma. Please include only legal, licensed industries such as Netflix, Funimation, Crunchyroll, or Hulu. 9anime, cartooncrazy, and similar sites are illegal and won't be listed here.", 120000, true); if (!options.streamAt) {return clearDM();} + if (options.streamAt.length > 150) {clearDM(); return dmch.send("No way there are actually that many streaming sites to watch the anime on...");} + options.streamAt = options.streamAt.split(/,\s+/gm); + if (options.streamAt.length > 7) {clearDM(); return dmch.send("No way there are actually that many streaming sites to watch the anime on...");} + + options.thumbnail = await ask(mesg, "Give me an image **URL** *do not upload the image* to use for the anime", 120000, true); if (!options.thumbnail) {return clearDM();} + if (options.thumbnail.length > 350) {clearDM(); return dmch.send("That URL is a bit too long. Consider uploading it to imgur or another image sharing site and trying again.");} + + options.characters = []; + clearDM(); + } + + let am; + let foptions = {}; + let option; for (option of Object.keys(options)) { + if (Array.isArray(options[option])) { + let s = ''; + let data; + for (data of options[option]) { + s += data; + s += options[option].indexOf(data) < (options[option].length - 1) ? ', ' : ''; + } + foptions[option] = s; + } + } + let amEmbed = new Discord.MessageEmbed() + .setTitle(`New Anime -> ${options.name}`) + .setDescription(`${queue ? 'Requested' : 'Added'} by ${message.author.tag}`) + .addField('Info', `**Name:** ${options.name}\n**Japanese Name:** ${options.japname}\n\n**Publishers:** ${foptions.publishers}\n**Studios:** ${foptions.studios}`) + .addField('Length', `**# of Seasons:** ${options.seasons}\n**# of Episodes:** ${options.episodes}`) + .addField('Airing', `**Began:** ${options.airStartDate}\n**Ended:** ${options.isComplete ? options.airEndDate : 'This anime is still airing!'}`) + .addField('Other', `**Genre(s):** ${foptions.genres}\n**Tags:** ${foptions.tags}\n**Characters:** ${foptions.characters}\n**Stream this at:** ${foptions.streamAt}`) + .setColor("328ba8") + .setImage(options.thumbnail) + .setFooter('Luno', client.user.avatarURL()) + .setTimestamp(); + try { + am = await message.channel.send(amEmbed); + await am.react('👍'); + await am.react('👎'); + } catch {return message.channel.send(":thinking: hmmm... something went wrong there. I might not have permissions to add reactions to messages, and this could be the issue.");} + try { + let rc = am.createReactionCollector((r, u) => ['👍', '👎'].includes(r.emoji.name) && u.id === message.author.id, {max: 1, time: 60000}); + rc.on("collect", async r => { + if (r.emoji.name !== '👎') { + client.guilds.fetch('762707532417335296').then(g => g.channels.cache.get('817466729293938698').send(amEmbed)); + while (true) {options.id = require('../../util/makeid')(4); if (!await AniData.findOne({id: options.id})) {break;}} + await new AniData(options).save(); + return message.author.send(`Your anime has been ${!queue ? "added" : "submitted"}`); + } else { + return message.author.send("Oh, okay. I'll discard that then!"); + } + }); + rc.on("end", collected => {if (!collected.size) {return message.author.send("Looks like you ran out of time! Try again?");}}); + } catch {return message.channel.send("Hmm... there was some kind of error when I tried to submit that anime. Try again, and if it keeps not working, then go yell at my devs!");} + } + } +}; \ No newline at end of file diff --git a/commands/dev/admin.js b/commands/dev/admin.js new file mode 100644 index 0000000..92b85c0 --- /dev/null +++ b/commands/dev/admin.js @@ -0,0 +1,42 @@ +const Discord = require('discord.js'); +const UserData = require('../../models/user'); + +module.exports = { + name: "admin", + help: new Discord.MessageEmbed() + .setTitle("Help -> Admin") + .setDescription("Make a user a Luno admin") + .addField("Syntax", "`admin <@user|userID>`") + .addField("Notice", "This command is only available to Luno developers."), + meta: { + category: 'Developer', + description: "Add or remove users as Luno admins", + syntax: '`admin <@user|userID>`', + extra: "You can check if a user is an admin without being a developer." + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This is a guild-only command.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}\``);} + let person = mention ? mention : args[1] ? client.users.cache.has(args[1]) ? client.users.cache.get(args[1]) : null : null; + let tu = await UserData.findOne({uid: person ? person.id : message.author.id}) ? await UserData.findOne({uid: person ? person.id : message.author.id}) : new UserData({uid: person ? person.id : message.author.id}); + if (['c', 'check'].includes(args[0])) {return message.reply(`${person ? person : message.member.displayName} ${tu.admin ? 'is' : 'is not'} a Luno Admin.`);} + if (!['a', 'add', 'r', 'remove'].includes(args[0])) {return message.reply("You must specify whether to `add` or `remove` someone as an admin.");} + if (!person) {return message.reply("You must mention someone to add as an admin, or use their ID.");} + let atu = await UserData.findOne({uid: message.author.id}); + if ((!atu || !atu.developer) && !client.developers.includes(message.author.id)) {return message.reply('You must be a developer in order to add set admin statuses.');} + if (['a', 'add'].includes(args[0])) {tu.support = true; tu.staff = true; tu.admin = true;} + else {tu.admin = false; tu.developer = false;} + tu.save(); + const logemb = (act) => new Discord.MessageEmbed() + .setAuthor(`Admin ${act}`, message.author.avatarURL()) + .setDescription("A user's Admin status was updated.") + .setThumbnail(person.avatarURL({size: 1024})) + .addField("Name", person.username, true) + .addField("Developer", message.author.username, true) + .setColor("e8da3a") + .setFooter("Luno") + .setTimestamp(); + client.guilds.cache.get('762707532417335296').channels.cache.get('762732961753595915').send(logemb(['a', 'add'].includes(args[0]) ? 'Added' : 'Removed')); + return message.reply(`${message.guild.members.cache.get(person.id).displayName} is no${['a', 'add'].includes(args[0]) ? 'w' : ' longer'} an admin!`); + } +}; \ No newline at end of file diff --git a/commands/dev/blacklist.js b/commands/dev/blacklist.js new file mode 100644 index 0000000..d73d52f --- /dev/null +++ b/commands/dev/blacklist.js @@ -0,0 +1,97 @@ +const Discord = require('discord.js'); +const UserData = require('../../models/user'); +const GuildData = require('../../models/guild') + +module.exports = { + name: "blacklist", + aliases: ['bl'], + meta: { + category: 'Developer', + description: "Completely blocks a user or server from using Luno!", + syntax: '`blacklist [@mention|ID]`', + extra: null + }, + help: "Disables a user from using Luno (Usage: `{{p}}blacklist [@mention|ID])`", + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send("Syntax: `blacklist [@mention|ID]`");} + + let tu = await UserData.findOne({uid: message.author.id}); + + if (['g', 'guild'].includes(args[0].toLowerCase())) { + if (!tu || !tu.admin) {return message.channel.send('Sorry... you have to be a Luno Admin to do this!');} + + let guild = !args[1].match(/\d+/) ? message.guild ? message.guild : null : client.guilds.cache.has(args[1]) ? client.guilds.cache.get(args[1]) : null; + if (!guild) {return message.channel.send("You must provide a guild ID or be in a guild that you wish to blacklist!");} + + let tg = await GuildData.findOne({gid: guild.id}) || new GuildData({gid: guild.id}); + + if (args[1].match(/\d+/)) {args.shift();} + if (!args[1]) {return message.channel.send("You must specify whether to `add` or `del` a guild's blacklist!");} + + if (message.guild.id === "762707532417335296") {return message.reply("You can't blacklist my support server!");} + + if (['a', 'add'].includes(args[1].toLowerCase())) { + if (tg.blacklisted) {return message.reply("That guild is already blacklisted!");} + tg.blacklisted = true; + tg.save(); + client.misc.cache.bl.guild.push(message.guild.id); + return message.channel.send("Gotcha! This server will not be able to use my commands!"); + } + + if (['r', 'rem', 'remove', 'd', 'del', 'delete'].includes(args[1].toLowerCase())) { + if (!tg.blacklisted) {return message.reply("That guild isn't blacklisted in the first place!");} + tg.blacklisted = false; + tg.save(); + delete client.misc.cache.bl.guild[client.misc.cache.bl.guild.indexOf(message.guild.id)]; + return message.channel.send("I have graced your merciful request; this server can once again make use of my wonderous abilities!"); + } + + return message.channel.send("Valid args: `[guildID] `"); + } + + if (['u', 'user'].includes(args[0].toLowerCase())) { + args.shift(); + + if (!args[0]) {return message.channel.send("You must specify whether to `add` or `del` a user's blacklist!");} + + function checkPerms(tu, bu) { + if (!tu.developer && bu.support) {message.channel.send("You can't blacklist any member of staff unless you're a developer!"); return null;} + if (!tu.admin) {message.channel.send("You must be at least admin to do that!"); return null;} + if (bu.developer) {message.channel.send("Developers cannot be blacklisted!"); return null;} + } + + if (['a', 'add'].includes(args[0].toLowerCase())) { + let blacklistUser = args[1].match(/^<@(?:!?)(?:\d+)>$/) && mention && client.users.cache.has(mention.id) ? mention.id : client.users.cache.has(args[1]) ? client.users.cache.get(args[1]).id : null; + if (!blacklistUser) {return message.reply("You must specify a user to blacklist!");} + let usersData = await UserData.findOne( { uid: blacklistUser } ) || new UserData({uid: blacklistUser}); + + if (!checkPerms(tu, usersData)) {return;} + + if (usersData.blacklisted === true) {return message.reply('they\'re already blacklisted :eyes:');} + + await UserData.findOneAndUpdate({ uid: blacklistUser }, { blacklisted: true }.catch(() => {})); + client.misc.cache.bl.user.push(blacklistUser); + + return message.channel.send(`Another one bites the dust! **${blacklistUser.user.tag}** has been blacklisted!`) + } + + if (['r', 'rem', 'remove', 'd', 'del', 'delete'].includes(args[0].toLowerCase())) { + let blacklistedUser = args[1].match(/^<@(?:!?)(?:\d+)>$/) && mention && client.users.cache.has(mention.id) ? mention.id : client.users.cache.has(args[1]) ? client.users.cache.get(args[1]).id : null; + if (!blacklistedUser) { return message.reply("You need to specify who you're letting free..." );} + let userData = await UserData.findOne( { uid: blacklistedUser } ) || new UserData({uid: blacklistedUser}); + + if (!checkPerms(tu, userData)) {return;} + + if(userData.blacklisted === false) {return message.reply('hate to break it you... they\'re not even blacklisted!');} + + await UserData.findOneAndUpdate({ uid: blacklistedUser }, { blacklisted: false }.catch(() => {})); + delete client.misc.cache.bl.user[client.misc.cache.bl.user.indexOf(blacklistedUser)]; + + return message.channel.send(`Alright, there you go, I unblacklisted **${blacklistedUser.user.tag}**`) + } + + return message.channel.send("Valid args: ` `"); + } + + return message.channel.send("Valid args: ``"); + }}; \ No newline at end of file diff --git a/commands/dev/developer.js b/commands/dev/developer.js new file mode 100644 index 0000000..ec6f679 --- /dev/null +++ b/commands/dev/developer.js @@ -0,0 +1,44 @@ +const Discord = require('discord.js'); +const mongoose = require('mongoose'); +const UserData = require('../../models/user'); + +module.exports = { + name: "developer", + aliases: ['dev'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Developer") + .setDescription("Add or remove users as Luno developers.") + .addField("Syntax", "`developer <@user|userID>`") + .addField("Notice", "You must already be a developer of Luno in order to use this command."), + meta: { + category: 'Developer', + description: "Add or remove users as Luno developers", + syntax: '`developer <@user|userID>`', + extra: "You can check if a user is a developer without being a developer." + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This is a guild-only command!");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}developer <@user|userID>\``);} + let person = mention ? mention : args[1] ? client.users.cache.has(args[1]) ? client.users.cache.get(args[1]) : null : null; + let tu = await UserData.findOne({uid: person ? person.id : message.author.id}) ? await UserData.findOne({uid: person ? person.id : message.author.id}) : new UserData({uid: person ? person.id : message.author.id}); + if (['c', 'check'].includes(args[0])) {return message.reply(`${person ? person : message.member.displayName} ${tu.developer ? 'is' : 'is not'} a Luno developer.`);} + if (!['a', 'add', 'r', 'remove'].includes(args[0])) {return message.reply("You must specify whether to `add` or `remove` someone as a developer.");} + if (!person) {return message.reply("You must mention someone to add as a developer, or use their ID.");} + let atu = await UserData.findOne({uid: message.author.id}); + if ((!atu || !atu.developer) && !client.developers.includes(message.author.id)) {return message.reply('You must be a developer in order to add or remove someone else as a developer.');} + if (['a', 'add'].includes(args[0])) {tu.support = true; tu.staff = true; tu.admin = true; tu.developer = true;} + else {tu.developer = false;} + tu.save(); + const logemb = (act) => new Discord.MessageEmbed() + .setAuthor(`Developer ${act}`, message.author.avatarURL()) + .setDescription("A user's Developer status was updated.") + .setThumbnail(person.avatarURL({size: 1024})) + .addField("Name", person.username, true) + .addField("Developer", message.author.username, true) + .setColor("e8da3a") + .setFooter("Luno") + .setTimestamp(); + client.guilds.cache.get('762707532417335296').channels.cache.get('762732961753595915').send(logemb(['a', 'add'].includes(args[0]) ? 'Added' : 'Removed')); + return message.reply(`${message.guild.members.cache.get(person.id).displayName} is no${['a', 'add'].includes(args[0]) ? 'w' : ' longer'} a developer!`); + } +}; \ No newline at end of file diff --git a/commands/dev/eval.js b/commands/dev/eval.js new file mode 100644 index 0000000..97cee4a --- /dev/null +++ b/commands/dev/eval.js @@ -0,0 +1,48 @@ +const Discord = require('discord.js'); +const chalk = require('chalk'); + +const {Tag} = require('../../util/tag'); +const {TagFilter} = require('../../util/tagfilter'); + +module.exports = { + name: 'eval', + aliases: ['ev', ':', 'e'], + help: "Evaluates raw JavaScript code. *This is a __developer-only__ command.* Usage: `{{p}}eval `", + meta: { + category: 'Developer', + description: "Evaluates raw JavaScript code. Nerd access only.", + syntax: '`eval `', + extra: null + }, + execute(message, msg, args, cmd, prefix, mention, client) { + try { + if (!client.developers.includes(message.author.id)) {return message.channel.send("Sorry, but I've got trust issues, so only me devs can go commanding me around like that.");}; + if (!args.length) return message.channel.send(`Syntax: \`${prefix}eval \``); + + let options = new TagFilter([new Tag(['s', 'silent', 'nr', 'noreply'], 'silent', 'toggle')]).test(args[0]); + if (options.silent) {args.shift();} + + if (!args.length) {return message.channel.send("Silly goose, if you want me to do something, you have to tell me what!");} + const result = new Promise((resolve) => resolve(eval(args.join(' ')))); + return result.then((output) => { + if (typeof output !== 'string') { + output = require('util').inspect(output, {depth: 0}); + } + output = output.replace(client.config.token, 'Client Token') + .replace(client.config.database.password, 'Database Password') + .replace(client.config.database.cluster, 'Database Cluster'); + + return options.silent ? null : message.channel.send(new Discord.MessageEmbed() + .setTitle('Client Evaluation') + .setDescription(`\`\`\`js\n${output}\n\`\`\``) + .setColor('328ba8') + .setFooter(`Luno`, client.user.avatarURL()) + .setTimestamp()); + }).catch(error => {return message.channel.send(`Error: \`${error}\`.`);}); + } catch (error) { + let date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); + console.error(`\n${chalk.red('[ERROR]')} >> ${chalk.yellow(`At [${date}] | Occurred while trying to run n?eval`)}`, error); + return message.channel.send(`Error: \`${error}\`.`); + } + }, +}; \ No newline at end of file diff --git a/commands/dev/logger.js b/commands/dev/logger.js new file mode 100644 index 0000000..3812d86 --- /dev/null +++ b/commands/dev/logger.js @@ -0,0 +1,20 @@ +const Discord = require('discord.js'); + +const ws = require('ws'); + +module.exports = { + name: "logger", + aliases: [], + meta: { + category: 'Developer', + description: "Websocket logs cause im cool", + syntax: '`nonya`', + extra: null + }, + help: "Websocket logs cause im cool", + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!client.developers.includes(message.author.id)) {return message.channel.send("Fuck off");} + client.misc.loggers[args[0]] = new ws(`ws://${args[1]}:${args[2]}`); + return message.channel.send("Logger set"); + } +}; \ No newline at end of file diff --git a/commands/dev/pull.js b/commands/dev/pull.js new file mode 100644 index 0000000..e429b22 --- /dev/null +++ b/commands/dev/pull.js @@ -0,0 +1,36 @@ +const Discord = require('discord.js'); +const fs = require('fs'); +const chalk = require('chalk'); +const UserData = require('../../models/user'); +const cp = require('child_process'); + +module.exports = { + name: "pull", + help: new Discord.MessageEmbed() + .setTitle("Help -> VCS Pull") + .setDescription("Pulls new commits from VCS") + .addField("Syntax", "`pull`") + .addField("Notice", "This command is only available to Luno developers."), + meta: { + category: 'Developer', + description: "Pull new commits from VSC and update the bot. Otaku zone, non-otakus not allowed.", + syntax: '`pull`', + extra: "You'll still need to use `reload` afterwards" + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + const tu = await UserData.findOne({uid: message.author.id}); + if (!tu || !tu.developer) {return message.channel.send("You must be a Luno developer in order to do this!");} + + console.log(`\n${chalk.yellow('[WARN]')} >> ${chalk.gray('VCS:')} ${chalk.white('Initiating remote->local VCS sync/refresh!')}`); + + cp.exec("git pull origin master", function(error, stdout, stderr) { + if (error) { + let date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); + console.error(`\n${chalk.red('[ERROR]')} >> ${chalk.yellow(`At [${date}] | Occurred while trying to pull from VCS`)}`, stderr || error); + } else { + console.log(`\n${chalk.gray('[INFO]')} >> ${chalk.hex('ff4fd0')(`VCS Pull successful`)}\n`); + } + return message.channel.send(`Done with ${error ? 'an error' : 'no errors'}!`); + }); + } +}; \ No newline at end of file diff --git a/commands/dev/reload.js b/commands/dev/reload.js new file mode 100644 index 0000000..de1c490 --- /dev/null +++ b/commands/dev/reload.js @@ -0,0 +1,80 @@ +const Discord = require('discord.js'); +const fs = require('fs'); +const chalk = require('chalk'); +const ora = require('ora'); + +const UserData = require("../../models/user"); + +module.exports = { + name: "reload", + aliases: ['relog', 'rel', 'refresh'], + help: new Discord.MessageEmbed() + .setTitle("Help -> System Reloading") + .setDescription("Reloads the system extensions by refreshing all command and event files into client without terminating the node process. *Hi I'm Wubzy and this makes no sense to anyone but discord.js devs because we're nerds*") + .addField("Syntax", "`refresh [log]`. Adding 'log' will log to the console as though the bot were in startup.") + .addField("Notice", "This command is only available to Luno developers."), + meta: { + category: 'Developer', + description: "Refresh all client commands and events and clear most of the require cache. Only two people can use this command and they're probably not you.", + syntax: '`reload`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) { + const tu = await UserData.findOne({uid: message.author.id}); + if (!tu || !tu.developer) {return message.channel.send("You must be a Luno developer in order to do this!");} + + let commands = fs.readdirSync('./commands').filter(file => file.endsWith('.js')); + let dirSet = new Map(); + fs.readdirSync('./commands').filter(file => !file.includes('.')).forEach(dir => fs.readdirSync(`./commands/${dir}`).filter(file => file.endsWith('.js')).forEach(x => {commands.push(x); dirSet.set(x, dir)})); + console.log(`\n${chalk.yellow('[WARN]')} >> ${chalk.gray('Reload:')} ${chalk.white('All commands and events are being reloaded!')}`); + console.log(`${chalk.gray('[INFO]')} >> ${chalk.hex('ff4fd0')(`Developer ${message.author.username} initiated the system refresh`)}\n`); + + let cmdspinner = ora(chalk.blue('Loading Commands')).start(); + ['commands', 'aliases'].forEach(x => client[x] = new Discord.Collection()); + for (let commandf of commands) { + if (Object.keys(require.cache).includes(require.resolve(`../../commands/${dirSet.has(commandf) ? `${dirSet.get(commandf)}/`: ''}${commandf}`))) {delete require.cache[require.resolve(`../../commands/${dirSet.has(commandf) ? `${dirSet.get(commandf)}/`: ''}${commandf}`)];} + let command = require(`../../commands/${dirSet.has(commandf) ? `${dirSet.get(commandf)}/`: ''}${commandf}`); + client.commands.set(command.name, command); + if (command.aliases) {command.aliases.forEach(a => client.aliases.set(a, command.name));} + } + cmdspinner.stop(); cmdspinner.clear(); + console.log(`${chalk.gray('[LOG]')} >> ${chalk.blue('Loaded all Commands')}`); + + let eventspinner = ora(chalk.blue('Loading Events')).start(); + let eventFilter = fs.readdirSync('./events/').filter(x => x.endsWith('.js')); + for (let file of eventFilter) { + let evtName = file.split('.')[0]; + if (Object.keys(require.cache).includes(require.resolve('../../events/' + file))) {delete require.cache[require.resolve('../../events/' + file)];} + let evt = require('../../events/' + file); + client.removeAllListeners(evtName); + client.on(evtName, evt.bind(null, client)); + } + eventspinner.stop(); eventspinner.clear(); + console.log(`${chalk.gray('[LOG]')} >> ${chalk.blue('Loaded all Events')}`); + + let rspspinner = ora(chalk.blue('Loading Commands')).start(); + let responses = fs.readdirSync('./responses').filter(file => file.endsWith('.js')); + client.responses.triggers = []; + for (let responsef of responses) { + if (Object.keys(require.cache).includes(require.resolve(`../../responses/${responsef}`))) {delete require.cache[require.resolve(`../../responses/${responsef}`)];} + let response = require(`../../responses/${responsef}`); + client.responses.triggers.push([response.name, response.condition]); + client.responses.commands.set(response.name, response); + } + rspspinner.stop(); rspspinner.clear(); + console.log(`${chalk.gray('[LOG]')} >> ${chalk.blue('Loaded all Responses')}`); + + console.log(`\n${chalk.gray('[INFO]')} >> ${chalk.hex('ff4fd0')(`Client refresh successful`)}\n`); + + return message.channel.send("Done!") + } + if (['l', 'log', 'ns', 'nosilent', 'notsilent'].includes(args[0].toLowerCase())) { + ['commands', 'aliases'].forEach(x => client[x] = new Discord.Collection()); + client.responses = {triggers: [], commands: new Discord.Collection()}; + ['command', 'event', 'response'].forEach(x => require(`./${x}`)(client)); + return message.channel.send("Done!"); + } + else {return message.channel.send("Oi! 'log' is the only valid arg to use. Use no args if you want a cleaner console output instead.");} + } +}; \ No newline at end of file diff --git a/commands/dev/setstatus.js b/commands/dev/setstatus.js new file mode 100644 index 0000000..d22cbf4 --- /dev/null +++ b/commands/dev/setstatus.js @@ -0,0 +1,42 @@ +const Discord = require('discord.js'); + +const UserData = require('../../models/user'); + +const {TagFilter} = require('../../util/tagfilter'); +const {Tag} = require('../../util/tag'); + +module.exports = { + name: "setstatus", + aliases: ['sst'], + meta: { + category: 'Developer', + description: "Set my public status. Don't make me say something weird! (Also only available to devs, of course.)", + syntax: '`setstatus <-s status> <-t type>`', + extra: "You can check if a user is an admin without being a developer." + }, + tags: [], + help: new Discord.MessageEmbed() + .setTitle("Help -> Status-Setting") + .setDescription("Sets the bot's status") + .addField("Syntax", "`setstatus <-s status> <-t type>`") + .addField('Notice', "This command is **developer-only**"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}setstatus [type]\``);} + + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.developers.includes(message.author.id)) {return message.channel.send("You must be my developer in order to do that!");} + + let options = new TagFilter([ + new Tag(['s', 'status', 'm', 'msg', 'message'], 'status', 'append'), + new Tag(['t', 'type'], 'type', 'append') + ]).test(args.join(" ")); + if (!options.status || !options.status.length) {return message.channel.send("You must use the -status tag (and -type if you want a custom type)!");} + + if (options.status.length > 30) {return message.reply("That status is a bit too long.");} + if (options.type) {if (!['playing', 'watching', 'listening'].includes(options.type.toLowerCase())) {return message.channel.send("That's not a valid type!");}} + + if (options.type) {client.user.setActivity(options.status, {type: options.type.toUpperCase()});} + else {client.user.setActivity(options.status);} + return message.channel.send(`Status set to: \`${options.type ? `${options.type.slice(0, 1).toUpperCase()}${options.type.slice(1).toLowerCase()}` : "Playing"} ${options.status}\``); + } +}; \ No newline at end of file diff --git a/commands/dev/staff.js b/commands/dev/staff.js new file mode 100644 index 0000000..c7ea719 --- /dev/null +++ b/commands/dev/staff.js @@ -0,0 +1,42 @@ +const Discord = require('discord.js'); +const UserData = require('../../models/user'); + +module.exports = { + name: "staff", + help: new Discord.MessageEmbed() + .setTitle("Help -> Staff") + .setDescription("Make a user a Luno staff member") + .addField("Syntax", "`staff <@user|userID>`") + .addField("Notice", "This command is only available to Luno developers."), + meta: { + category: 'Developer', + description: "Add or remove users as Luno staff", + syntax: '`staff <@user|userID>`', + extra: "You can check if a user is staff without being a developer." + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This is a guild-only command.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}\``);} + let person = mention ? mention : args[1] ? client.users.cache.has(args[1]) ? client.users.cache.get(args[1]) : null : null; + let tu = await UserData.findOne({uid: person ? person.id : message.author.id}) ? await UserData.findOne({uid: person ? person.id : message.author.id}) : new UserData({uid: person ? person.id : message.author.id}); + if (['c', 'check'].includes(args[0])) {return message.reply(`${person ? person : message.member.displayName} ${tu.staff ? 'is' : 'is not'} a part of Luno Staff.`);} + if (!['a', 'add', 'r', 'remove'].includes(args[0])) {return message.reply("You must specify whether to `add` or `remove` someone as a Staff Member.");} + if (!person) {return message.reply("You must mention someone to add as a staff member, or use their ID.");} + let atu = await UserData.findOne({uid: message.author.id}); + if ((!atu || !atu.developer) && !client.developers.includes(message.author.id)) {return message.reply('You must be a developer in order to add set staff member statuses.');} + if (['a', 'add'].includes(args[0])) {tu.support = true; tu.staff = true;} + else {tu.staff = false; tu.admin = false; tu.developer = false;} + tu.save(); + const logemb = (act) => new Discord.MessageEmbed() + .setAuthor(`Staff ${act}`, message.author.avatarURL()) + .setDescription("A user's Staff status was updated.") + .setThumbnail(person.avatarURL({size: 1024})) + .addField("Name", person.username, true) + .addField("Developer", message.author.username, true) + .setColor("e8da3a") + .setFooter("Luno") + .setTimestamp(); + client.guilds.cache.get('762707532417335296').channels.cache.get('762732961753595915').send(logemb(['a', 'add'].includes(args[0]) ? 'Added' : 'Removed')); + return message.reply(`${message.guild.members.cache.get(person.id).displayName} is no${['a', 'add'].includes(args[0]) ? 'w' : ' longer'} a staff member!`); + } +}; \ No newline at end of file diff --git a/commands/dev/support.js b/commands/dev/support.js new file mode 100644 index 0000000..7529c6e --- /dev/null +++ b/commands/dev/support.js @@ -0,0 +1,42 @@ +const Discord = require('discord.js'); +const UserData = require('../../models/user'); + +module.exports = { + name: "support", + help: new Discord.MessageEmbed() + .setTitle("Help -> Support") + .setDescription("Make a user a Luno Support Team member") + .addField("Syntax", "`support <@user|userID>`") + .addField("Notice", "This command is only available to Luno admin."), + meta: { + category: 'Developer', + description: "Add or remove users as Luno support", + syntax: '`support <@user|userID>`', + extra: "You can check if a user is a support member without being a developer." + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This is a guild-only command.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}\``);} + let person = mention ? mention : args[1] ? client.users.cache.has(args[1]) ? client.users.cache.get(args[1]) : null : null; + let tu = await UserData.findOne({uid: person ? person.id : message.author.id}) ? await UserData.findOne({uid: person ? person.id : message.author.id}) : new UserData({uid: person ? person.id : message.author.id}); + if (['c', 'check'].includes(args[0])) {return message.reply(`${person ? person : message.member.displayName} ${tu.support ? 'is' : 'is not'} a part of Luno Support.`);} + if (!['a', 'add', 'r', 'remove'].includes(args[0])) {return message.reply("You must specify whether to `add` or `remove` someone as a Support Team Member.");} + if (!person) {return message.reply("You must mention someone to add as a support member, or use their ID.");} + let atu = await UserData.findOne({uid: message.author.id}); + if (!atu || !atu.admin) {return message.reply('You must be an admin in order to add set support team member statuses.');} + if (['a', 'add'].includes(args[0])) {tu.support = true;} + else {tu.support = false; tu.staff = false; tu.admin = false; tu.developer = false;} + tu.save(); + const logemb = (act) => new Discord.MessageEmbed() + .setAuthor(`Support ${act}`, message.author.avatarURL()) + .setDescription("A user's Support status was updated.") + .setThumbnail(person.avatarURL({size: 1024})) + .addField("Name", person.username, true) + .addField("Developer", message.author.username, true) + .setColor("e8da3a") + .setFooter("Luno") + .setTimestamp(); + client.guilds.cache.get('762707532417335296').channels.cache.get('762732961753595915').send(logemb(['a', 'add'].includes(args[0]) ? 'Added' : 'Removed')); + return message.reply(`${message.guild.members.cache.get(person.id).displayName} is no${['a', 'add'].includes(args[0]) ? 'w' : ' longer'} a Support Team member!`); + } +}; \ No newline at end of file diff --git a/commands/dev/vip.js b/commands/dev/vip.js new file mode 100644 index 0000000..3fb532e --- /dev/null +++ b/commands/dev/vip.js @@ -0,0 +1,54 @@ +const Discord = require("discord.js"); + +module.exports = { + name: "vip", + aliases: ["premium"], + help: new Discord.MessageEmbed() + .setTitle("Help -> VIP") + .setDescription("Toggle a server as VIP. This grants a few small bonuses to your server that you can't get anywhere else!\n\nWant to become a VIP? Support the bot by [joining the support server](), donating to the bot's creators, or promoting/spreading the bot to other servers.") + .addField("Syntax", "`vip `") + .addField("Notice", "This command is **developer-only**."), + meta: { + category: 'Developer', + description: "Set server VIP status", + syntax: '`vip `', + extra: "This command is mostly cosmetic as there are no real perks *yet*" + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This command is server-only!");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}vip \``);} + if (!client.developers.includes(message.author.id) && !['check', 'c', 'view', 'v'].includes(args[0])) {return message.reply("Unfortunately, this is a **developer-only command**!");} + const GuildSettings = require('../../models/guild'); + const logemb = (act) => new Discord.MessageEmbed() + .setAuthor(`VIP Server ${act}`, message.author.avatarURL()) + .setDescription("A Server's VIP status was updated.") + .setThumbnail(message.guild.iconURL({size: 1024})) + .addField("Name", message.guild.name, true) + .addField("Admin", message.author.username, true) + .setColor("e8da3a") + .setFooter("Luno") + .setTimestamp(); + + if (['add', 'a', 'make', 'm'].includes(args[0])) { + let tguild = await GuildSettings.findOne({gid: message.guild.id}) + ? await GuildSettings.findOne({gid: message.guild.id}) + : new GuildSettings({gid: message.guild.id}); + if (tguild.vip === true) {return message.reply("This server is already a VIP server.");} + tguild.vip = true; + tguild.save(); + client.guilds.cache.get('762707532417335296').channels.cache.get('762732961753595915').send(logemb("Added")); + return message.reply("This server is now a VIP server!"); + } else if (['remove', 'r', 'delete', 'd'].includes(args[0])) { + let tguild = await GuildSettings.findOne({gid: message.guild.id}); + if (tguild) { + if (tguild.vip === false) {return message.reply("This server wasn't a VIP server anyways...");} + await GuildSettings.findOneAndUpdate({gid: message.guild.id, vip: false}); + client.guilds.cache.get('762707532417335296').channels.cache.get('762732961753595915').send(logemb("Removed")); + } else {return message.reply("This server wasn't a VIP server anyways...");} + return message.reply("This server is no longer a VIP server!"); + } else if (['check', 'c', 'view', 'v'].includes(args[0])) { + let tguild = await GuildSettings.findOne({gid: message.guild.id}); + return message.reply((tguild && tguild.vip) ? 'This server is a VIP server.' : 'This server is not a VIP server.'); + } + } +}; \ No newline at end of file diff --git a/commands/fun/8ball.js b/commands/fun/8ball.js new file mode 100644 index 0000000..3c1e9ef --- /dev/null +++ b/commands/fun/8ball.js @@ -0,0 +1,35 @@ +const Discord = require("discord.js"); + +module.exports = { + name: "8ball", + aliases: ["8b"], + help: new Discord.MessageEmbed() + .setTitle("Help -> 8ball") + .setDescription("Gives you moral support, decides if you really do want that third taco, or helps you decide on your existential crisis. Answers come with an accuracy guarantee of 0%!") + .addField("Syntax", "`8ball `"), + meta: { + category: 'Fun', + description: "Gives you moral support, decides if you really do want that third taco, or helps you decide on your existential crisis. Answers come with an accuracy guarantee of 0%!", + syntax: '`8ball `', + extra: null + }, + execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}8ball \``);} + let question = args.join(" "); + if (question.length > 230) {return message.reply("Listen, I'm no fortune-teller. I can't help you with a question that long!");} + + let responses = [ + /*Positive Responses*/ "Yes!", "I think so", "Possibly", "Certainly", "Definitely", "Absolutely", "Sure!", "Most Likely", "I believe so", "If you're asking for my honest opinion... yes" + /*Negative Responses*/ ,"No!", "I don't think so", "Probably not", "Impossible", "Nope", "Absolutely *not*", "Nah", "Doubt it", "I don't believe so", "If you're asking for my honest opinion... no" + /*Neutral Responses */ ,"Maybe", "I'm not sure", "I'll think about it", "Uhh Luno isn't here right now. I can take a message?", "I'm sure if you look deep within your heart, which is currently all over that tree, you'll find the answer", "I mean, if you think so...", "I don't have an opinion on that.", "I'll choose to remain silent." + ]; + let name = message.guild ? message.member.displayName : message.author.username; + + return message.reply(new Discord.MessageEmbed() + .setAuthor("8ball Question", message.author.avatarURL()) + .setDescription("**Question:** " + question + "\n**Answer:** " + responses[Math.floor(Math.random() * responses.length)]) + .setColor("328ba8") + .setFooter(`Asked by ${name} | Luno`) + .setTimestamp()); + } +}; \ No newline at end of file diff --git a/commands/fun/bite.js b/commands/fun/bite.js new file mode 100644 index 0000000..970bf39 --- /dev/null +++ b/commands/fun/bite.js @@ -0,0 +1,47 @@ +const Discord = require('discord.js'); + +const Saves = require('../../models/saves'); +const UserData = require('../../models/user'); + +const makeId = require('../../util/makeid'); + +module.exports = { + name: "bite", + aliases: [], + help: "Use `{{p}}slap @person` to have me personally deliver your anger to them with a nice s l a p.", + meta: { + category: 'Fun', + description: "Slap another user! Virtually, of course.", + syntax: '`slap <@user>`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'bite'}) || new Saves({name: 'bite'}); + let saves = savess.saves; + if (!args.length) { + return message.channel.send(message.guild ? "Please mention someone to bite!" : "Oi! I get it if you don't like me but you can't just waltz into my DMs and bite me!");} + if (mention && args[0].match(/^<@(?:!?)(?:\d+)>$/)) { + if (!message.guild) {return message.reply("Oi! I get it if you don't like me but you can't just waltz into my DMs and bite me!");} + if (!message.guild.members.cache.has(mention.id)) {return message.reply("That user is not in this server!");} + if (message.author.id === mention.id) {return message.reply("Ew quit tryna bite yourself, that's weird.");} + return message.channel.send(new Discord.MessageEmbed() + .setAuthor(`${message.guild ? message.member.displayName : message.author.username} bites ${message.guild.members.cache.get(mention.id).displayName}`, message.author.avatarURL()) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('d93846') + ); + } + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Luno Developer in order to add new bite GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/fun/deathnote.js b/commands/fun/deathnote.js new file mode 100644 index 0000000..cfd85da --- /dev/null +++ b/commands/fun/deathnote.js @@ -0,0 +1,144 @@ +const Discord = require('discord.js'); +const moment = require('moment'); + +const VC = require('../../models/vscount'); + +const {Tag} = require('../../util/tag'); +const {TagFilter} = require('../../util/tagfilter'); + +const deaths = [ + "watching too much anime", "an overdose of waifus", "Hypotakunemia", "trying to self-isekai", + "Bass:tm:", "cranking the music just a little too loud", "trying to swim in lava", "an unknown cause", + "Despacito", "something really cliche and unoriginal", "'shrooms", + "clicking 'I agree' without reading the Terms of Service", "alchemy", "rusty spoons", + "picking the wrong waifu", "body pillows", "fur-con", "something to do with lamb sauce", + "grandma's cookies" +]; // a list of preset death methods that can occur + +const before = [ + "A name is being written...", "Someone will perish soon...", "A body is *about* to be discovered...", + "{p} is scribbling something in their notebook...", "\*Manical laughter echoes around you*...", + "{p} laughs maniacally..." +]; // things it says before the response is sent + +const responses = { + /*an obj controlling the possible formats for the death note report*/ + news: { + titles: ["Breaking News"], + texts: [ + "This just in: **{p}** was found dead at **{ds}** today.\n\nAfter some investigation, the authorities ruled the cause of death to be **{c}**.", + "We're now live at the crime scene where it is believed that **{p}** died because of **{c}**.", + "Authorities are reporting what is believed to be another Kira case, where **{c}** has taken the life of **{p}**." + ], + images: [], + }, // a news report of the dead body + writes: { + titles: ["Something sinister just happened", "A name has been written", "Fate has been changed"], + texts: [ + "With a maniacal laugh, **{w}** writes \"**{p}**\" in their Death Note. And the cause of death? They've written **{c}**.", + "**{w}** has sealed **{pa}** fate to die by **{c}**." + ], + images: [] + }, // "so-and-so writes blah blah blah's name in their death note" + /*hasdied: { + texts: [], + images: [] + }, // "so-and-so has died by...", + unserious: { + texts: [], + images: [] + } // other methods, mainly the un-serious or joking ones */ +}; + +//responses.unserious.images = responses.hasdied.images; + +module.exports = { + name: "deathnote", + aliases: ['dn'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Death Note") + .setDescription("Congratulations! You've picked up a death note. Write someone's name in it, and see for yourself if it's the real deal...") + .addField("Syntax", "\`deathnote <@member> [method of death]\`"), + meta: { + category: 'Fun', + description: "Write someone's name in your deathnote. I'm not legally responsible for anything that happens after that.", + syntax: '`deathnote <@member> [method of death]`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("Unfortunately, this is a **guild-only** command!");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}deathnote <@member> [method of death]\``);} + if (args[0] === "kill" || args[0] === "k") {args.shift();} // if someone adds in 'kill' it'll remove it and act like it wasn't there, proceeding as normal. + //if (!args[0].trim().match(/^<@(?:\!?)\d+>$/)) {return message.reply("You have to mention someone!");} + if (mention && mention.id === message.author.id) {return message.reply("Hehe I won't let you write your own name in the notebook! Just leave it somewhere for a few days and someone else will take it. Maybe they'll write your name...");} // users can't mention themselves + if (mention && mention.id === client.user.id) {return message.reply("You can't kill me! Little did you know, I'm actually a death god!");} + if (mention && mention.bot) {return message.reply("As a bot, I simply cannot let you attempt to kill another fellow bot!");} + + let reptype = responses[Object.keys(responses)[Math.floor(Math.random() * Object.keys(responses).length)]]; // report type + let title = reptype.titles[Math.floor(Math.random() * reptype.titles.length)]; + let options = new TagFilter([ + new Tag(['method', '-m', 'cause', '-c'], 'method', 'append'), + new Tag(['victim', 'v', 'against', 'a', 'name', 'n'], 'victim', 'append') + ]).test(args.join(" ")); + + let death = (!options.victim || (options.victim && !options.victim.length)) && (!options.method || (options.method && !options.method.length)) && args.length > 1 + ? args.join(" ").slice(args[0].length + 1) + : deaths[Math.floor(Math.random() * deaths.length)]; //kill method + if (options.method && options.method.length) {death = options.method;} + if (death.length > 750) {return message.channel.send("I'd rather you didn't try to fill the death note with a 7-page double-spaced essay in Times New Roman containing an advanced trajectory theorem on the death of your poor target.");} + + if (!mention && (!options.victim || !options.victim.length)) {return message.reply("You have to write their name down in order to kill them! (In other words, please mention the user whose name you wish to write.)");} + + if (options.victim && options.victim.length) { + let vargs = options.victim.trim().split(/\s+/g); + let nvargs = []; + let varg; for (varg of vargs) { + if (varg.match(/^<@(?:!?)\d+>$/)) { + nvargs.push(message.guild.members.cache.has(varg.slice(varg.search(/\d/), varg.search('>'))) ? message.guild.members.cache.get(varg.slice(varg.search(/\d/), varg.search('>'))).displayName : varg); + } else {nvargs.push(varg);} + } + options.victim = nvargs.join(" ").trim(); + } + let victim = options.victim && options.victim.length ? options.victim : message.mentions.members.first().displayName; + let killer = message.member; + + let pretext = before[Math.floor(Math.random() * before.length)].replace(/{p}/g, victim); + + let note = await message.channel.send(new Discord.MessageEmbed() + .setDescription(pretext) + .setColor('328ba8') + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp() + ); + + await require('../../util/wait')(2500); + + let text = reptype.texts[Math.floor(Math.random() * reptype.texts.length)] + .replace(/{p}/g, victim) //{p} = victim + .replace(/{pa}/g, victim.toLowerCase().endsWith('s') ? `${victim}'` : `${victim}'s`) //{pa} = victim but with a formatted apostrophe like "WubzyGD's" + .replace(/{c}/g, death) // {c} = death method + .replace(/{w}/g, killer.displayName) // {w} = killer or writer + .replace(/{ds}/g, moment().format("h:mm a")); // {ds} = date small, basically just the time. + // Create and format the kill text + + let dns; + if (mention && mention.id) { + dns = await VC.findOne({uid: message.author.id, countOf: 'dn'}) || new VC({uid: message.author.id, countOf: 'dn'}); + dns.against[mention.id] = dns.against[mention.id] ? dns.against[mention.id] + 1 : 1; + dns.total++; + dns.markModified(`against.${mention.id}`); + dns.save(); + } + + let finalEmbed = new Discord.MessageEmbed() + .setAuthor(title, message.author.avatarURL()) + .setDescription(`${text}${dns ? `\n\n_Their name is in your deathnote **${dns.against[mention.id] === 1 ? 'once' : `${dns.against[mention.id]} times`}.**_` : ''}`) + .setColor('328ba8') + .setFooter(`Luno${dns ? ` | ${dns.total} name${dns.total === 1 ? ' has been' : 's'} written in your deathnote!` : ''}`) + .setTimestamp(); + + if (mention) {finalEmbed.setThumbnail(mention.avatarURL({size: 1024}));} + + return note.edit(finalEmbed); + } +}; \ No newline at end of file diff --git a/commands/fun/kiss.js b/commands/fun/kiss.js new file mode 100644 index 0000000..e5d4ebf --- /dev/null +++ b/commands/fun/kiss.js @@ -0,0 +1,52 @@ +const Discord = require('discord.js'); +const Saves = require('../../models/saves'); +const UserData = require('../../models/user'); +const makeId = require('../../util/makeid'); + +module.exports = { + name: "kiss", + help: "Ask for a kiss with `{{p}}kiss`, or give one by mentioning someone!", + meta: { + category: 'Fun', + description: "Give someone a kiss, or show that you're looking for one. Best of luck pal!", + syntax: '`kiss <@user>`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'kiss'}) ? await Saves.findOne({name: 'kiss'}) : new Saves({name: 'kiss'}); + let saves = savess.saves; + if (!args.length) { + return message.channel.send(message.guild ? new Discord.MessageEmbed() + .setTitle(`${message.guild ? message.member.displayName : message.author.username} wants a kiss!`) + .setThumbnail(message.author.avatarURL({size: 2048})) + .setDescription(`Give them a little kiss with \`${prefix}kiss @${message.member.displayName}\`!`) + .setColor('328ba8') + .setFooter('Luno', client.user.avatarURL()) + .setTimestamp() + : "I'm not really into that kind of thing. Maybe try asking in a server?" + );} + if (mention && args[0].match(/^<@(?:!?)(?:\d+)>$/)) { + if (!message.guild) {return message.reply("Please make sure you're in a server so you can mention someone other than me to kiss!");} + if (!message.guild.members.cache.has(mention.id)) {return message.reply("That user is not in this server!");} + if (message.author.id === mention.id) {return message.reply("A self-kiss ought to be a little hard, don't you think?");} + return message.channel.send(new Discord.MessageEmbed() + .setAuthor(`${message.guild ? message.member.displayName : message.author.username} kisses ${message.guild.members.cache.get(mention.id).displayName}`, message.author.avatarURL()) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('d428a0') + ); + } + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Luno Developer in order to add new kiss GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/fun/secretsanta.js b/commands/fun/secretsanta.js new file mode 100644 index 0000000..f14c38f --- /dev/null +++ b/commands/fun/secretsanta.js @@ -0,0 +1,182 @@ +const Discord = require('discord.js'); +const SS = require('../../models/secretsanta'); + +const ask = require('../../util/ask'); + +module.exports = { + name: "secretsanta", + aliases: ['ss'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Secret Santa") + .setDescription("Create a secret santa for all of your friends or for your server! Whether you celebrate the holidays or not, this can still be loads of fun!") + .addField("Syntax", "`secretsanta `"), + meta: { + category: 'Fun', + description: "Create and join fully-functioning secret santas. I even randomize the assignments for you, how neat!", + syntax: '`secretsanta `', + extra: "It's not Christmas anymore, but you know what, who cares. Some features of this command, such as `end` and `kick` are not available/not working." + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}secretsanta \``);} + if (['create', 'new', 'c', 'n', 's'].includes(args[0].toLowerCase())) { + function clearDM() {client.misc.activeDMs.delete(message.author.id);} + if (client.misc.activeDMs.has(message.author.id)) {return message.reply("I'm already asking you questions in DM for a separate command! Finish that command before using this one.");} + client.misc.activeDMs.set(message.author.id, 'secretsanta-make'); + let mesg = await message.author.send("I'll be asking you a few questions here about how you want your Secret Santa to go! You can simply ignore the messages for a few minutes to cancel the process.").catch(() => {message.reply("Please open your DMs so I can ask you some questions!");}); + let dmch = mesg.channel; + + let conf = await ask(mesg, "This secret santa will be tied to your account, and you will be considered the host. Is this okay?", 60000, true); if (!conf) {return clearDM();} + if (['n', 'no'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Oh, alrighty! Have the person who wants to be the host execute this same command.");} + if (!['yes', 'ye', 'y', 'sure'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Please specify yes or no you weeb!");} + + let start = await ask(mesg, "When will you begin the secret santa? (You'll start it manually, so don't worry about formatting.", 60000, true); if (!start) {return clearDM();} + if (start.length > 150) {clearDM(); return dmch.send("Heya there, just a few words, please! I don't wanna have to read out an essay about when it's starting to all the people that want to hear about your secret santa!");} + + let end = await ask(mesg, "When will you end the secret santa? (You'll also end it manually.)", 60000, true); if (!end) {return clearDM();} + if (end.length > 150) {clearDM(); return dmch.send("Heya there, just a few words, please! I don't wanna have to read out an essay about when it's ending to all the people that want to hear about your secret santa!");} + + let spend = await ask(mesg, "What is your maximum and minimum spending? This is useful so that everyone gets an equal gift or gifts. This will be shown to the people that buy their gifts.", 360000, true); if (!spend) {return clearDM();} + if (spend.length > 500) {clearDM(); return dmch.send("Mate, this is not a dissertation! Let's keep it under 500 characters, please!");} + + let anon = await ask(mesg, "Will you be keeping this secret santa totally anonymous, or will you let the gift recipients know who their gifters are when they are opened? Type \"yes\" if you will be keeping it anonymous, and \"no\" otherwise.", 360000, true); if (!anon) {return clearDM();} + if (['n', 'no'].includes(anon.trim().toLowerCase())) {anon = false;} + else if (['yes', 'ye', 'y', 'sure'].includes(anon.trim().toLowerCase())) {anon = true;} + else {clearDM(); return dmch.send("Please specify yes or no you weeb!");} + + let info = await ask(mesg, "What information would you like me to ask joining members to provide when they join your secret santa? (Whatever you type will be shown directly to them when.)", 360000, true); if (!info) {return clearDM();} + if (info.length > 750) {clearDM(); return dmch.send("Let's keep it under 750 characters, please.");} + + let notes = await ask(mesg, "Are there any other notes you'd like to add? If not, just write N/A or something of that nature.", 360000, true); if (!ask) {return clearDM();} + if (notes.length > 500) {clearDM(); return dmch.send("Let's keep it under 500 characters, please.");} + + let fconf = await ask(mesg, "Would you like to continue and create the secret santa? By doing so, you agree that:\n-Luno and its developers are not responsible for anything financially-related to your secret santa\n-Anyone with your join code can join the secret santa\n-You are responsible for notifying your members of any changes or updates.\n-I am not responsible for any eggnog that may or may not be stolen from you by Wubzy. *for legal reasons this is a joke*\n\n-The answers you have submitted are what you want to use, as they cannot be changed.", 120000, true); if (!fconf) {return clearDM();} + if (['n', 'no'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Oh, yikes. Is it about the eggnog? Sorry about that hehe...");} + if (!['yes', 'ye', 'y', 'sure'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Please specify yes or no you weeb!");} + + let id; + while (true) { + id = require('../../util/makeid')(6); + let test = await SS.findOne({ssid: id}); + if (!test) {break;} + } + + let tss = new SS({ + ssid: id, + owner: message.author.id, + start: start, + end: end, + anon: anon, + spend: spend, + info: info, + notes: notes, + members: [], + started: false + }); + tss.save(); + + clearDM(); + return dmch.send(new Discord.MessageEmbed() + .setTitle("Secret Santa Created!") + .setDescription("Your Secret Santa has been completed! Have your members join by using `n?secretsanta join ` where the ID is the ID displayed below. You can start your secret santa when you have at least 3 members with `n?secretsanta start `. If someone joins that you don't want in your secret santa, use `n?secretsanta kick <@member|userID>`. If you want to also participate, just join the same way as everyone else.") + .setThumbnail(message.author.avatarURL({size: 1024})) + .addField("ID", `\`${id}\``, true) + .addField("Owner", message.author.username, true) + .setColor("01bd2f") + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp() + ); + } + + if (['j', 'join'].includes(args[0].toLowerCase())) { + if (!args[1] || args[1].length !== 6) {return message.channel.send("You must provide a 6-digit join code! Ask the host to copy it and send it to you.");} + + let tss = await SS.findOne({ssid: args[1]}); + if (!tss) {return message.channel.send("A secret santa with that join code does not exist!");} + if (tss.started) {return message.channel.send("That secret santa has already started.");} + let min = false; let m; for (m of tss.members) {if (m.id === message.author.id) {min = true;}} + if (tss.members && min) {return message.channel.send("You're already in that secret santa!");} + + function clearDM() {client.misc.activeDMs.delete(message.author.id);} + if (client.misc.activeDMs.has(message.author.id)) {return message.reply("I'm already asking you questions in DM for a separate command! Finish that command before using this one.");} + client.misc.activeDMs.set(message.author.id, 'secretsanta-join'); + + let mesg = await message.author.send("I'll be asking you a few questions here about you and what you want! You can simply ignore the messages for a few minutes to cancel the process.").catch(() => {message.reply("Please open your DMs so I can ask you some questions!");}); + let dmch = mesg.channel; + + let o = await client.users.fetch(tss.owner); + + await dmch.send(new Discord.MessageEmbed() + .setTitle("This Secret Santa!") + .setDescription("This is the one you're trying to join!") + .addField("Start", tss.start) + .addField("End", tss.end) + .addField("Spending", tss.spend) + .addField("Notes", tss.notes) + .addField("Anonymous Gifters", tss.anon ? "Yes" : "No") + .addField("ID", `\`${tss.ssid}\``, true) + .addField("Owner", o.username, true) + .addField("Members", tss.members ? tss.members.length : 0, true) + .setColor("01bd2f") + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp() + ); + + let name = await ask(mesg, "What is your name? This can be seen by everyone in the secret santa.", 60000, true); if (!name) {return clearDM();} + if (name.length > 50) {clearDM(); return dmch.send("Maybe just the *first* name? I doubt it's over 50 characters.");} + + await dmch.send("This is the information the host has asked you to provide:"); + let info = await ask(mesg, tss.info, 600000, true); if (!info) {return clearDM();} + if (info.length > 750) {clearDM(); return dmch.send("Let's keep that under 750 characters. No need to put your entire Christmas list on there :smirk:");} + + let conf = await ask(mesg, "Before we finish, do you agree to the following things:\n-I, Luno, and my developers, are not responsible for anything financially-related to your Secret Santa\n-You should contact the host if you have questions\n-These answers you gave are final and will be seen by the person who draws you.\n-You *need* to have your DMs open so that I can reach you when drawing time comes!", 120000, true); + if (['n', 'no'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Alrighty! I've discarded your responses :P");} + if (!['yes', 'ye', 'y', 'sure'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Please specify yes or no you weeb!");} + + let tssmembers = tss.members ? tss.members : {}; + tssmembers.push({id: message.author.id, name: name, info: info}); + tss.members = tssmembers; + tss.save(); + + clearDM(); + + await o.send(`${message.author.username} has joined your Secret Santa! (Code: \`${tss.ssid}\` in case you have more than one)`); + return dmch.send("All done! You've now joined."); + } + + if (['start'].includes(args[0])) { + if (!args[1] || args[1].length !== 6) {return message.channel.send("You must specify the join code/ID to your Secret Santa!");} + let tss = await SS.findOne({ssid: args[1]}); + if (!tss) {return message.channel.send("That Secret Santa doesn't exist; your code is invalid!");} + if (tss.owner !== message.author.id) {return message.channel.send("You must be the host to do that!");} + if (tss.started) {return message.channel.send("Your Secret Santa is already started!");} + if (tss.members.length < 3) {return message.channel.send("You need to have at least 3 members in order to start.");} + + let dm = []; let rm; + let m; for (m of tss.members) {dm.push({name: m.id, assignedTo: null});} + for (m of dm) { + while (true) { + let rm = tss.members[Math.floor(Math.random() * tss.members.length)]; + let exists = false; + let cdm; for (cdm of dm) {if (!exists) {exists = cdm.assignedTo === rm.id;}} + if (!exists && rm.id !== m.name) {dm[dm.indexOf(m)] = {name: m.name, assignedTo: rm.id}; break;} + } + } + tss.assignments = dm; + tss.started = true; + tss.save(); + + for (m of tss.members) { + let mem = await client.users.fetch(m.id); + let assignmentt; let a; for (a of tss.assignments) {if (a.name === m.id) {assignmentt = a.assignedTo; break;}} + let assignment; let tm; for (tm of tss.members) {if (tm.id === assignmentt) {assignment = tm; break;}} + + await mem.send(`The secret santa has been started!\nYou've been assigned to **${assignment.name}**. They've written the following about what they want:\n\`${assignment.info}\`\n\nThe host has said this about when you should when you should have your gifts purchased and when you'll do gift exchange:\n\`${tss.end}\`\n\nThe following info is written on how much to spend on gifts: \n\`${tss.spend}\``) + .catch(async () => { + let host = await client.users.fetch(tss.owner); + await host.send(`There was a problem sending ${mem.name} their info. Please tell that member that they have been assigned to \`${assignment.name}\` and that they want \`${assignment.info}\`.`); + }); + } + message.channel.send("**The secret santa has been started!** Everyone should have their assignments."); + } + } +}; \ No newline at end of file diff --git a/commands/fun/slap.js b/commands/fun/slap.js new file mode 100644 index 0000000..6aa13d2 --- /dev/null +++ b/commands/fun/slap.js @@ -0,0 +1,55 @@ +const Discord = require('discord.js'); + +const Saves = require('../../models/saves'); +const UserData = require('../../models/user'); +const VC = require('../../models/vscount'); + +const makeId = require('../../util/makeid'); + +module.exports = { + name: "slap", + aliases: ['hit'], + help: "Use `{{p}}slap @person` to have me personally deliver your anger to them with a nice s l a p.", + meta: { + category: 'Fun', + description: "Slap another user! Virtually, of course.", + syntax: '`slap <@user>`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'slap'}) ? await Saves.findOne({name: 'slap'}) : new Saves({name: 'slap'}); + let saves = savess.saves; + if (!args.length) { + return message.channel.send(message.guild ? "Please mention someone to slap!" : "Oi! You don't get to waltz into my DM just to slap me!");} + if (mention && args[0].match(/^<@(?:!?)(?:\d+)>$/)) { + if (!message.guild) {return message.reply("Oi! You don't get to waltz into my DM just to slap me!");} + if (!message.guild.members.cache.has(mention.id)) {return message.reply("That user is not in this server!");} + if (message.author.id === mention.id) {return message.reply("Wait wouldn't slapping yourself be a form of self-harm? ToS is that you??");} + let slaps = await VC.findOne({uid: message.author.id, countOf: 'slap'}) || new VC({uid: message.author.id, countOf: 'slap'}); + slaps.against[mention.id] = slaps.against[mention.id] ? slaps.against[mention.id] + 1 : 1; + slaps.total++; + slaps.markModified(`against.${mention.id}`); + slaps.save(); + return message.channel.send(new Discord.MessageEmbed() + .setAuthor(`${message.guild ? message.member.displayName : message.author.username} slaps ${message.guild.members.cache.get(mention.id).displayName}`, message.author.avatarURL()) + .setDescription(`That makes slap **#${slaps.against[mention.id]}** from you to them!`) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('d93846') + .setFooter(`${slaps.total} slap${slaps.total === 1 ? '' : 's'} total`) + ); + } + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Luno Developer in order to add new slap GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/leveling/levelchannel.js b/commands/leveling/levelchannel.js new file mode 100644 index 0000000..2e55f2d --- /dev/null +++ b/commands/leveling/levelchannel.js @@ -0,0 +1,49 @@ +const Discord = require('discord.js'); + +const UserData = require('../../models/user'); +const LXP = require('../../models/localxp'); + +module.exports = { + name: "levelchannel", + aliases: ['lvch', 'lvlch', 'levelch', 'lvmsgch'], + meta: { + category: 'Leveling', + description: "Set the channel to send levelup messages to", + syntax: '`levelchannel [#channel]`', + extra: null, + guildOnly: true + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> Level Message Channel") + .setDescription("Specify a channel for me to send levelup messages to, or `clear` it to have me send the message in the same channel as the user.") + .addField("Syntax", "`levelchannel [#channel]`") + .addField("Notice", "You must be an administrator or have the specified staff role in your server to be able to use this command.") + .addField("See Also", "Looking for how to turn off level up messages in the server? Use `levelmessage`"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}levelchannel [#channel]\``);} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.staffrole || !tu.staffrole.length || !message.member.roles.cache.has(tu.staffrole)) && !message.member.permissions.has("ADMINISTRATOR")) {return message.channel.send("You don't have the permissions to do that in this server!");} + + let xp = await LXP.findOne({gid: message.guild.id}); + if (!xp) {return message.channel.send("Leveling isn't enabled in this server!");} + + if (['s', 'set'].includes(args[0].toLowerCase())) { + args.shift(); + if (!args.length) {return message.channel.send("Please try again and provide a channel to set the level up messages to be sent to.");} + let ch = message.mentions.channels.first() || message.guild.channels.cache.get(args[0]); + if (!ch) {return message.reply("I couldn't find that channel! Try again?");} + xp.lvch = ch.id; + xp.save(); + return message.channel.send(`Got it! I'll send levelup messages to <#${ch.id}>`); + } + + if (['c', 'clear'].includes(args[0].toLowerCase())) { + if (!xp.lvch.length) {return message.channel.send("I'm already not sending levelup messages to any specific channel!");} + xp.lvch = ''; + xp.save(); + return message.channel.send("Level up message channel cleared. I'll now send messages to the channel where the member levels up in."); + } + + return message.channel.send("Invalid arg! Use `set` or `clear`."); + } +}; \ No newline at end of file diff --git a/commands/leveling/levelrole.js b/commands/leveling/levelrole.js new file mode 100644 index 0000000..3ddf80a --- /dev/null +++ b/commands/leveling/levelrole.js @@ -0,0 +1,98 @@ +const Discord = require('discord.js'); + +const GuildData = require('../../models/guild'); +const LR = require('../../models/levelroles'); + +const ask = require('../../util/ask'); + +module.exports = { + name: "levelrole", + aliases: ['lr', 'levelroles', 'levelingroles', 'rolereward', 'rolerewards'], + meta: { + category: 'Leveling', + description: "Sets a role to be given to users when they reach a certain level.", + syntax: '`levelrole `', + extra: null, + guildOnly: true + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> Level Roles") + .setDescription("Sets a role to be given to users when they reach a certain level.") + .addField("Syntax", "`levelrole `"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}levelrole \``);} + let tg = await GuildData.findOne({gid: message.guild.id}); + if ((!tg || !tg.staffrole || !tg.staffrole.length || !message.member.roles.cache.has(tg.staffrole)) && !message.member.permissions.has("ADMINISTRATOR")) {return message.channel.send("You don't have the permissions to do that in this server! Ask a server admin to do it for you");} + + if (['set', 's', 'add', 'a'].includes(args[0].toLowerCase())) { + if (!message.guild.me.permissions.has("MANAGE_ROLES")) {return message.channel.send("I don't have permissions to add roles to members in this server, so it would be useless to try and setup any level roles!");} + if (!args[1]) {return message.reply("please provide a level and a role to reward for reaching that level!");} + let level = args[1]; + if (isNaN(Number(level)) || Number(level) > 200 || Number(level) < 1) {return message.reply("the level must be a positive number lower than 200!");} + if (!args[2]) {return message.channel.send("Please try again and provide a role mention or ID of the role you'd like to add after the level!");} + if (!args[2].match(/<@\&\d+>/gm) && !args[2].match(/\d+/gm)) {return message.channel.send("Hmm, it doesn't look like you gave me a role.");} + let role = message.mentions.roles.first() || message.guild.roles.cache.get(args[2]); + if (!role) {return message.channel.send("I can't find that role!");} + role = role.id; + let lr = await LR.findOne({gid: message.guild.id}) || new LR({gid: message.guild.id}); + if (Object.keys(lr.roles).length >= 10) {return message.channel.send("Due to data storage concerns, you can only have 10 level roles in this server. If you believe you need more, come to the support server and talk to my devs and see if they would be willing to raise this requirement for you.");} + lr.roles[level] = role; + lr.markModified(`roles.${level}`); + lr.save(); + if (!client.misc.cache.lxp.hasLevelRoles.includes(message.guild.id)) {client.misc.cache.lxp.hasLevelRoles.push(message.guild.id);} + return message.channel.send(`Got it, I'll now give members the role \`${message.guild.roles.cache.get(role).name}\` when they reach Level ${level}`); + } + + if (['v', 'view', 'l', 'list'].includes(args[0].toLowerCase())) { + let lr = await LR.findOne({gid: message.guild.id}); + if (!lr) {return message.channel.send("Your server doesn't seem to have any leveling roles set up!");} + let s = ''; + let rs = []; + let r; for (r of Object.keys(lr.roles)) { + let role = message.guild.roles.cache.get(lr.roles[r]); + if (role) {rs.push({level: r, role: role});} + else { + role = await message.guild.roles.fetch(lr.roles[r]); + if (role) {rs.push({level: r, role: role});} + } + } + rs.sort((a, b) => a.level - b.level); + for (let i = 0; i < rs.length; i++) {s += `**${i + 1}.** Level ${rs[i].level} - <@&${rs[i].role.id}>\n`;} + if (!s.length) {return message.channel.send("Hmm, there was some kind of error there. It may be that your server's leveling roles were deleted, or there was some internal error when trying to read them. Contact my devs if the problem persists.");} + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Server Leveling Roles") + .setThumbnail(message.guild.iconURL({size: 2048})) + .setDescription(s) + .setColor('328ba8') + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp() + ); + } + + if (['d', 'delete', 'r', 'remove'].includes(args[0].toLowerCase())) { + let lr = await LR.findOne({gid: message.guild.id}); + if (!lr) {return message.channel.send("Your server doesn't seem to have any leveling roles set up!");} + if (!args[1]) {return message.channel.send("Please provide the level you'd like to remove from the level roles *not the role you want to remove*");} + if (!lr.roles[args[1]]) {return message.channel.send("Hmm, it looks like that level doesn't have a role for it. Make sure you provided the *level* and not the *role*.");} + delete lr.roles[args[1]]; + lr.markModified(`roles.${args[1]}`); + lr.save(); + return message.channel.send("Removed that leveling role!"); + } + + if (['c', 'clear'].includes(args[0].toLowerCase())) { + if (!message.member.permissions.has("ADMINISTRATOR")) {return message.channel.send("Unfortunately, you must be an administrator in this server to clear all the leveling roles.");} + let lr = await LR.findOne({gid: message.guild.id}); + if (!lr) {return message.channel.send("Your server doesn't seem to have any leveling roles set up!");} + let conf = await ask(message, "Are you sure you want to clear your server's leveling roles? This is irreversible! (Accepts only \"yes\" or \"no\")"); if (!conf) {return;} + if (conf.toLowerCase() !== "yes") {return message.channel.send("Fear not! Your leveling roles are safe, I will still be giving roles to people when they level up.");} + return LR.deleteOne({gid: message.guild.id}).then(() => { + return message.channel.send("Leveling roles cleared successfully!"); + }).catch(() => { + return message.channel.send("There was some kind of issue in deleting your server's leveling roles. Contact my devs if the problem persists!"); + }); + } + + return message.channel.send(`Invalid arg! Syntax: \`${prefix}levelrole \``); + } +}; \ No newline at end of file diff --git a/commands/leveling/setupleveling.js b/commands/leveling/setupleveling.js new file mode 100644 index 0000000..797c90e --- /dev/null +++ b/commands/leveling/setupleveling.js @@ -0,0 +1,48 @@ +const Discord = require('discord.js'); + +const LXP = require('../../models/localxp'); + +module.exports = { + name: "setupleveling", + aliases: ['setuplvl', 'setlvl', 'setleveling', 'levelingsetup', 'lvlset', 'lvlsetup'], + meta: { + category: 'Leveling', + description: "Setup and enable your server's local leveling!", + syntax: '`setupleveling`', + extra: "Requires administrator permissions. Disabled by default.", + guildOnly: true + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> Local Leveling Setup") + .setDescription("Set up your local leveling system to allow your server's members to progress in ranks, which lets you enable leveling roles and shops *features not available at this time*") //TODO remove the "features not available" as soon as they are available + .addField("Syntax", "`setupleveling`") + .addField("Important Notice", "You must be a server administrator in order to use this command. Please know that local leveling systems can cost a great deal of our database storage space when used on larger servers, so please only enable this feature **if you will actually make use of it**, not just for fun."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.member.permissions.has("ADMINISTRATOR")) {return message.channel.send("You must be a **server administrator** in order to use this command.");} + if (await LXP.findOne({gid: message.guild.id})) {return message.channel.send("Your leveling is already set up!");} //TODO add message to lead to disable cmd when complete + let am; + try { + am = await message.channel.send("Would you like to enable :thumbsup: or disable :thumbsdown: level up messages? (You can specify a set channel for this later.)"); + await am.react('👍'); + await am.react('👎'); + } catch {return message.channel.send(":thinking: hmmm... something went wrong there. I might not have permissions to add reactions to messages, and this could be the issue.");} + try { + let rc = am.createReactionCollector((r, u) => ['👍', '👎'].includes(r.emoji.name) && u.id === message.author.id, {max: 1, time: 60000}); + rc.on("collect", async r => { + let xp = new LXP({gid: message.guild.id}); + xp.msg = r.emoji.name === "👍"; + xp.save(); + client.misc.cache.lxp.enabled.push(message.guild.id); + return message.channel.send(new Discord.MessageEmbed() + .setTitle("XP System Enabled!") + .setThumbnail(message.guild.iconURL({size: 2048})) + .setDescription(`Your server now has its leveling system enabled! If you enabled level up messages, you can set the channel for that using \`${prefix}levelchannel\`.`) //TODO update this with info on how the shiz works + .setColor("328ba8") + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp() + ); + }); + rc.on("end", collected => {if (!collected.size) {return message.channel.send("Looks like you ran out of time! Try again?");}}); + } catch {return message.channel.send("Hmm... there was some error problem thingy that happened when I tried to enable XP for your server. If it keeps not working, then go yell at my devs!");} + } +}; \ No newline at end of file diff --git a/commands/leveling/stats.js b/commands/leveling/stats.js new file mode 100644 index 0000000..34fed78 --- /dev/null +++ b/commands/leveling/stats.js @@ -0,0 +1,41 @@ +const Discord = require('discord.js'); + +const LXP = require('../../models/localxp'); + +module.exports = { + name: "stats", + aliases: ['level', 'xp', 'lvl'], + meta: { + category: 'Leveling', + description: "View your rank in the server", + syntax: '`stats [@user|userID]`', + extra: null, + guildOnly: true + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> Stats") + .setDescription("View your level and XP in the server, or someone else's") + .addField("Syntax", "`stats [@user|userID]`"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!client.misc.cache.lxp.enabled.includes(message.guild.id)) {return message.channel.send("Your server doesn't have leveling enabled!");} + let u = args[0] ? (message.mentions.members.first() || message.guild.members.cache.get(args[0])) : message.member; + if (!u) {return message.channel.send("I can't find that user!");} + let xp; + if (!client.misc.cache.lxp.xp[message.guild.id] || !client.misc.cache.lxp.xp[message.guild.id][u.id]) { + let txp = await LXP.findOne({gid: message.guild.id}); + if (!txp) {return message.channel.send("Your server doesn't have leveling enabled!");} + if (!txp.xp[u.id]) {return message.channel.send(`${u.id === message.author.id ? "You" : "That user"} doesn't have any leveling info available!`);} + xp = {xp: txp.xp[u.id][0], level: txp.xp[u.id][1]}; + } else {xp = client.misc.cache.lxp.xp[message.guild.id][u.id];} + return message.channel.send(new Discord.MessageEmbed() + .setTitle(`${u.displayName}${u.displayName.toLowerCase().endsWith('s') ? "'" : "'s"} Stats`) + .setDescription("Local leveling stats") + .addField("Level", xp.level, true) + .addField("XP", `**${xp.xp}** of **${Math.ceil(100 + (((xp.level / 3) ** 2) * 2))}** needed to level up`, true) + .setThumbnail(client.users.cache.get(u.id).avatarURL({size: 2048})) + .setColor("328ba8") + .setFooter("Luno") + .setTimestamp() + ) + } +}; \ No newline at end of file diff --git a/commands/misc/ar.js b/commands/misc/ar.js new file mode 100644 index 0000000..da872a8 --- /dev/null +++ b/commands/misc/ar.js @@ -0,0 +1,159 @@ +const Discord = require('discord.js'); + +const AR = require('../../models/ar'); +const GuildData = require('../../models/guild'); + +const ask = require('../../util/ask'); + +module.exports = { + name: "ar", + aliases: ['autoresponse', 'autoresponses'], + meta: { + category: 'Misc', + description: "Create and edit automatic responses, which lets the bot say stuff when you say something in your server!", + syntax: '`ar `', + extra: null + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> Auto Responses") + .setDescription("Create and edit automatic responses, which lets the bot say stuff when you say something in your server!") + .addField("Syntax", "`ar `") + .addField("Notice", "This command is server-only, and requires you to be an administrator or have the staff role."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.channel.send("You must be in a server in order to use this command.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}ar \``);} + const tg = await GuildData.findOne({gid: message.guild.id}); + if (['a', 'add', 'e', 'edit', 'delete', 'd', 's', 'settings'].includes(args[0].toLowerCase()) && ((!tg || !tg.staffrole || !tg.staffrole.length || !message.member.roles.cache.has(tg.staffrole)) && !message.member.permissions.has("ADMINISTRATOR"))) {return message.channel.send("You must have the staff role or be an administrator in this server in order to edit AR settings.");} + + function sortARs(tar) { + let t = tar.triggers; + let ar = tar.ars; + let s = ''; + for (let i=0;i ${ar[i]}\n\n`;} + return s; + } + + function viewARs(string) { + return new Discord.MessageEmbed() + .setTitle("Auto-Responses in this Server") + .setDescription(string) + .setColor('328ba8') + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp(); + } + + if (['a', 'add'].includes(args[0].toLowerCase())) { + let trigger = await ask(message, "What would you like the trigger to be? This is the message that will make your AR work.", 120000); if (!trigger) {return;} + if (`${trigger}`.length > 150) {return message.channel.send("Your trigger needs to be less than 150 characters, please!");} + let response = await ask(message, "What would you like my response to be?", 120000); if (!response) {return;} + if (`${response}`.length > 300) {return message.channel.send("Your response needs to be less than 300 characters, please!");} + + let tar = await AR.findOne({gid: message.guild.id}) || new AR({gid: message.guild.id}); + if (tar.triggers.length === 20) {return message.channel.send("Because of data storage concerns, your ARs are capped at 20 per server. You can join the official support server and talk to the devs if you have a legitimate reason for raising this limit and they can see about raising it for you!");} + let h = false; let ar; for (ar of tar.triggers) {if (ar && ar.toLowerCase() === `${trigger}`.toLowerCase()) {h = true;}} + if (h) {return message.channel.send("You seem to already have that trigger. Try using `edit` instead!");} + tar.triggers.push(trigger); + client.misc.cache.ar.set(message.guild.id, tar.triggers); + tar.ars.push(response); + tar.save(); + return message.channel.send("AR added!"); + } + + if (['e', 'edit'].includes(args[0].toLowerCase())) { + let tar = await AR.findOne({gid: message.guild.id}); + if (!tar || !tar.triggers.length) {return message.channel.send("You can't edit any auto-responses... because there aren't any here...");} + + let sar = sortARs(tar); + await message.channel.send(viewARs(sar).addField("Editing", "Please say the **number** of the AR you wish to edit.")); + let collected; + try {collected = await message.channel.awaitMessages(m => m.author.id === message.author.id, {errors: ['time'], time: 60001, max: 1});} + catch {return message.channel.send("This question has timed out. Please try again!");} + collected = collected.first().content.trim(); + if (isNaN(Number(collected))) {return message.channel.send("Hmmm, maybe try replying with a *number*!");} + let id = Number(collected); + if (id < 1 || id > tar.triggers.length) {return message.channel.send("Your number was either below 1 or doesn't have a trigger to match it.");} + try { + let response = await ask(message, "What would you like the new response to be?", 120000); if (!response) {return;} + if (`${response}`.length > 300) {return message.channel.send("Your response needs to be less than 300 characters, please!");} + let tarars = tar.ars; + tarars[id-1] = response; + tar.ars = tarars; + tar.markModified('ars'); + tar.save(); + return message.channel.send("Yeah, that response seems to fit better than the last one."); + } catch {return message.channel.send("There seemed to have been a problem deleting that AR. Contact my devs if the problem persists.");} + } + + if (['d', 'delete'].includes(args[0].toLowerCase())) { + let tar = await AR.findOne({gid: message.guild.id}); + if (!tar || !tar.triggers.length) {return message.channel.send("It's not like this server has any ARs for me to delete in the first place!");} + + let sar = sortARs(tar); + await message.channel.send(viewARs(sar).addField("Deletion", "Please say the **number** of the AR you wish to delete.")); + let collected; + try {collected = await message.channel.awaitMessages(m => m.author.id === message.author.id, {errors: ['time'], time: 60000, max: 1});} + catch {return message.channel.send("This question has timed out. Please try again!");} + collected = collected.first().content.trim(); + if (isNaN(Number(collected))) {return message.channel.send("You didn't reply with a number!");} + let id = Number(collected); + if (id < 1 || id > tar.triggers.length) {return message.channel.send("Your number was either below 1 or doesn't have a trigger to match it.");} + try { + let temptt = tar.triggers; + temptt.splice(id-1, 1); + let tempar = tar.ars; + tempar.splice(id-1, 1); + tar.triggers = temptt; tar.ars = tempar; + tar.markModified('triggers'); tar.markModified('ars'); + tar.save(); + client.misc.cache.ar.set(message.guild.id, tar.triggers); + return message.channel.send("I didn't like saying that anyway."); + } catch {return message.channel.send("There seemed to have been a problem deleting that AR. Contact my devs if the problem persists.");} + } + + if (['v', 'view', 'l', 'list'].includes(args[0].toLowerCase())) { + let tar = await AR.findOne({gid: message.guild.id}); + if (!tar || !tar.triggers.length) {return message.channel.send("This server has no ARs!");} + return message.channel.send(viewARs(sortARs(tar))); + } + + if (['s', 'settings'].includes(args[0].toLowerCase())) { + args.shift(); + if (!args.length) {return message.reply("You can `view` your server's AR settings or `ignore` a channel");} + + let tar = await AR.findOne({gid: message.guild.id}); + if (!tar || !tar.triggers.length) {return message.channel.send("This server doesn't have any auto-responses, so changing or viewing the settings would be pointless...");} + + if (['v', 'view'].includes(args[0].toLowerCase())) { + if (!tar.ignoreChs.length) {return message.channel.send("This server doesn't have any channels set to be ignored for ARs.");} + let s = ''; + let c; for (c of tar.ignoreChs) {s += `<#${c}>, `} + return message.channel.send(`Channels I won't do auto-responses in: ${s.slice(0, s.length - 2)}`); + } + + if (['i', 'ignore'].includes(args[0].toLowerCase())) { + let ch; + if (args[1]) { + ch = message.mentions.channels.first() || message.guild.channels.cache.get(args[1]); + if (!ch) {return message.channel.send("I couldn't find that channel. Please try again!");} + } else {ch = message.channel;} + ch = ch.id; + if (tar.ignoreChs.includes(ch)) { + let ti = tar.ignoreChs; + ti.splice(ti.indexOf(ch), 1); + tar.ignoreChs = ti; + tar.markModified('ignoreChs'); + tar.save(); + client.misc.cache.arIgnore.set(message.guild.id, tar.ignoreChs); + return message.channel.send("I'll start replying to Auto Responses in this channel from now on."); + } else { + tar.ignoreChs.push(ch); + tar.save(); + client.misc.cache.arIgnore.set(message.guild.id, tar.ignoreChs); + return message.channel.send("Got it. I'll ignore Auto Responses here from now on."); + } + } + } + + return message.channel.send(`That's not a valid argument! Try \`${prefix}help ar\``); + } +}; \ No newline at end of file diff --git a/commands/misc/avatar.js b/commands/misc/avatar.js new file mode 100644 index 0000000..fa3c9fa --- /dev/null +++ b/commands/misc/avatar.js @@ -0,0 +1,32 @@ +const Discord = require('discord.js'); +const {Tag} = require('../../util/tag'); +const {TagFilter} = require('../../util/tagfilter'); + +module.exports = { + name: "avatar", + aliases: ['av', 'a', 'pfp'], + help: "Use `{{p}}avatar` to get your own profile picture, or mention someone to get theirs!", + meta: { + category: 'Misc', + description: "Flare your avatar or peek at others'", + syntax: '`avatar [@mention]`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + let member = !args.length ? message.author : mention ? mention : client.users.cache.has(args[0]) ? client.users.cache.get(args[0]) : message.author; + let name = !args.length ? message.member ? message.member.displayName : message.author.username : mention ? mention.username : client.users.cache.has(args[0]) ? client.users.cache.get(args[0]).username : message.author.username; + let options = new TagFilter([ + new Tag(['small', 's', 'mini', 'm'], 'small', 'toggle'), + new Tag(['verysmall', 'vsmall', '-vs', 'xs'], 'vsmall', 'toggle') + ]).test(args.join(" ")); + try { + let avem = new Discord.MessageEmbed() + .setTitle(`${name.endsWith('s') ? `${name}'` : `${name}'s`} Avatar`) + .setImage(member.avatarURL({size: options.vsmall ? 128 : options.small ? 256 : 2048, dynamic: true})) + .setColor('328ba8') + .setFooter("Luno", client.user.avatarURL()) + if (!options.vsmall) {avem.setTimestamp();} + return message.channel.send(avem); + } catch {return message.reply("Hmm, there seems to have been an error while I tried to show you that user's avatar.");} + } +}; \ No newline at end of file diff --git a/commands/misc/commands.js b/commands/misc/commands.js new file mode 100644 index 0000000..1eac956 --- /dev/null +++ b/commands/misc/commands.js @@ -0,0 +1,25 @@ +const Discord = require('discord.js'); + +module.exports = { + name: "commands", + aliases: ['cmds', 'commandlist', 'cmdlist'], + meta: { + category: 'Misc', + description: "Shows a list of my commands", + syntax: '`commands`', + extra: null + }, + help: "Shows a list of all my commands", + async execute(message, msg, args, cmd, prefix, mention, client) { + let categories = []; + Array.from(client.commands.values()).forEach(c => {console.log(c); if (!categories.includes(c.meta ? c.meta.category : 'Uncategorized')) {categories.push(c.meta ? c.meta.category : 'Uncategorized');}}); + let ce = new Discord.MessageEmbed() + .setTitle("Commands") + .setDescription(`You can use \`${prefix}help\` on any command to get more help on it.`) + .setColor('328ba8') + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp(); + categories.forEach(category => ce.addField(category, Array.from(client.commands.values()).filter(command => command.meta ? command.meta.category === category : category === "Uncategorized").map(cmd => `\`${cmd.name}\``).join(', '))); + return message.channel.send(ce); + } +}; \ No newline at end of file diff --git a/commands/misc/help.js b/commands/misc/help.js new file mode 100644 index 0000000..4917a35 --- /dev/null +++ b/commands/misc/help.js @@ -0,0 +1,72 @@ +const Discord = require("discord.js"); + +const {Pagination} = require('../../util/pagination'); +const ask = require('../../util/ask'); + +module.exports = { + name: "help", + aliases: ["h", "commands"], + help: 'you silly! What did you expect me to respond with?', + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) { + let sorted = {}; + await Array.from(client.commands.values()).forEach(command => {if (command.name !== "help" && command.meta) { + sorted[command.meta.category] = sorted[command.meta.category] ? sorted[command.meta.category] : {}; + sorted[command.meta.category][command.name] = command; + }}); + let helpSorted = {}; + let category; for (category of Object.keys(sorted)) { + let categorySorted = []; + let current = 1; + let currentEmbed = new Discord.MessageEmbed().setAuthor("Help Menu", message.author.avatarURL()).setTitle(category).setDescription("React to control the menu! You can also specify a command name when doing the help command to get more info about it.").setColor("328ba8"); + let commands = Object.keys(sorted[category]); + let command; for (command of commands) { + let aliases = ''; + let a; if (sorted[category][command].aliases) {for (a of sorted[category][command].aliases) {aliases += `\`${a}\`, `}} + aliases = aliases.length ? aliases.slice(0, aliases.length - 2) : 'None'; + currentEmbed.addField(`${command.slice(0,1).toUpperCase()}${command.slice(1)}`, `${sorted[category][command].meta.description}\n\nAliases: ${aliases}\nSyntax: ${sorted[category][command].meta.syntax}${sorted[category][command].meta.extra ? '\n\n' + sorted[category][command].meta.extra : ''}`); + current += 1; + if (current === 5) { + categorySorted.push(currentEmbed); + current = 1; + currentEmbed = new Discord.MessageEmbed().setAuthor("Help Menu", message.author.avatarURL()).setTitle(category).setDescription("React to control the menu! You can also specify a command name when doing the help command to get more info about it.").setColor("328ba8"); + } + } + if (current > 1) {categorySorted.push(currentEmbed);} + helpSorted[category] = categorySorted; + } + + let cat = await ask(message, "What would you like help with? (`Fun`|`Utility`|`Misc`|`Moderation`|`Social`|`Leveling`) or `all` if you'd like to browse all commands", 60000); if (!cat) {return;} + if (!['f', 'fun', 'u', 'util', 'utility', 'utilities', 'm', 'misc', 'miscellaneous', 'mod', 'moderation', 's', 'social', 'leveling', 'l', 'level', 'a', 'all'].includes(`${cat}`.trim().toLowerCase())) {return message.channel.send("That wasn't a valid response! Try again?");} + + let pages; + if (['f', 'fun'].includes(`${cat}`.trim().toLowerCase())) {pages = helpSorted['Fun'];} + if (['u', 'util', 'utility', 'utilities'].includes(`${cat}`.trim().toLowerCase())) {pages = helpSorted['Utility'];} + if (['m', 'misc', 'miscellaneous'].includes(`${cat}`.trim().toLowerCase())) {pages = helpSorted['Misc'];} + if (['d', 'dev', 'developer'].includes(`${cat}`.trim().toLowerCase())) {pages = helpSorted['Developer'];} + if (['mod', 'moderation'].includes(`${cat}`.trim().toLowerCase())) {pages = helpSorted['Moderation'];} + if (['s', 'social'].includes(`${cat}`.trim().toLowerCase())) {pages = helpSorted['Social'];} + if (['l', 'leveling', 'level'].includes(`${cat}`.trim().toLowerCase())) {pages = helpSorted['Leveling'];} + if (['a', 'all'].includes(`${cat}`.trim().toLowerCase())) {pages = []; let c; for (c of Object.values(helpSorted)) {let h; for (h of c) {pages.push(h)}}} + + await require('../../util/wait')(500); + + if (pages.length > 1) { + let help = new Pagination(message.channel, pages, message, client, true); + return await help.start({endTime: 120000, user: message.author.id}); + } else {return message.channel.send(pages[0].setFooter("Luno", client.user.avatarURL()).setTimestamp());} + } else { + let command; + if (client.commands.has(args[0])) {command = client.commands.get(args[0]);} + else if (client.aliases.has(args[0])) {command = client.commands.get(client.aliases.get(args[0]));} + else {return message.reply("I don't have that command! Try using `" + prefix + "help` to get a list of my commands");} + + return message.reply(command.help + ? command.help instanceof Discord.MessageEmbed + ? command.help.setFooter("Luno | [optional]", client.user.avatarURL()).setColor("328ba8").setTimestamp() + : command.help.replace(/{{p}}/g, prefix) + : "I don't seem to have any help info available for that command." + ); + } + } +}; \ No newline at end of file diff --git a/commands/misc/info.js b/commands/misc/info.js new file mode 100644 index 0000000..4aba6a6 --- /dev/null +++ b/commands/misc/info.js @@ -0,0 +1,34 @@ +const Discord = require("discord.js"); +const moment = require('moment'); +const os = require('os'); + +const UserData = require('../../models/user'); + +module.exports = { + name: "info", + aliases: ["i", "botinfo", "bot"], + help: "There's not really anything to help with here! Just use `{{p}}info` to learn more about me!", + meta: { + category: 'Misc', + description: "Get info about me, my creators, and my status.", + syntax: '`info`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + let botData = await require('../../models/bot').findOne({finder: 'lel'}); + let user = await UserData.findOne({uid: message.author.id}); + + return message.channel.send(new Discord.MessageEmbed() + .setAuthor("About Me!", client.users.cache.get(client.developers[Math.floor(Math.random() * client.developers.length)]).avatarURL()) + .setThumbnail(client.user.avatarURL({size: 1024})) + .setDescription(`I am created by WubzyGD#8766 and Slushie#1234 - a pair conveniently known as LunoDev - in JavaScript/Discord.js!\n\nI'm a powerful all-purpose bot with everything you could want or need, and I have my own set of unique skills that you won't find anywhere else ^^`) + .addField("Presence", `I'm currently in **${client.guilds.cache.size}** servers, and I'm watching over approximately **${client.users.cache.size}** people!`) + .addField("Restarts", botData.restarts, true) + .addField("Commands Executed", `${botData.commands}${user ? `\nYou: **${user.commands}** | **${Math.floor((user.commands / botData.commands) * 100)}%**` : ''}`, true) + .addField("Last Restart", moment(botData.lastRestart).fromNow(), true) + .addField("Mem", `\`${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)}MB\` heap of \`${(process.memoryUsage().heapTotal / 1024 / 1024).toFixed(2)}MB\` allocated. | **${Math.floor((process.memoryUsage().heapUsed / process.memoryUsage().heapTotal) * 100)}%**\nTotal RAM: \`${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)}GB\` | Free RAM: \`${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)}GB\``, true) + .setColor("328ba8") + .setFooter("Luno") + .setTimestamp()); + } +}; \ No newline at end of file diff --git a/commands/misc/ingorear.js b/commands/misc/ingorear.js new file mode 100644 index 0000000..a0fd217 --- /dev/null +++ b/commands/misc/ingorear.js @@ -0,0 +1,48 @@ +const Discord = require('discord.js'); + +const AR = require('../../models/ar'); +const GuildData = require('../../models/guild'); + +module.exports = { + name: "ignorear", + aliases: ['arignore', 'noar'], + meta: { + category: 'Misc', + description: "Stop auto responses from being sent to a specific channel.", + syntax: '`ignorear [#channel|channelID]`', + extra: null, + guildOnly: true + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> AR Ignoring") + .setDescription("Provide a channel (or don't to use the current channel) to be voided from auto-responses, that way the responses won't send in places you don't want them to.") + .addField("Syntax", "`[#channel|channelID]` - channel is optional."), + async execute(message, msg, args, cmd, prefix, mention, client) { + const tg = await GuildData.findOne({gid: message.guild.id}); + if ((!tg || !tg.staffrole || !tg.staffrole.length || !message.member.roles.cache.has(tg.staffrole) && !message.member.permissions.has("ADMINISTRATOR"))) {return message.channel.send("You must have the staff role or be an administrator in this server in order to edit AR settings.");} + + let tar = await AR.findOne({gid: message.guild.id}); + if (!tar || !tar.triggers.length) {return message.channel.send("This server doesn't have any auto-responses. Try adding some first, then you can set some channels to be ignored.");} + + let ch; + if (args[1]) { + ch = message.mentions.channels.first() || message.guild.channels.cache.get(args[1]); + if (!ch) {return message.channel.send("I couldn't find that channel. Please try again!");} + } else {ch = message.channel;} + ch = ch.id; + if (tar.ignoreChs.includes(ch)) { + let ti = tar.ignoreChs; + ti.splice(ti.indexOf(ch), 1); + tar.ignoreChs = ti; + tar.markModified('ignoreChs'); + tar.save(); + client.misc.cache.arIgnore.set(message.guild.id, tar.ignoreChs); + return message.channel.send("I'll start replying to Auto Responses in this channel from now on."); + } else { + tar.ignoreChs.push(ch); + tar.save(); + client.misc.cache.arIgnore.set(message.guild.id, tar.ignoreChs); + return message.channel.send("Got it. I'll ignore Auto Responses here from now on."); + } + } +}; \ No newline at end of file diff --git a/commands/misc/invite.js b/commands/misc/invite.js new file mode 100644 index 0000000..ac68d0b --- /dev/null +++ b/commands/misc/invite.js @@ -0,0 +1,23 @@ +const Discord = require('discord.js'); + +module.exports = { + name: "invite", + aliases: ['inv', 'botinvite'], + meta: { + category: 'Misc', + description: "Get the bot invite and support server invite", + syntax: '`invite`', + extra: null + }, + help: "Shows you my invite and support server invite", + async execute(message, msg, args, cmd, prefix, mention, client) { + return message.channel.send(new Discord.MessageEmbed() + .setTitle("My links!") + .setThumbnail(client.user.avatarURL({size: 2048})) + .setDescription("[Bot Invite](https://discord.com/oauth2/authorize?client_id=762701327431237644&scope=bot&permissions=1581116647)\n`->` Use this link to invite Luno to your server! This has all the required permissions in it that the I need to work, and it is not recommended that you change them. Doing so will make it so that some commands don't function properly or won't complete (I'll usually tell you when I'm missing a permission).\n\n[Support Server Invite](https://discord.gg/u9c2uD24wB)\n`->` Use this to join my support server! Here you can talk to the devs, suggest features, hang out with the community, get update alerts, report bugs/issues and get help, or just stop and say hi!") + .setColor("328ba8") + .setFooter("Luno") + .setTimestamp() + ); + } +}; \ No newline at end of file diff --git a/commands/misc/mem.js b/commands/misc/mem.js new file mode 100644 index 0000000..a250efd --- /dev/null +++ b/commands/misc/mem.js @@ -0,0 +1,21 @@ +const Discord = require('discord.js'); +const os = require('os'); + +module.exports = { + name: "mem", + aliases: ['memory', 'ram', 'memstats'], + meta: { + category: 'Misc', + description: "Shows memory usage stats", + syntax: '`mem`', + extra: null + }, + help: "shows my memory usage stats", + async execute(message, msg, args, cmd, prefix, mention, client) { + return message.channel.send(new Discord.MessageEmbed() + .setTitle("RAM Usage") + .setDescription(`\`${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)}MB\` heap of \`${(process.memoryUsage().heapTotal / 1024 / 1024).toFixed(2)}MB\` allocated. | **${Math.floor((process.memoryUsage().heapUsed / process.memoryUsage().heapTotal) * 100)}%**\nTotal RAM: \`${(os.totalmem() / 1024 / 1024 / 1024).toFixed(2)}GB\` | Free RAM: \`${(os.freemem() / 1024 / 1024 / 1024).toFixed(2)}GB\``) + .setColor('328ba8') + ); + } +}; \ No newline at end of file diff --git a/commands/misc/prefix.js b/commands/misc/prefix.js new file mode 100644 index 0000000..9655c30 --- /dev/null +++ b/commands/misc/prefix.js @@ -0,0 +1,48 @@ +const Discord = require('discord.js'); +const mongoose = require('mongoose'); +const GuildSettings = require('../../models/guild'); + +module.exports = { + name: "prefix", + help: new Discord.MessageEmbed() + .setTitle("Help -> Prefix") + .setDescription("Changes your server's prefix.") + .addField("Syntax", "`prefix `") + .addField("Staff Command", "This command requires you to either be admin, or to have the designated staff role.") + .addField("Notice", "Prefixes are cached, and may take up to a minute to update."), + meta: { + category: 'Misc', + description: "Change my prefix in your server.", + syntax: '`prefix `', + extra: "You can always mention me and then type your command in place of a prefix in case you forget it." + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This is a guild-only command!");} + let tguild = await GuildSettings.findOne({gid: message.guild.id}) + ? await GuildSettings.findOne({gid: message.guild.id}) + : new GuildSettings({gid: message.guild.id}); + if (!message.member.permissions.has("ADMINISTRATOR") && (!tguild.staffrole.length || !message.guild.roles.cache.has(tguild.staffrole) || !message.member.roles.cache.has(tguild.staffrole))) {return message.reply("You don't have the permissions to use this command here.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix} \`. My current prefix in this server is \`${tguild.prefix.length ? tguild.prefix : 'n?'}\``);} + let np = args[0]; + if (np.length > 7) {return message.reply("Hmmm, that prefix is a bit long. Try making something smaller!");} + if (!np.match(/^[a-zA-Z0-9,.!?<>\-_+=/;$#%^&*]+$/)) {return message.reply('Your custom prefix contains some *wonky* characters. Please use only alphanumerics and basic symbols.');} + tguild.prefix = ['c', 'clear', 'n', 'none'].includes(np.trim().toLowerCase()) ? '' : np; + tguild.save(); + if (['c', 'clear', 'n', 'none'].includes(np.trim().toLowerCase())) { + client.guildconfig.prefixes.set(message.guild.id, null); + return message.reply('this server\'s prefix has been reset to the default, `n?`.'); + } + client.guildconfig.prefixes.set(message.guild.id, np); + let upm = await message.reply("sure thing!"); + await require('../../util/wait')(1750); + return upm.edit(new Discord.MessageEmbed() + .setAuthor('Prefix updated!', message.author.avatarURL()) + .setDescription(`New prefix: \`${np}\``) + .addField('Auditing Admin', `<@${message.member.id}>`, true) + .addField("Notice", "Prefixes are cached, and may take up to a minute to update.") + .setColor('328ba8') + .setFooter('Luno', client.user.avatarURL()) + .setTimestamp() + ); + } +}; \ No newline at end of file diff --git a/commands/misc/serverinfo.js b/commands/misc/serverinfo.js new file mode 100644 index 0000000..f28e0b4 --- /dev/null +++ b/commands/misc/serverinfo.js @@ -0,0 +1,31 @@ +const Discord = require("discord.js"); +const moment = require('moment'); + +module.exports = { + name: "serverinfo", + aliases: ['si'], + help: "Displays your server's information", + meta: { + category: 'Misc', + description: "Displays your server's information", + syntax: '`serverinfo`', + extra: null, + guildOnly: true + }, + execute(message, msg, args, cmd, prefix, mention, client) { + let now = new Date(); + return message.channel.send(new Discord.MessageEmbed() + .setAuthor("Server info", message.author.avatarURL()) + .setTitle(message.guild.name) + .setThumbnail(message.guild.iconURL({size: 2048})) + .setDescription(`Name: \`${message.guild.name}\`\n\nOwner: <@${message.guild.ownerID}>\nRegion: ${message.guild.region}\nIcon: [URL](${message.guild.iconURL({size: 2048})})`) + .addField("Members", `${message.guild.members.cache.size}\n[${message.guild.members.cache.filter(m => !client.users.cache.get(m.id).bot).size} Humans | ${message.guild.members.cache.filter(m => client.users.cache.get(m.id).bot).size} Bots]\n\nOnline: ${message.guild.members.cache.filter(m => client.users.cache.get(m.id).presence.status === "online").size} | Idle: ${message.guild.members.cache.filter(m => client.users.cache.get(m.id).presence.status === "idle").size} | Do not Disturb: ${message.guild.members.cache.filter(m => client.users.cache.get(m.id).presence.status === "dnd").size}`) + .addField("Channels", `${message.guild.channels.cache.size}\n[${message.guild.channels.cache.filter(ch => ch.type === "text").size} Text | ${message.guild.channels.cache.filter(ch => ch.type === "voice").size} Voice]`, true) + .addField("Roles", `${message.guild.roles.cache.size} (you have ${message.member.roles.cache.size})\nYour highest is <@&${message.member.roles.highest.id}>`, true) + .addField("Other Info", `Server created roughly **${moment(message.guild.createdAt).fromNow()}**\n\nYou joined ${moment(message.member.joinedAt).fromNow()} (Member for **${Math.round(((now.getTime() - new Date(message.member.joinedAt.getTime()).getTime()) / (new Date(message.guild.createdAt).getTime() - now.getTime())) * -100)}%** of server lifetime)`) + .setColor('328ba8') + .setFooter("Luno") + .setTimestamp() + ); + } +}; \ No newline at end of file diff --git a/commands/misc/supportserver.js b/commands/misc/supportserver.js new file mode 100644 index 0000000..75ce666 --- /dev/null +++ b/commands/misc/supportserver.js @@ -0,0 +1,23 @@ +const Discord = require('discord.js'); + +module.exports = { + name: "supportserver", + aliases: ['helpserver'], + meta: { + category: 'Misc', + description: "Get an invite to Luno's support server!", + syntax: '`supportserver`', + extra: null + }, + help: "Get an invite to Luno's support server!", + async execute(message, msg, args, cmd, prefix, mention, client) { + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Sure thing!") + .setThumbnail(client.user.avatarURL({size: 2048})) + .setDescription("Join the server with [this link](https://discord.gg/u9c2uD24wB)!\n\n`->` Here you can talk to the devs, suggest features, hang out with the community, get update alerts, report bugs/issues and get help, or just stop and say hi!") + .setColor("328ba8") + .setFooter("Luno") + .setTimestamp() + ); + } +}; \ No newline at end of file diff --git a/commands/misc/uptime.js b/commands/misc/uptime.js new file mode 100644 index 0000000..0ec559d --- /dev/null +++ b/commands/misc/uptime.js @@ -0,0 +1,28 @@ +const Discord = require('discord.js'); + +const Bot = require('../../models/bot'); + +const moment = require('moment'); +require('moment-precise-range-plugin'); + +module.exports = { + name: "uptime", + aliases: ['ut', 'up'], + meta: { + category: 'Misc', + description: "Shows the bot's uptime", + syntax: '`uptime`', + extra: null + }, + help: "Shows my uptime, which is how long it's been since my last restart.", + async execute(message, msg, args, cmd, prefix, mention, client) { + const bot = await Bot.findOne({finder: 'lel'}); + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Uptime") + .setDescription(moment.preciseDiff(moment(bot.lastRestart), moment())) + .setColor('328ba8') + .setFooter("Luno") + .setTimestamp() + ) + } +}; \ No newline at end of file diff --git a/commands/misc/userinfo.js b/commands/misc/userinfo.js new file mode 100644 index 0000000..ec19493 --- /dev/null +++ b/commands/misc/userinfo.js @@ -0,0 +1,45 @@ +const Discord = require('discord.js'); +const moment = require('moment'); +const mongoose = require('mongoose'); +const UserData = require('../../models/user'); + +module.exports = { + name: "userinfo", + aliases: ['ui', 'memberinfo', 'user'], + help: "Shows your info, or shows the info of a user you ping.", + meta: { + category: 'Misc', + description: "See some info about a user", + syntax: '`userinfo [@user]`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + let person = message.guild ? mention ? message.guild.members.cache.get(mention.id) : args[0] ? message.guild.members.cache.has(args[0]) ? message.guild.members.cache.get(args[0]) : message.member : message.member : message.author; + let name = message.guild ? person.displayName : person.username; + let tu = await UserData.findOne({uid: person.id}); + let now = new Date(); + let infoembed = new Discord.MessageEmbed() + .setTitle(`User Info for ${name}`) + .setDescription(`Requested by ${message.guild ? message.member.displayName : message.author.username}`) + .setThumbnail(client.users.cache.get(person.id).avatarURL({size: 2048})) + .addField("Account Created", moment(client.users.cache.get(person.id).createdAt).fromNow(), true) + .addField("Bot User?", client.users.cache.get(person.id).bot ? "Is a bot" : "Is not a bot", true) + .setColor('328ba8') + .setFooter('Luno', client.user.avatarURL()) + .setTimestamp(); + + if (message.guild) { + infoembed.addField('In Server Since', `${moment(person.joinedAt).fromNow()}${!moment(person.joinedAt).fromNow().includes('days') ? ` | ${Math.floor((new Date().getTime() - person.joinedAt.getTime()) / (60 * 60 * 24 * 1000))} days` : ''}\nMember for **${Math.round(((now.getTime() - new Date(message.member.joinedAt.getTime()).getTime()) / (new Date(message.guild.createdAt).getTime() - now.getTime())) * -100)}%** of server lifetime`, false) + .addField('Roles', `**${person.roles.cache.size}** roles | [${person.roles.cache.size}/${message.guild.roles.cache.size}] - ${Math.round((person.roles.cache.size / message.guild.roles.cache.size) * 100)}%\nHighest: ${person.roles.highest ? `<@&${person.roles.highest.id}>` : 'No roles!'}`, true) + if (message.guild.owner.id === person.id) {infoembed.addField("Extra", "User is the server's owner!");} + else if (person.permissions.has("ADMINISTRATOR")) {infoembed.addField("Extra", "User is an admin! Watch out :eyes:");} + } + + if (tu) { + infoembed.addField('Luno Commands Executed', tu.commands) + .addField('Donator?', tu.developer ? `Well, ${name} makes me work, so they're a supporter in my book!` : tu.donator ? 'Yes! They have donated or supported me in the past!' : 'No', true) + .addField('Luno Staff Level', tu.developer ? 'Developer' : tu.admin ? 'Admin; Audit access to the bot' : tu.staff ? 'Staff; Support but with maintenance permissions' : tu.support ? 'Support; Answers tickets and help queries' : 'Member; Does not have a staff rank.', true); + } + return message.channel.send(infoembed); + } +}; \ No newline at end of file diff --git a/commands/moderation/autorole.js b/commands/moderation/autorole.js new file mode 100644 index 0000000..ec0f57a --- /dev/null +++ b/commands/moderation/autorole.js @@ -0,0 +1,39 @@ +const Discord = require('discord.js'); +const GuildData = require('../../models/guild'); + +module.exports = { + name: "autorole", + aliases: ['joinrole', 'jr'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Auto Role/Join Role") + .setDescription("Set a role to be automatically added to users when they join the server.") + .addField("Syntax", "`autorole `") + .addField('Notice', "This command can only be used by server staff members and admins."), + meta: { + category: 'Moderation', + description: "Set a role to be automatically added when a member joins the server.", + syntax: '`autorole `', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This command is only available in servers.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}autorole \``);} + let tg = await GuildData.findOne({gid: message.guild.id}) ? await GuildData.findOne({gid: message.guild.id}) : new GuildData({gid: message.guild.id}); + if (['v', 'view', 'check'].includes(args[0])) {return message.channel.send(tg.joinrole.length && message.guild.roles.cache.has(tg.joinrole) ? `I am currently adding \`${message.guild.roles.cache.get(tg.joinrole).name}\` to new members.` : "At the moment, I'm not adding a role to new members.");} + if ((!tg.staffrole.length || !message.member.roles.cache.has(tg.staffrole)) && !message.member.permissions.has("ADMINISTRATOR")) {return message.reply("You don't have the permissions to edit this setting.");} + if (['s', 'set', 'c', 'clear'].includes(args[0])) { + let role = message.mentions.roles.first() ? message.mentions.roles.first().id : args[1] && message.guild.roles.cache.has(args[1]) ? args[1] : ['c', 'clear'].includes(args[0]) ? '' : null; + if (role === null) {return message.reply("That doesn't seem to be a role!");} + tg.joinrole = role; + tg.save(); + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Join Role Updated") + .setThumbnail(message.author.avatarURL({size: 2048})) + .setDescription(`Role: ${tg.joinrole.length ? `<@&${tg.joinrole}>` : "None"}`) + .setColor("328ba8") + .setFooter('Luno', client.user.avatarURL()) + .setTimestamp() + ); + } + } +}; \ No newline at end of file diff --git a/commands/moderation/ban.js b/commands/moderation/ban.js new file mode 100644 index 0000000..7716479 --- /dev/null +++ b/commands/moderation/ban.js @@ -0,0 +1,73 @@ +const Discord = require('discord.js'); + +const Mod = require('../../models/mod'); + +const {Tag} = require('../../util/tag'); +const {TagFilter} = require('../../util/tagfilter'); + +module.exports = { + name: "ban", + aliases: [], + meta: { + category: 'Moderation', + description: "Bans a member from the server!", + syntax: '`ban <@member|memberID> [reason]`', + extra: null, + guildOnly: true + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> Member Banning") + .setDescription("This command bans a member from the server permanently, making it so they cannot rejoin. *Yikes*") + .addField("Syntax", "`ban <@member|memberID> [reason]`") + .addField("Permissions", "You'll want to have the `ban members` permission in your server or be an administrator to do this!"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}ban <@member|memberID> [reason]\``);} + + if (!message.member.permissions.has("BAN_MEMBERS")) {return message.channel.send("You don't have permissions to do that!");} + if (!message.guild.me.permissions.has("BAN_MEMBERS")) {return message.channel.send("I don't have permissions to ban members in your server.");} + let user = message.guild.members.cache.get(args[0]) || message.mentions.members.first(); + + if (!user) {return message.channel.send("You must mention a user to ban, or provide their ID.");} + if (user.roles.highest.position >= message.member.roles.highest.position) {return message.channel.send("You don't have permissions to ban that member as they are above you in the roles list.");} + if (user.roles.highest.position >= message.guild.me.roles.highest.position) {return message.channel.send("I can't ban that member as their highest role is above mine! (Or the same as mine, too)");} + if (!user.bannable) {return message.channel.send("Hmm, it seems like I can't ban that member. This is probably a permissions issue. Or maybe they were already banned?");} + + let options = new TagFilter([ + new Tag(['r', 'reason'], 'reason', 'append'), + new Tag(['n', 'notes'], 'notes', 'append'), + new Tag(['d', 'days', 'm', 'messages'], 'days', 'append') + ]).test(args.join(" ")); + let reason; let days; + if (options.reason && options.reason.length) {reason = options.reason;} + if (options.days && options.days.length) { + if (isNaN(Number(options.days)) || Number(options.days) < 0 || Number(options.days) > 7 || options.days.includes('.')) {return message.channel.send("The `days` option must be a whole number between 0 and 7.");} + days = Number(options.days); + } + if (options.notes && options.notes.length > 250) {return message.channel.send("I mean I get it, they pissed you off, but do you really need to give me that much info on why you're banning them? I can't keep track of all that!");} + else {if (args[1] && !options.days /*&& (!options.notes || !options.notes.length)*/ && (!options.reason || !options.reason.length)) {args.shift(); reason = args.join(" ");}} + if (reason && reason.length > 250) {return message.channel.send("I mean I get it, they pissed you off, but do you really need to give me that much info on why you're banning them? I can't keep track of all that!");} + + return user.ban({reason: reason, days: typeof days === "number" ? days : 0}) + .then(async () => { + /*let mh = await Mod.findOne({gid: message.guild.id}) || new Mod({gid: message.guild.id}); + let mhcases = mh.cases; + + mhcases.push({ + members: [user.id], + punishment: "Banned", + reason: reason ? reason : "", + status: "Closed", + moderators: [message.author.id], + notes: options.notes, + history: [`${new Date().toISOString()} - ${message.author.username} - Created case`, `${new Date().toISOString()} - ${message.author.username} - Banned ${client.users.cache.get(user.id).username}`], + issued: new Date().toUTCString() + }); + + mh.cases = mhcases; + mh.save();*/ + + return message.channel.send(`The hammer of justice has spoken!${reason ? ` Reason for banning: ${reason}` : ''}`); + }) + .catch(() => {return message.channel.send("Something went wrong while trying to ban that user! If the problem persists, contact my devs.");}); + } +}; \ No newline at end of file diff --git a/commands/moderation/checkwarnings.js b/commands/moderation/checkwarnings.js new file mode 100644 index 0000000..5cfe00c --- /dev/null +++ b/commands/moderation/checkwarnings.js @@ -0,0 +1,50 @@ +const Discord = require('discord.js'); + +const Mod = require('../../models/mod'); + +module.exports = { + name: "checkwarnings", + aliases: ['checkwarn', 'chw', 'warncheck', 'checkwarning'], + meta: { + category: 'Moderation', + description: "Check a user's warnings in your server.", + syntax: '`checkwarnings [@user|userID]`', + extra: null, + guildOnly: true + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> Warn Clearing") + .setDescription("Checks the warnings of a user") + .addField("Syntax", "`checkwarnings [@user|userID]`") + .addField("Notice", "You must be a server moderator in order to use this command."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}checkwarnings [@user|userID]\``);} + if (!message.member.permissions.has("MANAGE_MESSAGES") && !message.member.permissions.has("MANAGE_GUILD")) {return message.reply("You must be a server moderator (manage messages or manage server permissions) to use this command.");} + + let user = message.mentions.members.first() && args[0].match(/^<@(?:!)(?:\d+)>$/) ? message.mentions.members.first() : message.guild.members.cache.has(args[0]) ? message.guild.members.cache.get(args[0]) : message.member; + let mh = await Mod.findOne({gid: message.guild.id}); + if (!mh || !Object.keys(mh.warnings).length) {return message.reply("There are no warnings available in this server.");} + + if (!mh.warnings[user.id] || !mh.warnings[user.id].length) {return message.reply(`${user.id === message.author.id ? 'You have' : 'That user has'} never been warned in this server.`);} + //console.log(mh.cases, mh.warnings); + let ws = ''; + let cwc = 0; + let warning; for (warning of mh.warnings[user.id]) { + let tcase = mh.cases[warning - 1]; + if (tcase.status !== "Cleared") { + ws += `\`Case #${warning}\` - Issued by <@${tcase.moderators[0]}>\n${tcase.reason}\n\n`; + } else {cwc++;} + } + if (cwc > 0) {ws += '*Plus ' + cwc + ' other warnings that have been cleared.*';} + if (cwc === mh.warnings[user.id].length) {return message.reply("That user has no uncleared warnings.");} + return message.channel.send(new Discord.MessageEmbed() + .setTitle("User Warnings") + .setThumbnail(client.users.cache.get(user.id).avatarURL({size: 1024})) + .setDescription(`For ${user.displayName}`) + .addField("Warnings", ws) + .setColor("328ba8") + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp() + ); + } +}; \ No newline at end of file diff --git a/commands/moderation/clearwarnings.js b/commands/moderation/clearwarnings.js new file mode 100644 index 0000000..40a96e4 --- /dev/null +++ b/commands/moderation/clearwarnings.js @@ -0,0 +1,58 @@ +const Discord = require('discord.js'); + +const Mod = require('../../models/mod'); + +module.exports = { + name: "clearwarnings", + aliases: ['clearwarn', 'cw', 'warnclear', 'wc', 'clearwarning'], + meta: { + category: 'Moderation', + description: "Clear a user's warnings in your server.", + syntax: '`clearwarnings <@user|userID>`', + extra: null, + guildOnly: true + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> Warn Clearing") + .setDescription("Clears the warnings of a user") + .addField("Syntax", "`clearwarnings <@user|userID>`") + .addField("Notice", "You must be a server moderator in order to use this command."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}clearwarnings <@user|userID>\``);} + if (!message.member.permissions.has("MANAGE_MESSAGES") && !message.member.permissions.has("MANAGE_GUILD")) {return message.reply("You must be a server moderator (manage messages or manage server permissions) to use this command.");} + + let user = message.mentions.members.first() && args[0].match(/^<@(?:!)(?:\d+)>$/) ? message.mentions.members.first() : message.guild.members.cache.has(args[0]) ? message.guild.members.cache.get(args[0]) : null; + if (!user) {return message.channel.send("Either you didn't mention a user, or I can't find that user!");} + + if (user.id === client.user.id) {return message.reply("don't worry about clearing any warnings from me... you can't give me warnings in the first place");} + if (client.users.cache.get(user.id).bot) {return message.reply("it's not like a bot would have any warnings in the first place...");} + + user = user ? user : message.member; + let mh = await Mod.findOne({gid: message.guild.id}); + if (!mh || !Object.keys(mh.warnings).length) {return message.reply("There are no warnings available in this server.");} + + if (!Object.keys(mh.warnings).includes(user.id) || !mh.warnings[user.id].length) {return message.reply(`${user.id === message.author.id ? 'You have' : 'That user has'} never been warned in this server.`);} + + let mhcases = mh.cases; + let moddedcases = []; + let cwc = 0; var wc = 0; + let warning; for (warning of mh.warnings[user.id]) { + if (mhcases[`${warning - 1}`].status !== "Cleared") { + let tcase = mhcases[`${warning - 1}`]; + tcase.status = "Cleared"; + tcase.history.push(`${new Date().toISOString()} - ${message.author.username} - Cleared the warning.`); + moddedcases.push(`${warning - 1}`); + wc++; + if (!tcase.moderators.includes(message.author.id)) {tcase.moderators.push(message.author.id);} + mhcases[`${warning - 1}`] = tcase; + } else {cwc++;} + } + if (cwc === mh.warnings[user.id].length) {return message.reply("That user has no uncleared warnings.");} + + if (moddedcases.length) {let c; for (c of moddedcases) {mh.markModified(`cases.${c}.history`);}} + + mh.cases = mhcases; + mh.save(); + return message.reply(`Cleared ${wc} warnings from ${user.displayName}.`); + } +}; \ No newline at end of file diff --git a/commands/moderation/kick.js b/commands/moderation/kick.js new file mode 100644 index 0000000..73fd359 --- /dev/null +++ b/commands/moderation/kick.js @@ -0,0 +1,68 @@ +const Discord = require('discord.js'); + +const Mod = require('../../models/mod'); + +const {Tag} = require('../../util/tag'); +const {TagFilter} = require('../../util/tagfilter'); + +module.exports = { + name: "kick", + aliases: ['kicc', 'k'], + meta: { + category: 'Moderation', + description: "Kicks a user from the server!", + syntax: '`kick <@user|userID> [reason]`', + extra: null, + guildOnly: true + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> Kick") + .setDescription("Kicks a user from the server!") + .addField("Syntax", "`kick <@user|userID> [reason]`") + .addField("Notice", "This command requires you to have `kick` permissions in the server."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}kick <@user|userID> [reason]\``);} + + if (!message.member.permissions.has("KICK_MEMBERS")) {return message.channel.send("You don't have permissions to do that!");} + if (!message.guild.me.permissions.has("KICK_MEMBERS")) {return message.channel.send("I don't have permissions to kick members in your server.");} + let user = message.guild.members.cache.get(args[0]) || message.mentions.members.first(); + if (!user) {return message.channel.send("You must mention a user to kick, or provide their ID.");} + + if (user.roles.highest.position >= message.member.roles.highest.position) {return message.channel.send("You don't have permissions to kick that member as they are above you in the roles list.");} + if (user.roles.highest.position >= message.guild.me.roles.highest.position) {return message.channel.send("I can't kick that member as their highest role is above mine! (Or the same as mine, too)");} + if (!user.kickable) {return message.channel.send("For some reason, I can't kick that user!");} + + let options = new TagFilter([ + new Tag(['r', 'reason'], 'reason', 'append')/*, + new Tag(['n', 'notes'], 'notes', 'append')*/ + ]).test(args.join(" ")); + let reason; + if (options.reason && options.reason.length) {reason = options.reason;} + //if (options.notes && options.notes.length > 250) {return message.channel.send("Hey, listen, let's not write an essay on why you're kicking that member!");} + else {if (args[1]) {args.shift(); reason = args.join(" ");}} + if (reason && reason.length > 250) {return message.channel.send("Hey, listen, let's not write an essay on why you're kicking that member!");} + + return user.kick(reason) + .then(async () => { + /*let mh = await Mod.findOne({gid: message.guild.id}) || new Mod({gid: message.guild.id}); + let mhcases = mh.cases; + + mhcases.push({ + members: [user.id], + punishment: "Kicked", + reason: reason ? reason : "", + status: "Closed", + moderators: [message.author.id], + notes: options.notes, + history: [`${new Date().toISOString()} - ${message.author.username} - Created case`, `${new Date().toISOString()} - ${message.author.username} - Kicked ${client.users.cache.get(user.id).username}`], + issued: new Date().toUTCString() + }); + + mh.cases = mhcases; + mh.save();*/ + + return message.channel.send(`I got em outta here!${reason ? ` Reason for kicking: ${reason}` : ''}`); + }) + .catch(() => {return message.channel.send("Something went wrong while trying to kick that user! If the problem persists, contact my devs.");}); + } +}; \ No newline at end of file diff --git a/commands/moderation/leave.js b/commands/moderation/leave.js new file mode 100644 index 0000000..4f51d04 --- /dev/null +++ b/commands/moderation/leave.js @@ -0,0 +1,72 @@ +const Discord = require('discord.js'); +const GuildData = require('../../models/guild'); +const Responses = require('../../models/responses'); +const sendResponse = require('../../util/response/sendresponse'); + +module.exports = { + name: "leave", + aliases: ['lv', 'leavemsg', 'leavemessage', 'leavechannel', 'lch', 'lmsg', 'leavech'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Leave Messages") + .setDescription("Set the channel and message for your leave messages!") + .addField("Syntax", "`leave `") + .addField("Notice", "You must be a staff or admin in your server to edit these settings.") + .addField("Responses", "Your leave message should be generated through a response using my `response` command, and then bound to the leave message by providing your response's name."), + meta: { + category: 'Moderation', + description: "Set the channel and message to be sent when a user leaves the server.", + syntax: '`leave `', + extra: "You must use the `response` command to create a response. The response's name is what will be given in this command." + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This command is server-only.");} + let tg = await GuildData.findOne({gid: message.guild.id}) ? await GuildData.findOne({gid: message.guild.id}) : new GuildData({gid: message.guild.id}); + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}welcome \``);} + if (['v', 'view', 'c', 'check'].includes(args[0].toLowerCase())) {} + if ((!tg.staffrole.length || !message.member.roles.cache.has(tg.staffrole)) && !message.member.permissions.has("ADMINISTRATOR")) {return message.reply("You can't do that without staff or admin permissions, silly!");} + + if (['s', 'set'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.reply("You need to specify a channel for your leave messages to be sent in!");} + let ch = message.mentions.channels.first() && args[1].match(/^<#(?:\d+)>$/) ? message.mentions.channels.first().id : message.guild.channels.cache.has(args[1]) ? message.guild.channels.cache.get(args[1]).id : null; + if (!ch) {return message.reply("I can't find that channel!");} + if (!message.guild.channels.cache.get(ch).permissionsFor(client.user.id).has("SEND_MESSAGES")) {return message.reply("I can't send messages in that channel. Try fixing the permissions or using a different channel!");} + if (!args[2]) {return message.reply(`You have to specify a response to use! You can make one with \`${prefix}response new\`.`);} + let tr = await Responses.findOne({gid: message.guild.id}) ? await Responses.findOne({gid: message.guild.id}) : new Responses({gid: message.guild.id}); + if (!tr.responses.has(args[2].toLowerCase())) {return message.reply("Silly, I can't let you know that someone left with a response that doesn't exist! Try making one or make sure you spelled the name correctly.");} + tg.lch = ch; + tg.save(); + tr.bindings.set('leave', args[2].toLowerCase()); + tr.save(); + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Leave Channel/Message Updated") + .setDescription(`This server's leave-notifying settings have been altered by ${message.author.tag}.\n\n**Channel**: <#${ch}>\n**Response Name**: \`${args[2].toLowerCase()}\``) + .setColor('328ba8') + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp() + ) + } + + if (['t', 'test'].includes(args[0].toLowerCase())) { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr || !tr.bindings.has('leave') || !tr.responses.has(tr.bindings.get('leave'))) {return message.reply("I can't test your leave message because the response doesn't exist, a leave response isn't set, or you haven't made any responses in this server.");} + await sendResponse(message.member, message.channel, 'this shit aint matter anymore lol', client, tr.responses.get(tr.bindings.get('leave'))); + } + + if (['clear'].includes(args[0].toLowerCase())) { + tg.lch = ''; + tg.save(); + let tr = await Responses.findOne({gid: message.guild.id}) ? await Responses.findOne({gid: message.guild.id}) : new Responses({gid: message.guild.id}); + if (tr) { + tr.bindings.delete('leave'); + tr.save(); + } + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Leave Channel/Message Updated") + .setDescription(`This server's leave-notifying settings have been altered by ${message.author.tag}.\n\n**Channel**: None`) + .setColor('328ba8') + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp() + ); + } + } +}; \ No newline at end of file diff --git a/commands/moderation/logs.js b/commands/moderation/logs.js new file mode 100644 index 0000000..7e84e66 --- /dev/null +++ b/commands/moderation/logs.js @@ -0,0 +1,85 @@ +const Discord = require("discord.js"); + +const GuildData = require('../../models/guild'); +const LogData = require('../../models/log'); + + +const ObjLogTypes = { + mdelete: ['md', 'mdelete', 'messagedelete', 'deletemessage', 'deletemsg', 'msgdelete'], + medit: ['me', 'medit', 'messageedit', 'editmessage', 'msgedit', 'editmsg'], + chnew: ['chn', 'chc', 'newch', 'newchannel', 'chcreate', 'channelcreate'], + //chedit: ['channeledit'], + chdelete: ['chd', 'channeldelete', 'deletechannel', 'deletech', 'chdelete'], + //vcjoin: [], + //vcleave: [], + //servervcmute: [], + //servervcdeafen: [], + //kick: [], + //ban: [], + //mute: [], + //warn: [], + //giverole: [], + //takerole: [], + //addrole: [], + //editrole: [], + //deleterole: [], + //serverjoin: [], + //serverleave: [], + //nickname: [], + //username: [], + //avatar: [] +}; const LogTypes = new Map(); + +let keys = Object.keys(ObjLogTypes); +let key; for (key of keys) {let vs = ObjLogTypes[key]; let v; for (v of vs) {LogTypes.set(v, key);}} + + +module.exports = { + name: "logs", + aliases: ["log", "l", "modlog", "modlogs"], + help: new Discord.MessageEmbed() + .setTitle("Help -> Server Logs") + .setDescription("Configure your server's log settings.\n\nLogs will update you on events in your server that have the potential to require moderator intervention, like someone deleting a hateful message before you can see it or a misbehaving moderator kicking/banning a member when they aren't supposed to.") + .addField("Syntax", "`log [logType] [#channel]`") + .addField("Notice", "You must be an admin or have the specified staff role in order to use this command."), + meta: { + category: 'Moderation', + description: "Configure your server's log settings, which allow mods to see potentially suspicious activity in the server.", + syntax: '`log [logType] [#channel]`', + extra: "**Please note** that this command is still in the works, and that not all log types are available. The currently existing ones have been thoroughly tested, though." + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This command is server-only!");} + let tg = await GuildData.findOne({gid: message.guild.id}); + if ((!message.member.permissions.has("ADMINISTRATOR")) && (!tg || !tg.staffrole || !tg.staffrole.length || !message.member.roles.cache.has(tg.staffrole))) {return message.reply("You must be an administrator or have the specified staff role in this server in order to edit or view log settings.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}log [logType] [#channel]\``);} + + if (['s', 'set'].includes(args[0].toLowerCase())) { + if (args.length < 3) {return message.channel.send(`You must specify the log type and the channel to send the log to. Use \`${prefix}log list\` to see a list of valid log types.`);} + if (!LogTypes.has(args[1].toLowerCase())) {return message.channel.send("That's not a valid log type. Use \`${prefix}log list\` to see a list of valid log types.");} + let lt = LogTypes.get(args[1].toLowerCase()); + let ch = args[2].match(/<\#(?:\d+)>/m) && message.guild.channels.cache.has(message.mentions.channels.first().id) ? message.mentions.channels.first() : message.guild.channels.cache.has(args[2]) ? message.guild.channels.cache.get(args[2]) : null; + if (!ch) {return message.channel.send("I can't find that channel! Make sure that you've mentioned one, or that the ID you provided is correct, and that I can see it.");} + if (!ch.permissionsFor(client.user.id).has("SEND_MESSAGES")) {return message.reply("I don't have permissions to send messages in that channel. Please give me access and try again.");} + let tl = await LogData.findOne({gid: message.guild.id}) || new LogData({gid: message.guild.id}); + tl[lt] = ch.id; + tl.save(); + if (!client.guildconfig.logs.has(message.guild.id)) {client.guildconfig.logs.set(message.guild.id, new Map());} + client.guildconfig.logs.get(message.guild.id).set(lt, ch.id); + return message.channel.send("Log settings updated!") + } + + if (['l', 'list'].includes(args[0].toLowerCase())) { + return message.channel.send("Valid log types:\n\n-`msgdelete` - Shows the content of a message that was deleted, in any channel.\n-`msgedit` - Shows both the old and new versions of a message when it is edited."); + } + + if (['v', 'view'].includes(args[0].toLowerCase())) { + if (client.guildconfig.logs.has(message.guild.id) && client.guildconfig.logs.get(message.guild.id).size) {return message.channel.send(`This server's logs: \n\n${function bonk(){let s = ''; Array.from(client.guildconfig.logs.get(message.guild.id).keys()).forEach(v => s+=`\`${v}\`: <#${client.guildconfig.logs.get(message.guild.id).get(v)}>, `); return s;}().slice(0, -2)}`);} + else {return message.channel.send("Your server doesn't have any logs set up at the moment, or they aren't cached. If you keep seeing this issue even after setting logs, please contact my developers!");} + } + + if (['c', 'clear'].includes(args[0].toLowerCase())) { + + } + } +}; \ No newline at end of file diff --git a/commands/moderation/response.js b/commands/moderation/response.js new file mode 100644 index 0000000..75f991e --- /dev/null +++ b/commands/moderation/response.js @@ -0,0 +1,85 @@ +const Discord = require('discord.js'); + +const GuildData = require('../../models/guild'); +const Responses = require('../../models/responses'); + +const sendResponse = require('../../util/response/sendresponse'); +const parseResponse = require('../../util/response/parseresponse'); +const saveResponse = require('../../util/response/saveresponse'); +const getResponse = require('../../util/response/getresponse'); + +module.exports = { + name: "response", + aliases: ['r', 'resp'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Responses") + .setDescription("Configure your server's saved responses. These are reusable and editable, and can be placed in things like welcome messages and used for announcements.") + .addField("Syntax", "`response `") + .addField("Notice", "You must have your server's staff role or be an admin to use this command."), + meta: { + category: 'Moderation', + description: "Set responses that can be used for various purposes in your server, namely welcome and leave messages.", + syntax: '`response `', + extra: "Response editing is currently not available and will be Soon:tm:" + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("You must be in a server to use this command.");} + let tg = await GuildData.findOne({gid: message.guild.id}); + if (!['q', 'quick'].includes(args[0].toLowerCase()) && ((tg && tg.staffrole.length && !message.member.roles.cache.has(tg.staffrole))) && !message.member.permissions.has("ADMINISTRATOR")) {return message.reply("you need to be staff or admin in this server in order to edit those settings.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}response \``);} + + if (args.length < 1) {return message.reply("You have to tell me what I'm supposed to find or save!");} + + if (['q', 'quick'].includes(args[0].toLowerCase())) {return await sendResponse(message.member, message.channel, 'quick', client, await parseResponse(message, client, args));} + if (['n', 'new', 's', 'save'].includes(args[0].toLowerCase())) {return await saveResponse(await parseResponse(message, client, args), message);} + if (['t', 'test', 'send'].includes(args[0].toLowerCase())) {return await sendResponse(message.member, message.channel, 'quick', client, await getResponse(message, args[1]));} + if (['r', 'remove', 'd', 'delete', 'del'].includes(args[0].toLowerCase())) { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr) {return message.reply("This server has no responses for me to delete.");} + if (!tr.responses.has(args[1].toLowerCase())) {return message.reply("I can't find that response.");} + tr.responses.delete(args[1].toLowerCase()); + let hadBinding = false; + let bm = ''; + tr.bindings.forEach((v, k) => {if (v === args[1].toLowerCase()) { + tr.bindings.delete(v); + hadBinding = true; + bm += `This response was bound to \`${k}\`, so that has also been removed.\n`; + }}); + tr.save(); + return message.channel.send(`I removed the response \`${args[1].toLowerCase()}\`.${hadBinding ? `\n\n${bm}` : ''}`); + } + if (['list', 'l'].includes(args[0].toLowerCase())) { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr && !tr.responses.size) {return message.reply("This server has no responses for me to show you.");} + let s = "This server's response names: "; let resps = Array.from(tr.responses.keys()); + let resp; for (resp of resps) {s += `\`${resp}\`${resps.indexOf(resp) !== resps.length - 1 ? ', ' : ''}`;} + return message.channel.send(s); + } + if (['view', 'v'].includes(args[0].toLowerCase())) { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr) {return message.reply("I'd give you information on a response, but this server doesn't have any.");} + if (!tr.responses.has(args[1].toLowerCase())) {return message.reply("I can't find that response.");} + let hasBinding = false; + let bm = ''; + tr.bindings.forEach((v, k) => {if (v === args[1].toLowerCase()) {hasBinding = true; bm += !bm.length ? `\`${k}\`` : `, \`${k}\``}}); + let infoEmbed = new Discord.MessageEmbed() + .setTitle("Response Info") + .setDescription(`Requested by ${message.member.displayName}`) + .addField("Name/ID", args[1].toLowerCase(), true) + .addField("Type", tr.responses.get(args[1].toLowerCase()).embed ? "Embed" : "Message", true) + .setColor('328ba8') + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp(); + if (hasBinding) {infoEmbed.addField("Server Bindings", bm);} + return message.channel.send(infoEmbed); + } + if (['edit', 'e', 'm', 'modify'].includes(args[0].toLowerCase())) { + let options = await getResponse(message, args[1]); + if (!options) {return;} + + } + + + return message.channel.send(`Syntax: \`${prefix}response \``); + } +}; \ No newline at end of file diff --git a/commands/moderation/softban.js b/commands/moderation/softban.js new file mode 100644 index 0000000..b8d1826 --- /dev/null +++ b/commands/moderation/softban.js @@ -0,0 +1,72 @@ +const Discord = require('discord.js'); + +const {Tag} = require("../../util/tag"); +const {TagFilter} = require("../../util/tagfilter"); + +module.exports = { + name: "softban", + aliases: ['falseban', 'sfb'], + meta: { + category: 'Moderation', + description: "Bans a user from the server, deletes their messages, then unbans them", + syntax: '`softban <@user|userID> [reason]`', + extra: null, + guildOnly: true + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> SoftBan") + .setDescription("Bans a user from the server and deletes their messages, then unbans them. This is a great way to kick a member from the server while getting the message delete effect of a ban.") + .addField("Syntax", "`softban <@user|userID> [reason]`"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}softban <@user|userID> [reason]\``);} + + if (!message.member.permissions.has("BAN_MEMBERS")) {return message.channel.send("You don't have permissions to do that!");} + if (!message.guild.me.permissions.has("BAN_MEMBERS")) {return message.channel.send("I don't have permissions to ban members in your server.");} + let user = message.guild.members.cache.get(args[0]) || message.mentions.members.first(); + + if (!user) {return message.channel.send("You must mention a user to softban, or provide their ID.");} + if (user.roles.highest.position >= message.member.roles.highest.position) {return message.channel.send("You don't have permissions to softban that member as they are above you in the roles list.");} + if (user.roles.highest.position >= message.guild.me.roles.highest.position) {return message.channel.send("I can't ban that member as their highest role is above mine! (Or the same as mine, too)");} + if (!user.bannable) {return message.channel.send("Hmm, it seems like I can't ban that member. This is probably a permissions issue. Or maybe they were already banned? In that case, just use `unban`");} + + let options = new TagFilter([ + new Tag(['r', 'reason'], 'reason', 'append'), + new Tag(['n', 'notes'], 'notes', 'append'), + new Tag(['d', 'days', 'm', 'messages'], 'days', 'append') + ]).test(args.join(" ")); + let reason; let days; + if (options.reason && options.reason.length) {reason = options.reason;} + if (options.days && options.days.length) { + if (isNaN(Number(options.days)) || Number(options.days) < 1 || Number(options.days) > 7 || options.days.includes('.')) {return message.channel.send("The `days` option must be a whole number between 1 and 7.");} + days = Number(options.days); + } else {days = 7;} + if (options.notes && options.notes.length > 250) {return message.channel.send("I mean I get it, they pissed you off, but do you really need to give me that much info on why you're softbanning them? I can't keep track of all that!");} + else {if (args[1] && !options.days /*&& (!options.notes || !options.notes.length)*/ && (!options.reason || !options.reason.length)) {args.shift(); reason = args.join(" ");}} + if (reason && reason.length > 250) {return message.channel.send("I mean I get it, they pissed you off, but do you really need to give me that much info on why you're softbanning them? I can't keep track of all that!");} + + return user.ban({reason: reason, days: days}) + .then(async () => { + /*let mh = await Mod.findOne({gid: message.guild.id}) || new Mod({gid: message.guild.id}); + let mhcases = mh.cases; + + mhcases.push({ + members: [user.id], + punishment: "Banned", + reason: reason ? reason : "", + status: "Closed", + moderators: [message.author.id], + notes: options.notes, + history: [`${new Date().toISOString()} - ${message.author.username} - Created case`, `${new Date().toISOString()} - ${message.author.username} - Banned ${client.users.cache.get(user.id).username}`], + issued: new Date().toUTCString() + }); + + mh.cases = mhcases; + mh.save();*/ + + return message.guild.members.unban(user.id, reason) + .then(async () => {return message.channel.send("That user has been softbanned, and can now be re-invited to the server.");}) + .catch(() => {return message.channel.send("Something went wrong while trying to unban that user! This means that the member has been banned, but not unbanned afterward, so you'll need to unban them using the `unban` command or by doing it manually. If the problem persists, contact my devs.");}); + }) + .catch(() => {return message.channel.send("Something went wrong while trying to ban that user! If the problem persists, contact my devs.");}); + } +}; \ No newline at end of file diff --git a/commands/moderation/staffrole.js b/commands/moderation/staffrole.js new file mode 100644 index 0000000..439a8dd --- /dev/null +++ b/commands/moderation/staffrole.js @@ -0,0 +1,55 @@ +const Discord = require('discord.js'); +const mongoose = require('mongoose'); +const GuildSettings = require('../../models/guild'); + +module.exports = { + name: "staffrole", + aliases: ['sr', 'setstaffrole'], + help: "Set your server's staff role, which allows users with that role to modify my settings in this server. You must be an admin in the server to change this setting.", + meta: { + category: 'Moderation', + description: "Set the role that can edit my settings for the server", + syntax: '`staffrole <@role|roleID|clear|view>`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This is a guild-only command!");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}staffrole <@role|roleID|clear|view>\``);} + if (!message.member.permissions.has("ADMINISTRATOR")) {return message.reply("You must be an admin in this server in order to change this setting!");} + + let tguild = await GuildSettings.findOne({gid: message.guild.id}) + ? await GuildSettings.findOne({gid: message.guild.id}) + : new GuildSettings({gid: message.guild.id}); + + if (['view', 'v'].includes(args[0].trim().toLocaleLowerCase())) {return message.reply( + tguild.staffrole.length + ? message.guild.roles.cache.has(tguild.staffrole) + ? `people with the \`${message.guild.roles.cache.get(tguild.staffrole).name}\` role can edit my setting here.` + : `I have a role stored for this server, but it doesn't seem to exist anymore, so only admins can edit my settings right now.` + : 'only admins may edit settings in this server.' + );} + + let role = !['c', 'clear', 'n', 'none'].includes(args[0].trim().toLowerCase()) ? message.mentions.roles.size ? message.mentions.roles.first() : message.guild.roles.cache.has(args[0]) ? message.guild.roles.cache.get(args[0]) : null : 'c'; + + if (!role) {return message.reply("I couldn't find that role!");} + if (role === "c") { + tguild.staffrole = ''; + tguild.save(); + return message.reply("got it, only admins can edit my settings in this server."); + } else { + tguild.staffrole = role.id; + tguild.save(); + let upm = await message.reply("sure thing!"); + await require('../../util/wait')(1750); + return upm.edit(new Discord.MessageEmbed() + .setAuthor('Staff role updated!', message.author.avatarURL()) + .setDescription(`<@&${tguild.staffrole}> can now edit my settings in this server.`) + .addField('Auditing Admin', `<@${message.member.id}>`, true) + .addField('Role-Holders', `${message.guild.members.cache.filter(m => m.roles.cache.has(tguild.staffrole) && !client.users.cache.get(m.id).bot).size}+ members have this role`, true) + .setColor('328ba8') + .setFooter('Luno', client.user.avatarURL()) + .setTimestamp() + ); + } + } +}; \ No newline at end of file diff --git a/commands/moderation/togglestatuses.js b/commands/moderation/togglestatuses.js new file mode 100644 index 0000000..0290cc9 --- /dev/null +++ b/commands/moderation/togglestatuses.js @@ -0,0 +1,27 @@ +const Discord = require('discord.js'); +const GuildSettings = require('../../models/guild'); + +module.exports = { + name: "togglestatuses", + aliases: ['ts', 'tsw', 'togglestatuswarnings', 'togglestatus', 'statustoggle', 'statusestoggle'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Server Status-Toggling") + .setDescription("Disables or enables the warning that appears when you ping someone that has a status set.") + .addField("Syntax", "`togglestatuses [c]` (add `c` to the end of the message if you want to check if they're enabled or not.)"), + meta: { + category: 'Moderation', + description: "Toggle the warning I give members when they ping someone with a status. Some people find it annoying, but here's my mute button!", + syntax: '`togglestatuses [-c]`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply('You must be in a server to use this command.');} + let tg = await GuildSettings.findOne({gid: message.guild.id}); + if (!message.member.permissions.has('ADMINISTRATOR') && (tg && tg.staffrole.length && !message.member.roles.cache.has(tg.staffrole))) {return message.reply("You don't have permissions to use this command in your server.");} + if (args[0] && ['c', 'check', 'v', 'view'].includes(args[0].toLowerCase())) {return message.channel.send(`I ${tg && !tg.nostatus ? 'will' : 'will not'} send a warning when pinging a member with a status.`);} + if (!tg) {tg = new GuildSettings({gid: message.guild.id});} + tg.nostatus = !tg.nostatus; + tg.save(); + return message.channel.send(`I ${!tg.nostatus ? 'will' : 'will not'} send a warning when pinging a member with a status.`); + } +}; \ No newline at end of file diff --git a/commands/moderation/unban.js b/commands/moderation/unban.js new file mode 100644 index 0000000..8f8873a --- /dev/null +++ b/commands/moderation/unban.js @@ -0,0 +1,33 @@ +const Discord = require('discord.js'); + +module.exports = { + name: "unban", + aliases: ['ub'], + meta: { + category: 'Moderation', + description: "Unban a user from the server", + syntax: '`unban `', + extra: null, + guildOnly: true + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> Unban") + .setDescription("Unbans a user from the server, allowing them to join again if they have an invite.") + .addField("Syntax", "`unban `"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}unban \``);} + + if (!message.member.permissions.has("BAN_MEMBERS")) {return message.channel.send("You don't have permissions to do that!");} + if (!message.guild.me.permissions.has("BAN_MEMBERS")) {return message.channel.send("I don't have permissions to unban members in your server.");} + let user = client.users.cache.get(args[0]) || message.mentions.users.first(); + + if (!user) { + user = await client.users.fetch(args[0]); + if (!user) {return message.channel.send("You must mention a user to unban, or provide their ID.");} + } + + return message.guild.members.unban(user.id) + .then(async () => {return message.channel.send("I've unbanned that user!");}) + .catch(() => {return message.channel.send("Something went wrong while trying to unban that user! If the problem persists, contact my devs.");}); + } +}; \ No newline at end of file diff --git a/commands/moderation/warn.js b/commands/moderation/warn.js new file mode 100644 index 0000000..92d1fd0 --- /dev/null +++ b/commands/moderation/warn.js @@ -0,0 +1,140 @@ +const Discord = require('discord.js'); + +const Mod = require('../../models/mod'); + +const {TagFilter} = require('../../util/tagfilter'); +const {Tag} = require('../../util/tag'); + +module.exports = { + name: "warn", + help: new Discord.MessageEmbed() + .setTitle("Help -> Warnings") + .setDescription("Warn misbehaving members that what they are doing is wrong, and have it stored in a database in order to see a list of all their past warnings") + .addField("Syntax", "`warn <@member> `") + .addField("Notice", "You must be a server administrator in order to use this command."), + meta: { + category: 'Moderation', + description: "Warn misbehaving members that what they are doing is wrong, and have it stored in a database in order to see a list of all their past warnings", + syntax: '`warn <@member> `', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.channel.send("This is a server-only command.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}warn <@member> \``);} + if (!message.member.permissions.has("MANAGE_MESSAGES") && !message.member.permissions.has("MANAGE_GUILD")) {return message.reply("You must be a server moderator (manage messages or manage server permissions) to use this command.");} + if (args.length < 2 && !['check', 'c', 'list', 'l', 'clear', 'e', 'empty'].includes(args[0].toLowerCase())) {return message.channel.send("You must provide a reason for warning the user, or `check` or `clear`.");} + + let user = message.mentions.members.first() && args[0].match(/^<@(?:!)(?:\d+)>$/) ? message.mentions.members.first() : message.guild.members.cache.has(args[0]) ? message.guild.members.cache.get(args[0]) : null; + if (!user && args.length > 1) {return message.channel.send("Either you didn't mention a user, or I can't find that user!");} + if (args.length > 1) {args.shift();} + + if (['check', 'c', 'list', 'l'].includes(args[0].toLowerCase())) { + user = user ? user : message.member; + let mh = await Mod.findOne({gid: message.guild.id}); + if (!mh || !Object.keys(mh.warnings).length) {return message.reply("There are no warnings available in this server.");} + + if (!mh.warnings[user.id] || !mh.warnings[user.id].length) {return message.reply(`${user.id === message.author.id ? 'You have' : 'That user has'} never been warned in this server.`);} + //console.log(mh.cases, mh.warnings); + let ws = ''; + let cwc = 0; + let warning; for (warning of mh.warnings[user.id]) { + let tcase = mh.cases[warning - 1]; + if (tcase.status !== "Cleared") { + ws += `\`Case #${warning}\` - Issued by <@${tcase.moderators[0]}>\n${tcase.reason}\n\n`; + } else {cwc++;} + } + if (cwc > 0) {ws += '*Plus ' + cwc + ' other warnings that have been cleared.*';} + if (cwc === mh.warnings[user.id].length) {return message.reply("That user has no uncleared warnings.");} + return message.channel.send(new Discord.MessageEmbed() + .setTitle("User Warnings") + .setThumbnail(client.users.cache.get(user.id).avatarURL({size: 1024})) + .setDescription(`For ${user.displayName}`) + .addField("Warnings", ws) + .setColor("328ba8") + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp() + ); + } + + else if (['clear', 'e', 'empty'].includes(args[0].toLowerCase())) { + user = user ? user : message.member; + let mh = await Mod.findOne({gid: message.guild.id}); + if (!mh || !Object.keys(mh.warnings).length) {return message.reply("There are no warnings available in this server.");} + + if (!Object.keys(mh.warnings).includes(user.id) || !mh.warnings[user.id].length) {return message.reply(`${user.id === message.author.id ? 'You have' : 'That user has'} never been warned in this server.`);} + + let mhcases = mh.cases; + let moddedcases = []; + let cwc = 0; var wc = 0; + let warning; for (warning of mh.warnings[user.id]) { + if (mhcases[`${warning - 1}`].status !== "Cleared") { + let tcase = mhcases[`${warning - 1}`]; + tcase.status = "Cleared"; + tcase.history.push(`${new Date().toISOString()} - ${message.author.username} - Cleared the warning.`); + moddedcases.push(`${warning - 1}`); + wc++; + if (!tcase.moderators.includes(message.author.id)) {tcase.moderators.push(message.author.id);} + mhcases[`${warning - 1}`] = tcase; + } else {cwc++;} + } + if (cwc === mh.warnings[user.id].length) {return message.reply("That user has no uncleared warnings.");} + + if (moddedcases.length) {let c; for (c of moddedcases) {mh.markModified(`cases.${c}.history`);}} + + mh.cases = mhcases; + mh.save(); + return message.reply(`Cleared ${wc} warnings from ${user.displayName}.`); + } + + else { + if (user.id === message.author.id) {return message.channel.send("You can't warn yourself!");} + if (user.id === client.user.id) {return message.channel.send("You can't warn me, silly! What do you want me to do, spank myself?");} + if (client.users.cache.get(user.id).bot) {return message.channel.send("You can't warn a bot!");} + + let options = new TagFilter([ + new Tag(['r', 'reason'], 'reason' ,'append'), + new Tag(['nd', 'nm', 'nomessage', 'nodm'], 'nodm' ,'toggle'), + new Tag(['silent', 's'], 'silent', 'toggle'), + new Tag(['note', 'n', 'notes'], 'notes', 'append') + ]).test(args.join(" ")); + + if (Object.keys(options).length && (!options.reason || !options.reason.length)) {return message.channel.send("You *must* use -r or -reason to specify your reason if you include any other tags. Please try again!");} + let reason = options.reason && options.reason.length ? options.reason : args.join(" "); + if (reason.length > 200) {return message.reply("Please keep your reason short and sweet - less than 200 characters, please!");} + if (options.notes && options.notes.length > 150) {return message.reply("Please keep your notes short and sweet - less than 150 characters, please!");} + let notes = options.notes && options.notes.length ? [options.notes] : []; + + let mh = await Mod.findOne({gid: message.guild.id}) || new Mod({gid: message.guild.id}); + + let mhcases = mh.cases; + mhcases.push({ + members: [user.id], + punishment: "Warned", + reason: reason, + status: "Closed", + moderators: [message.author.id], + notes: notes, + history: [`${new Date().toISOString()} - ${message.author.username} - Created case`, `${new Date().toISOString()} - ${message.author.username} - Warned ${client.users.cache.get(user.id).username}`], + issued: new Date().toUTCString() + }); + + let mhwarnings = mh.warnings; + let mhwarningsk = Object.keys(mhwarnings); + + if (mhwarningsk.includes(user.id)) {let tw = mhwarnings[user.id]; tw.push(mhcases.length); mhwarnings[user.id] = tw;} + else {mhwarnings[user.id] = [mhcases.length];} + + mh.warnings = mhwarnings; + mh.warnings[user.id] = mhwarnings[user.id]; + mh.cases = mhcases; + + if (!options.silent) {message.channel.send(`Case ${mh.cases.length} - Member has been warned. Reason: \`${reason}\``);} + if (!options.silent && !options.nodm) {client.users.cache.get(user.id).send(`\`${message.author.username}\` has warned you in \`${message.guild.name}\`. Reason: **${reason}**`);} + + mh.markModified(`warnings.${user.id}`); + + mh.save(); + return null; + } + } +}; \ No newline at end of file diff --git a/commands/moderation/welcome.js b/commands/moderation/welcome.js new file mode 100644 index 0000000..76ca5f3 --- /dev/null +++ b/commands/moderation/welcome.js @@ -0,0 +1,72 @@ +const Discord = require('discord.js'); +const GuildData = require('../../models/guild'); +const Responses = require('../../models/responses'); +const sendResponse = require('../../util/response/sendresponse'); + +module.exports = { + name: "welcome", + aliases: ['wel', 'welcomemsg', 'welcomemessage', 'welcomechannel', 'wch', 'wmsg', 'welcomech'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Welcome Messages") + .setDescription("Set the channel and message for your welcome messages!") + .addField("Syntax", "`welcome `") + .addField("Notice", "You must be a staff or admin in your server to edit these settings.") + .addField("Responses", "Your welcome message should be generated through a response using my `response` command, and then bound to the welcome message by providing your response's name."), + meta: { + category: 'Moderation', + description: "Set the channel and message to be sent when a user joins the server.", + syntax: '`welcome `', + extra: "You'll need to use `response` to configure the message that you want sent with this command. The name you give the response is what you'll give to this command" + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This command is server-only.");} + let tg = await GuildData.findOne({gid: message.guild.id}) ? await GuildData.findOne({gid: message.guild.id}) : new GuildData({gid: message.guild.id}); + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}welcome \``);} + if (['v', 'view', 'c', 'check'].includes(args[0].toLowerCase())) {} + if ((!tg || !tg.staffrole.length || !message.member.roles.cache.has(tg.staffrole)) && !message.member.permissions.has("ADMINISTRATOR")) {return message.reply("You can't do that without staff or admin permissions, silly!");} + + if (['s', 'set'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.reply("You need to specify a channel for your welcome messages to be sent in!");} + let ch = message.mentions.channels.first() && args[1].match(/^<#(?:\d+)>$/) ? message.mentions.channels.first().id : message.guild.channels.cache.has(args[1]) ? message.guild.channels.cache.get(args[1]).id : null; + if (!ch) {return message.reply("I can't find that channel!");} + if (!message.guild.channels.cache.get(ch).permissionsFor(client.user.id).has("SEND_MESSAGES")) {return message.reply("I can't send messages in that channel. Try fixing the permissions or using a different channel!");} + if (!args[2]) {return message.reply(`You have to specify a response to use! You can make one with \`${prefix}response new\`.`);} + let tr = await Responses.findOne({gid: message.guild.id}) ? await Responses.findOne({gid: message.guild.id}) : new Responses({gid: message.guild.id}); + if (!tr.responses.has(args[2].toLowerCase())) {return message.reply("Silly, I can't welcome someone with a response that doesn't exist! Try making one or make sure you spelled the name correctly.");} + tg.wch = ch; + tg.save(); + tr.bindings.set('welcome', args[2].toLowerCase()); + tr.save(); + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Welcome Channel/Message Updated") + .setDescription(`This server's member-welcoming settings have been altered by ${message.author.tag}.\n\n**Channel**: <#${ch}>\n**Response Name**: \`${args[2].toLowerCase()}\``) + .setColor('328ba8') + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp() + ) + } + + if (['t', 'test'].includes(args[0].toLowerCase())) { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr || !tr.bindings.has('welcome') || !tr.responses.has(tr.bindings.get('welcome'))) {return message.reply("I can't test your welcome message because the response doesn't exist, a welcome response isn't set, or you haven't made any responses in this server.");} + await sendResponse(message.member, message.channel, 'this shit aint matter anymore lol', client, tr.responses.get(tr.bindings.get('welcome'))); + } + + if (['clear'].includes(args[0].toLowerCase())) { + tg.wch = ''; + tg.save(); + let tr = await Responses.findOne({gid: message.guild.id}) ? await Responses.findOne({gid: message.guild.id}) : new Responses({gid: message.guild.id}); + if (tr) { + tr.bindings.delete('welcome'); + tr.save(); + } + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Welcome Channel/Message Updated") + .setDescription(`This server's member-welcoming settings have been altered by ${message.author.tag}.\n\n**Channel**: None`) + .setColor('328ba8') + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp() + ); + } + } +}; \ No newline at end of file diff --git a/commands/social/afk.js b/commands/social/afk.js new file mode 100644 index 0000000..eeb5bcd --- /dev/null +++ b/commands/social/afk.js @@ -0,0 +1,42 @@ +const Discord = require('discord.js'); +const mongoose = require('mongoose'); + +const Status = require('../../models/status'); +const UserData = require('../../models/user'); + +module.exports = { + name: "afk", + aliases: ['setafk'], + help: new Discord.MessageEmbed() + .setTitle("Help -> AFK") + .setDescription("Set your status within the bot as AFK and specify a reason. Then, when other people ping you, I can let them know that you're not available!") + .addField("Syntax", "`afk [clearMode] `") + .addField("Notice","Your status clear mode can be set to either 'auto' or 'manual'. If not specified, it will clear next time you send a message (auto)."), + meta: { + category: 'Social', + description: "Tell others that you're AFK so that they'll be notified when you ping them.", + syntax: '`afk [clearMode] `', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}afk [clearMode] \``);} + let tu = await UserData.findOne({uid: message.author.id}) + ? await UserData.findOne({uid: message.author.id}) + : new UserData({uid: message.author.id}); + if (['m', 'manual', 'a', 'auto'].includes(args[0])) { + tu.statusclearmode = ['m', 'manual'].includes(args[0]) ? 'manual' : 'auto'; + args.shift(); + } else {tu.statusclearmode = 'auto';} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}afk [clearMode] \``);} + let reason = args.join(" "); + if (reason.length > 150) {return message.reply("That status a bit long; keep it under 150 characters.");} + tu.statustype = 'afk'; + tu.statusmsg = reason.trim(); + tu.statussetat = new Date(); + let tempDate = new Date(); + tu.statusclearat = tempDate.setHours(tempDate.getHours() + 12); + tu.save(); + require('../../util/cachestatus')(message.author.id, tempDate.setHours(tempDate.getHours() + 12)); + return message.reply(`I set your ${tu.statusclearmode === 'auto' ? 'automatically' : 'manually'}-clearing AFK message to: ${reason.trim()}`); + } +}; \ No newline at end of file diff --git a/commands/social/bio.js b/commands/social/bio.js new file mode 100644 index 0000000..e67a209 --- /dev/null +++ b/commands/social/bio.js @@ -0,0 +1,55 @@ +const Discord = require('discord.js'); +const UserData = require('../../models/user'); + +module.exports = { + name: "bio", + help: new Discord.MessageEmbed() + .setTitle("Help -> Bio") + .setDescription("Set and view user bios, which are fun ways to express yourself!") + .addField("Syntax", "`bio `"), + meta: { + category: 'Social', + description: "Set your own user bio, which can be seen by everyone!", + syntax: '`bio `', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}bio \``);} + let tu = await UserData.findOne({uid: message.author.id}) ? await UserData.findOne({uid: message.author.id}) : new UserData({uid: message.author.id}); + + if (['v', 'view', 'check'].includes(args[0].toLowerCase())) { + let person = args[1] ? args[1].match(/^<@(?:!?)(?:\d+)>$/) && message.mentions.users.first() ? message.mentions.users.first().id : message.guild && message.guild.members.cache.has(args[1]) ? args[1] : message.author.id : message.author.id; + let pud = await UserData.findOne({uid: person}); + if (!pud || !pud.bio || !pud.bio.length) {return message.reply(person === message.author.id ? "You don't have a bio set!" : "That user has no bio for me to show you!");} + return message.channel.send(new Discord.MessageEmbed() + .setTitle(`Bio for ${message.guild ? message.guild.members.cache.get(person).displayName : message.author.username}`) + .setThumbnail(client.users.cache.get(person).avatarURL({size: 2048})) + .setDescription(pud.bio) + .setColor(pud.color && pud.color.length ? pud.color : '328ba8') + .setFooter('Luno', client.user.avatarURL()) + .setTimestamp() + ); + } + if (['s', 'set'].includes(args[0].toLowerCase())) { + args.shift(); + if (!args.length) {return message.reply("Please specify a bio!");} + let bio = args.join(" "); + if (bio.length > 200) {return message.reply("Please keep your bio under 200 characters!");} + tu.bio = bio; + tu.save(); + return message.channel.send(new Discord.MessageEmbed() + .setTitle(`Bio Set!`) + .setThumbnail(message.author.avatarURL({size: 2048})) + .setDescription(tu.bio) + .setColor(tu.color && tu.color.length ? tu.color : '328ba8') + .setFooter('Luno', client.user.avatarURL()) + .setTimestamp() + ); + } + if (['c', 'clear'].includes(args[0].toLowerCase())) { + tu.bio = ''; + tu.save(); + return message.reply("Bio cleared!"); + } + } +}; \ No newline at end of file diff --git a/commands/social/clearstatus.js b/commands/social/clearstatus.js new file mode 100644 index 0000000..e449d41 --- /dev/null +++ b/commands/social/clearstatus.js @@ -0,0 +1,26 @@ +const Discord = require('discord.js'); +const mongooes = require('mongoose'); + +const UserData = require('../../models/user'); + +module.exports = { + name: "clearstatus", + aliases: ['statusclear', 'cs'], + help: "Clears your status, if you have one set. Does not take any arguments.", + meta: { + category: 'Social', + description: "Clear your status, if you have one set.", + syntax: '`clearstatus`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + let tu = await UserData.findOne({uid: message.author.id}); + if (!tu && !tu.statusmsg.length) {return message.reply("you have no status for me to clear");} + if (tu.statusclearmode === "auto") {return;} + tu.statusmsg = ''; + tu.statustype = ''; + tu.save(); + require('../../util/siftstatuses')(client, message.author.id, true); + return message.reply("welcome back! I cleared your status.").then(m => {m.delete({timeout: 5000}).then(() => {if (message.guild && message.guild.me.permissions.has("DELETE_MESSAGES")) {message.delete().catch(() => {});}})}); + } +}; \ No newline at end of file diff --git a/commands/social/cry.js b/commands/social/cry.js new file mode 100644 index 0000000..dffe640 --- /dev/null +++ b/commands/social/cry.js @@ -0,0 +1,38 @@ +const Discord = require('discord.js'); +const Saves = require('../../models/saves'); +const UserData = require('../../models/user'); +const makeId = require('../../util/makeid'); + +module.exports = { + name: "cry", + aliases: ['sob'], + help: "Tell others that you're crying with `{{p}}cry`. We're here for you!", + meta: { + category: 'Social', + description: "Tell others that you're not feeling so well using this command.", + syntax: '`cry`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'cry'}) ? await Saves.findOne({name: 'cry'}) : new Saves({name: 'cry'}); + let saves = savess.saves; + if (!args.length) {return message.channel.send(new Discord.MessageEmbed() + .setTitle(`${message.guild ? message.member.displayName : message.author.username} is Crying!`) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('8d42f5') + );} + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Luno Developer in order to add new cry GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/social/dnd.js b/commands/social/dnd.js new file mode 100644 index 0000000..53dbc0e --- /dev/null +++ b/commands/social/dnd.js @@ -0,0 +1,40 @@ +const Discord = require('discord.js'); +const mongoose = require('mongoose'); +const UserData = require('../../models/user'); + +module.exports = { + name: "dnd", + aliases: ['donotdisturb'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Do Not Disturb") + .setDescription("Set your status within the bot as DnD and specify a reason. Then, when other people ping you, I can let them know that you don't want to be disturbed!") + .addField("Syntax", "`dnd [clearMode] `") + .addField("Notice","Your status clear mode can be set to either 'auto' or 'manual'. If not specified, it will clear when you use `n?clearstatus`."), + meta: { + category: 'Social', + description: "Tell others not to disturb you so that they'll know not to ping you.", + syntax: '`dnd [clearMode] `', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}dnd [clearMode] \``);} + let tu = await UserData.findOne({uid: message.author.id}) + ? await UserData.findOne({uid: message.author.id}) + : new UserData({uid: message.author.id}); + if (['m', 'manual', 'a', 'auto'].includes(args[0])) { + tu.statusclearmode = ['m', 'manual'].includes(args[0]) ? 'manual' : 'auto'; + args.shift(); + } else {tu.statusclearmode = 'manual';} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}afk [clearMode] \``);} + let reason = args.join(" "); + if (reason.length > 150) {return message.reply("That status a bit long; keep it under 150 characters.");} + tu.statustype = 'dnd'; + tu.statusmsg = reason.trim(); + tu.statussetat = new Date(); + let tempDate = new Date(); + tu.statusclearat = tempDate.setHours(tempDate.getHours() + 12); + tu.save(); + require('../../util/cachestatus')(message.author.id, tempDate.setHours(tempDate.getHours() + 12)); + return message.reply(`I set your ${tu.statusclearmode === 'auto' ? 'automatically' : 'manually'}-clearing Do not Disturb message to: ${reason.trim()}`); + } +}; \ No newline at end of file diff --git a/commands/social/hug.js b/commands/social/hug.js new file mode 100644 index 0000000..749d8be --- /dev/null +++ b/commands/social/hug.js @@ -0,0 +1,62 @@ +const Discord = require('discord.js'); + +const Saves = require('../../models/saves'); +const UserData = require('../../models/user'); +const VC = require('../../models/vscount'); + +const makeId = require('../../util/makeid'); + +module.exports = { + name: "hug", + help: "Tell others that you need a hug with `{{p}}hug`, or give one by mentioning someone to hug!", + meta: { + category: 'Social', + description: "Give someone a hug, or tell others that you need one! We've got your back :p", + syntax: '`hug <@user>`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'hug'}) ? await Saves.findOne({name: 'hug'}) : new Saves({name: 'hug'}); + let saves = savess.saves; + if (!args.length) { + return message.channel.send(message.guild ? new Discord.MessageEmbed() + .setTitle(`${message.guild ? message.member.displayName : message.author.username} needs a hug!`) + .setThumbnail(message.author.avatarURL({size: 2048})) + .setDescription(`Show them some love with \`${prefix}hug @${message.member.displayName}\`!`) + .setColor('328ba8') + .setFooter('Luno', client.user.avatarURL()) + .setTimestamp() + : "Sorry, but I'm a bot, and I can't hug you. Go into a server and ask for some hugs!" + );} + if (mention && args[0].match(/^<@(?:!?)(?:\d+)>$/)) { + if (!message.guild) {return message.reply("Please make sure you're in a server so you can mention someone other than me to hug!");} + if (!message.guild.members.cache.has(mention.id)) {return message.reply("That user is not in this server!");} + if (message.author.id === mention.id) {return message.reply("Sorry if you're that lonely, but you can't hug yourself!");} + let hugs = await VC.findOne({uid: message.author.id, countOf: 'hug'}) || new VC({uid: message.author.id, countOf: 'hug'}); + hugs.against[mention.id] = hugs.against[mention.id] ? hugs.against[mention.id] + 1 : 1; + hugs.total++; + hugs.markModified(`against.${mention.id}`); + hugs.save(); + return message.channel.send(new Discord.MessageEmbed() + .setAuthor(`${message.guild ? message.member.displayName : message.author.username} gives ${message.guild.members.cache.get(mention.id).displayName} a hug!`, message.author.avatarURL()) + .setDescription(`You've hugged them **${hugs.against[mention.id] === 1 ? 'once' : `${hugs.against[mention.id]} times!`}**`) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('52c7bb') + .setFooter(`${hugs.total} hug${hugs.total === 1 ? '' : 's'} total`) + ); + } + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Luno Developer in order to add new hug GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/social/pat.js b/commands/social/pat.js new file mode 100644 index 0000000..edf4148 --- /dev/null +++ b/commands/social/pat.js @@ -0,0 +1,63 @@ +const Discord = require('discord.js'); + +const Saves = require('../../models/saves'); +const UserData = require('../../models/user'); +const VC = require('../../models/vscount'); + +const makeId = require('../../util/makeid'); + +module.exports = { + name: "pat", + help: "Give someone a pat to let them know they're wholesome ^^", + meta: { + category: 'Social', + description: "Give someone some pats, or ask for some", + syntax: '`pat <@user>`', + extra: null + }, + aliases: ['p', 'pet'], + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'pat'}) ? await Saves.findOne({name: 'pat'}) : new Saves({name: 'pat'}); + let saves = savess.saves; + if (!args.length) { + return message.channel.send(message.guild ? new Discord.MessageEmbed() + .setTitle(`${message.guild ? message.member.displayName : message.author.username} wants some pats!`) + .setThumbnail(message.author.avatarURL({size: 2048})) + .setDescription(`Give them some with \`${prefix}pat @${message.member.displayName}\`!`) + .setColor('328ba8') + .setFooter('Luno', client.user.avatarURL()) + .setTimestamp() + : "Sorry, but I'm only able to pat one person, and it's not you ^^" + );} + if (mention && args[0].match(/^<@(?:!?)(?:\d+)>$/)) { + if (!message.guild) {return message.reply("Please make sure you're in a server so you can mention someone other than me to pat!");} + if (!message.guild.members.cache.has(mention.id)) {return message.reply("That user is not in this server!");} + if (message.author.id === mention.id) {return message.reply("Self pats just don't work mate. Maybe try asking for some!");} + let pats = await VC.findOne({uid: message.author.id, countOf: 'pat'}) || new VC({uid: message.author.id, countOf: 'pat'}); + pats.against[mention.id] = pats.against[mention.id] ? pats.against[mention.id] + 1 : 1; + pats.total++; + pats.markModified(`against.${mention.id}`); + pats.save(); + return message.channel.send(new Discord.MessageEmbed() + .setAuthor(`${message.guild ? message.member.displayName : message.author.username} pats ${message.guild.members.cache.get(mention.id).displayName}!`, message.author.avatarURL()) + .setDescription(`You've given them **${pats.against[mention.id]}** pat${pats.against[mention.id] === 1 ? '' : 's'}!`) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('52c7bb') + .setFooter(`${pats.total} pat${pats.total === 1 ? '' : 's'} total`) + ); + } + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Luno Developer in order to add new hug GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/social/sip.js b/commands/social/sip.js new file mode 100644 index 0000000..d63fc69 --- /dev/null +++ b/commands/social/sip.js @@ -0,0 +1,37 @@ +const Discord = require('discord.js'); +const Saves = require('../../models/saves'); +const UserData = require('../../models/user'); +const makeId = require('../../util/makeid'); + +module.exports = { + name: "sip", + help: "Take a sip and watch the shenanigans unfold using `{{p}}sip`.", + meta: { + category: 'Social', + description: "Take a sip and watch the shenanigans unfold. Slurp if you're feeling... risqué.", + syntax: '`sip`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'sip'}) ? await Saves.findOne({name: 'sip'}) : new Saves({name: 'sip'}); + let saves = savess.saves; + if (!args.length) {return message.channel.send(new Discord.MessageEmbed() + .setTitle(`${message.guild ? message.member.displayName : message.author.username} takes a sip...`) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('69310d') + );} + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Luno Developer in order to add new sip GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/social/starboard.js b/commands/social/starboard.js new file mode 100644 index 0000000..c60b751 --- /dev/null +++ b/commands/social/starboard.js @@ -0,0 +1,65 @@ +const Discord = require('discord.js'); +const GuildData = require('../../models/guild'); +const ask = require('../../util/ask'); + +module.exports = { + name: "starboard", + aliases: ['sb'], + help: new Discord.MessageEmbed() + .setTitle("Help -> StarBoard") + .setDescription("Setup and view information on this server's starboard! This allows messages to be sent to a dedicated channel when they receive a set number of star messages.") + .addField("Syntax", "`starboard `") + .addField("Notice", "You must have the staff-role or be an admin in order to set up or toggle the starboard"), + meta: { + category: 'Social', + description: "Set up a star board to feature users' messages in", + syntax: '`starboard `', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("You must be in a server in order to use this command.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}starboard \``);} + let tg = await GuildData.findOne({gid: message.guild.id}) ? await GuildData.findOne({gid: message.guild.id}) : new GuildData({gid: message.guild.id}); + if (['v', 'view', 'c', 'check'].includes(args[0])) {return message.reply(tg.starchannel.length ? tg.starsenabled ? `I'm watching for stars in <#${tg.starchannel}>. Messages will be Starred when they receive ${tg.starreq} :star: reactions.` : "StarBoard has been set up for this server, but isn't enabled at this time." : "StarBoard has not yet been set up in this server. You can do so with `" + prefix + "starboard setup`.");} + if ((tg.staffrole.length && !message.member.roles.cache.has(tg.staffrole)) && !message.member.permissions.has("ADMINISTRATOR")) {return message.channel.send("You don't have permissions to edit starboard settings in this server!");} + + if (['setup', 's', 'config', 'c'].includes(args[0])) { + if (tg.starchannel.length) {message.channel.send("You already have a starboard set up in your server! This means that by continuing the setup, you'll be overwriting your previous settings.");} + + let ch = await ask(message, 'What channel would you like the starboard to be in? (Time: 30s)', 30000); if (!ch) {return;} + if (ch.match(/^<#(?:\d+)>$/) && message.guild.channels.cache.has(ch.slice(ch.search(/\d/), ch.search('>')))) {tg.starchannel = ch.slice(ch.search(/\d/), ch.search('>'));} + else if (message.guild.channels.cache.has(ch)) {tg.starchannel = ch;} + else {return message.reply("Please specify a channel that actually exists! Try again.");} + + let starNum = await ask(message, 'How many stars should be reacted to a message in order for it to be starred? Please just send a number. (Time: 30s)', 30000); if (!starNum) {return;} + if (isNaN(Number(starNum))) {return message.reply("That isn't a number! Please try again.");} + starNum = Number(starNum); + if (starNum < 3) {return message.reply("You need to have at least 3 stars. Try again");} + if (starNum > 100) {return message.reply("You can't require more than 100 stars! Try again.");} + tg.starreq = starNum; + + tg.starsenabled = true; + tg.save(); + + return message.channel.send(`Got it! I will now be watching for messages with at least ${starNum} reactions, and I'll send them to <#${tg.starchannel}>!`); + } + + else if (['e', 'enable'].includes(args[0]) || (['t', 'toggle'].includes(args[0]) && !tg.starsenabled)) { + if (tg.starsenabled) {return message.reply("StarBoard is already enabled in this server!");} + if (!tg.starchannel.length) {return message.reply(`Please setup StarBoard first! \`${prefix}starboard setup\`.`);} + tg.starsenabled = true; + tg.save(); + return message.channel.send(`I've re-enabled your StarBoard. It's kept the same settings as you had when you disabled it!`); + } + + else if (['d', 'disable'].includes(args[0]) || (['t', 'toggle'].includes(args[0]) && tg.starsenabled)) { + if (!tg.starsenabled) {return message.reply("StarBoard is already disabled in this server!");} + if (!tg.starchannel.length) {return message.reply(`Please setup StarBoard first! \`${prefix}starboard setup\`.`);} + tg.starsenabled = false; + tg.save(); + return message.channel.send(`I've disabled your StarBoard. Your StarBoard configuration will be kept for if/when you re-enable StarBoard.`); + } + + else {return message.reply(`Invalid arg! Syntax: \`${prefix}starboard \``);} + } +}; \ No newline at end of file diff --git a/commands/utility/randnum.js b/commands/utility/randnum.js new file mode 100644 index 0000000..e452b49 --- /dev/null +++ b/commands/utility/randnum.js @@ -0,0 +1,51 @@ +const Discord = require('discord.js'); + +module.exports = { + name: "randnum", + aliases: ['rn', 'randnumber', 'randomnum', 'randomnumber'], + meta: { + category: "", + perms: "", + staff: false, + vip: "", + serverPerms: [], + writtenBy: "", + serverOnly: false + }, + tags: [], + help: new Discord.MessageEmbed() + .setTitle("Help -> Random Numbers") + .setDescription("Generates a Random Number in the specified range.") + .addField("Syntax", "`randnum [count]`"), + meta: { + category: 'Utility', + description: "Generate a random number... or a lot of them. It's up to you, really.", + syntax: '`randnum [count]`', + extra: null + }, + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}randnum [count]\``);} + if (args.length < 2) {return message.channel.send("You have to specify two numbers");} + if (![args[0], args[1]].forEach(x => {if (isNaN(Number(x))) {return false;}})) {return message.channel.send("One of your numbers was not actually a number!");} + if (![args[0], args[1]].forEach(x => {if (Number(x) < 0 || Number(x) > 10000) {return false;}})) {return message.channel.send("Your number must be positive and less than 10,000");} + let nums = Number(args[0]) > Number(args[1]) ? [Number(args[1]), Number(args[0])] : [Number(args[0]), Number(args[1])]; + let count; + if (args[2]) { + if (isNaN(Number(args[2]))) {return message.channel.send("You must use a number for your count.");} + count = Number(args[2]); + if (count < 1 || count > 10) {return message.channel.send("You have to have between 1 and 10 for your count.");} + } + count = count ? count : 1; + let res = ''; + for (let i=0; i 1 ? 's' : ''}`) + .setDescription(res) + .setColor('328ba8') + .setFooter('Luno', client.user.avatarURL()) + .setTimestamp() + ); + } +}; \ No newline at end of file diff --git a/commands/utility/todo.js b/commands/utility/todo.js new file mode 100644 index 0000000..f4534b5 --- /dev/null +++ b/commands/utility/todo.js @@ -0,0 +1,100 @@ +const Discord = require('discord.js'); + +const TD = require('../../models/todo'); + +const ask = require('../../util/ask'); + +module.exports = { + name: "todo", + aliases: ['td'], + meta: { + category: 'Utility', + description: "Create and manage your To-Do lists!", + syntax: '`todo `', + extra: null + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> To-Do Lists") + .setDescription("Create and manage your To-Do lists. You can use the commands like `add` or `view` without specifying a list to see your `quick` list, which is just quick random stuff. Otherwise, you can specify a list name first to manage it.") + .addField("Syntax", "`todo `"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}todo \``);} + + if (['add', 'a'].includes(args[0].toLowerCase())) { + let list = 'quick'; + let td = await TD.findOne({uid: message.author.id}); + if (td && td.lists.quick.length > 20) {return message.channel.send("Sorry, but your list can only have 20 items or less.");} + let item; + if (!args[1]) {item = await ask(message, "What would you like to add to your quick list?", 90000); if (!item) {return;}} + else {args.shift(); item = args.join(" ");} + if (item.length > 100) {return message.channel.send("ToDo items can only be less than 100 characters.");} + td = td || new TD({uid: message.author.id}); + td.lists.quick.push(item); + td.markModified(`lists.${list}`); + td.save(); + return message.channel.send(new Discord.MessageEmbed() + .setAuthor("To-Do Added!", message.author.avatarURL()) + .setDescription(`${item}\n\`->\` In list '${list}'`) + .setColor('328ba8') + ); + } + + else if (['v', 'view'].includes(args[0].toLowerCase())) { + let list = 'quick'; + let td = await TD.findOne({uid: message.author.id}); + if (!td) {return message.channel.send("You don't have any todo lists!");} + if (!td.lists[list]) {return message.channel.send("That list doesn't exist!");} + if (!td.lists[list].length) {return message.channel.send("That list is empty!");} + let s = ''; + let n = 0; let i; for (i of td.lists[list]) {n++; s += `**${n}.** ${i}\n`;} + return message.channel.send(new Discord.MessageEmbed() + .setAuthor(message.guild ? message.member.displayName : message.author.username, message.author.avatarURL()) + .setTitle(list === "quick" ? "Personal Quick List" : `List "${list}"`) + .setDescription(s) + .setColor("328ba8") + .setFooter("Luno") + .setTimestamp() + ); + } + + else if (['d', 'delete', 'r', 'remove'].includes(args[0].toLowerCase())) { + let list = 'quick'; + let td = await TD.findOne({uid: message.author.id}); + if (!td) {return message.channel.send("You don't have any todo lists!");} + if (!td.lists[list]) {return message.channel.send("That list doesn't exist!");} + if (!td.lists[list].length) {return message.channel.send("That list is empty!");} + let collected; + if (!args[1]) { + let s = ''; + let n = 0; let i; for (i of td.lists[list]) {n++; s += `**${n}.** ${i}\n`;} + await message.channel.send(new Discord.MessageEmbed() + .setAuthor(message.guild ? message.member.displayName : message.author.username, message.author.avatarURL()) + .setTitle(list === "quick" ? "Personal Quick List" : `List "${list}"`) + .setDescription(s) + .addField("Deletion", "To remove an item from your list, please reply with the number of the item you no longer want on your list.") + .setColor("328ba8") + .setFooter("Luno") + .setTimestamp() + ); + try {collected = await message.channel.awaitMessages(m => m.author.id === message.author.id, {errors: ['time'], time: 60000, max: 1});} + catch {return message.channel.send("This question has timed out. Please try again!");} + collected = collected.first().content.trim(); + } else {collected = args[1];} + if (isNaN(Number(collected))) {return message.channel.send("You didn't give me a number!");} + let id = Number(collected); + if (id < 1 || id > td.lists[list].length) {return message.channel.send("Your number was either below 1 or doesn't have a trigger to match it.");} + try { + let templists = td.lists; + let temptt = templists[list]; + temptt.splice(id-1, 1); + templists[list] = temptt; + td.lists = templists; + td.markModified(`lists.${list}`); + td.save(); + return message.channel.send(["That's one item off the list for ya!", "And another one bites the dust! I've removed that item from your todo list.", "And thus the list grows one item smaller!"][Math.floor(Math.random()*3)]); + } catch {return message.channel.send("There seemed to have been a problem deleting that list item. Contact my devs if the problem persists.");} + } + + else {return message.channel.send("Invalid arg! Use ``");} + } +}; \ No newline at end of file diff --git a/events/guildCreate.js b/events/guildCreate.js new file mode 100644 index 0000000..30fa79e --- /dev/null +++ b/events/guildCreate.js @@ -0,0 +1,28 @@ +const Discord = require('discord.js'); +const BotDataSchema = require('../models/bot'); + +module.exports = async (client, guild) => { + + /* + * Top.gg API + * GBL API never happening + * Other APIs idk + */ + + let botData = await BotDataSchema.findOne({finder: 'lel'}); + botData.servers = client.guilds.cache.size; + botData.servers_all += 1; + botData.save(); + + client.guilds.cache.get('762707532417335296').channels.cache.get('766031709866557471').send(new Discord.MessageEmbed() + .setAuthor('New Guild Added', client.users.cache.get(guild.owner.id).avatarURL()) + .setTitle(guild.name) + .setThumbnail(guild.iconURL({size: 2048})) + .addField('Owner', client.users.cache.get(guild.owner.id).tag, true) + .addField('Members', guild.members.cache.size, true) + .addField('Position', `Server #${client.guilds.cache.size}`, true) + .setColor('55ff7f') + .setFooter('Luno') + .setTimestamp() + ); +}; \ No newline at end of file diff --git a/events/guildDelete.js b/events/guildDelete.js new file mode 100644 index 0000000..4c9f030 --- /dev/null +++ b/events/guildDelete.js @@ -0,0 +1,27 @@ +const Discord = require('discord.js'); +const BotDataSchema = require('../models/bot'); + +module.exports = async (client, guild) => { + + /* + * Top.gg API + * GBL API never happening + * Other APIs idk + */ + + let botData = await BotDataSchema.findOne({finder: 'lel'}); + botData.servers = client.guilds.cache.size; + botData.save(); + + client.guilds.cache.get('762707532417335296').channels.cache.get('766031709866557471').send(new Discord.MessageEmbed() + .setAuthor('Server Lost', client.users.cache.get(guild.owner.id).avatarURL()) + .setTitle(guild.name) + .setThumbnail(guild.iconURL({size: 2048})) + .addField('Owner', client.users.cache.get(guild.owner.id).tag, true) + .addField('Members', guild.members.cache.size, true) + .addField('Position', `Server #${client.guilds.cache.size + 1}`, true) + .setColor('ff5d6a') + .setFooter('Luno') + .setTimestamp() + ); +}; \ No newline at end of file diff --git a/events/guildMemberAdd.js b/events/guildMemberAdd.js new file mode 100644 index 0000000..f837d38 --- /dev/null +++ b/events/guildMemberAdd.js @@ -0,0 +1,19 @@ +const GuildData = require('../models/guild'); +const Responses = require('../models/responses'); +const sendResponse = require('../util/response/sendresponse'); + +module.exports = async (client, member) => { + let tg = await GuildData.findOne({gid: member.guild.id}); + let tr = await Responses.findOne({gid: member.guild.id}); + if (tg && tg.joinrole.length && member.guild.roles.cache.has(tg.joinrole)) { + if (member.guild.members.cache.get(client.user.id).permissions.has("MANAGE_ROLES")) {member.roles.add(tg.joinrole);} + } + if ( + tr && tr.bindings.has('welcome') && tr.responses.has(tr.bindings.get('welcome')) + && tg.wch.length && member.guild.channels.cache.has(tg.wch) + && member.guild.channels.cache.get(tg.wch).permissionsFor(client.user.id).has("SEND_MESSAGES") + && !client.users.cache.get(member.id).bot + ) { + try {member.guild.channels.cache.get(tg.wch).send(await sendResponse(member, member.guild.channels.cache.get(tg.wch), 'xdlol', client, tr.responses.get(tr.bindings.get('welcome'))));} catch {} + } +}; \ No newline at end of file diff --git a/events/guildMemberRemove.js b/events/guildMemberRemove.js new file mode 100644 index 0000000..63d8fcf --- /dev/null +++ b/events/guildMemberRemove.js @@ -0,0 +1,16 @@ +const GuildData = require('../models/guild'); +const Responses = require('../models/responses'); +const sendResponse = require('../util/response/sendresponse'); + +module.exports = async (client, member) => { + let tg = await GuildData.findOne({gid: member.guild.id}); + let tr = await Responses.findOne({gid: member.guild.id}); + if ( + tr && tr.bindings.has('leave') && tr.responses.has(tr.bindings.get('leave')) + && tg.lch.length && member.guild.channels.cache.has(tg.lch) + && member.guild.channels.cache.get(tg.lch).permissionsFor(client.user.id).has("SEND_MESSAGES") + && !client.users.cache.get(member.id).bot + ) { + try {member.guild.channels.cache.get(tg.lch).send(await sendResponse(member, member.guild.channels.cache.get(tg.lch), 'xdlol', client, tr.responses.get(tr.bindings.get('leave'))));} catch {} + } +}; \ No newline at end of file diff --git a/events/message.js b/events/message.js new file mode 100644 index 0000000..d03341c --- /dev/null +++ b/events/message.js @@ -0,0 +1,93 @@ +const Discord = require('discord.js'); +const chalk = require('chalk'); + +const wait = require('../util/wait'); + +const UserData = require('../models/user'); +const AR = require('../models/ar'); +const LXP = require('../models/localxp'); + +module.exports = async (client, message) => { + if (message.author.bot) {return undefined;} + if (message.channel.type !== 'text' && message.channel.type !== 'dm') {return undefined;} + + //if (message.channel.type == "text") {if (settings[message.guild.id]) {prefix = settings[message.guild.id].prefix;};}; + + if (message.guild && !message.member.permissions.has("SEND_MESSAGES")) {return undefined;} + + let prefix = message.guild ? client.guildconfig.prefixes.has(message.guild.id) ? client.guildconfig.prefixes.get(message.guild.id) !== null ? client.guildconfig.prefixes.get(message.guild.id) : 'l.' : 'l.' : 'l.'; + + let msg = message.content.toLowerCase(); + let mention = message.mentions.users.first(); + let args = msg.startsWith(prefix) + ? message.content.slice(prefix.length).trim().split(/\s+/g) + : msg.startsWith('<@!') + ? message.content.slice(4 + client.user.id.length).trim().split(/\s+/g) + : message.content.slice(3 + client.user.id.length).trim().split(/\s+/g); + let cmd = args.shift().toLowerCase().trim(); + + if (message.content.includes("@everyone")) {return;} + + if ([`<@${client.user.id}>`, `<@!${client.user.id}>`].includes(msg)) { + return message.channel.send(new Discord.MessageEmbed() + .setTitle(["Yep, that's me!", "^^ Hiya!", "Oh, hi there!", "Sure, what's up?", "How can I help!", "Luno is busy, but I can take a message for you!", "Teehee that's me!", "You were looking for Luno Tivastl, right?", "Sure! What's up?", "Pong!"][Math.floor(Math.random() * 10)]) + .setDescription(`My prefix here is \`${prefix}\`. Use \`${prefix}help\` to see what commands you can use.`) + .setColor('328ba8')); + } + + if (mention && message.guild) {require('../util/mention')(message, msg, args, cmd, prefix, mention, client);} + UserData.findOne({uid: message.author.id}).then(async (tu) => { + if (tu && tu.statusmsg.length && tu.statusclearmode === 'auto') { + tu.statusmsg = ''; + tu.statustype = ''; + tu.save(); + require('../util/siftstatuses')(client, message.author.id, true); + message.reply('Hey there! You asked me to clear your status when you send a message next, so I went ahead and did that for you.').then(m => {m.delete({timeout: 5000});}); + }}); + + if (message.guild && client.misc.cache.ar.has(message.guild.id) && client.misc.cache.ar.get(message.guild.id).includes(msg.trim()) && !(client.misc.cache.arIgnore.has(message.guild.id) && client.misc.cache.arIgnore.get(message.guild.id).includes(message.channel.id))) { + AR.findOne({gid: message.guild.id}).then(ar => { + if (ar && ar.triggers.length && ar.triggers.includes(msg.trim())) {return message.channel.send(ar.ars[ar.triggers.indexOf(msg.trim())]);} + }); + } + + if (message.guild && client.misc.cache.lxp.enabled.includes(message.guild.id)) { + LXP.findOne({gid: message.guild.id}).then(xp => { + if (!client.misc.cache.lxp.xp[message.guild.id]) {client.misc.cache.lxp.xp[message.guild.id] = {};} + if (!client.misc.cache.lxp.xp[message.guild.id][message.author.id]) {client.misc.cache.lxp.xp[message.guild.id][message.author.id] = { + xp: xp.xp[message.author.id] ? xp.xp[message.author.id][0] : 0, + level: xp.xp[message.author.id] ? xp.xp[message.author.id][1] : 1, + lastXP: new Date().getTime() - 60000 + };} + if (new Date().getTime() - client.misc.cache.lxp.xp[message.guild.id][message.author.id].lastXP > 60000) { + require('../util/lxp/gainxp')(client, message.member.id, message.channel); + } + }); + } + + + + try { + if (msg.startsWith(prefix) || msg.startsWith(`<@${client.user.id}>`) || msg.startsWith(`<@!${client.user.id}>`)) { + let command = client.commands.get(cmd) || client.commands.get(client.aliases.get(cmd)); + + if (command && command.name !== "blacklist") { + if (message.guild && client.misc.cache.bl.guild.includes(message.guild.id)) {return message.channel.send("Your server has been blacklisted from using my commands! Shame, tsk tsk");} + if (client.misc.cache.bl.user.includes(message.author.id)) {return message.channel.send("You've been blacklisted from using my commands! Now what'd ya do to deserve that??");} + } + + if (!command) {let trigger; for (trigger of client.responses.triggers) {if (await trigger[1](message, msg, args, cmd, prefix, mention, client)) {await client.responses.commands.get(trigger[0]).execute(message, msg, args, cmd, prefix, mention, client); break;}} return;} + message.channel.startTyping(); + await wait(800); + message.channel.stopTyping(); + if (command.meta && command.meta.guildOnly && !message.guild) {return message.channel.send("You must be in a server to use this command!");} + require('../util/oncommand')(message, msg, args, cmd, prefix, mention, client); + if (client.misc.loggers.cmds) {client.misc.loggers.cmds.send(`${chalk.gray("[CMDL]")} >> ${chalk.white("Command")} ${chalk.blue(command.name)} ${message.guild ? `|| ${chalk.blue("Guild ID: ")} ${chalk.blueBright(message.guild.id)}` : ''} || ${chalk.blue("User ID: ")} ${chalk.blueBright(message.author.id)}`);} + return command.execute(message, msg, args, cmd, prefix, mention, client); + } + let trigger; for (trigger of client.responses.triggers) {if (await trigger[1](message, msg, args, cmd, prefix, mention, client)) {await client.responses.commands.get(trigger[0]).execute(message, msg, args, cmd, prefix, mention, client); break;}} + } catch (e) { + let date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); + console.error(`\n${chalk.red('[ERROR]')} >> ${chalk.yellow(`At [${date}] | In ${message.guild ? message.guild.name : `a DM with ${message.author.username}`}\n`)}`, e); + } +}; \ No newline at end of file diff --git a/events/messageDelete.js b/events/messageDelete.js new file mode 100644 index 0000000..8655d2a --- /dev/null +++ b/events/messageDelete.js @@ -0,0 +1,27 @@ +const Discord = require('discord.js'); + +module.exports = async (client, message) => { + if (message.channel.type != "text") {return;}; + //if (!Object.keys(snipe.delete).includes(message.guild.id)) {snipe.delete[message.guild.id] = {};}; + //snipe.delete[message.guild.id][message.channel.id] = message; + + let ts = client.guildconfig.logs.has(message.guild.id) && client.guildconfig.logs.get(message.guild.id).has('mdelete') ? client.guildconfig.logs.get(message.guild.id).get('mdelete') : null; + if (ts) {if (message.guild.channels.cache.has(ts) && message.guild.channels.cache.get(ts).permissionsFor(client.user.id).has("SEND_MESSAGES")) { + let mde = new Discord.MessageEmbed() + .setTitle('Message Deleted') + .setDescription(`Sent by <@${message.author.id}> | In <#${message.channel.id}>`) + .setThumbnail(message.author.avatarURL({size: 1024})) + .setColor('ecff8f').setFooter("Luno", client.user.avatarURL()).setTimestamp(); + if (message.content && message.content.length) {mde.addField("Message", "`-> `" + message.content.toString());} + if (message.attachments.size) { + if (message.attachments.first().url.includes(".png") || message.attachments.first().url.includes(".jpg") || message.attachments.first().url.includes(".gif")) {/*console.log('e');*/ try {mde.setImage(message.attachments.first().url);} catch {}} + let av = Array.from(message.attachments.values()); + let as = ''; for (let a of av) { + as += `[Att. ${av.indexOf(a) + 1}](${a.url})`; + if (av.indexOf(a) + 1 < av.length) {as += ' | ';} + } + if (as.length) {mde.addField('Attachments', as);} + } + message.guild.channels.cache.get(ts).send(mde).catch(() => {}); + }} +} \ No newline at end of file diff --git a/events/messageReactionAdd.js b/events/messageReactionAdd.js new file mode 100644 index 0000000..e344b02 --- /dev/null +++ b/events/messageReactionAdd.js @@ -0,0 +1,41 @@ +const Discord = require("discord.js"); +const GuildData = require('../models/guild'); +const StarData = require('../models/starboard'); + +module.exports = async (client, reaction, user) => { + if (reaction.partial) {try {await reaction.fetch();} catch {return;}} + + if (reaction.emoji.name === "⭐") { + if (!reaction.message.guild) {return;} + let tg = await GuildData.findOne({gid: reaction.message.guild.id}); + if (!tg) {return;} + if (tg.starchannel.length && tg.starsenabled && reaction.message.guild.channels.cache.has(tg.starchannel) && reaction.message.guild.channels.cache.get(tg.starchannel).permissionsFor(client.user.id).has('SEND_MESSAGES')) { + if (reaction.message.channel.id === tg.starchannel) {return;} + let sd = await StarData.findOne({gid: reaction.message.guild.id}) ? await StarData.findOne({gid: reaction.message.guild.id}) : new StarData({gid: reaction.message.guild.id}); + + let starEmbed = new Discord.MessageEmbed() + .setTitle('Starred Message!') + .setDescription(`Sent by ${reaction.message.member.displayName} (<@${reaction.message.author.id}>) || Channel: ${reaction.message.channel.name} (<#${reaction.message.channel.id}>)\n[Jump to Message](${reaction.message.url})`) + .setThumbnail(reaction.message.author.avatarURL({size: 2048})) + .setColor('ebb931') + .setFooter("Luno", client.user.avatarURL()) + .setTimestamp(); + if (reaction.message.content.length) {starEmbed.addField("Message", reaction.message.content);} + starEmbed + .addField("Stars", `:star: ${reaction.count}`, true) + .addField(`${reaction.message.member.displayName.toLowerCase().endsWith('s') ? `${reaction.message.member.displayName}'` : `${reaction.message.member.displayName}'s`} StarBoard Count`, sd.starCount[reaction.message.author.id] ? sd.starCount[reaction.message.author.id] + 1 : 1, true); + if (reaction.message.attachments.size) {starEmbed.setImage(reaction.message.attachments.first().url);} + if (Object.keys(sd.stars).includes(reaction.message.id)) { + let starMessage = await reaction.message.guild.channels.cache.get(tg.starchannel).messages.fetch(sd.stars[reaction.message.id]); + if (starMessage) {await starMessage.edit(starEmbed);} + } else { + if (reaction.count < tg.starreq) {return;} + let starEmbedMessage = await reaction.message.guild.channels.cache.get(tg.starchannel).send(starEmbed); + sd.stars[reaction.message.id] = starEmbedMessage.id; + sd.starCount[reaction.message.author.id] = sd.starCount[reaction.message.author.id] ? sd.starCount[reaction.message.author.id] + 1 : 1; + sd.serverStarCount += 1; + sd.save(); + } + } + } +}; \ No newline at end of file diff --git a/events/messageUpdate.js b/events/messageUpdate.js new file mode 100644 index 0000000..48fc6d4 --- /dev/null +++ b/events/messageUpdate.js @@ -0,0 +1,22 @@ +const Discord = require('discord.js'); + +module.exports = async (client, oldM, newM) => { + if (oldM.channel.type != "text") {return;}; + if (oldM.author.bot) {return;} + if (oldM.deleted) {return;} + //if (!Object.keys(snipe.edit).includes(oldM.guild.id)) {snipe.edit[oldM.guild.id] = {};}; + //snipe.edit[oldM.guild.id][oldM.channel.id] = {old: oldM, cur: newM}; + + let ts = client.guildconfig.logs.has(oldM.guild.id) && client.guildconfig.logs.get(oldM.guild.id).has('medit') ? client.guildconfig.logs.get(oldM.guild.id).get('medit') : null; + if (ts) {if (oldM.guild.channels.cache.has(ts) && oldM.guild.channels.cache.get(ts).permissionsFor(client.user.id).has("SEND_oldMS")) { + let embed = new Discord.MessageEmbed() + .setTitle('Message Edited') + .setDescription(`Sent by <@${oldM.author.id}> | In <#${oldM.channel.id}> | [See Message](${oldM.url})`) + .setThumbnail(oldM.author.avatarURL({size: 1024})) + .addField("Old Message", "`-> `" + oldM.content.toString()) + .addField("New Message", "`-> `" + newM.content.toString()) + .setColor('8034eb').setFooter("Luno", client.user.avatarURL()).setTimestamp(); + if (newM.attachments.size && ['.png', '.jpg', '.gif'].includes(newM.attachments.first().url.slice(newM.attachments.first().url.length - 4, newM.attachments.first().url.length))) {embed.setImage(newM.attachments.first().url);} + oldM.guild.channels.cache.get(ts).send(embed).catch(() => {}); + }} +} \ No newline at end of file diff --git a/events/ready.js b/events/ready.js new file mode 100644 index 0000000..80a5d71 --- /dev/null +++ b/events/ready.js @@ -0,0 +1,98 @@ +const Discord = require('discord.js'); +const chalk = require('chalk'); +const moment = require('moment'); +const mongoose = require('mongoose'); +const ora = require('ora'); + +const GuildSettings = require('../models/guild'); +const BotDataSchema = require('../models/bot'); +const LogData = require('../models/log'); + +const siftStatuses = require('../util/siftstatuses'); +const localXPCacheClean = require('../util/lxp/cacheloop'); + +let prefix = 'l.'; + +module.exports = async client => { + const config = client.config; + + /*let db = mongoose.connection; + await db.guild.update({}, {"$set": {'prefix': ''}}, false, true);*/ + + console.log(`\n${chalk.green('[BOOT]')} >> [${moment().format('L LTS')}] -> ${chalk.greenBright("Connected to Discord")}.`); + let date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); + console.log(`\n${chalk.gray('[INFO]')} >> ${chalk.white(`Logged in at ${date}.`)}`); + console.log(`\n${chalk.gray('[INFO]')} >> ${chalk.white(`Logged in as ${client.user.username}!`)}`); + console.log(`${chalk.gray('[INFO]')} >> ${chalk.white(`Client ID: ${client.user.id}`)}`); + console.log(`${chalk.gray('[INFO]')} >> ${chalk.white(`Running on ${client.guilds.cache.size} servers!`)}`); + console.log(`${chalk.gray('[INFO]')} >> ${chalk.white(`Serving ${client.users.cache.size} users!`)}`); + + let responses = { + "PLAYING": [ + `with my darling`, 'RAIN: Shadow Lords', "with my waifu", "with the neko formula", + "with magic", "terrible anime games", "anime OSTs at max volume", + `${Math.ceil(Math.random() * 100)} days of trying to become a samurai`, + "with the sauce", "witch hats are >", "explosion magic is the best magic", + "with Kazuma's sanity" + ,`in ${client.guilds.cache.size} servers` + ], + "WATCHING": [ + `for ${client.commands.size} commands`, + "I'm not a bad slime, slurp!", "Lelouch rule the world!", + "a slime somehow start an empire", "a fox-maid get her tail fluffed", + "a raccoon-girl and some guy with a shield", "some chick with unusually red hair", + "Mob hit 100", "a really bad harem anime", "The Black Swordsman", + "The Misfit of Demon King Academy", "Akame ga Kill" + ,`over ${client.guilds.cache.size} servers` + ] + }; + const setR = () => { + let type = Object.keys(responses)[Math.floor(Math.random() * Object.keys(responses).length)]; + if (type === "PLAYING") {client.user.setActivity(responses[type][Math.floor(Math.random() * responses[type].length)] + " | " + prefix + "help");} + else {client.user.setActivity(responses[type][Math.floor(Math.random() * responses[type].length)] + " | " + prefix + "help", {type: type});} + } + setR(); + setInterval(setR, 14400000); + + const setPL = async () => {let tg; for (tg of Array.from(client.guilds.cache.values)) { + let tguild = await GuildSettings.findOne({gid: tg.id}); + if (tguild && tguild.prefix && tguild.prefix.length) {client.guildconfig.prefixes.set(tg.id, tguild.prefix);} + let tl = await LogData.findOne({gid: tg.id}); + if (tl) { + let keys = Object.keys(tl); + let k; for (k of keys) {if (typeof tl[k] === "string" && tl[k].length) { + if (!client.guildconfig.logs.has(tg.id)) {client.guildconfig.logs.set(tg.id, new Map());} + client.guildconfig.logs.get(tg.id).set(k, tl[k]); + }} + } + }}; + setPL(); + + siftStatuses(); + setInterval(() => {setPL(); siftStatuses(client, null);}, 120000); + + await require('../util/cache')(client); + + setInterval(() => localXPCacheClean(client), 150000); + + let botData = await BotDataSchema.findOne({finder: 'lel'}) + ? await BotDataSchema.findOne({finder: 'lel'}) + : new BotDataSchema({ + finder: 'lel', + commands: 0, + servers: 0, + servers_all: 0, + restarts: 0, + lastRestart: new Date(), + errors_all: 0, + }); + botData.restarts = botData.restarts + 1; + botData.lastRestart = new Date(); + + console.log(`${chalk.gray('\n[INFO]')} >> ${chalk.white(`This is restart #${botData.restarts}.`)}`); + + let cms = new Date().getTime(); + console.log(`${chalk.gray('\n[INFO]')} >> ${chalk.white(`Startup completed in ${cms - client.misc.startup.getTime()}ms (${cms - client.misc.startupNoConnect.getTime()}ms post-connect).`)}`); + + await botData.save(); +}; \ No newline at end of file diff --git a/handle/command.js b/handle/command.js new file mode 100644 index 0000000..395c987 --- /dev/null +++ b/handle/command.js @@ -0,0 +1,31 @@ +const Discord = require('discord.js'); +const fs = require('fs'); +const chalk = require('chalk'); +//const ora = require('ora'); + +module.exports = client => { + let commands = fs.readdirSync('./commands').filter(file => file.endsWith('.js')); + let dirSet = new Map(); + fs.readdirSync('./commands').filter(file => !file.includes('.')).forEach(dir => fs.readdirSync(`./commands/${dir}`).filter(file => file.endsWith('.js')).forEach(x => {commands.push(x); dirSet.set(x, dir)})); + + //console.log(''); + //let cora = ora(`${chalk.white("Loading commands into client.")} ${chalk.blue("[")}${chalk.blueBright("0")}${chalk.blue("/")}${chalk.blueBright(`${commands.length}`)}${chalk.blue("]")}`).start(); + //let num = 0; + commands.sort(); + console.log(`\n${chalk.gray('[BOOT]')} >> ${chalk.blue('Getting Commands...')}\n`); + for (let commandf of commands) { + //num++; + //cora.text = `${chalk.white("Loading commands into client.")} ${chalk.blue("[")}${chalk.blueBright(`${num}`)}${chalk.blue("/")}${chalk.blueBright(`${commands.length}`)}${chalk.blue("]")}`; + if (Object.keys(require.cache).includes(require.resolve(`../commands/${dirSet.has(commandf) ? `${dirSet.get(commandf)}/`: ''}${commandf}`))) {delete require.cache[require.resolve(`../commands/${dirSet.has(commandf) ? `${dirSet.get(commandf)}/`: ''}${commandf}`)];} + let command = require(`../commands/${dirSet.has(commandf) ? `${dirSet.get(commandf)}/`: ''}${commandf}`); + client.commands.set(command.name, command); + if (command.aliases) {command.aliases.forEach(a => client.aliases.set(a, command.name));} + console.log(`${chalk.gray('[LOAD]')} >> ${chalk.blueBright('Loaded Command')} ${chalk.white(command.name)} ${chalk.blueBright('with')} ${chalk.white(command.aliases && command.aliases.length ? command.aliases.length : 0)} ${chalk.blueBright('aliases')}`); + } + /*cora.stop(); cora.clear(); + console.log(`${chalk.gray('[BOOT]')} >> ${chalk.blue('Getting Commands...')}\n`); + Array.from(client.commands.values()).forEach(command => { + console.log(`${chalk.gray('[LOAD]')} >> ${chalk.blueBright('Loaded Command')} ${chalk.white(command.name)} ${chalk.blueBright('with')} ${chalk.white(command.aliases && command.aliases.length ? command.aliases.length : 0)} ${chalk.blueBright('aliases')}`); + });*/ + console.log(`\n${chalk.gray('[BOOT]')} >> ${chalk.blue('Loaded all Commands')}`); +}; \ No newline at end of file diff --git a/handle/event.js b/handle/event.js new file mode 100644 index 0000000..aeea4ce --- /dev/null +++ b/handle/event.js @@ -0,0 +1,17 @@ +const fs = require('fs'); +const moment = require('moment'); +const chalk = require('chalk'); + +module.exports = client => { + let eventFilter = fs.readdirSync('./events/').filter(x => x.endsWith('.js')); + console.log(`\n${chalk.gray('[BOOT]')} >> ${chalk.blue('Getting Events...')}\n`); + for (let file of eventFilter) { + let evtName = file.split('.')[0]; + if (Object.keys(require.cache).includes(require.resolve('../events/' + file))) {delete require.cache[require.resolve('../events/' + file)];} + let evt = require('../events/' + file); + client.removeAllListeners(evtName); + client.on(evtName, evt.bind(null, client)); + console.log(`${chalk.gray('[LOAD]')} >> ${chalk.blueBright('Loaded Event')} ${chalk.white(evtName)}`); + } + console.log(`\n${chalk.gray('[BOOT]')} >> ${chalk.blue('Loaded all Events')}`); +}; \ No newline at end of file diff --git a/handle/response.js b/handle/response.js new file mode 100644 index 0000000..a8af7d7 --- /dev/null +++ b/handle/response.js @@ -0,0 +1,16 @@ +const Discord = require('discord.js'); +const fs = require('fs'); +const chalk = require('chalk'); + +module.exports = client => { + var responses = fs.readdirSync('./responses').filter(file => file.endsWith('.js')); + console.log(`\n${chalk.gray('[BOOT]')} >> ${chalk.blue('Getting Responses...')}\n`); + for (let responsef of responses) { + if (Object.keys(require.cache).includes(require.resolve(`../responses/${responsef}`))) {delete require.cache[require.resolve(`../responses/${responsef}`)];} + var response = require(`../responses/${responsef}`); + client.responses.triggers.push([response.name, response.condition]); + client.responses.commands.set(response.name, response); + console.log(`${chalk.gray('[LOAD]')} >> ${chalk.blueBright('Loaded Response')} ${chalk.white(response.name)}`); + } + console.log(`\n${chalk.gray('[BOOT]')} >> ${chalk.blue('Loaded all Responses')}`); +}; \ No newline at end of file diff --git a/loggers/cmds.js b/loggers/cmds.js new file mode 100644 index 0000000..e7ef816 --- /dev/null +++ b/loggers/cmds.js @@ -0,0 +1,13 @@ +const ws = require('ws'); +const chalk = require('chalk'); + +const port = 1029; + +const wss = new ws.Server({port: port}); + +console.log(`\n${chalk.white("[BOOT]")} >> ${chalk.greenBright(`Initialized Websocket on`)} ${chalk.white(`port ${port}`)}\n`); + +wss.on('connection', (ws) => { + console.log(`\n${chalk.white("[SCKT]")} >> ${chalk.blue("Received new websocket connection")}\n`); + ws.on('message', (msg) => {console.log(msg);}); +}); \ No newline at end of file diff --git a/models/anime.js b/models/anime.js new file mode 100644 index 0000000..db0013c --- /dev/null +++ b/models/anime.js @@ -0,0 +1,27 @@ +const mongoose = require('mongoose'); + +const AniSchema = new mongoose.Schema({ + id: {type: String, unique: true}, + name: String, + japname: String, + plot: String, + publishers: [String], + studios: [String], + airStartDate: String, + airEndDate: String, + isComplete: Boolean, + seasons: Number, + episodes: Number, + genres: [String], + tags: [String], + characters: [String], + streamAt: [String], + watchers: {type: Number, default: 0}, + listed: {type: Number, default: 0}, + liked: {type: Number, default: 0}, + rating: {type: Number, default: 0}, + lastUpdate: String, + thumbnail: String +}); + +module.exports = mongoose.model('anime', AniSchema); \ No newline at end of file diff --git a/models/ar.js b/models/ar.js new file mode 100644 index 0000000..1d4659e --- /dev/null +++ b/models/ar.js @@ -0,0 +1,10 @@ +const mongoose = require('mongoose'); + +const AR = new mongoose.Schema({ + gid: {type: String, unique: true}, + ars: {type: [String], default: []}, + triggers: {type: [String], default: []}, + ignoreChs: {type: [String], default: []} +}); + +module.exports = mongoose.model('ar', AR); \ No newline at end of file diff --git a/models/bot.js b/models/bot.js new file mode 100644 index 0000000..0b6fc55 --- /dev/null +++ b/models/bot.js @@ -0,0 +1,14 @@ +const mongoose = require('mongoose'); + +const botDataSchema = new mongoose.Schema({ + finder: String, + commands: Number, + servers: Number, + servers_all: Number, + restarts: Number, + lastRestart: Date, + errors_all: Number, + totalErrors: Number +}); + +module.exports = mongoose.model("bot", botDataSchema); \ No newline at end of file diff --git a/models/guild.js b/models/guild.js new file mode 100644 index 0000000..d7b8cd6 --- /dev/null +++ b/models/guild.js @@ -0,0 +1,25 @@ +const mongoose = require('mongoose'); + +const guildSchema = new mongoose.Schema({ + gid: {type: String, unique: true}, + staffrole: {type: String, default: ''}, + vip: {type: Boolean, default: false}, + wch: {type: String, default: ''}, + lch: {type: String, default: ''}, + logch: {type: String, default: ''}, + chests: {type: Boolean, default: false}, + autoquote: {type: Boolean, default: true}, + welcomerole: {type: String, default: ''}, + joinrole: {type: String, default: ''}, + cooldowns: {type: Boolean, default: false}, + levelmessage: {type: String, default: '**{{u}}** has reached level **{{l}}**! :tada:'}, + levelmessages: {type: Boolean, default: false}, + prefix: {type: String, default: ''}, + nostatus: {type: Boolean, default: false}, + starchannel: {type: String, default: ''}, + starreq: {type: Number, default: 5}, + starsenabled: {type: Boolean, default: false}, + blacklisted: {type: Boolean, default: false} +}); + +module.exports = mongoose.model("guild", guildSchema); \ No newline at end of file diff --git a/models/levelroles.js b/models/levelroles.js new file mode 100644 index 0000000..e31f435 --- /dev/null +++ b/models/levelroles.js @@ -0,0 +1,8 @@ +const mongoose = require('mongoose'); + +const LR = new mongoose.Schema({ + gid: {type: String, unique: true}, + roles: {type: Object, default: {}} +}); + +module.exports = mongoose.model('levelroles', LR); \ No newline at end of file diff --git a/models/localxp.js b/models/localxp.js new file mode 100644 index 0000000..cd4b995 --- /dev/null +++ b/models/localxp.js @@ -0,0 +1,10 @@ +const mongoose = require('mongoose'); + +const lxp = new mongoose.Schema({ + gid: {type: String, unique: true}, + msg: {type: Boolean, default: true}, + xp: {type: Object, default: {}}, + lvch: {type: String, default: ''} +}); + +module.exports = mongoose.model('localxp', lxp); \ No newline at end of file diff --git a/models/log.js b/models/log.js new file mode 100644 index 0000000..9e6d247 --- /dev/null +++ b/models/log.js @@ -0,0 +1,30 @@ +const mongoose = require('mongoose'); + +const logSchema = new mongoose.Schema({ + gid: {type: String, unique: true}, + mdelete: {type: String, default: ''}, + medit: {type: String, default: ''}, + chnew: {type: String, default: ''}, + chedit: {type: String, default: ''}, + chdelete: {type: String, default: ''}, + vcjoin: {type: String, default: ''}, + vcleave: {type: String, default: ''}, + servervcmute: {type: String, default: ''}, + servervcdeafen: {type: String, default: ''}, + kick: {type: String, default: ''}, + ban: {type: String, default: ''}, + mute: {type: String, default: ''}, + warn: {type: String, default: ''}, + giverole: {type: String, default: ''}, + takerole: {type: String, default: ''}, + addrole: {type: String, default: ''}, + editrole: {type: String, default: ''}, + deleterole: {type: String, default: ''}, + serverjoin: {type: String, default: ''}, + serverleave: {type: String, default: ''}, + nickname: {type: String, default: ''}, + username: {type: String, default: ''}, + avatar: {type: String, default: ''} +}); + +module.exports = mongoose.model('log', logSchema); \ No newline at end of file diff --git a/models/mod.js b/models/mod.js new file mode 100644 index 0000000..9d07e15 --- /dev/null +++ b/models/mod.js @@ -0,0 +1,27 @@ +const mongoose = require('mongoose'); + +const ModModel = new mongoose.Schema({ + gid: {type: String, unique: true}, + cases: {type: [{ + members: [String], + punishment: String, + reason: String, + status: String, + moderators: [String], + notes: [String], + history: [String], + issued: Date + }], default: []}, + rules: {type: [{ + name: String, + description: String, + punishment: String, + automod: String + }], default: []}, + auto: {type: Object, default: {}}, + warnings: {type: Object, default: {}}, + maxWarnings: {type: Number, default: 0}, + onMaxWarn: {type: String, default: 'kick'} +}); + +module.exports = mongoose.model('mod', ModModel); \ No newline at end of file diff --git a/models/responses.js b/models/responses.js new file mode 100644 index 0000000..4f83bbf --- /dev/null +++ b/models/responses.js @@ -0,0 +1,9 @@ +const mongoose = require('mongoose'); + +const ResponseSchema = new mongoose.Schema({ + gid: {type: String, unique: true}, + responses: {type: Map, default: new Map()}, + bindings: {type: Map, default: new Map()} +}); + +module.exports = mongoose.model('responses', ResponseSchema); \ No newline at end of file diff --git a/models/saves.js b/models/saves.js new file mode 100644 index 0000000..a15d1dc --- /dev/null +++ b/models/saves.js @@ -0,0 +1,8 @@ +const mongoose = require('mongoose'); + +const SaveSchema = new mongoose.Schema({ + name: {type: String, unique: true}, + saves: {type: Map, default: new Map()} +}); + +module.exports = mongoose.model('saves', SaveSchema); \ No newline at end of file diff --git a/models/secretsanta.js b/models/secretsanta.js new file mode 100644 index 0000000..7f591cb --- /dev/null +++ b/models/secretsanta.js @@ -0,0 +1,17 @@ +const mongoose = require('mongoose'); + +const SS = new mongoose.Schema({ + ssid: {type: String, unique: true}, + owner: String, + start: String, + end: String, + anon: Boolean, + info: String, + notes: String, + members: [{name: String, id: String, info: String}], + started: Boolean, + spend: String, + assignments: [{name: String, assignedTo: String}] +}); + +module.exports = mongoose.model('ss', SS); \ No newline at end of file diff --git a/models/starboard.js b/models/starboard.js new file mode 100644 index 0000000..df758e5 --- /dev/null +++ b/models/starboard.js @@ -0,0 +1,10 @@ +const mongoose = require('mongoose'); + +const StarSchema = new mongoose.Schema({ + gid: {type: String, unique: true}, + stars: {type: Object, default: {}}, + starCount: {type: Object, default: {}}, + serverStarCount: {type: Number, default: 0} +}); + +module.exports = mongoose.model('stars', StarSchema); \ No newline at end of file diff --git a/models/status.js b/models/status.js new file mode 100644 index 0000000..d191bf0 --- /dev/null +++ b/models/status.js @@ -0,0 +1,9 @@ +const mongoose = require('mongoose'); + +const Statuses = new mongoose.Schema({ + uid: {type: String, unique: true}, + local: {type: Object, default: null}, + global: {type: Object, default: null} +}); + +module.exports = mongoose.model('customStatuses', Statuses); \ No newline at end of file diff --git a/models/statuses.js b/models/statuses.js new file mode 100644 index 0000000..e070c1a --- /dev/null +++ b/models/statuses.js @@ -0,0 +1,8 @@ +const mongoose = require('mongoose'); + +const StatusSchema = new mongoose.Schema({ + f: String, + statuses: [{id: String, clear: Date}] +}); + +module.exports = new mongoose.model('statuses', StatusSchema); \ No newline at end of file diff --git a/models/todo.js b/models/todo.js new file mode 100644 index 0000000..5738bfc --- /dev/null +++ b/models/todo.js @@ -0,0 +1,9 @@ +const mongoose = require('mongoose'); + +const td = new mongoose.Schema({ + uid: {type: String, unique: true}, + lists: {type: Object, default: {quick: []}}, + publicLists: {type: Object, default: {}} +}); + +module.exports = mongoose.model('todo', td); \ No newline at end of file diff --git a/models/user.js b/models/user.js new file mode 100644 index 0000000..9b6f51c --- /dev/null +++ b/models/user.js @@ -0,0 +1,23 @@ +const mongoose = require('mongoose'); + +const UserSchema = new mongoose.Schema({ + uid: {type: String, unique: true}, + commands: {type: Number, default: 0}, + statusmsg: {type: String, default: ''}, + statustype: {type: String, default: ''}, + statusclearmode: {type: String, default: 'auto'}, + statusclearat: {type: Date, default: null}, + statussetat: {type: Date, default: null}, + statusshowcleartime: {type: Boolean, default: true}, + statusshowsettime: {type: Boolean, default: true}, + support: {type: Boolean, default: false}, + staff: {type: Boolean, default: false}, + admin: {type: Boolean, default: false}, + developer: {type: Boolean, default: false}, + blacklisted: {type: Boolean, default: false}, + donator: {type: Boolean, default: false}, + bio: {type: String, default: ''}, + color: {type: String, default: ''} +}); + +module.exports = mongoose.model("user", UserSchema); \ No newline at end of file diff --git a/models/vscount.js b/models/vscount.js new file mode 100644 index 0000000..90ede8d --- /dev/null +++ b/models/vscount.js @@ -0,0 +1,10 @@ +const mongoose = require('mongoose'); + +const vscount = new mongoose.Schema({ + uid: String, + countOf: String, + total: ({type: Number, default: 0}), + against: ({type: Object, default: {}}) +}); + +module.exports = mongoose.model("vscount", vscount); \ No newline at end of file diff --git a/models/xp.js b/models/xp.js new file mode 100644 index 0000000..abbc38d --- /dev/null +++ b/models/xp.js @@ -0,0 +1,6 @@ +const mongoose = require('mongoose'); + +const XP = mongoose.Schema({ + uid: {type: String, unique: true}, + level: {type: Number} +}) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..154e2de --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2893 @@ +{ + "name": "Luno", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "version": "1.0.0", + "dependencies": { + "canvas": "^2.6.1", + "chalk": "^4.1.0", + "dblapi.js": "^2.4.1", + "discord.io": "https://github.com/woor/discord.io/tarball/gateway_v6", + "discord.js": "^12.2.0", + "manyitems": "^1.0.2", + "moment": "^2.28.0", + "moment-precise-range-plugin": "^1.3.0", + "mongoose": "^5.10.3", + "node-fetch": "^2.6.1", + "ora": "^5.3.0", + "swwrap": "^1.0.0", + "winston": "^3.3.3", + "ws": "^7.4.2" + }, + "engines": { + "node": "12.14.1" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", + "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@discordjs/collection": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", + "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" + }, + "node_modules/@discordjs/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/bson": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", + "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mongodb": { + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.9.tgz", + "integrity": "sha512-2XSGr/+IOKeFQ5tU9ATcIiIr7bpHqWyOXNGLOOhp0kg2NnfEvoKZF1SZ25j4zvJRqM2WeSUjfWSvymFJ3HBGJQ==", + "dependencies": { + "@types/bson": "*", + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "14.14.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", + "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "node_modules/are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bson": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.7.0.tgz", + "integrity": "sha512-pzCxtkHb+5su5MQjTtepMDlIOtaXo277x0C0u3nMOxtkhTyQ+h2yNKhlROAaDllWgRyePAUitC08sXw26Eb6aw==", + "hasInstallScript": true, + "dependencies": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.15.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/cjopus": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/cjopus/-/cjopus-0.0.4.tgz", + "integrity": "sha1-RvPPyWuD99eERt6LTP2ZS1iJqaA=" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.5.0.tgz", + "integrity": "sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "dependencies": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz", + "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "dependencies": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/dblapi.js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/dblapi.js/-/dblapi.js-2.4.1.tgz", + "integrity": "sha512-g+u1inF/qOLit5qPK4hBGk5pKL1vy09uBLV+nukkRMvw2S9D1PyiyO70n4fboUXOgbExPp6Sho/Y782OqQOUiQ==", + "deprecated": "Module is no longer maintained" + }, + "node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "node_modules/denque": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/discord.io": { + "version": "2.5.3", + "resolved": "https://github.com/woor/discord.io/tarball/gateway_v6", + "integrity": "sha512-bIK3ETS+kdW7BhFNcTzEY3xFZWjm/7nvLEmhrsKqelpqT+RHlDeU1ZGNGnOTtjbFM77GqDzt9s/BVQnIUh3JYA==", + "license": "MIT", + "dependencies": { + "cjopus": "^0.0.4", + "tweetnacl": "^0.14.0", + "ws": "^1.1.0" + } + }, + "node_modules/discord.io/node_modules/ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "dependencies": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } + }, + "node_modules/discord.js": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", + "integrity": "sha512-VwZkVaUAIOB9mKdca0I5MefPMTQJTNg0qdgi1huF3iwsFwJ0L5s/Y69AQe+iPmjuV6j9rtKoG0Ta0n9vgEIL6w==", + "dependencies": { + "@discordjs/collection": "^0.1.6", + "@discordjs/form-data": "^3.0.1", + "abort-controller": "^3.0.0", + "node-fetch": "^2.6.1", + "prism-media": "^1.2.2", + "setimmediate": "^1.0.5", + "tweetnacl": "^1.0.3", + "ws": "^7.3.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/discord.js/node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, + "node_modules/fecha": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", + "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "node_modules/fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dependencies": { + "minipass": "^2.6.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "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" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/kareem": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", + "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/logform": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", + "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", + "dependencies": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + } + }, + "node_modules/manyitems": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/manyitems/-/manyitems-1.1.5.tgz", + "integrity": "sha512-NQo+Us1La0cXeuEX3SrXqJ4ZiDuinERHgAZMCuAB4EJYWZsKXC2YTpGigAWsTGw1JYOcpfMMNNdP/2sqd4VQ1w==", + "dependencies": { + "typescript": "^3.9.7" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "node_modules/mime-db": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", + "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.29", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", + "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", + "dependencies": { + "mime-db": "1.46.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dependencies": { + "minipass": "^2.9.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-precise-range-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/moment-precise-range-plugin/-/moment-precise-range-plugin-1.3.0.tgz", + "integrity": "sha1-YKwHX9/RRon20QKvdR0XGoC0q2A=", + "peerDependencies": { + "moment": ">=2.9.0" + } + }, + "node_modules/mongodb": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.4.tgz", + "integrity": "sha512-Y+Ki9iXE9jI+n9bVtbTOOdK0B95d6wVGSucwtBkvQ+HIvVdTCfpVRp01FDC24uhC/Q2WXQ8Lpq3/zwtB5Op9Qw==", + "dependencies": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4" + }, + "optionalDependencies": { + "saslprep": "^1.0.0" + }, + "peerDependenciesMeta": { + "aws4": { + "optional": true + }, + "bson-ext": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "mongodb-extjson": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongoose": { + "version": "5.11.19", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.11.19.tgz", + "integrity": "sha512-+oMf4XVg+j7ygnALi7K5vWZfKvC9gs9jdN/6Y1GV5OUAc7KQWoa6hzFO7nSj5jMJlhHNvC6tcS2uU7BV5aH8Lg==", + "dependencies": { + "@types/mongodb": "^3.5.27", + "bson": "^1.1.4", + "kareem": "2.3.2", + "mongodb": "3.6.4", + "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" + }, + "engines": { + "node": ">=4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose-legacy-pluralize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", + "peerDependencies": { + "mongoose": "*" + } + }, + "node_modules/mpath": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz", + "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.4.tgz", + "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" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, + "node_modules/needle": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "deprecated": "Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future", + "dependencies": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, + "node_modules/npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "dependencies": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "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" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ora": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", + "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "dependencies": { + "bl": "^4.0.3", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/ora/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prism-media": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.7.tgz", + "integrity": "sha512-thS1z3L6BDmf724sqLC73bHGjSYArFTYHa7cqInyS3EdDNTHKgDCXy7l+IhRvlnX7aFNiUb8jJcC+R8ezxwgMA==", + "peerDependencies": { + "@discordjs/opus": "^0.4.0", + "ffmpeg-static": "^4.2.7 || ^3.0.0 || ^2.4.0", + "node-opus": "^0.3.3", + "opusscript": "^0.0.7" + }, + "peerDependenciesMeta": { + "@discordjs/opus": { + "optional": true + }, + "ffmpeg-static": { + "optional": true + }, + "node-opus": { + "optional": true + }, + "opusscript": { + "optional": true + } + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/regexp-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", + "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" + }, + "node_modules/require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "dependencies": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + } + }, + "node_modules/resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "node_modules/sift": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", + "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "engines": { + "node": "*" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/swwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/swwrap/-/swwrap-1.0.0.tgz", + "integrity": "sha512-Hjpa/2giqpQR7Few0RHZ09X7Y/tfvlyuY0dJTVxQ05ifX1SGrbYSo+C28CLJh8owkKxvCgnYmitng6t6UiQSag==", + "dependencies": { + "@types/node": "^14.14.20", + "node-fetch": "^2.6.1", + "typescript": "^3.9.7" + } + }, + "node_modules/tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dependencies": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "engines": { + "node": ">=4.5" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "node_modules/typescript": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/ultron": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/winston": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", + "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", + "dependencies": { + "@dabh/diagnostics": "^2.0.2", + "async": "^3.1.0", + "is-stream": "^2.0.0", + "logform": "^2.2.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston-transport": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", + "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", + "dependencies": { + "readable-stream": "^2.3.7", + "triple-beam": "^1.2.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/ws": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", + "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + }, + "dependencies": { + "@dabh/diagnostics": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", + "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "@discordjs/collection": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", + "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" + }, + "@discordjs/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "@types/bson": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", + "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", + "requires": { + "@types/node": "*" + } + }, + "@types/mongodb": { + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.9.tgz", + "integrity": "sha512-2XSGr/+IOKeFQ5tU9ATcIiIr7bpHqWyOXNGLOOhp0kg2NnfEvoKZF1SZ25j4zvJRqM2WeSUjfWSvymFJ3HBGJQ==", + "requires": { + "@types/bson": "*", + "@types/node": "*" + } + }, + "@types/node": { + "version": "14.14.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", + "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==" + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "bson": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.7.0.tgz", + "integrity": "sha512-pzCxtkHb+5su5MQjTtepMDlIOtaXo277x0C0u3nMOxtkhTyQ+h2yNKhlROAaDllWgRyePAUitC08sXw26Eb6aw==", + "requires": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.15.0", + "simple-get": "^3.0.3" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "cjopus": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/cjopus/-/cjopus-0.0.4.tgz", + "integrity": "sha1-RvPPyWuD99eERt6LTP2ZS1iJqaA=" + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.5.0.tgz", + "integrity": "sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ==" + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + }, + "dependencies": { + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "color-string": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz", + "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dblapi.js": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/dblapi.js/-/dblapi.js-2.4.1.tgz", + "integrity": "sha512-g+u1inF/qOLit5qPK4hBGk5pKL1vy09uBLV+nukkRMvw2S9D1PyiyO70n4fboUXOgbExPp6Sho/Y782OqQOUiQ==" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "requires": { + "mimic-response": "^2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "requires": { + "clone": "^1.0.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "denque": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "discord.io": { + "version": "https://github.com/woor/discord.io/tarball/gateway_v6", + "integrity": "sha512-bIK3ETS+kdW7BhFNcTzEY3xFZWjm/7nvLEmhrsKqelpqT+RHlDeU1ZGNGnOTtjbFM77GqDzt9s/BVQnIUh3JYA==", + "requires": { + "cjopus": "^0.0.4", + "tweetnacl": "^0.14.0", + "ws": "^1.1.0" + }, + "dependencies": { + "ws": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", + "requires": { + "options": ">=0.0.5", + "ultron": "1.0.x" + } + } + } + }, + "discord.js": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.1.tgz", + "integrity": "sha512-VwZkVaUAIOB9mKdca0I5MefPMTQJTNg0qdgi1huF3iwsFwJ0L5s/Y69AQe+iPmjuV6j9rtKoG0Ta0n9vgEIL6w==", + "requires": { + "@discordjs/collection": "^0.1.6", + "@discordjs/form-data": "^3.0.1", + "abort-controller": "^3.0.0", + "node-fetch": "^2.6.1", + "prism-media": "^1.2.2", + "setimmediate": "^1.0.5", + "tweetnacl": "^1.0.3", + "ws": "^7.3.1" + }, + "dependencies": { + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + } + } + }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, + "fecha": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", + "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" + }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "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" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "kareem": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", + "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" + }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "log-symbols": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", + "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "requires": { + "chalk": "^4.0.0" + } + }, + "logform": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", + "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + } + }, + "manyitems": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/manyitems/-/manyitems-1.1.5.tgz", + "integrity": "sha512-NQo+Us1La0cXeuEX3SrXqJ4ZiDuinERHgAZMCuAB4EJYWZsKXC2YTpGigAWsTGw1JYOcpfMMNNdP/2sqd4VQ1w==", + "requires": { + "typescript": "^3.9.7" + } + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "mime-db": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", + "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==" + }, + "mime-types": { + "version": "2.1.29", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", + "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", + "requires": { + "mime-db": "1.46.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "moment-precise-range-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/moment-precise-range-plugin/-/moment-precise-range-plugin-1.3.0.tgz", + "integrity": "sha1-YKwHX9/RRon20QKvdR0XGoC0q2A=", + "requires": {} + }, + "mongodb": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.4.tgz", + "integrity": "sha512-Y+Ki9iXE9jI+n9bVtbTOOdK0B95d6wVGSucwtBkvQ+HIvVdTCfpVRp01FDC24uhC/Q2WXQ8Lpq3/zwtB5Op9Qw==", + "requires": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, + "mongoose": { + "version": "5.11.19", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.11.19.tgz", + "integrity": "sha512-+oMf4XVg+j7ygnALi7K5vWZfKvC9gs9jdN/6Y1GV5OUAc7KQWoa6hzFO7nSj5jMJlhHNvC6tcS2uU7BV5aH8Lg==", + "requires": { + "@types/mongodb": "^3.5.27", + "bson": "^1.1.4", + "kareem": "2.3.2", + "mongodb": "3.6.4", + "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" + } + }, + "mongoose-legacy-pluralize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", + "requires": {} + }, + "mpath": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz", + "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==" + }, + "mquery": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.4.tgz", + "integrity": "sha512-uOLpp7iRX0BV1Uu6YpsqJ5b42LwYnmu0WeF/f8qgD/On3g0XDaQM6pfn0m6UxO6SM8DioZ9Bk6xxbWIGHm2zHg==", + "requires": { + "bluebird": "3.5.1", + "debug": "3.1.0", + "regexp-clone": "^1.0.0", + "safe-buffer": "5.1.2", + "sliced": "1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, + "needle": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, + "node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + } + }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "options": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", + "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" + }, + "ora": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", + "integrity": "sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==", + "requires": { + "bl": "^4.0.3", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "prism-media": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.7.tgz", + "integrity": "sha512-thS1z3L6BDmf724sqLC73bHGjSYArFTYHa7cqInyS3EdDNTHKgDCXy7l+IhRvlnX7aFNiUb8jJcC+R8ezxwgMA==", + "requires": {} + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "regexp-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", + "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" + }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + } + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "sift": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", + "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + } + }, + "sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "swwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/swwrap/-/swwrap-1.0.0.tgz", + "integrity": "sha512-Hjpa/2giqpQR7Few0RHZ09X7Y/tfvlyuY0dJTVxQ05ifX1SGrbYSo+C28CLJh8owkKxvCgnYmitng6t6UiQSag==", + "requires": { + "@types/node": "^14.14.20", + "node-fetch": "^2.6.1", + "typescript": "^3.9.7" + } + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "typescript": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==" + }, + "ultron": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", + "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "requires": { + "defaults": "^1.0.3" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "winston": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", + "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", + "requires": { + "@dabh/diagnostics": "^2.0.2", + "async": "^3.1.0", + "is-stream": "^2.0.0", + "logform": "^2.2.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "winston-transport": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", + "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", + "requires": { + "readable-stream": "^2.3.7", + "triple-beam": "^1.2.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "ws": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", + "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==", + "requires": {} + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a9d95bc --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "Luno", + "version": "1.0.0", + "description": "An anime-themed multipurpose bot", + "main": "bot.js", + "author": "LunoDev", + "dependencies": { + "canvas": "^2.6.1", + "chalk": "^4.1.0", + "dblapi.js": "^2.4.1", + "discord.io": "https://github.com/woor/discord.io/tarball/gateway_v6", + "discord.js": "^12.2.0", + "manyitems": "^1.0.2", + "moment": "^2.28.0", + "moment-precise-range-plugin": "^1.3.0", + "mongoose": "^5.10.3", + "node-fetch": "^2.6.1", + "ora": "^5.3.0", + "swwrap": "^1.0.0", + "winston": "^3.3.3", + "ws": "^7.4.2" + }, + "engines": { + "node": "12.14.1" + } +} diff --git a/pull.bat b/pull.bat new file mode 100644 index 0000000..724e698 --- /dev/null +++ b/pull.bat @@ -0,0 +1 @@ +git pull origin master \ No newline at end of file diff --git a/responses/decide.js b/responses/decide.js new file mode 100644 index 0000000..8dbf4bd --- /dev/null +++ b/responses/decide.js @@ -0,0 +1,19 @@ +const Discord = require('discord.js'); + +module.exports = { + name: "decide", + help: new Discord.MessageEmbed() + .setTitle("Help -> ") + .setDescription("") + .addField("Syntax", "``"), + async condition (message, msg, args, cmd, prefix, mention, client) {return msg.split(/\s+/gm).length > 3 && (msg.startsWith(`<@${client.user.id}>`) || msg.startsWith(`<@!${client.user.id}>`));}, + async execute(message, msg, args, cmd, prefix, mention, client) { + let e = message.content.split(/\s+/g); e.shift(); + let options = e.join(" ").split(/(?:(?:\s+)[oO][rR] [sS][hH][oO][uU][lL][dD] [iI](?:\s+)|,(?:\s*)[oO][rR] [sS][hH][oO][uU][lL][dD] [iI](?:\s+)|(?:\s+)[oO][rR] [dD][oO] [iI](?:\s+)|,(?:\s*)[dD][oO] [iI](?:\s+)|,(?:\s*)[sS][hH][oO][uU][lL][dD] [iI](?:\s+)|,(?:\s*)[oO][rR] [dD][oO] [iI](?:\s+)|,(?:\s*)[oO][rR](?:\s+)|(?:\s+)[oO][rR](?:\s+)|,(?:\s*))/gm); + //console.log(e, options); + if (options.length < 2) {return;} + let cleanups = ['should i ', 'do i ']; + let option; for (option of options) {let c; for (c of cleanups) {if (option.trim().toLowerCase().startsWith(c)) {options[options.indexOf(option)] = option.trim().slice(c.length);}}} + return message.channel.send(`${['You should', 'How about', 'Hmmm... I pick', 'How about you', 'I think you should'][Math.floor(Math.random() * 5)]} ${options[Math.floor(Math.random() * options.length)]}.`); + } +}; \ No newline at end of file diff --git a/responses/wubzy.js b/responses/wubzy.js new file mode 100644 index 0000000..725c0cc --- /dev/null +++ b/responses/wubzy.js @@ -0,0 +1,98 @@ +const Discord = require('discord.js'); + +const UserData = require('../models/user'); + +const ask = require('../util/ask'); + +module.exports = { + name: "wubzy", + help: new Discord.MessageEmbed() + .setTitle("Help -> ") + .setDescription("") + .addField("Syntax", "``"), + async condition (message, msg, args, cmd, prefix, mention, client) {return message.author.id === "330547934951112705"}, + async execute(message, msg, args, cmd, prefix, mention, client) { + const inc = (m,s) => s ? s.toLowerCase().includes(m) : msg.includes(m); + const is = m => msg.trim() === m; + function incl(ml, s) {let tm; for (tm of ml) {if (inc(tm, s)) {return true;}}} + + if (incl(["thanks Luno", "thank you Luno", "ty Luno"])) { + const r = ["Anytime!", "Anything for my creator!", "I hope I was at least a little bit helpful!", + ":P Happy to help!", "You're welcome, Wubzy!", "Always happy to help you, Wubz", + "I do take tips :D"]; + return message.channel.send(r[Math.floor(Math.random() * r.length)]); + } + + if (is("❤")) { + let m = message.channel.messages.fetch({limit: 2}).then(mm => mm.first()); + console.log(m); + if (m.author.id === client.user.id) {return message.channel.send(":heart:");} + } + + if (incl(['gn Luno', 'goodnight Luno', 'night Luno'])) { + const r = ["Goodnight! :)", "Night Wubbo. Hope you weren't up too late working on me!", "Sleep well!", "Yeah, I was just headed to bed, too.", + "<:awoo:750131415693393950> glad you're getting some sleep ^^ ~"]; + message.channel.send(`${r[Math.floor(Math.random() * r.length)]} Want me to set your status before you go off?`); + let to = false; let sconf; + try {sconf = await message.channel.awaitMessages(m => m.author.id === "330547934951112705", {time: 15000, errors: ['time'], max: 1});} + catch {message.channel.send("Oh, I guess he already went to bed, huh? I'll just... set his status anyways-"); to = true;} + if (sconf) {sconf = sconf.first().content.trim().toLowerCase();} + if (to || incl(['ye', 'mhm', 'sure'], sconf)) { + let w = await UserData.findOne({uid: message.author.id}); + w.statusclearmode = 'manual'; + w.statusmsg = "Sleeping "; + w.statussetat = new Date(); + let tempDate = new Date(); + w.statusclearat = tempDate.setHours(tempDate.getHours() + 12); + w.statustype = 'dnd'; + w.save(); + if (!to) {message.channel.send("I set your status for you so you can get some sleep! Message me when you're up - I get lonely when you sleep ;-;");} + return; + } else {return message.channel.send("Oh... well, goodnight! Let me know when you're up, this castle gets so lonely when you're asleep");} + } + + if (inc('anime') && incl(['we binging', 'am i watching', 'should i watch', 'to watch', 'we watching', "am i gonna watch", "are we gonna watch"]) && inc("Luno")) { + async function q() { + let sconf; + const r1 = ["Hmm, you could", "You could always", "How about you", "You can", "Tough question. Maybe", "Glad you asked! You should", "Oh I know the perfect one:"]; + const r2 = ["rebinge Arifureta", "watch ~~tits the anime~~ DxD :wink:", "do Fairy Tail", "sob to Violet Evergarden again", "see Tokyo Ghoul for the millionth time", "watch ReZero", "finally start Attack on Titan Season 4", + "watch quintessential quintuplets", "have your heart wrenched out by Senko-San again", "finish Bleach", "finish Akame ga Kill", "start Guren Lagann", "finish Darling in the Franxx again", + "watch Akashic Records again", "finish Rent-a-Girlfriend", "rebinge Kabaneri and question reality", "rewatch Code Geass", "do a thing called getting the hell off anime and doing something productive with your life!"]; + message.channel.send(`${r1[Math.floor(Math.random() * r1.length)]} ${r2[Math.floor(Math.random() * r2.length)]}`); + try {sconf = await message.channel.awaitMessages(m => m.author.id === "330547934951112705", {time: 15000, errors: ['time'], max: 1});} + catch {message.channel.send("Oh, I guess he liked the idea that much and just left..."); return 2;} + sconf = sconf.first().content.trim().toLowerCase(); + if (incl(["bet", "i like", "yes", "sure", "fine", "alright", "ok"], sconf)) { + const r3 = ["Glad you liked the idea!", "Smart move!", "Hehe I'm good at reccomendations, aren't I?", "Yay I love it when Wubby likes my suggestions"]; + message.channel.send(`${r3[Math.floor(Math.random() * r3.length)]} Want me to set your status?`); + return 1; + } else { + const r3 = ["Oh, my bad.", "Sorry bout that. Lemme try again", "Bad idea? Yeah I didn't think it was that good of an idea anyways.", "Uhhhh- pfff I was just joking anyways"]; + message.channel.send(r3[Math.floor(Math.random() * r3.length)]); + return await q(); + } + } + let res = await q(); + if (res === 2) {return;} + let to = false; let sconf; + try {sconf = await message.channel.awaitMessages(m => m.author.id === "330547934951112705", {time: 15000, errors: ['time'], max: 1});} + catch {message.channel.send("Guess my recommendations are just that good that he left... Don't mind me while I set his status anyways"); to = true;} + if (sconf) {sconf = sconf.first().content.trim().toLowerCase();} + if (to || incl(['ye', 'mhm', 'sure'], sconf)) { + let w = await UserData.findOne({uid: message.author.id}); + w.statusclearmode = 'manual'; + w.statusmsg = "Watching anime (that I personally recommended him because I'm cool so leave poor Wubzy be!)"; + w.statussetat = new Date(); + let tempDate = new Date(); + w.statusclearat = tempDate.setHours(tempDate.getHours() + 12); + w.statustype = 'dnd'; + w.save(); + if (!to) {message.channel.send("I set your status for you so you can get some binging in! Let me know if your anime is any good, I've been looking for a good watch myself.");} + return; + } else {return message.channel.send("Watching anime in incognito are we? I gotcha :p");} + } + if (['hey Luno', 'hey Luno?', 'yo Luno'].includes(msg.trim())) { + let r = ["What's up?", "Hm?", "How can I help?", "Heya ^^", "Hey there Wubbo", "What's up Wubzy?", "Soup", "hehe sup"]; + } + } +}; \ No newline at end of file diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..96d8894 --- /dev/null +++ b/run.bat @@ -0,0 +1,4 @@ +title Luno +node bot.js +title !!CRASH Luno +pause \ No newline at end of file diff --git a/sync.bat b/sync.bat new file mode 100644 index 0000000..9bb23d6 --- /dev/null +++ b/sync.bat @@ -0,0 +1,10 @@ +@echo off +title Put quotes around your damn commit message +set /p msg="Enter Commit Message: " +title pulling... +git pull origin master +git add . +title commit... +git commit -m %msg% +title pushing... +git push origin master \ No newline at end of file diff --git a/template.js b/template.js new file mode 100644 index 0000000..74112d2 --- /dev/null +++ b/template.js @@ -0,0 +1,38 @@ +const Discord = require('discord.js'); + +module.exports = { + name: "", + aliases: [], + meta: { + category: '', + description: "", + syntax: '` <>`', + extra: null + }, + help: new Discord.MessageEmbed() + .setTitle("Help -> ") + .setDescription("") + .addField("Syntax", "``"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}\``);} + } +}; + +//OR + +const Discord = require('discord.js'); + +module.exports = { + name: "", + aliases: [], + meta: { + category: '', + description: "", + syntax: '` <>`', + extra: null + }, + help: "", + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}\``);} + } +}; \ No newline at end of file diff --git a/util/ask.js b/util/ask.js new file mode 100644 index 0000000..7418954 --- /dev/null +++ b/util/ask.js @@ -0,0 +1,12 @@ +module.exports = async (message, toAsk, time, nf) => { + let msg = await message.channel.send(toAsk); + let filter = nf ? () => true : m => m.author.id === message.author.id; + try { + let collected = await msg.channel.awaitMessages(filter, {max: 1, errors: ['time'], time: time}); + collected = collected.first().content; + return collected; + } catch { + message.reply("This question has timed out! Please try again."); + return null; + } +}; \ No newline at end of file diff --git a/util/cache.js b/util/cache.js new file mode 100644 index 0000000..19c959c --- /dev/null +++ b/util/cache.js @@ -0,0 +1,26 @@ +const ora = require('ora'); +const chalk = require('chalk'); + +module.exports = async (client) => { + console.log(''); + let ora_arCache = ora("Caching ARs...").start(); + await require('./cache/ar')(client); + ora_arCache.stop(); ora_arCache.clear(); + console.log(`${chalk.gray('[PROC]')} >> ${chalk.blueBright(`Cached`)} ${chalk.white(`${client.misc.cache.ar.size}`)} ${chalk.blueBright(`guilds with auto responses.`)}`); + + let ora_blCache = ora("Caching Blacklists...").start(); + await require('./cache/bl')(client); + ora_blCache.stop(); ora_blCache.clear(); + console.log(`${chalk.gray('[PROC]')} >> ${chalk.blueBright(`Cached`)} ${chalk.white(`${client.misc.cache.bl.guild.length}`)} ${chalk.blueBright(`guild blacklists`)}`); + console.log(`${chalk.gray('[PROC]')} >> ${chalk.blueBright(`Cached`)} ${chalk.white(`${client.misc.cache.bl.user.length}`)} ${chalk.blueBright(`user blacklists`)}`); + + let ora_lxpCache = ora("Caching Local XPs...").start(); + await require('./cache/lxp')(client); + ora_lxpCache.stop(); ora_lxpCache.clear(); + console.log(`${chalk.gray('[PROC]')} >> ${chalk.blueBright(`Cached`)} ${chalk.white(`${client.misc.cache.lxp.enabled.length}`)} ${chalk.blueBright(`guilds with XP enabled.`)}`); + + let ora_lrCache = ora("Caching Level Roles...").start(); + await require('./cache/lr')(client); + ora_lrCache.stop(); ora_lrCache.clear(); + console.log(`${chalk.gray('[PROC]')} >> ${chalk.blueBright(`Cached`)} ${chalk.white(`${client.misc.cache.lxp.hasLevelRoles.length}`)} ${chalk.blueBright(`guilds with Level Roles enabled.`)}`); +}; \ No newline at end of file diff --git a/util/cache/ar.js b/util/cache/ar.js new file mode 100644 index 0000000..f096a75 --- /dev/null +++ b/util/cache/ar.js @@ -0,0 +1,11 @@ +const AR = require('../../models/ar'); + +module.exports = async client => { + client.misc.cache.ar = new Map(); + client.misc.cache.arIgnore = new Map(); + + for await (const ar of AR.find()) { + client.misc.cache.ar.set(ar.gid, ar.triggers); + if (ar.ignoreChs.length) {client.misc.cache.arIgnore.set(ar.gid, ar.ignoreChs);} + } +}; \ No newline at end of file diff --git a/util/cache/bl.js b/util/cache/bl.js new file mode 100644 index 0000000..a74d16f --- /dev/null +++ b/util/cache/bl.js @@ -0,0 +1,17 @@ +const GuildData = require('../../models/guild'); +const UserData = require('../../models/user'); + +module.exports = async (client) => { + client.misc.cache.bl = { + guild: [], + user: [] + }; + + for await (const guild of GuildData.find()) { + if (guild.blacklisted) {client.misc.cache.bl.guild.push(guild.gid);} + } + + for await (const user of UserData.find()) { + if (user.blackisted) {client.misc.cache.bl.user.push(user.uid);} + } +}; \ No newline at end of file diff --git a/util/cache/lr.js b/util/cache/lr.js new file mode 100644 index 0000000..1b0bb7b --- /dev/null +++ b/util/cache/lr.js @@ -0,0 +1,9 @@ +const LR = require('../../models/levelroles'); + +module.exports = async client => { + client.misc.cache.lxp.hasLevelRoles = []; + + for await (const lr of LR.find()) { + client.misc.cache.lxp.hasLevelRoles.push(lr.gid); + } +}; \ No newline at end of file diff --git a/util/cache/lxp.js b/util/cache/lxp.js new file mode 100644 index 0000000..3c9e1b9 --- /dev/null +++ b/util/cache/lxp.js @@ -0,0 +1,9 @@ +const LXP = require('../../models/localxp'); + +module.exports = async client => { + client.misc.cache.lxp.enabled = []; + + for await (const xp of LXP.find()) { + client.misc.cache.lxp.enabled.push(xp.gid); + } +}; \ No newline at end of file diff --git a/util/cachestatus.js b/util/cachestatus.js new file mode 100644 index 0000000..da88db0 --- /dev/null +++ b/util/cachestatus.js @@ -0,0 +1,11 @@ +const StatusCache = require('../models/statuses'); + +module.exports = async (id, time) => { + let statuses = await StatusCache.findOne({f: 'lol'}) || new StatusCache({f: 'lol', statuses: []}); + let exists = false; + let status; for (status of statuses.statuses) { + if (status.id === id) {statuses.statuses[statuses.statuses.indexOf(status)].clear = time; exists = true;} + } + if (!exists) {statuses.statuses.push({id: id, clear: time});} + return statuses.save(); +}; \ No newline at end of file diff --git a/util/lxp/cacheloop.js b/util/lxp/cacheloop.js new file mode 100644 index 0000000..a3f1ed2 --- /dev/null +++ b/util/lxp/cacheloop.js @@ -0,0 +1,19 @@ +const LXP = require('../../models/localxp'); + +module.exports = async (client) => { + let cd = new Date().getTime(); + await Object.keys(client.misc.cache.lxp.xp).forEach(gxp => { + LXP.findOne({gid: gxp}).then(xp => { + if (!xp) {return;} + Object.keys(client.misc.cache.lxp.xp[gxp]).forEach(user => { + xp.xp[user] = [client.misc.cache.lxp.xp[gxp][user].xp, client.misc.cache.lxp.xp[gxp][user].level]; + xp.markModified(`xp.${user}`); + if (cd - client.misc.cache.lxp.xp[gxp][user].lastXP > 600000) { + delete client.misc.cache.lxp.xp[gxp][user]; + if (!Object.keys(client.misc.cache.lxp.xp[gxp]).length) {delete client.misc.cache.lxp.xp[gxp];} + } + }); + xp.save(); + }) + }) +}; \ No newline at end of file diff --git a/util/lxp/gainxp.js b/util/lxp/gainxp.js new file mode 100644 index 0000000..6258030 --- /dev/null +++ b/util/lxp/gainxp.js @@ -0,0 +1,39 @@ +const LXP = require('../../models/localxp'); +const LR = require('../../models/levelroles'); + +module.exports = async (client, member, channel) => { + client.misc.cache.lxp.xp[channel.guild.id][member].lastXP = new Date().getTime(); + client.misc.cache.lxp.xp[channel.guild.id][member].xp += 10; + + let x = client.misc.cache.lxp.xp[channel.guild.id][member].level; + let max = Math.ceil(100 + (((x / 3) ** 2) * 2)); + + if (client.misc.cache.lxp.xp[channel.guild.id][member].xp > max) { + client.misc.cache.lxp.xp[channel.guild.id][member].xp -= max; + client.misc.cache.lxp.xp[channel.guild.id][member].level += 1; + + LXP.findOne({gid: channel.guild.id}).then(async xp => { + if (!xp || !xp.msg) {return;} + try { + let ch = xp.lvch.length ? channel.guild.channels.cache.get(xp.lvch) : channel; + if (ch.partial) {await ch.fetch().catch(() => {});} + if (ch && ch.permissionsFor(ch.guild.me.id).has('SEND_MESSAGES')) {ch.send(`<:awoo:560193779764559896> <@${member}> has reached **Level ${x + 1}**!`).catch((e) => {/*console.error(e)*/});} + if (client.misc.cache.lxp.hasLevelRoles.includes(channel.guild.id)) { + LR.findOne({gid: channel.guild.id}).then(async lr => { + if (!lr) {return;} + if (Object.keys(lr.roles).includes(`${client.misc.cache.lxp.xp[channel.guild.id][member].level}`)) { + try { + let role = channel.guild.roles.cache.get(`${lr.roles[client.misc.cache.lxp.xp[channel.guild.id][member].level]}`); + if (!role) {return;} + if (!channel.guild.me.permissions.has("MANAGE_ROLES")) {return;} + let m = channel.guild.members.cache.get(member); + if (!m) {return;} + m.roles.add(role).catch((e) => {/*console.error(e);*/}); + } catch (e) {/*console.error(e);*/} + } + }); + } + } catch (e) {/*console.error(e);*/} + }).catch((e) => {/*console.error(e);*/}) + } +}; \ No newline at end of file diff --git a/util/makeid.js b/util/makeid.js new file mode 100644 index 0000000..215eaa4 --- /dev/null +++ b/util/makeid.js @@ -0,0 +1,10 @@ +module.exports = (length) => { + let result = ''; + let characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; + let charactersLength = characters.length; + let i; + for (i = 0; i < length; i++ ) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} \ No newline at end of file diff --git a/util/mention.js b/util/mention.js new file mode 100644 index 0000000..959d2c6 --- /dev/null +++ b/util/mention.js @@ -0,0 +1,20 @@ +const Discord = require('discord.js'); +const moment = require('moment'); + +const UserData = require('../models/user'); +const GuildSettings = require('../models/guild'); + +module.exports = async(message, msg, args, cmd, prefix, mention, client) => { + let tu = await UserData.findOne({uid: mention.id}); + let tg = message.guild ? await GuildSettings.findOne({gid: message.guild.id}) : null; + if (tg && tg.nostatus) {return;} + if (client.misc.statusPings.has(message.guild.id) && client.misc.statusPings.get(message.guild.id).has(mention.id) && new Date().getTime() - client.misc.statusPings.get(message.guild.id).get(mention.id).getTime() < 300000) {return;} + if (tu && tu.statusmsg.length) { + if (!client.misc.statusPings.has(message.guild.id)) {client.misc.statusPings.set(message.guild.id, new Discord.Collection());} + client.misc.statusPings.get(message.guild.id).set(mention.id, new Date()); + let m = await message.channel.send(`That user ${tu.statustype === 'dnd' ? 'wishes not to be disturbed' : 'is AFK'}. Reason: ${tu.statusmsg}.${tu.statssetat ? ` \`(This status was set ${moment(tu.statussetat.getTime()).fromNow()})\`` : ''}`); + await require('../util/wait')(10000); + m.delete().catch((e) => {console.log(e);}); + //console.log(m); + } +}; \ No newline at end of file diff --git a/util/oncommand.js b/util/oncommand.js new file mode 100644 index 0000000..10569e0 --- /dev/null +++ b/util/oncommand.js @@ -0,0 +1,25 @@ +const Discord = require('discord.js'); +const mongoose = require('mongoose'); +const chalk = require('chalk'); +const UserData = require('../models/user'); + +module.exports = async (message, msg, args, cmd, prefix, mention, client) => { + /*const config = client.config; + try { + await mongoose.connect(`mongodb+srv://${config.database.user}:${config.database.password}@${config.database.cluster}.uqyvv.mongodb.net`, { + useFindAndModify: false, useNewUrlParser: true, dbName: 'valk', useUnifiedTopology: true + }); + } catch (e) { + let date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); + console.error(`\n${chalk.red('[ERROR]')} >> ${chalk.yellow(`At [${date}] | Occurred while trying to connect to Mongo Cluster`)}`, e); + }*/ + + let botData = await require('../models/bot').findOne({finder: 'lel'}); + botData.commands = botData.commands + 1; + botData.save(); + let tu = await UserData.findOne({uid: message.author.id}) + ? await UserData.findOne({uid: message.author.id}) + : new UserData({uid: message.author.id}); + tu.commands = tu.commands + 1; + tu.save(); +}; \ No newline at end of file diff --git a/util/pagination.d.ts b/util/pagination.d.ts new file mode 100644 index 0000000..dc8d0ec --- /dev/null +++ b/util/pagination.d.ts @@ -0,0 +1,36 @@ +import { TextChannel, Message, MessageEmbed, Client, ReactionCollector } from 'discord.js'; +export declare class Pagination { + channel: TextChannel; + message: Message; + pages: MessageEmbed[]; + originalMessage: Message; + currentPage: number; + client: Client; + loopPages: boolean; + controllers: ControllerData; + timeoutInterval: any; + constructor(channel: TextChannel, pages: MessageEmbed[], originalMessage: Message, client: Client, loopPages?: boolean, message?: Message); + setPage(page: number): Promise; + nextPage(): Promise; + prevPage(): Promise; + addPage(page: MessageEmbed): Pagination; + replacePage(index: number, page: MessageEmbed): Pagination; + setControllers(endTime: number, user?: 'any' | string, extraControls?: ExtraControls): Promise; + updateControllers(): Promise; + endControllers(): Promise; + start(options?: { + endTime?: number; + startPage?: number; + user?: 'any' | string; + }): Promise; + stop(): Promise; +} +interface ExtraControls { +} +interface ControllerData { + endTime: number; + enabled: boolean; + lastInteraction: Date; + collector: ReactionCollector; +} +export {}; diff --git a/util/pagination.js b/util/pagination.js new file mode 100644 index 0000000..04c7a2e --- /dev/null +++ b/util/pagination.js @@ -0,0 +1,134 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Pagination = void 0; +class Pagination { + constructor(channel, pages, originalMessage, client, loopPages, message) { + this.loopPages = true; + this.controllers = { enabled: false, endTime: null, collector: null, lastInteraction: null }; + this.channel = channel; + this.pages = pages; + this.originalMessage = message; + this.client = client; + this.currentPage = 0; + if (message) { + this.message = message; + } + if (loopPages) { + this.loopPages = loopPages; + } + } + ; + async setPage(page) { + if (this.pages.length < page + 1) { } + if (!this.message) { + let tempm = await this.channel.send("One moment...") + .catch(() => { this.originalMessage.reply("There seemed to be a problem doing that..."); return this; }); + if (tempm instanceof Pagination) { + return this; + } + else { + this.message = tempm; + } + } + await this.message.edit('', this.pages[page] + .setFooter(`Luno | Page ${page + 1} of ${this.pages.length}`, this.client.user.avatarURL()) + .setTimestamp()); + this.currentPage = page; + return this; + } + ; + async nextPage() { + await this.setPage(typeof this.currentPage === "number" + ? this.currentPage + 1 == this.pages.length + ? this.loopPages + ? 0 + : this.currentPage + : this.currentPage + 1 + : 0); + return this; + } + ; + async prevPage() { + await this.setPage(typeof this.currentPage === "number" + ? this.currentPage === 0 + ? this.loopPages + ? this.pages.length - 1 + : 0 + : this.currentPage - 1 + : this.pages.length - 1); + return this; + } + ; + addPage(page) { + this.pages.push(page); + return this; + } + ; + replacePage(index, page) { + if (index < 0) { + throw new RangeError("replacePage() param 'index' must be a value greater than 0"); + } + if (index > this.pages.length - 1) { + throw new RangeError("replacePage() param 'index' must be a value corresponding to an index that already exists in this instance's pages."); + } + this.pages[index] = page; + return this; + } + ; + async setControllers(endTime, user, extraControls) { + if (this.controllers.enabled) { + return; + } + await this.message.react('⬅'); + await this.message.react('➡'); + await this.message.react('⏹'); + let emoji = ['⬅', '➡', '⏹']; + let filter = user && user.toLowerCase().trim() !== 'any' + ? (r, u) => { return u.id === user.trim() && emoji.includes(r.emoji.name); } + : (r) => { return emoji.includes(r.emoji.name); }; + this.controllers.collector = this.message.createReactionCollector(filter, { time: 450000 }); + this.controllers.collector.on('collect', async (r) => { + let functions = { + '⬅': () => { return this.prevPage(); }, + '➡': () => { return this.nextPage(); }, + '⏹': () => { return this.endControllers(); } + }; + this.controllers.lastInteraction = new Date(); + return functions[r.emoji.name](); + }); + this.controllers.enabled = true; + this.controllers.endTime = endTime; + this.controllers.lastInteraction = new Date(); + this.timeoutInterval = setInterval(() => { + if (new Date().getTime() - this.controllers.lastInteraction.getTime() > this.controllers.endTime && this.controllers.enabled) { + return this.endControllers(); + } + }, this.controllers.endTime); + return this; + } + ; + async updateControllers() { return this; } + ; + async endControllers() { + await this.message.reactions.removeAll(); + this.controllers.collector.stop(); + let fe = this.message.embeds[0]; + fe.setDescription(`${fe.description}\n\n*This menu has ended, start a new one to interact with it!*`); + fe.setFooter(`${fe.footer.text} | Menu ended`, this.client.user.avatarURL()); + await this.message.edit(fe); + clearInterval(this.timeoutInterval); + return this; + } + ; + async start(options) { + await this.setPage(options && options.startPage ? options.startPage : 0); + await this.setControllers(options && options.endTime ? options.endTime : 60, options && options.user ? options.user : 'any'); + return this; + } + ; + async stop() { + return await this.endControllers(); + } + ; +} +exports.Pagination = Pagination; diff --git a/util/response/filterresponse.js b/util/response/filterresponse.js new file mode 100644 index 0000000..3813574 --- /dev/null +++ b/util/response/filterresponse.js @@ -0,0 +1,11 @@ +module.exports = async (member, client, text) => { + text = text + .replace(/(?:{{member}}|{{m}})/gm, member.displayName) + .replace(/(?:{{membercount}}|{{mc}})/gm, `${member.guild.members.cache.size}`) + .replace(/(?:{{owner}}|{{o}})/gm, member.guild.owner.displayName) + .replace(/(?:{{ping}}|{{mp}}|{{memberping}}|{{p}})/gm, `<@${member.id}>`) + .replace(/(?:{{s}}|{{server}}|{{servername}}|{{sn}})/gm, member.guild.name) + .replace(/{{n}}/gm, '\n') + .replace(/{{nn}}/gm, '\n\n'); + return text; +}; \ No newline at end of file diff --git a/util/response/getresponse.js b/util/response/getresponse.js new file mode 100644 index 0000000..b23f427 --- /dev/null +++ b/util/response/getresponse.js @@ -0,0 +1,9 @@ +const Responses = require('../../models/responses'); + +module.exports = async (message, name) => { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr) {message.reply("This server does not have any responses saved!"); return null;} + if (!tr.responses.has(name.toLowerCase())) {message.reply("I don't have that response saved here."); return null;} + if (message.guild.me.permissions.has("DELETE_MESSAGES")) {message.delete();} + return tr.responses.get(name.toLowerCase()); +}; \ No newline at end of file diff --git a/util/response/parseresponse.js b/util/response/parseresponse.js new file mode 100644 index 0000000..011b76b --- /dev/null +++ b/util/response/parseresponse.js @@ -0,0 +1,63 @@ +const {Tag} = require('../tag'); +const {TagFilter} = require('../tagfilter'); + +module.exports = async (message, client, args) => { + let options = new TagFilter([ + new Tag(['em', '-embed'], 'embed', 'toggle'), + new Tag(['-msg', 'message'], 'message', 'toggle'), + + new Tag(['name', 'n'], 'name', 'append'), + new Tag(['ch', 'channel'], 'channel', 'append'), + + new Tag(['text', 'txt'], 'text', 'append'), + + new Tag(['title', 't'], 'title', 'append'), + new Tag(['description', 'desc', 'd'], 'description', 'append'), + new Tag(['fieldname', 'fn', 'newfield', 'nf'], 'fieldnames', 'listAppend'), + new Tag(['fieldtext', 'ft', 'fieldcontent', 'fc'], 'fieldtexts', 'listAppend'), + new Tag(['image', 'i'], 'image', 'append'), + new Tag(['thumbnail', 'thumb', 'th'], 'thumbnail', 'append'), + new Tag(['servericonthumbnail', 'serverthumbnail', 'sit', 'st'], 'guildthumb', 'toggle'), + new Tag(['servericonimage', 'serverimage', 'sii', 'si'], 'guildimage', 'toggle'), + new Tag(['color', 'colour', 'col', 'c'], 'color', 'append'), + ]).test(args.join(" ")); + + if (options.fieldnames && options.fieldnames.length) { + if (!options.fieldtexts || !options.fieldtexts.length || options.fieldnames.length !== options.fieldtexts.length) { + message.reply("You must have the same amount of field names as you do field texts."); return null; + } + } + if (options.embed) { + if (options.text && options.text.length && (options.text.includes(`@everyone`) || options.text.includes('@here')) && !message.member.permissions.has("MENTION_EVERYONE")) {message.reply("You don't have permissions to mention everyone!"); return null;} + if (options.fieldnames && options.fieldnames.length > 10) {message.reply("You can't have more than 10 fields!"); return null;} + if (options.color && options.color.length && (![3, 6].includes(options.color.length))) {message.reply("Your color must be a hex value 3 or 6 digits long."); return null;} + if (options.title && options.title.length > 65) {message.reply("Your title should be less than 65 characters, please :)"); return null;} + if (options.description && options.description.length > 750) {message.reply("Your description should be less than 750 characters."); return null;} + if ((!options.title || !options.title.length) || (!options.description || !options.description.length)) {message.reply("You need have a title and a description!"); return null;} + if (options.image && options.image.length > 300) {message.reply("Your image URL is a bit too long. Try shortening the URL or hosting it somewhere like imgur."); return null;} + if (options.thumbnail && options.image.thumbnail > 300) {message.reply("Your thumbnail URL is a bit too long. Try shortening the URL or hosting it somewhere like imgur."); return null;} + if (options.fieldnames) { + let fn; let ft; + for (fn of options.fieldnames) { + if (fn.length > 65) {message.reply("One of your field names is longer than 65 characters. Please shorten it!"); return null;} + } for (ft of options.fieldtexts) { + if (ft.length > 500) {message.reply("One of your field texts is longer than 500 characters. Please shorten it!"); return null;} + } + } + if (options.guildthumb) {options.thumbnail = message.guild.iconURL({size: 2048});} + if (options.guildimage) {options.image = message.guild.iconURL({size: 2048});} + } else if (options.message) { + if (options.text && options.text.length > 750) {message.reply("Please keep your message text under 750 characters!"); return null;} + if (!options.text || !options.text.length) {return message.reply("You must specify -text for your message.");} + } else {message.reply("You must specify either '-message' or '-embed' for the format of your response."); return null;} + + if (options.channel && options.channel.length) {if (!options.channel.match(/^<#(?:\d+)>$/) && !message.guild.channels.cache.has(options.channel.slice(options.channel.search(/\d/), options.channel.search(">")))) {message.reply("You must use a valid channel in this server."); return null;}} + + if (options.name && options.name.length) { + options.name = options.name.toLowerCase(); + if (options.name.length > 10) {message.reply("The option name must be less than 10 characters."); return null;} + if (!options.name.match(/^[a-z0-9-_]+$/)) {message.reply("You can only use a-z, numbers, hyphens, and underscores."); return null;} + } + + return options; +}; \ No newline at end of file diff --git a/util/response/saveresponse.js b/util/response/saveresponse.js new file mode 100644 index 0000000..f983666 --- /dev/null +++ b/util/response/saveresponse.js @@ -0,0 +1,15 @@ +const Responses = require('../../models/responses'); + +module.exports = async (options, message) => { + try { + if (!options) {return null;} + if (!options.name || !options.name.length) {message.reply("You need to have a name in order to save a response."); return null;} + let sr = await Responses.findOne({gid: message.guild.id}) ? await Responses.findOne({gid: message.guild.id}) : new Responses({gid: message.guild.id}); + if (sr.responses.has(options.name)) {message.reply("You already have a response with that name. Use `edit` instead."); return null;} + sr.responses.set(options.name, options); + sr.save(); + message.channel.send("Response added!"); + } catch {message.reply("There seems to have been an error in saving your response. If this persists, please contact the developers or join the support sever."); return null;} + + return options; +}; \ No newline at end of file diff --git a/util/response/sendresponse.js b/util/response/sendresponse.js new file mode 100644 index 0000000..74ec7e1 --- /dev/null +++ b/util/response/sendresponse.js @@ -0,0 +1,22 @@ +const Discord = require('discord.js'); + +const filterResponse = require('./filterresponse'); + +module.exports = async(member, channel, mode, client, options) => { + if (!options) {return;} + if (options.channel && options.channel.length) {channel = channel.guild.channels.cache.get(options.channel.slice(options.channel.search(/\d/), options.channel.search('>')));} + try { + if (options.embed) { + var responseEmbed = new Discord.MessageEmbed().setTitle(options.title).setDescription(await filterResponse(member, client, options.description)); + if (options.fieldnames && options.fieldnames.length) {let i; for (i=0;i status.clear.getTime() || forcePass) { + if (lookFor && status.id !== lookFor) {continue;} + let tu = await UserData.findOne({uid: status.id}); + if (tu) { + tu.statusmsg = ''; + tu.statustype = ''; + tu.save(); + let u = await client.users.fetch(status.id); + if (u && !forceClear) {u.send("Heya! Your status has been set for 12 hours, so I've cleared it for you.").catch(() => {});} + } + } else {ns.push(status);} + } + statusesm.statuses = ns; + return statusesm.save(); +}; \ No newline at end of file diff --git a/util/tag.d.ts b/util/tag.d.ts new file mode 100644 index 0000000..a112d4b --- /dev/null +++ b/util/tag.d.ts @@ -0,0 +1,9 @@ +export declare class Tag { + triggers: string[]; + tagName: string; + filterType: TagFilterType; + constructor(triggers: string[], tagName: string, filterType: TagFilterType); + addTrigger(trigger: string): Tag; +} +declare type TagFilterType = "append" | "toggle" | "listAppend"; +export {}; diff --git a/util/tag.js b/util/tag.js new file mode 100644 index 0000000..64df85f --- /dev/null +++ b/util/tag.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Tag = void 0; +class Tag { + constructor(triggers, tagName, filterType) { + this.triggers = []; + let trigger; + for (trigger of triggers) { + this.triggers.push(trigger.trim().startsWith("-") ? trigger.trim() : `-${trigger.trim()}`); + } + this.tagName = tagName; + this.filterType = filterType; + } + ; + addTrigger(trigger) { + this.triggers.push(trigger.trim().startsWith("-") ? trigger.trim() : `-${trigger.trim()}`); + return this; + } + ; +} +exports.Tag = Tag; diff --git a/util/tagfilter.d.ts b/util/tagfilter.d.ts new file mode 100644 index 0000000..8146e00 --- /dev/null +++ b/util/tagfilter.d.ts @@ -0,0 +1,10 @@ +import { Tag } from "./tag"; +export declare class TagFilter { + tags: Tag[]; + triggers: Map; + filterTypes: Map; + constructor(tags: Tag[]); + test(text: string): object; +} +declare type TagFilterType = "append" | "toggle" | "listAppend"; +export {}; diff --git a/util/tagfilter.js b/util/tagfilter.js new file mode 100644 index 0000000..53b2c83 --- /dev/null +++ b/util/tagfilter.js @@ -0,0 +1,75 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TagFilter = void 0; +class TagFilter { + constructor(tags) { + this.tags = tags; + this.triggers = new Map(); + this.filterTypes = new Map(); + let tag; + for (tag of this.tags) { + let trigger; + for (trigger of tag.triggers) { + this.triggers.set(trigger, tag.tagName); + } + if (!this.filterTypes.has(tag.tagName)) { + this.filterTypes.set(tag.tagName, tag.filterType); + } + } + } + test(text) { + var filtered = {}; + var reading = null; + var filterType; + var ticks = {}; + let words = text.trim().split(/\s+/g); + let word; + for (word of words) { + if (word.startsWith('-') && word.length > 1 && this.triggers.has(word.trim())) { + filterType = this.filterTypes.get(this.triggers.get(word.trim())); + reading = !['append', 'listAppend'].includes(filterType) ? null : word.trim(); + if (!reading) { + filtered[`${this.triggers.get(word.trim())}`] = true; + } + else { + filtered[`${this.triggers.get(reading)}`] = filterType == 'append' ? '' : Array.isArray(filtered[`${this.triggers.get(reading)}`]) ? filtered[`${this.triggers.get(reading)}`] : []; + } + if (filterType == "listAppend") { + if (ticks[`${this.triggers.get(word.trim())}`] && ticks[`${this.triggers.get(word.trim())}`].length) { + filtered[`${this.triggers.get(word.trim())}`].push(ticks[`${this.triggers.get(word.trim())}`]); + } + ticks[`${this.triggers.get(word.trim())}`] = ''; + } + } + else if (reading) { + if (filterType == "listAppend") { + ticks[`${this.triggers.get(reading)}`] += ` ${word}`; + } + else { + filtered[`${this.triggers.get(reading)}`] = `${filtered[`${this.triggers.get(reading)}`]} ${word}`; + } + } + } + let tick; + for (tick of Object.keys(ticks)) { + if (ticks[tick].length) { + filtered[tick].push(ticks[tick]); + } + } + let key; + for (key of Object.keys(filtered)) { + if (typeof filtered[key] == 'string') { + filtered[key] = filtered[key].trim(); + } + else if (Array.isArray(filtered[key])) { + let subkey; + for (subkey of filtered[key]) { + filtered[key][filtered[key].indexOf(subkey)] = subkey.trim(); + } + } + } + return filtered; + } + ; +} +exports.TagFilter = TagFilter; diff --git a/util/ts/pagination.ts b/util/ts/pagination.ts new file mode 100644 index 0000000..935780d --- /dev/null +++ b/util/ts/pagination.ts @@ -0,0 +1,162 @@ +import {TextChannel, Message, MessageEmbed, Client, MessageReaction, ReactionCollector} from 'discord.js'; + +export class Pagination { + channel: TextChannel; + message: Message; + pages: MessageEmbed[]; + originalMessage: Message; + currentPage: number; + client: Client; + loopPages: boolean = true; + controllers: ControllerData = {enabled: false, endTime: null, collector: null, lastInteraction: null}; + timeoutInterval: any; + + + + constructor (channel: TextChannel, pages: MessageEmbed[], originalMessage: Message, client: Client, loopPages?: boolean, message?: Message) { + this.channel = channel; + this.pages = pages; + this.originalMessage = message; + this.client = client; + this.currentPage = 0; + + if (message) {this.message = message;} + if (loopPages) {this.loopPages = loopPages;} + }; + + + + public async setPage(page: number): Promise { + if (this.pages.length < page + 1) {} + + if (!this.message) { + let tempm = await this.channel.send("One moment...") + .catch(() => {this.originalMessage.reply("There seemed to be a problem doing that..."); return this;}); + if (tempm instanceof Pagination) {return this;} + else {this.message = tempm;} + } + + await this.message.edit('', this.pages[page] + .setFooter(`Luno | Page ${page + 1} of ${this.pages.length}`, this.client.user.avatarURL()) + .setTimestamp() + ); + this.currentPage = page; + + return this; + }; + + public async nextPage(): Promise { + await this.setPage(typeof this.currentPage === "number" + ? this.currentPage + 1 == this.pages.length + ? this.loopPages + ? 0 + : this.currentPage + : this.currentPage + 1 + : 0 + ); + return this; + }; + + public async prevPage(): Promise { + await this.setPage(typeof this.currentPage === "number" + ? this.currentPage === 0 + ? this.loopPages + ? this.pages.length - 1 + : 0 + : this.currentPage - 1 + : this.pages.length - 1 + ); + return this; + }; + + public addPage(page: MessageEmbed): Pagination { + this.pages.push(page); + return this; + }; + + public replacePage(index: number, page: MessageEmbed): Pagination { + if (index < 0) {throw new RangeError("replacePage() param 'index' must be a value greater than 0");} + if (index > this.pages.length - 1) {throw new RangeError("replacePage() param 'index' must be a value corresponding to an index that already exists in this instance's pages.");} + + this.pages[index] = page; + return this; + }; + + + public async setControllers(endTime: number, user?: 'any' | string, extraControls?: ExtraControls): Promise { + if (this.controllers.enabled) {return;} + + await this.message.react('⬅'); + await this.message.react('➡'); + await this.message.react('⏹'); + + let emoji = ['⬅', '➡', '⏹']; + let filter = user && user.toLowerCase().trim() !== 'any' + ? (r: MessageReaction, u) => {return u.id === user.trim() && emoji.includes(r.emoji.name);} + : (r: MessageReaction) => {return emoji.includes(r.emoji.name);}; + + this.controllers.collector = this.message.createReactionCollector(filter, {time: 450000}); + + this.controllers.collector.on('collect', async (r: MessageReaction) => { + let functions = { + '⬅': () => {return this.prevPage();}, + '➡': () => {return this.nextPage();}, + '⏹': () => {return this.endControllers();} + } + this.controllers.lastInteraction = new Date(); + return functions[r.emoji.name](); + }); + + this.controllers.enabled = true; + this.controllers.endTime = endTime; + this.controllers.lastInteraction = new Date(); + + this.timeoutInterval = setInterval(() => { + if (new Date().getTime() - this.controllers.lastInteraction.getTime() > this.controllers.endTime && this.controllers.enabled) {return this.endControllers();} + }, this.controllers.endTime); + + return this; + }; + + public async updateControllers(): Promise {return this;}; + + public async endControllers(): Promise { + await this.message.reactions.removeAll(); + this.controllers.collector.stop(); + + let fe = this.message.embeds[0]; + fe.setDescription(`${fe.description}\n\n*This menu has ended, start a new one to interact with it!*`); + fe.setFooter(`${fe.footer.text} | Menu ended`, this.client.user.avatarURL()); + await this.message.edit(fe); + + clearInterval(this.timeoutInterval); + + return this; + }; + + + public async start(options?: {endTime?: number, startPage?: number, user?: 'any' | string}): Promise { + await this.setPage(options && options.startPage ? options.startPage : 0); + await this.setControllers(options && options.endTime ? options.endTime : 60, options && options.user ? options.user : 'any'); + + return this; + }; + + public async stop(): Promise { + return await this.endControllers(); + }; + +} + + + +interface ExtraControls { + +} + +interface ControllerData { + endTime: number, + enabled: boolean, + lastInteraction: Date, + collector: ReactionCollector +} \ No newline at end of file diff --git a/util/ts/tag.ts b/util/ts/tag.ts new file mode 100644 index 0000000..e57fa7e --- /dev/null +++ b/util/ts/tag.ts @@ -0,0 +1,25 @@ +export class Tag { + triggers: string[] = []; + tagName: string; + filterType: TagFilterType; + + constructor(triggers: string[], tagName: string, filterType: TagFilterType) { + let trigger: string; for (trigger of triggers) { + this.triggers.push( + trigger.trim().startsWith("-") ? trigger.trim() : `-${trigger.trim()}` + ); + } + + this.tagName = tagName; + this.filterType = filterType; + }; + + public addTrigger(trigger: string): Tag { + this.triggers.push( + trigger.trim().startsWith("-") ? trigger.trim() : `-${trigger.trim()}` + ); + return this; + }; +} + +type TagFilterType = "append" | "toggle" | "listAppend"; \ No newline at end of file diff --git a/util/ts/tagfilter.ts b/util/ts/tagfilter.ts new file mode 100644 index 0000000..828c027 --- /dev/null +++ b/util/ts/tagfilter.ts @@ -0,0 +1,62 @@ +import {Tag} from "./tag"; + +export class TagFilter { + tags: Tag[]; + triggers: Map; + filterTypes: Map; + + constructor(tags: Tag[]) { + this.tags = tags; + this.triggers = new Map(); + this.filterTypes = new Map(); + let tag: Tag; + for (tag of this.tags) { + let trigger: string; for (trigger of tag.triggers) { + this.triggers.set(trigger, tag.tagName); + } + if (!this.filterTypes.has(tag.tagName)) {this.filterTypes.set(tag.tagName, tag.filterType);} + } + } + + public test(text: string): object { + var filtered: object = {}; + var reading: string = null; + var filterType: TagFilterType; + var ticks = {}; + + let words = text.trim().split(/\s+/g); + let word: string; for (word of words) { + if (word.startsWith('-') && word.length > 1 && this.triggers.has(word.trim())) { + filterType = this.filterTypes.get(this.triggers.get(word.trim())); + reading = !['append', 'listAppend'].includes(filterType) ? null : word.trim(); + if (!reading) {filtered[`${this.triggers.get(word.trim())}`] = true;} + else {filtered[`${this.triggers.get(reading)}`] = filterType == 'append' ? '' : Array.isArray(filtered[`${this.triggers.get(reading)}`]) ? filtered[`${this.triggers.get(reading)}`] : [];} + if (filterType == "listAppend") { + if (ticks[`${this.triggers.get(word.trim())}`] && ticks[`${this.triggers.get(word.trim())}`].length) {filtered[`${this.triggers.get(word.trim())}`].push(ticks[`${this.triggers.get(word.trim())}`]);} + ticks[`${this.triggers.get(word.trim())}`] = ''; + } + } + else if (reading) { + if (filterType == "listAppend") {ticks[`${this.triggers.get(reading)}`] += ` ${word}`;} + else {filtered[`${this.triggers.get(reading)}`] = `${filtered[`${this.triggers.get(reading)}`]} ${word}`;} + } + } + + let tick: string; for (tick of Object.keys(ticks)) { + if (ticks[tick].length) {filtered[tick].push(ticks[tick]);} + } + + let key: string; for (key of Object.keys(filtered)) { + if (typeof filtered[key] == 'string') {filtered[key] = filtered[key].trim();} + else if (Array.isArray(filtered[key])) { + let subkey: string; for (subkey of filtered[key]) { + filtered[key][filtered[key].indexOf(subkey)] = subkey.trim(); + } + } + } + + return filtered; + }; +} + +type TagFilterType = "append" | "toggle" | "listAppend"; \ No newline at end of file diff --git a/util/ts/tsconfig.json b/util/ts/tsconfig.json new file mode 100644 index 0000000..4b2de3c --- /dev/null +++ b/util/ts/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": ["es2017", "es6", "dom"], + "target": "es2017", + "declaration": true, + "outDir": "../" + }, + "include": ["**/*"], + "exclude": [] +} \ No newline at end of file diff --git a/util/wait.js b/util/wait.js new file mode 100644 index 0000000..587c4ca --- /dev/null +++ b/util/wait.js @@ -0,0 +1,7 @@ +module.exports = (time) => { + return new Promise(function (resolve) { + setTimeout(function () { + resolve("anything"); + }, time); + }); +}; \ No newline at end of file