From 2939850377e9d25313fd0f552875bd15d7f0485e Mon Sep 17 00:00:00 2001 From: LBlend Date: Thu, 18 Mar 2021 03:16:16 +0100 Subject: [PATCH] init commit --- .gitignore | 14 + LICENSE | 674 ++++++++++++++++++++++++ README.md | 5 + requirements.txt | 5 + src/assets/fonts/RobotoMono-Medium.ttf | Bin 0 -> 108940 bytes src/assets/misc/heart.png | Bin 0 -> 8040 bytes src/cogs/botinfo.py | 142 +++++ src/cogs/dev_tools.py | 359 +++++++++++++ src/cogs/errors.py | 107 ++++ src/cogs/fun.py | 45 ++ src/cogs/funreplies.py | 20 + src/cogs/info.py | 703 +++++++++++++++++++++++++ src/cogs/misc.py | 196 +++++++ src/cogs/utils/discord_utils.py | 18 + src/cogs/utils/embed_templates.py | 53 ++ src/cogs/utils/misc_utils.py | 33 ++ src/config/config.yaml.example | 19 + src/run.py | 73 +++ 18 files changed, 2466 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 requirements.txt create mode 100644 src/assets/fonts/RobotoMono-Medium.ttf create mode 100644 src/assets/misc/heart.png create mode 100644 src/cogs/botinfo.py create mode 100644 src/cogs/dev_tools.py create mode 100644 src/cogs/errors.py create mode 100644 src/cogs/fun.py create mode 100644 src/cogs/funreplies.py create mode 100644 src/cogs/info.py create mode 100644 src/cogs/misc.py create mode 100644 src/cogs/utils/discord_utils.py create mode 100644 src/cogs/utils/embed_templates.py create mode 100644 src/cogs/utils/misc_utils.py create mode 100644 src/config/config.yaml.example create mode 100644 src/run.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfe343b --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.yaml +*.pyc +*.log +__pycache__/ +.vscode/ +.idea/ +venv/ +lib64 +pyvenv.cfg +lib/ +bin/ +include/ + +src/assets/temp/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e72bfdd --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff2f805 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# UiO Gaming Discord Bot + +UiO Gaming Discord Bot er en chattebot for ulike formål. Den skal hjelpe oss med ulike oppgaver og ha mange ulike funksjoner + +Mer dokumentasjon kommer snart™ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5bf945e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +discord.py +pyyaml +requests +asyncio +psutil \ No newline at end of file diff --git a/src/assets/fonts/RobotoMono-Medium.ttf b/src/assets/fonts/RobotoMono-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f40bc588197a470b61b9e528bfa57c481c55e6fb GIT binary patch literal 108940 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 str: + """ + Returns the current uptime of the bot in string format + + Parameters + ---------- + + Returns + ---------- + (str) The bot's uptime in human readable format + """ + now = time() + diff = int(now - self.bot.uptime) + days, remainder = divmod(diff, 24 * 60 * 60) + hours, remainder = divmod(remainder, 60 * 60) + minutes, seconds = divmod(remainder, 60) + + return f'{days}d, {hours}t, {minutes}m, {seconds}s' + + +def setup(bot): + bot.add_cog(BotInfo(bot)) diff --git a/src/cogs/dev_tools.py b/src/cogs/dev_tools.py new file mode 100644 index 0000000..5b807bd --- /dev/null +++ b/src/cogs/dev_tools.py @@ -0,0 +1,359 @@ +from discord.ext import commands +import discord + +import asyncio +from sys import exit +from os import listdir, system +import socket +from requests import get + +from cogs.utils import misc_utils, embed_templates + + +class DevTools(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command(aliases=['shutdown', 'stoppbått']) + async def stoppbot(self, ctx): + """ + Log out and shut down the bot + """ + + embed = discord.Embed(color=ctx.me.color, description='Logger av...') + await ctx.send(embed=embed) + await self.bot.logout() + exit('Bot stopped through command') + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command() + async def custommsg(self, ctx, channel: int, *text): + """ + Send a message to a specified channel + """ + + channel = self.bot.get_channel(channel) + custommessage = ' '.join(text) + await channel.send(custommessage) + + embed = discord.Embed(color=ctx.me.color) + embed.add_field(name='Sent', value=custommessage) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command(aliases=['listservers']) + async def listguilds(self, ctx, page=0): + """ + Lists all guilds the bot is a member of + """ + + guild_list = [f'{guild.name} - {guild.id}' for guild in self.bot.guilds] + + page_data = misc_utils.paginator(guild_list, int(page)) + page = page_data['page'] + pagecount = page_data['pagecount'] + guilds = '\n'.join(page_data['page_content']) + + embed = discord.Embed(color=ctx.me.color) + embed.add_field(name='Guilds', value=guilds) + embed.set_footer(text=f'page: {page}/{pagecount}') + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command() + async def listusers(self, ctx, page=0): + """ + Lists all users the bot has access to + """ + + user_list = [f'{user.name}#{user.discriminator} - {user.id}' for user in self.bot.users] + page_data = misc_utils.paginator(user_list, int(page)) + + pagecount = page_data['pagecount'] + page = page_data['page'] + users = '\n'.join(page_data['page_content']) + + embed = discord.Embed(color=ctx.me.color) + embed.add_field(name='Users', value=users) + embed.set_footer(text=f'page: {page}/{pagecount}') + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command(alises=['unloadcog', 'disablecog']) + async def unload(self, ctx, cog: str): + """ + Disables a specified cog + """ + + for file in listdir('./src/cogs'): + if file.endswith('.py'): + name = file[:-3] + if name == cog: + self.bot.unload_extension(f'cogs.{name}') + embed = discord.Embed(color=ctx.me.color, description=f'{cog} has been disabled') + return await ctx.send(embed=embed) + + embed = embed_templates.error_fatal(ctx, text=f'{cog} does not exist') + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command(aliases=['loadcog', 'enablecog']) + async def load(self, ctx, cog): + """ + Enables a speicifed cog + """ + + for file in listdir('./src/cogs'): + if file.endswith('.py'): + name = file[:-3] + if name == cog: + self.bot.load_extension(f'cogs.{name}') + embed = discord.Embed(color=ctx.me.color, description=f'{cog} loaded') + return await ctx.send(embed=embed) + + embed = embed_templates.error_fatal(ctx, text=f'{cog} does not exist') + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command(aliases=['reloadcog']) + async def reload(self, ctx, cog): + """ + Reloads a specified cog + """ + + for file in listdir('./src/cogs'): + if file.endswith('.py'): + name = file[:-3] + if name == cog: + self.bot.reload_extension(f'cogs.{name}') + embed = discord.Embed(color=ctx.me.color, description=f'{cog} has been reloaded') + return await ctx.send(embed=embed) + + embed = embed_templates.error_fatal(ctx, text=f'{cog} does not exist') + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command() + async def reloadcommand(self, ctx, cog): + """ + Reloads a specified cog + """ + + for file in listdir('./src/cogs'): + if file.endswith('.py'): + name = file[:-3] + if name == cog: + self.bot.reload_extension(f'cogs.{name}') + embed = discord.Embed(color=ctx.me.color, description=f'{cog} has been reloaded') + return await ctx.send(embed=embed) + + embed = embed_templates.error_fatal(ctx, text=f'{cog} does not exist') + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command(aliases=['reloadunloadedcogs']) + async def reloadunloaded(self, ctx): + """ + Reloads all cogs, including previously disabled ones + """ + + for file in listdir('./src/cogs'): + if file.endswith('.py'): + name = file[:-3] + self.bot.unload_extension(f'cogs.{name}') + + for file in listdir('./src/cogs'): + if file.endswith('.py'): + name = file[:-3] + self.bot.load_extension(f'cogs.{name}') + + embed = discord.Embed(color=ctx.me.color, description='Reloaded all cogs') + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command(aliases=['reloadallcogs']) + async def reloadall(self, ctx): + """ + Reloads all previously enabled cogs + """ + + for file in listdir('./src/cogs'): + if file.endswith('.py'): + name = file[:-3] + self.bot.reload_extension(f'cogs.{name}') + + embed = discord.Embed(color=ctx.me.color, description='Reloaded all previously enabled cogs') + await ctx.send(embed=embed) + + @commands.is_owner() + @commands.command() + async def cmd(self, ctx, *, command: str): + """ + Execute terminal commands + """ + + system(command) + await ctx.send('Done!') + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command() + async def localip(self, ctx): + """ + Sends LAN IP-address + """ + + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(('8.8.8.8', 80)) + embed = discord.Embed(color=ctx.me.color) + embed.add_field(name='LAN IP-address', value=s.getsockname()[0]) + await ctx.send(embed=embed) + s.close() + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command() + async def publicip(self, ctx): + """ + Sends WAN IP-address + inb4 I leak my IP-address + """ + + data = get('https://wtfismyip.com/json').json() + ip = data['YourFuckingIPAddress'] + location = data['YourFuckingLocation'] + isp = data['YourFuckingISP'] + + embed = discord.Embed(color=ctx.me.color) + embed.add_field(name='WAN IP-address', value=f'{ip}\n{location}\n{isp}') + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.cooldown(1, 10, commands.BucketType.guild) + @commands.command() + async def changepresence(self, ctx, activity_type, message, status_type): + """ + Changes presence status + """ + + activities = { + 'playing': 0, + 'listening': 2, + 'watching': 3 + } + if activity_type in activities: + activity_type = activities[activity_type] + else: + activity_type = 0 + + status_types = { + 'online': discord.Status.online, + 'dnd': discord.Status.dnd, + 'idle': discord.Status.idle, + 'offline': discord.Status.offline + } + if status_type in status_types: + status_types = status_types[status_type] + else: + status_type = status_types[status_type] + + await self.bot.change_presence( + status=status_type, + activity=discord.Activity(type=activity_type, name=message) + ) + embed = discord.Embed(color=ctx.me.color, description='Endret Presence!') + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True, add_reactions=True) + @commands.is_owner() + @commands.command() + async def leave(self, ctx, *guild_id: int): + """ + Leaves a specified guild + """ + + if guild_id == (): + guild_id = ctx.guild.id + else: + guild_id = guild_id + + try: + guild = await self.bot.fetch_guild(guild_id) + except discord.errors.Forbidden: + embed = embed_templates.error_fatal(ctx, text='Bot is not a member of this guild') + return await ctx.send(embed=embed) + + confirmation_msg = await ctx.send(f'Do you want to leave {guild.name} (`{guild.id}`)?') + await confirmation_msg.add_reaction('✅') + + def comfirm(reaction, user): + return user == ctx.author and str(reaction.emoji) == '✅' + + try: + reaction, user = await self.bot.wait_for('reaction_add', timeout=15.0, check=comfirm) + except asyncio.TimeoutError: + await ctx.message.delete() + await confirmation_msg.delete() + else: + await guild.leave() + try: + embed = discord.Embed(color=ctx.me.color, description='Guild left!') + await ctx.send(embed=embed) + except discord.errors.Forbidden: + pass + + @commands.bot_has_permissions(embed_links=True) + @commands.is_owner() + @commands.command() + async def resetcooldown(self, ctx, command: str): + """ + Resets the cooldown period for a specified command + """ + + try: + self.bot.get_command(command).reset_cooldown(ctx) + except AttributeError: + embed = embed_templates.error_fatal(ctx, text=f'{command} is not a command') + return await ctx.send(embed=embed) + + embed = discord.Embed(color=ctx.me.color, description='Cooldown reset!') + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True, external_emojis=True) + @commands.is_owner() + @commands.command() + async def allemoji(self, ctx): + """ + Sends all emoji the bot has access to + """ + + embed = discord.Embed(colour=ctx.me.color) + + emoji_string = '' + for guild in self.bot.guilds: + emoji_string += f'\n**{guild.name}**\n' + for emoji in guild.emojis: + if len(emoji_string) > 2000: + embed.description = emoji_string + await ctx.send(embed=embed) + emoji_string = f'\n**{guild.name}**\n' + emoji_string += f'{emoji} ' + + embed.description = emoji_string + await ctx.send(embed=embed) + + +def setup(bot): + bot.add_cog(DevTools(bot)) diff --git a/src/cogs/errors.py b/src/cogs/errors.py new file mode 100644 index 0000000..511c06a --- /dev/null +++ b/src/cogs/errors.py @@ -0,0 +1,107 @@ +from discord.ext import commands +import discord + +import traceback +import sys +from datetime import datetime + +from cogs.utils import embed_templates + + +class Errors(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.Cog.listener() + async def on_command(self, ctx): + """ + Prints command execution metadata + """ + + print( + f'{datetime.now().strftime("%Y-%m-%d %H:%M:%S")} | ' + + f'{"❌ " if ctx.command_failed else "✔ "} {ctx.command} - ' + + f'{ctx.author.name}#{ctx.author.discriminator} ({ctx.author.id}) | ' + + f'{ctx.guild.id}-{ctx.channel.id}-{ctx.message.id}' + ) + + @commands.Cog.listener() + async def on_command_error(self, ctx, error): + """ + Returns something based on the type of command error + """ + + # Reset cooldown if command throws an error + try: + self.bot.get_command(f'{ctx.command}').reset_cooldown(ctx) + except AttributeError: + pass + + # Ignore command's own error handling + if hasattr(ctx.command, 'on_error'): + return + + # Ignored errors + ignored = commands.CommandNotFound + error = getattr(error, 'original', error) + if isinstance(error, ignored): + return + + send_help = ( + commands.MissingRequiredArgument, + commands.TooManyArguments, + commands.BadArgument + ) + if isinstance(error, send_help): + self.bot.get_command(f'{ctx.command}').reset_cooldown(ctx) + return await ctx.send_help(ctx.command) + + elif isinstance(error, commands.BotMissingPermissions): + permissions = ', '.join(error.missing_perms) + embed = embed_templates.error_warning(ctx, text='Jeg mangler følgende tillatelser:\n\n' + + f'```\n{permissions}\n```') + return await ctx.send(embed=embed) + + elif isinstance(error, commands.MissingPermissions): + permissions = ', '.join(error.missing_perms) + embed = embed_templates.error_warning(ctx, text='Du mangler følgende tillatelser\n\n' + + f'```\n{permissions}\n```') + return await ctx.send(embed=embed) + + elif isinstance(error, commands.NotOwner): + embed = embed_templates.error_fatal(ctx, text='Bare boteieren kan gjøre dette') + return await ctx.send(embed=embed) + + elif isinstance(error, commands.CommandOnCooldown): + embed = embed_templates.error_warning(ctx, text='Kommandoen har nettopp blitt brukt' + + f'Prøv igjen om `{error.retry_after:.1f}` sekunder.') + return await ctx.send(embed=embed) + + elif isinstance(error, commands.NoPrivateMessage): + try: + embed = embed_templates.error_fatal(ctx, text='Denne kommandoen kan bare utføres i servere') + return await ctx.send(embed=embed) + except discord.errors.Forbidden: # Thrown if bot is blocked by the user or if the user has closed their DMs + print("DM Blocked!") + + elif isinstance(error, commands.DisabledCommand): + pass + + elif isinstance(error, commands.CheckFailure): + return + + embed = embed_templates.error_fatal(ctx, text='En ukjent feil oppstod!') + await ctx.send(embed=embed) + + # Print full exception to console + print(f'Ignoring exception in command {ctx.command}:', file=sys.stderr) + traceback.print_exception( + type(error), + error, + error.__traceback__, + file=sys.stderr + ) + + +def setup(bot): + bot.add_cog(Errors(bot)) diff --git a/src/cogs/fun.py b/src/cogs/fun.py new file mode 100644 index 0000000..2232f00 --- /dev/null +++ b/src/cogs/fun.py @@ -0,0 +1,45 @@ +from discord.ext import commands + + +class Fun(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command() + async def norskeuniversiteter(self, ctx): + await ctx.send('') + + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command() + async def informatikkuio(self, ctx): + await ctx.send('https://i.imgur.com/ypyK1mi.jpg') + + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command() + async def informatikkuio2(self, ctx): + await ctx.send('https://i.imgur.com/ZqgZEEA.jpg') + + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command() + async def informatikkuio3(self, ctx): + await ctx.send('https://i.imgur.com/Gx9DQE5.jpg') + + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command() + async def uio(self, ctx): + await ctx.send('https://i.imgur.com/188MoIV.jpg') + + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command() + async def ntnu(self, ctx): + await ctx.send('https://twitter.com/NTNU/status/970667413564993536') + + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command() + async def ntnu2(self, ctx): + await ctx.send('https://i.imgur.com/h84fknj.jpg') + + +def setup(bot): + bot.add_cog(Fun(bot)) diff --git a/src/cogs/funreplies.py b/src/cogs/funreplies.py new file mode 100644 index 0000000..59c5d37 --- /dev/null +++ b/src/cogs/funreplies.py @@ -0,0 +1,20 @@ +from discord.ext import commands +import discord + + +class FunReplies(commands.Cog): + def __init__(self, bot): + self.bot = bot + + + async def react(self, message): + if message.author.bot: + return + + if ' borgerlønn ' in message.content.lower(): + await message.channel.send('<@267415183931080715> DE SNAKKER OM BORGERLØNN') + + +def setup(bot): + bot.add_listener(FunReplies(bot).react, 'on_message') + bot.add_cog(FunReplies(bot)) diff --git a/src/cogs/info.py b/src/cogs/info.py new file mode 100644 index 0000000..48edcbc --- /dev/null +++ b/src/cogs/info.py @@ -0,0 +1,703 @@ +from discord.ext import commands +import discord + +from math import ceil +from operator import itemgetter +from os import remove + +from cogs.utils import embed_templates, misc_utils + + +class Info(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.bot_has_permissions(embed_links=True, external_emojis=True) + @commands.guild_only() + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['guildinfo', 'server', 'serverinfo', 'si', 'gi']) + async def guild(self, ctx): + """Viser info om guilden""" + + guild_created_date = ctx.guild.created_at.strftime('%d. %b. %Y - %H:%M') + since_created_days = (ctx.message.created_at - ctx.guild.created_at).days + + if since_created_days == 1: + since_created_days_string = 'dag' + else: + since_created_days_string = 'dager' + + total_members = ctx.guild.member_count + bot_members = 0 + online_members = 0 + idle_members = 0 + dnd_members = 0 + offline_members = 0 + for member in ctx.guild.members: + if member.bot: + bot_members += 1 + if str(member.status) == 'online': + online_members += 1 + elif str(member.status) == 'idle': + idle_members += 1 + elif str(member.status) == 'dnd': + dnd_members += 1 + elif str(member.status) == 'offline': + offline_members += 1 + + roles = [] + for role in ctx.guild.roles: + if role.name != '@everyone': + roles.append(role.name) + roles.reverse() + roles = ', '.join(roles) + if len(roles) > 1024: + roles = f'Skriv `{self.bot.prefix}guildroller` for å se rollene' + if roles == '': + roles = '**Ingen roller**' + + boosters = [] + premium_subscribers = sorted( + ctx.guild.premium_subscribers, key=lambda m: m.premium_since) + for booster in premium_subscribers: + boosters.append(f'{booster.name}#{booster.discriminator}') + boosters = ', '.join(boosters) + if len(boosters) > 1024: + boosters = f'Skriv `{self.bot.prefix}boosters` for å se boosters' + if boosters == '': + boosters = '**Ingen boosters**' + + text_channels = len(ctx.guild.text_channels) + voice_channels = len(ctx.guild.voice_channels) + categories = len(ctx.guild.categories) + total_channels = text_channels + voice_channels + + flags = { + 'us': ':flag_us:', + 'eu': ':flag_eu:', + 'singapore': ':flag_sg:', + 'london': ':flag_gb:', + 'sydney': ':flag_au:', + 'amsterdam': ':flag_nl:', + 'frankfurt': ':flag_de:', + 'brazil': ':flag_br:', + 'dubai': ':flag_ae:', + 'japan': ':flag_jp:', + 'russia': ':flag_ru:', + 'southafrica': ':flag_za:', + 'hongkong': ':flag_hk:', + 'india': ':flag_in:' + } + region = str(ctx.guild.region) + if region.startswith('us'): + region = 'us' + elif region.startswith('eu'): + region = 'eu' + elif region.startswith('amsterdam'): + region = 'amsterdam' + try: + flag = flags[region] + except KeyError: + flag = ':question:' + + region_names = { + 'eu-central': 'Sentral-Europa', + 'eu-west': 'Vest-Europa', + 'europe': 'Europa', + 'hongkong': 'Hong Kong', + 'russia': 'Russland', + 'southafrica': 'Sør-Afrika', + 'us-central': 'Midt-USA', + 'us-east': 'New Jersey', + 'us-south': 'Sør-USA', + 'us-west': 'California', + 'vip-amsterdam': 'Amsterdam (VIP)', + 'vip-us-east': 'Øst-USA (VIP)', + 'vip-us-west': 'Vest-USA (VIP)', + } + try: + region_name = region_names[str(ctx.guild.region)] + except KeyError: + region_name = str(ctx.guild.region).title() + + features_string = '' + if ctx.guild.features is not []: + features = { + 'VIP_REGIONS': 'VIP', + 'VANITY_URL': 'Egen URL', + 'INVITE_SPLASH': 'Invitasjonsbilde', + 'VERIFIED': 'Verifisert', + 'PARTNERED': 'Discord Partner', + 'MORE_EMOJI': 'Ekstra emoji', + 'DISCOVERABLE': 'Fremhevet', + 'FEATURABLE': 'Kan fremheves', + 'COMMUNITY': 'Sammfunsguild', + 'COMMERCE': 'Butikkanaler', + 'PUBLIC': 'Offentlig guild', + 'NEWS': 'Nyhetskanaler', + 'BANNER': 'Banner', + 'ANIMATED_ICON': 'Animert ikon', + 'PUBLIC_DISABLED': 'Ikke offentlig', + 'WELCOME_SCREEN_ENABLED': 'Velkomstvindu', + 'PREVIEW_ENABLED': 'Forhåndsvisning', + 'MEMBER_VERIFICATION_GATE_ENABLED': 'Medlemsverifisering' + } + for feature in ctx.guild.features: + features_string += f'{features[feature]}\n' + + photos = {} + if ctx.guild.splash_url: + photos['Invitasjonsbilde'] = ctx.guild.splash_url_as(format='png') + if ctx.guild.banner_url: + photos['Banner'] = ctx.guild.banner_url_as(format='png') + + verification_level = { + 'none': 'ingen', + 'low': 'e-post', + 'medium': 'e-post, registrert i 5 min', + 'high': 'e-post, registrert i 5 min, medlem i 10 min', + 'extreme': 'telefon' + } + verification = verification_level[str(ctx.guild.verification_level)] + + content_filter = { + 'disabled': 'nei', + 'no_role': 'for alle uten rolle', + 'all_members': 'ja' + } + content = content_filter[str(ctx.guild.explicit_content_filter)] + + embed = discord.Embed(color=ctx.me.color, description=f'**Verifiseringskrav:** {verification}\n' + + f'**Innholdsfilter:** {content}\n' + + f'**Boost Tier:** {ctx.guild.premium_tier}\n' + + f'**Emoji:** {len(ctx.guild.emojis)}') + embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon_url) + embed.set_thumbnail(url=ctx.guild.icon_url_as(static_format='png')) + embed.add_field(name='ID', value=ctx.guild.id) + embed.add_field(name='Eier', value=ctx.guild.owner.mention) + embed.add_field(name='Region', value=f'{flag} {region_name}') + embed.add_field(name='Opprettet', value=f'{guild_created_date}\n{since_created_days} ' + + f'{since_created_days_string} siden') + embed.add_field(name=f'Kanaler ({total_channels})', value=f'💬 Tekst: **{text_channels}**\n' + + f'🔊 Tale: **{voice_channels}**\n' + + f'🗃️ Kategorier: **{categories}**') + embed.add_field(name=f'Medlemmer ({total_members})', + value=f'👤 Mennesker: **{int(total_members) - int(bot_members)}**\n' + + f'🤖 Båtter: **{bot_members}**\n' + + f'{self.bot.emoji["online"]}{online_members} ' + + f'{self.bot.emoji["idle"]}{idle_members} ' + + f'{self.bot.emoji["dnd"]}{dnd_members} ' + + f'{self.bot.emoji["offline"]}{offline_members}') + embed.add_field(name=f'Roller ({len(ctx.guild.roles) - 1})', value=roles, inline=False) + if ctx.guild.premium_tier is not 0: + embed.add_field(name=f'Boosters ({ctx.guild.premium_subscription_count})', value=boosters, inline=False) + + if features_string != '': + embed.add_field(name='Tillegsfunksjoner', value=features_string) + + if photos != {}: + photos_string = '' + for key, value in photos.items(): + photos_string += f'[{key}]({value})\n' + embed.add_field(name='Bilder', value=photos_string) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['serverroller', 'guildroles', 'serverroles']) + async def guildroller(self, ctx): + """Viser rollene i en guild""" + + roles = [] + for role in ctx.guild.roles: + if role.name != '@everyone': + roles.append(role.name) + if roles is []: + roles = ['**Ingen Roller**'] + roles.reverse() + roles = ', '.join(roles) + + roles = roles.replace(', --', '\n--') + roles = roles.replace('--, ', '--\n') + + if len(roles) > 2048: + with open(f'./assets/temp/{ctx.guild.id}_roles.txt', 'w') as f: + f.seek(0) + f.write(roles) + + txt_file = discord.File(f'./assets/temp/{ctx.guild.id}_roles.txt') + await ctx.send(file=txt_file) + + try: + remove(f'./assets/temp/{ctx.guild.id}_roles.txt') + except: + pass + + return + + if roles == '': + roles = '**Ingen roller**' + + embed = discord.Embed(color=ctx.me.color, description=roles) + embed.set_author(name=f'Roller ({len(ctx.guild.roles) - 1})', icon_url=ctx.guild.icon_url) + embed.set_footer(text=ctx.guild.name, icon_url=ctx.guild.icon_url) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['guildboosters', 'serverboosters', 'boosts']) + async def boosters(self, ctx): + """Viser boosters i en guild""" + + if len(ctx.guild.premium_subscribers) == 0: + embed = embed_templates.error_warning(ctx, text='Serveren har ikke noen boosts :(') + return await ctx.send(embed=embed) + + boosters = [] + premium_subscribers = sorted(ctx.guild.premium_subscribers, key=lambda m: m.premium_since) + for booster in premium_subscribers: + date = booster.premium_since.strftime('%d.%m.%Y %H:%M') + boosters.append(f'{booster.name}#{booster.discriminator} - {date}') + boosters = '\n'.join(boosters) + + embed = discord.Embed(color=ctx.me.color, description=boosters) + embed.set_author(name=f'Boosters ({premium_subscription_count})', icon_url=ctx.guild.icon_url) + embed.set_footer(text=ctx.guild.name, icon_url=ctx.guild.icon_url) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['serverikon', 'servericon', 'guildicon', 'icon', 'guildikon']) + async def ikon(self, ctx): + """Viser ikonet til serveren du er i""" + + url = ctx.guild.icon_url_as(static_format='png') + + embed = discord.Embed(color=ctx.me.color, description=f'[Lenke]({url})') + embed.set_author(name=ctx.guild.name, icon_url=url) + embed.set_image(url=url) + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['guildsplash', 'serversplash']) + async def splash(self, ctx): + """Viser invite splash til serveren""" + + if not ctx.guild.splash_url: + embed = embed_templates.error_warning(ctx, text='Serveren har ikke en splash') + return await ctx.send(embed=embed) + + url = ctx.guild.splash_url_as(format='png') + + embed = discord.Embed(color=ctx.me.color, description=f'[Lenke]({url})') + embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon_url) + embed.set_image(url=url) + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['guildbanner', 'serverbanner']) + async def banner(self, ctx): + """Viser invite splash til serveren""" + + if not ctx.guild.banner_url: + embed = embed_templates.error_warning(ctx, text='Serveren har ikke et banner :(') + return await ctx.send(embed=embed) + + url = ctx.guild.banner_url_as(format='png') + + embed = discord.Embed(color=ctx.me.color, description=f'[Lenke]({url})') + embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon_url) + embed.set_image(url=url) + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True, external_emojis=True) + @commands.guild_only() + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['userinfo', 'ui', 'brukerinfo', 'user']) + async def bruker(self, ctx, *, bruker: discord.Member=None): + """Viser info om en bruker""" + + if not bruker: + bruker = ctx.author + + app = '' + if str(bruker.mobile_status) != 'offline': + app += '📱 ' + if str(bruker.web_status) != 'offline': + app += '🌐 ' + if str(bruker.desktop_status) != 'offline': + app += '💻' + + join_index = sorted(ctx.guild.members, key=lambda m: m.joined_at).index(bruker) + 1 + creation_index = sorted(ctx.guild.members, key=lambda m: m.created_at).index(bruker) + 1 + if bruker.premium_since: + premium_index = sorted(ctx.guild.premium_subscribers, key=lambda m: m.premium_since).index(bruker) + 1 + + bruker_joined_date = bruker.joined_at.strftime('%d. %b. %Y - %H:%M') + bruker_created_date = bruker.created_at.strftime('%d. %b. %Y - %H:%M') + since_joined_days = (ctx.message.created_at - bruker.joined_at).days + since_created_days = (ctx.message.created_at - bruker.created_at).days + if since_created_days == 1: + since_created_days_string = 'dag' + else: + since_created_days_string = 'dager' + if since_joined_days == 1: + since_joined_days_string = 'dag' + else: + since_joined_days_string = 'dager' + + if bruker.premium_since: + premium_since = bruker.premium_since.strftime('%d. %b. %Y - %H:%M') + premium_since_days = (ctx.message.created_at - bruker.premium_since).days + if since_joined_days == 1: + premium_since_days_string = 'dag' + else: + premium_since_days_string = 'dager' + + roles = [] + for role in bruker.roles: + if role.name != '@everyone': + roles.append(role.name) + roles.reverse() + roles = ', '.join(roles) + + if len(roles) > 1024: + roles = f'Skriv `{self.bot.prefix}{ctx.command}` for å se rollene' + if roles == '': + roles = '**Ingen roller**' + + if str(bruker.color) != '#000000': + color = bruker.color + else: + color = discord.Colour(0x99AAB5) + + statuses = { + 'online': f'{self.bot.emoji["online"]} Pålogget', + 'idle': f'{self.bot.emoji["idle"]} Inaktiv', + 'dnd': f'{self.bot.emoji["dnd"]} Ikke forstyrr', + 'offline': f'{self.bot.emoji["offline"]} Frakoblet' + } + status = statuses[str(bruker.status)] + + embed = discord.Embed(color=color, description=f'{bruker.mention}\nID: {bruker.id}\n{status}\n{app}') + if bruker.display_name == bruker.name: + embed.set_author(name=f'{bruker.name}#{bruker.discriminator}', icon_url=bruker.avatar_url) + else: + embed.set_author(name=f'{bruker.name}#{bruker.discriminator} | {bruker.display_name}', + icon_url=bruker.avatar_url) + embed.set_thumbnail(url=bruker.avatar_url_as(static_format='png')) + embed.add_field(name='Opprettet', value=f'{bruker_created_date}\n{since_created_days} ' + + f'{since_created_days_string} siden') + embed.add_field(name='Ble med i serveren', value=f'{bruker_joined_date}\n{since_joined_days} ' + + f'{since_joined_days_string} siden') + if bruker.premium_since: + embed.add_field(name='Boost', value=f'{premium_since}\n{premium_since_days} ' + + f'{premium_since_days_string} siden\n' + + f'Booster #{premium_index} av serveren', inline=False) + embed.add_field(name=f'Roller ({len(bruker.roles) - 1})', value=roles, inline=False) + embed.set_footer(text=f'#{join_index} Medlem av serveren | #{creation_index} Eldste brukeren på serveren') + + if bruker.activities: + games = '' + for activity in bruker.activities: + games += f'{activity.name}\n' + if games: + embed.add_field(name='Spiller', value=games, inline=False) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['userroles']) + async def brukerroller(self, ctx, bruker: discord.Member=None): + """Viser rollene til en bruker""" + + if not bruker: + bruker = ctx.author + + roles = [] + for role in bruker.roles: + if role.name != '@everyone': + roles.append(role.name) + roles.reverse() + roles = ', '.join(roles) + + if len(roles) > 2048: + txt_file = discord.File(f'./assets/temp/{ctx.guild.id}_{ctx.author.id}_roles.txt') + await ctx.send(file=txt_file) + + try: + remove(f'./assets/temp/{ctx.guild.id}_{ctx.author.id}_roles.txt') + except: + pass + + return + + if roles == '': + roles = '**Ingen roller**' + + if str(bruker.color) != '#000000': + color = bruker.color + else: + color = discord.Colour(0x99AAB5) + + embed = discord.Embed(color=color, description=roles) + embed.set_author(name=f'Roller ({len(bruker.roles) - 1})', icon_url=bruker.avatar_url) + embed.set_footer(text=f'{bruker.name}#{bruker.discriminator}', icon_url=bruker.avatar_url) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['profilbilde', 'brukeravatar', 'useravatar']) + async def avatar(self, ctx, bruker: discord.Member=None): + """Viser avataren til en bruker""" + + if not bruker: + bruker = ctx.author + + if str(bruker.color) != '#000000': + color = bruker.color + else: + color = discord.Colour(0x99AAB5) + + url = bruker.avatar_url_as(static_format='png') + + embed = discord.Embed(color=color, description=f'[Lenke]({url})') + embed.set_author(name=f'{bruker.name}#{bruker.discriminator}', icon_url=url) + embed.set_image(url=url) + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['roleinfo', 'rolleinfo']) + async def rolle(self, ctx, *, rolle: discord.Role): + """Viser info om en rolle""" + + if rolle.name == '@everyone': + embed = embed_templates.fatal(ctx, text='Skriv inn en annen rolle enn @everyone') + return await ctx.send(embed=embed) + + if str(rolle.color) != '#000000': + color = rolle.color + else: + color = discord.Colour(0x99AAB5) + + if rolle.mentionable: + mentionable = 'Ja' + else: + mentionable = 'Nei' + + if rolle.hoist: + hoisted = 'Ja' + else: + hoisted = 'Nei' + + rolle_created_date = rolle.created_at.strftime('%d. %b. %Y - %H:%M') + since_created_days = (ctx.message.created_at - rolle.created_at).days + + if since_created_days is 1: + since_created_days_string = 'dag' + else: + since_created_days_string = 'dager' + + members = [] + for member in rolle.members: + members.append(f'{member.name}#{member.discriminator}') + members = ', '.join(members) + + if len(members) > 1024: + members = 'For mange medlemmer for å vise her' + if len(members) == 0: + members = '**Ingen**' + + permissions = ', '.join([permission for permission, value in iter(rolle.permissions) if value is True]) + + embed = discord.Embed(title=rolle.name, description=f'{rolle.mention}\n**ID:** {rolle.id}', color=color) + embed.set_author(name=rolle.guild.name, icon_url=rolle.guild.icon_url) + embed.add_field(name='Fargekode', value=str(rolle.color)) + embed.add_field(name='Opprettet', value=f'{rolle_created_date}\n{since_created_days} ' + + f'{since_created_days_string} siden') + embed.add_field(name='Posisjon', value=rolle.position) + embed.add_field(name='Nevnbar', value=mentionable) + embed.add_field(name='Vises separat i medlemsliste', value=hoisted) + if permissions: + embed.add_field(name='Tillatelser', value=permissions, inline=False) + embed.add_field(name=f'Brukere med rollen ({len(rolle.members)})', value=members, inline=False) + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 5, commands.BucketType.guild) + @commands.command(aliases=['kanal', 'channel', 'channelinfo', 'textchannel']) + async def tekstkanal(self, ctx, *, kanal: discord.TextChannel): + """Viser info om en tekstkanal""" + + nsfw = 'Nei' + if kanal.is_nsfw(): + nsfw = 'Ja' + + if kanal.slowmode_delay == 0: + saktemodus = 'Nei' + else: + saktemodus = f'Ja ({kanal.slowmode_delay} sekunder)' + + description = '**Ingen**' + if kanal.topic: + description = kanal.topic + + members = [] + for member in kanal.members: + members.append(f'{member.name}#{member.discriminator}') + members = ', '.join(members) + if len(members) > 1024: + members = 'For mange for å vise her' + + embed = discord.Embed(color=ctx.me.color, title=kanal.name, description=f'{kanal.mention}\nID: {kanal.id}') + embed.set_author(name=kanal.guild.name, icon_url=kanal.guild.icon_url) + embed.add_field(name='Beskrivelse', value=description, inline=False) + embed.add_field(name='Opprettet', value=kanal.created_at.strftime('%d. %b. %Y - %H:%M')) + embed.add_field(name='NSFW', value=nsfw) + embed.add_field(name='Saktemodus', value=saktemodus) + if kanal.category: + embed.add_field(name='Kategori', value=kanal.category.name) + embed.add_field(name=f'Antall med tilgang ({len(kanal.members)})', value=members) + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 5, commands.BucketType.guild) + @commands.command(aliases=['talekanalinfo', 'voicechannel']) + async def talekanal(self, ctx, *, kanal: discord.VoiceChannel): + """Viser info om en talekanal""" + + if kanal.user_limit == 0: + limit = '∞ personer' + else: + limit = f'{kanal.user_limit} personer' + + embed = discord.Embed(color=ctx.me.color, title=kanal.name, description=f'ID: {kanal.id}') + embed.set_author(name=kanal.guild.name, icon_url=kanal.guild.icon_url) + embed.add_field(name='Opprettet', value=kanal.created_at.strftime('%d. %b. %Y - %H:%M')) + embed.add_field(name='Bitrate', value=f'{int(kanal.bitrate / 1000)}kbps') + embed.add_field(name='Maksgrense', value=limit) + if kanal.category: + embed.add_field(name='Kategori', value=kanal.category.name) + embed.add_field(name=f'Antall koblet til', value=len(kanal.members)) + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 5, commands.BucketType.guild) + @commands.command(aliases=['emote']) + async def emoji(self, ctx, emoji: discord.Emoji): + """Viser info om en CUSTOM emoji som tilhører serveren""" + + animated = 'Nei' + if emoji.animated: + animated = 'Ja' + + emoji = await emoji.guild.fetch_emoji(emoji.id) + try: + emoji_creator = f'{emoji.user.mention}\n{emoji.user.name}#{emoji.user.discriminator}' + except AttributeError: + emoji_creator = 'Jeg trenger `manage_emojis`-tillatelsen på serveren den er fra for å hente dette' + + embed = discord.Embed(color=ctx.me.color, title=emoji.name, description=f'ID: {emoji.id}') + embed.set_author(name=emoji.guild.name, icon_url=emoji.guild.icon_url) + embed.add_field(name=f'Opprettet', value=emoji.created_at.strftime('%d. %b. %Y - %H:%M')) + embed.add_field(name='Animert', value=animated) + embed.add_field(name='Lagt til av', value=emoji_creator) + embed.set_image(url=emoji.url) + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 5, commands.BucketType.guild) + @commands.command(aliases=['serveremoji', 'serveremotes']) + async def guildemoji(self, ctx): + """Viser alle emoji som serveren har""" + + embed = discord.Embed(colour=ctx.me.color) + embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon_url) + + emoji_string = '' + for emoji in ctx.guild.emojis: + if len(emoji_string) > 2000: + embed.description = emoji_string + await ctx.send(embed=embed) + emoji_string = '' + emoji_string += f'{emoji} ' + + embed.description = emoji_string + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 5, commands.BucketType.guild) + @commands.command(aliases=['oldest']) + async def eldst(self, ctx, side=0): + """Liste over de eldste brukerene på serveren""" + + members = sorted(ctx.guild.members, key=lambda m: m.created_at) + + formatted_members = [] + for member in members: + member_created_date = member.created_at.strftime('%d. %b. %Y - %H:%M') + member_index = (members.index(member) + 1) + formatted_members.append(f'**#{member_index}** {member.name}#{member.discriminator} - {member_created_date}') + + page_data = misc_utils.paginator(formatted_members, int(side)) + side = page_data['page'] + pagecount = page_data['pagecount'] + members = '\n'.join(page_data['page_content']) + + embed = discord.Embed(color=ctx.me.color) + embed.add_field(name='Eldste Discordbrukerene på serveren', value=members) + embed.set_footer(text=f'Side: {side}/{pagecount}') + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.guild_only() + @commands.cooldown(1, 5, commands.BucketType.guild) + @commands.command() + async def joinorder(self, ctx, side=0): + """Liste over de første medlemmene av serveren""" + + members = sorted(ctx.guild.members, key=lambda m: m.joined_at) + + formatted_members = [] + for member in members: + member_joined_date = member.joined_at.strftime('%d. %b. %Y - %H:%M') + member_index = (members.index(member) + 1) + formatted_members.append(f'**#{member_index}** {member.name}#{member.discriminator} - {member_joined_date}') + + page_data = misc_utils.paginator(formatted_members, int(side)) + side = page_data['page'] + pagecount = page_data['pagecount'] + members = '\n'.join(page_data['page_content']) + + embed = discord.Embed(color=ctx.me.color) + embed.set_author(name=ctx.guild.name, icon_url=ctx.guild.icon_url) + embed.add_field(name='Første Discordbrukerene på serveren', value=members) + embed.set_footer(text=f'Side: {side}/{pagecount}') + await ctx.send(embed=embed) + + +def setup(bot): + bot.add_cog(Info(bot)) diff --git a/src/cogs/misc.py b/src/cogs/misc.py new file mode 100644 index 0000000..dab5e10 --- /dev/null +++ b/src/cogs/misc.py @@ -0,0 +1,196 @@ +from discord.ext import commands +import discord + +from re import sub +from datetime import datetime +import requests +import json +from asyncio import sleep +from PIL import Image, ImageDraw, ImageFont +from os import remove + +from cogs.utils import embed_templates + + +class Misc(commands.Cog): + def __init__(self, bot): + self.bot = bot + + @commands.bot_has_permissions(embed_links=True) + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['owoify', 'uwu']) + async def owo(self, ctx, *, tekst: str): + """Oversetter teksten din til owo""" + + owo_rules = { + 'r': 'w', + 'l': 'w', + 'R': 'W', + 'L': 'W', + 'n': 'ny', + 'N': 'Ny', + 'ove': 'uv' + } + for key, value in owo_rules.items(): + tekst = sub(key, value, tekst) + # https://kaomoji.moe/ + + if not tekst or len(tekst) >= 1000: + embed = embed_templates.error_warning(ctx, text='Teksten er for lang') + return await ctx.send(embed=embed) + + embed = discord.Embed(color=ctx.me.color, description=tekst) + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + + @commands.bot_has_permissions(embed_links=True) + @commands.cooldown(1, 2, commands.BucketType.guild) + @commands.command(aliases=['clapify']) + async def klappifiser(self, ctx, *, tekst): + """Klapppifiserer teksten din""" + + if not tekst or len(tekst) >= 1000: + embed = embed_templates.error_warning(ctx, text='Teksten er for lang') + return await ctx.send(embed=embed) + + tekst = sub(' ', '👏', tekst) + + embed = discord.Embed(color=ctx.me.color, description=f'**{tekst.upper()}**') + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.cooldown(1, 5, commands.BucketType.guild) + @commands.command(aliases=['identify']) + async def identifiser(self, ctx, bilde=None): + """Beskriver hva som er på bildet""" + + if ctx.message.attachments != []: + bilde = ctx.message.attachments[0].url + + if bilde is None: + embed = embed_templates.error_fatal(ctx, text='Du må gi meg et bilde!') + + async with ctx.channel.typing(): + + payload = {'inputs': [{'data': {'image': {'url': bilde}}}]} + header = {'Authorization': f'Key {self.bot.api_keys["clarifai"]}'} + url = 'https://api.clarifai.com/v2/models/aaa03c23b3724a16a56b629203edc62c/versions/aa7f35c01e0642fda5cf400f543e7c40/outputs' + data = requests.post(url, data=json.dumps(payload), headers=header).json() + + words = [] + for i, concepts in enumerate(data['outputs'][0]['data']['concepts']): + words.append(concepts['name']) + if i >= 5: + break + + words = ', '.join(words) + + await sleep(2) + + embed = discord.Embed(color=ctx.me.color) + embed.set_author(name='Clarifai AI', icon_url='https://github.com/Clarifai.png') + embed.description = f'**Ord som beskriver dette bildet:**\n\n{words}' + embed.set_image(url=bilde) + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + @commands.bot_has_permissions(embed_links=True) + @commands.cooldown(1, 5, commands.BucketType.guild) + @commands.command(aliases=['holidays', 'fridager', 'ferie']) + async def helligdager(self, ctx, land=None, aar=None): + """Se hellidagene i et land""" + + if not land: + land = 'NO' + else: + land = land.upper() + + if not aar: + aar = datetime.now().year + else: + int(aar) + + try: + data = requests.get(f'https://date.nager.at/api/v2/publicholidays/{aar}/{land}').json() + except: + embed = embed_templates.error_fatal(ctx, text='Ugyldig land\nHusk å bruke landskoder\n' + + 'For eksempel: `NO`') + return await ctx.send(embed=embed) + + country = data[0]['countryCode'].lower() + holiday_str = '' + for day in data: + date = day['date'] + date = datetime.strptime(date, '%Y-%m-%d').strftime('%d. %B') + holiday_str += f'**{date}**: {day["localName"]}\n' + + embed = discord.Embed(color=ctx.me.color, title=f':flag_{country}: Helligdager {aar} :flag_{country}:') + embed.description = holiday_str + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed) + + + @commands.bot_has_permissions(embed_links=True) + @commands.cooldown(1, 5, commands.BucketType.guild) + @commands.command() + async def match(self, ctx, *, bruker: discord.Member=None): + """Se hvor mye du matcher med en annen""" + + if not bruker: + embed = embed_templates.error_warning(ctx, text='Du må gi meg en bruker') + return await ctx.send(embed=embed) + if bruker == ctx.author: + embed = embed_templates.error_warning(ctx, text='Jeg vet du er ensom, men du kan ' + 'ikke matche med deg selv') + return await ctx.send(embed=embed) + + async with ctx.channel.typing(): + + invoker_id = int(str(ctx.author.id)[11:14]) + user_id = int(str(bruker.id)[11:14]) + + match_percent = int((invoker_id + user_id) % 100) + + if bruker.id == self.bot.user.id: + match_percent = 100 + + await ctx.author.avatar_url_as(format='png').save(fp=f'./src/assets/temp/{ctx.author.id}_raw.png') + await bruker.avatar_url_as(format='png').save(fp=f'./src/assets/temp/{bruker.id}_raw.png') + + invoker = Image.open(f'./src/assets/temp/{ctx.author.id}_raw.png').convert('RGBA') + invoker = invoker.resize((389, 389), Image.ANTIALIAS) + user = Image.open(f'./src/assets/temp/{bruker.id}_raw.png').convert('RGBA') + user = user.resize((389, 389), Image.ANTIALIAS) + heart = Image.open(f'./src/assets/misc/heart.png') + mask = Image.open(f'./src/assets/misc/heart.png', 'r') + + image = Image.new('RGBA', (1024, 576)) + image.paste(invoker, (0, 94)) + image.paste(user, (635, 94)) + image.paste(heart, (311, 94), mask=mask) + draw = ImageDraw.Draw(image) + font = ImageFont.truetype('./src/assets/fonts/RobotoMono-Medium.ttf', 86) + font_size = font.getsize(f'{match_percent}%') + font_size = ((image.size[0] - font_size[0]) / 2, (image.size[1] - font_size[1]) / 2) + draw.text(font_size, f'{match_percent}%', font=font, fill=(255,255,255,255)) + + image.save(f'./src/assets/temp/{ctx.author.id}_{bruker.id}_edit.png') + + f = discord.File(f'./src/assets/temp/{ctx.author.id}_{bruker.id}_edit.png') + embed = discord.Embed() + embed.set_image(url=f'attachment://{ctx.author.id}_{bruker.id}_edit.png') + embed = embed_templates.default_footer(ctx, embed) + await ctx.send(embed=embed, file=f) + + try: + remove(f'./src/assets/temp/{bruker.id}_raw.png') + remove(f'./src/assets/temp/{ctx.author.id}_raw.png') + remove(f'./src/assets/temp/{ctx.author.id}_{bruker.id}_edit.png') + except: + pass + + +def setup(bot): + bot.add_cog(Misc(bot)) diff --git a/src/cogs/utils/discord_utils.py b/src/cogs/utils/discord_utils.py new file mode 100644 index 0000000..e76ccde --- /dev/null +++ b/src/cogs/utils/discord_utils.py @@ -0,0 +1,18 @@ +import discord + + +def get_user_color(user: discord.User) -> discord.Color: + """ + Returns the user's top role color + Parameters + ----------- + user (discord.User): A discord user or member object + Returns + ----------- + (discord.Color): The user's displayed color + """ + + if str(user.color) != '#000000': + return user.color + + return discord.Colour(0x99AAB5) diff --git a/src/cogs/utils/embed_templates.py b/src/cogs/utils/embed_templates.py new file mode 100644 index 0000000..f6d29fa --- /dev/null +++ b/src/cogs/utils/embed_templates.py @@ -0,0 +1,53 @@ + +import discord + + +def default_footer(ctx: discord.ext.commands.Context, embed: discord.Embed) -> discord.Embed: + """ + Sets a footer containing the command invoker's name, discriminator and avatar + Parameters + ----------- + ctx (discord.ext.commands.Context): The current Discord context + embed (discord.Embed): An embed object to add the footer to + Returns + ----------- + (discord.Embed): The sent in embed + footer + """ + + return embed.set_footer(icon_url=ctx.author.avatar_url, text=f'{ctx.author.name}#{ctx.author.discriminator}') + + +def error_warning(ctx: discord.ext.commands.Context, text: str) -> discord.Embed: + """ + Creates an embed with a specified error message based on a warning template + Parameters + ----------- + ctx (discord.ext.commands.Context): The current Discord context + text (str): The error message + Returns + ----------- + (discord.Embed): An embed object based on the template with the specified text + """ + + embed = discord.Embed(color=discord.Color.gold(), description=f'⚠️ {text}') + default_footer(ctx, embed) + + return embed + + +def error_fatal(ctx: discord.ext.commands.Context, text: str) -> discord.Embed: + """ + Creates an embed with a specified error message based on a warning template + Parameters + ----------- + ctx (discord.ext.commands.Context): The current Discord context + text (str): The error message + Returns + ----------- + (discord.Embed): An embed object based on the template with the specified text + """ + + embed = discord.Embed(color=discord.Color.red(), description=f'❌ {text}') + default_footer(ctx, embed) + + return embed diff --git a/src/cogs/utils/misc_utils.py b/src/cogs/utils/misc_utils.py new file mode 100644 index 0000000..12d85e5 --- /dev/null +++ b/src/cogs/utils/misc_utils.py @@ -0,0 +1,33 @@ +from typing import Union, Dict +from math import ceil + + +def paginator(lst: list, page: int) -> Dict[str, Union[int, str]]: + """ + Divides content into 10 element pages + Parameters + ----------- + lst: A list of strings + page: The page + Returns + ----------- + (dict): A dictionay containing content and metadata + keys: pagecount, page, page_content + """ + + pagecount = ceil(len(lst) / 10) + + if not page or page <= 0 or page > pagecount: + page = 1 + + start_index = (page - 1) * 10 + end_index = page * 10 + + page_content = lst[start_index:end_index] + + page_data = { + 'pagecount': pagecount, + 'page': page, + 'page_content': page_content + } + return page_data diff --git a/src/config/config.yaml.example b/src/config/config.yaml.example new file mode 100644 index 0000000..dbc3633 --- /dev/null +++ b/src/config/config.yaml.example @@ -0,0 +1,19 @@ +# Bot settings +bot: + token: + prefix: 'u!' + presence: + message: Voldtekt av API-er + activity: watching + +# Emoji +emoji: + online: <:online:516328785910431754> + idle: <:idle:516328783347843082> + dnd: <:dnd:516328782844395579> + offline: <:offline:516328785407246356> + +# Misc +misc: + website: https://uiogaming.no + source_code: https://github.com/UiO-Gaming/uiogaming-bot \ No newline at end of file diff --git a/src/run.py b/src/run.py new file mode 100644 index 0000000..2af1398 --- /dev/null +++ b/src/run.py @@ -0,0 +1,73 @@ +from discord.ext import commands +import discord + +from codecs import open +import yaml + +from os import listdir +import locale +from time import time + + +locale.setlocale(locale.LC_ALL, '') + +with open('./src/config/config.yaml', 'r', encoding='utf8') as f: + config = yaml.load(f, Loader=yaml.SafeLoader) + +intents = discord.Intents.default() +intents.guilds = True +intents.members = True +intents.messages = True +intents.presences = True +intents.emojis = True + + +class Bot(commands.Bot): + def __init__(self): + super().__init__(command_prefix=commands.when_mentioned_or(config['bot']['prefix']), + case_insensitive=True, intents=intents) + + self.prefix = config['bot']['prefix'] + self.presence = config['bot'].get('presence', {}) + + self.api_keys = config.get('api', {}) + + self.emoji = config.get('emoji', {}) + self.misc = config.get('misc', {}) + + +bot = Bot() + + +activities = { + 'playing': 0, + 'listening': 2, + 'watching': 3 +} +if bot.presence['activity'].lower() in activities: + activity_type = activities[bot.presence['activity']] +else: + activity_type = 0 + + +@bot.event +async def on_ready(): + if not hasattr(bot, 'uptime'): + bot.uptime = time() + + for file in listdir('./src/cogs'): + if file.endswith('.py'): + name = file[:-3] + bot.load_extension(f'cogs.{name}') + + print(f'Username: {bot.user.name}') + print(f'ID: {bot.user.id}') + print(f'Version: {discord.__version__}') + print('.' * 50 + '\n') + await bot.change_presence( + activity=discord.Activity(type=activity_type, name=bot.presence['message']), + status=discord.Status.online + ) + + +bot.run(config['bot']['token'], bot=True, reconnect=True)