diff --git a/.env.dist b/.env.dist new file mode 100644 index 0000000..09e8e5b --- /dev/null +++ b/.env.dist @@ -0,0 +1,12 @@ +bot_token = "" + +api_id = "" +api_hash = "" + +group_id = "" +log_group_id = "" + +vt_api = "" + +db_url = "sqlite://db.db" +telegram_api_server = "127.0.0.1:5326" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d38e195 --- /dev/null +++ b/LICENSE @@ -0,0 +1,630 @@ +Copyright (C) 2021 Hack4th5tR + +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. + + 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. diff --git a/app.py b/app.py new file mode 100755 index 0000000..a3afe68 --- /dev/null +++ b/app.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +import logging +from aiogram import executor +from database import models + +from load import dp, bot +from load import loop,tgc + +import handlers +import config + + +logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) + +WEBAPP_HOST = '127.0.0.1' +WEBAPP_PORT = 3001 + +# Don`t touch anything! +WEBHOOK_HOST = f'http://{WEBAPP_HOST}:{WEBAPP_PORT}' +WEBHOOK_PATH = f'/bot{config.token}/' +WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}" + +async def on_startup(dp): + from utils.notify_start import notify_started_bot + await notify_started_bot(bot) + + from utils.default_commands import set_default_commands + await set_default_commands(dp) + + await bot.set_webhook(WEBHOOK_URL) + + # Connect to client + await tgc._connect() + +async def on_shutdown(dp): + await bot.delete_webhook() + + # Close Redis connection. + await dp.storage.close() + await dp.storage.wait_closed() + +def main() -> None: + models.build() + + if config.use_webhook: + executor.start_webhook( + dispatcher=dp, + webhook_path=WEBHOOK_PATH, + on_startup=on_startup, + on_shutdown=on_shutdown, + loop = loop, + skip_updates=True, + host=WEBAPP_HOST, + port=WEBAPP_PORT, + ) + + else: + executor.start_polling(dp,skip_updates=True) + + +if __name__ == '__main__': + main() diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..d085c3a --- /dev/null +++ b/config/__init__.py @@ -0,0 +1 @@ +from .config import * \ No newline at end of file diff --git a/config/config.py b/config/config.py new file mode 100644 index 0000000..571b744 --- /dev/null +++ b/config/config.py @@ -0,0 +1,36 @@ +import json + +from aiogram import Dispatcher,Bot +from environs import Env + +env = Env() +env.read_env() + +use_webhook = True + +# bot token +token = env.str("bot_token") + +group_id = env.str("group_id") +telegram_log_chat_id = env.str("log_group_id") + +# Telegram Application +api_id = env.str("api_id") +api_hash = env.str("api_hash") + +# Virus Total API +vt_api = env.str("vt_api") + +with open("config/roles.json","r") as jsonfile: + roles = json.load(jsonfile) + +db_url = env.str("db_url") + +# telegram-bot-api-service +telegram_api_server = env.str("telegram_api_server").split(":") +telegram_api_server = { + "ip":telegram_api_server[0], + "port":telegram_api_server[1] +} + +telegram_api_server = f"http://{telegram_api_server['ip']}:{telegram_api_server['port']}" diff --git a/config/roles.json b/config/roles.json new file mode 100644 index 0000000..fef6d36 --- /dev/null +++ b/config/roles.json @@ -0,0 +1,72 @@ +{ + "level": { + "owner": 3, + "admin": 2, + "helper": 1, + "member": 0 + }, + "group_permissions": { + "can_send_messages": true, + "can_send_media_messages": true, + "can_send_other_messages": true, + "can_send_polls": false, + "can_invite_users": false, + "can_change_info": false, + "can_add_web_page_previews": false, + "can_pin_messages": false + }, + "roles": { + "owner": { + "ban": true, + "kick": true, + "mute": true, + "umute": true, + "warn": true, + "pin": true, + "srole": true, + "media": true, + "stickers": true, + "ro": true, + "reload": true + }, + "admin": { + "ban": true, + "kick": true, + "mute": true, + "umute": true, + "warn": true, + "pin": true, + "srole": true, + "media": true, + "stickers": true, + "ro": true, + "reload": true + }, + "helper": { + "ban": false, + "kick": true, + "mute": false, + "umute": false, + "warn": true, + "pin": false, + "srole": false, + "media": true, + "stickers": true, + "ro": false, + "reload": true + }, + "member": { + "ban": false, + "kick": false, + "mute": false, + "umute": false, + "warn": false, + "pin": false, + "srole": false, + "media": false, + "stickers": false, + "ro": false, + "reload": false + } + } +} \ No newline at end of file diff --git a/database/__init__.py b/database/__init__.py new file mode 100644 index 0000000..ef3f969 --- /dev/null +++ b/database/__init__.py @@ -0,0 +1 @@ +from .database import Database diff --git a/database/database.py b/database/database.py new file mode 100644 index 0000000..a9dbde8 --- /dev/null +++ b/database/database.py @@ -0,0 +1,109 @@ +from .models import Member,Restriction +from peewee import Field + +class Database: + def check_data_exists(self, fieldname:Field, value) -> bool | None: + """Check if data exists in db""" + query = Member.select().where(fieldname == value) + + if (query is None): + return None + + return query.exists() + + def register_user(self, user_id, first_name, user_name=None, role:str='member') -> bool: + """If the user doesn't exist, returns true. Registers a user in the db.""" + + if self.check_data_exists(Member.user_id,user_id): + return False + + Member.create( + user_id = user_id, + first_name = first_name, + user_name = user_name, + + role = role, + + reports = 0, + ) + + return True + + def search_single_member(self,fieldname:Field,value) -> Member | None: + """If the user is found, returns dataclass. Returns user info.""" + exists = self.check_data_exists(fieldname,value) + + if not (exists): + return None + + user = Member.get(fieldname == value) + + return user + + def create_restriction(self, from_user_id, to_user_id, operation, reason): + from_admin = self.search_single_member(Member.user_id,to_user_id) + to_user = self.search_single_member(Member.user_id,from_user_id) + + if not (from_admin) or not (to_user): + return None + + Restriction.create( + operation = operation, + + from_admin = from_admin, + to_user = to_user, + + reason = reason, + ) + + def search_user_restriction(self, user_id) -> list[Restriction] | None: + user = Member.get(Member.user_id == user_id) + + query = Restriction.select().join(Member,on=Restriction.to_user) + + if (query is None): + return None + + return query.where(Restriction.to_user == user) + + def delete_user(self,user_id) -> bool: + """If the user exists, returns true. Deletes the user from the db.""" + + exists = self.check_data_exists(Member.user_id,user_id) + + if not (exists): + return False + + Member.delete().where(Member.user_id == user_id) + + return True + + def update_member_data(self, user_id, fieldnames:list[Field], newvalues:list) -> bool: + """Update member data.""" + exists = self.check_data_exists(Member.user_id,user_id) + + if (not exists): + return False + + for i in range(len(newvalues)): + query = Member.update({fieldnames[i]:newvalues[i]}).where(Member.user_id == user_id) + if (query is None): + return False + + return True + + def change_reports(self,user_id,delete=False) -> int | None: + """If the user exists, returns number reports. Gives the user a warning or retrieves it.""" + exists = self.check_data_exists(Member.user_id,user_id) + + if not (exists): + return False + + count = Member.get(Member.user_id == user_id).reports + + if delete:count += 1 + else:count -= 1 + + query = Member.update(reports = count).where(Member.user_id == user_id).execute() + + return count diff --git a/database/models.py b/database/models.py new file mode 100644 index 0000000..1473546 --- /dev/null +++ b/database/models.py @@ -0,0 +1,39 @@ +from peewee import Model, BigIntegerField, CharField, DateField, DateTimeField, ForeignKeyField + +import config +from playhouse.db_url import connect + +from datetime import datetime, date + +db = connect(config.db_url) + +class Member(Model): + user_id = BigIntegerField() + first_name = CharField() + user_name = CharField(null=True) + role = CharField() + + join_date = DateField(default=date.today()) + + reports = BigIntegerField() + + class Meta: + db_table = "members" + database = db + +class Restriction(Model): + restriction_id = BigIntegerField() + operation = CharField() + + from_admin = ForeignKeyField(Member,lazy_load=False) + to_user = ForeignKeyField(Member,lazy_load=False) + + reason = CharField(null=True) + date = DateTimeField(default=datetime.now) + + class Meta: + db_table = "restrictions" + database = db + +def build() -> None: + db.create_tables([Member,Restriction]) diff --git a/filters/__init__.py b/filters/__init__.py new file mode 100644 index 0000000..f9ac2c2 --- /dev/null +++ b/filters/__init__.py @@ -0,0 +1 @@ +from .filters import IsAdminFilter,ReplayMessageFilter,UserHasRights diff --git a/filters/filters.py b/filters/filters.py new file mode 100644 index 0000000..7e6603e --- /dev/null +++ b/filters/filters.py @@ -0,0 +1,77 @@ +from aiogram import types +from aiogram.dispatcher.filters import BoundFilter + +# from config import roles +from database.database import Member + +class IsAdminFilter(BoundFilter): + """Check admin permission on hadler""" + key = 'is_admin' + + def __init__(self, is_admin): + self.is_admin = is_admin + + async def check(self, message: types.Message): + member = await message.bot.get_chat_member(message.chat.id, message.from_user.id) + result = member.is_chat_admin() + if not result: + await message.reply("🔒This command can only be used by an admin!") + return result + +class UserHasRights(BoundFilter): + """Check command in user rights""" + + key = 'hasRights' + + def __init__(self,hasRights): + self.hasRights = hasRights + + async def check(self,message:types.Message): + import config + from load import database + + roles = config.roles["roles"] + + command = message.text.split()[0].lstrip("!") + + user = database.search_single_member(Member.user_id,message.from_user.id) + + # If data not exists,return False + if (user is None): + return False + + # If role not exist,return False. + if not (user.role in roles.keys()): + return False + + can_run_it = roles[user.role][command] + + replied = message.reply_to_message + + if (replied): + if (replied.from_user.id == message.from_user.id): + await message.answer("❌ You can't ") + return False + + if (str(replied.from_user.id) == config.token.split(":")[0]): + await message.answer("You can't restrict bot.") + return False + + if not (can_run_it): + await message.answer("You can't use this command.") + return False + + return roles[user.role][command] + +class ReplayMessageFilter(BoundFilter): + """Check if message replied""" + key = 'replied' + + def __init__(self, replied): + self.replied = replied + + async def check(self, message: types.Message): + if message.reply_to_message is None: + await message.reply("Is command must be reply") + return False + return True diff --git a/handlers/__init__.py b/handlers/__init__.py new file mode 100644 index 0000000..10dfc08 --- /dev/null +++ b/handlers/__init__.py @@ -0,0 +1,5 @@ +from . import groups +from . import private +from . import channels +from . import event +from . import errors diff --git a/handlers/channels/__init__.py b/handlers/channels/__init__.py new file mode 100644 index 0000000..af238c4 --- /dev/null +++ b/handlers/channels/__init__.py @@ -0,0 +1 @@ +from . import channels_handler diff --git a/handlers/channels/channels_handler.py b/handlers/channels/channels_handler.py new file mode 100644 index 0000000..787a508 --- /dev/null +++ b/handlers/channels/channels_handler.py @@ -0,0 +1,6 @@ +from load import dp,types + +# TODO: channel post forward in chat +@dp.channel_post_handler() +async def channel_handler(message:types.Message): + print(message.text) diff --git a/handlers/errors/__init__.py b/handlers/errors/__init__.py new file mode 100644 index 0000000..d2aa1e3 --- /dev/null +++ b/handlers/errors/__init__.py @@ -0,0 +1 @@ +from . import errors_handler diff --git a/handlers/errors/errors_handler.py b/handlers/errors/errors_handler.py new file mode 100644 index 0000000..3095a4c --- /dev/null +++ b/handlers/errors/errors_handler.py @@ -0,0 +1,23 @@ +import logging + +from load import dp,bot +import config + +from aiogram.utils.exceptions import Unauthorized + + +@dp.errors_handler() +async def errors_handler(update, exception): + if (isinstance(exception,Unauthorized)): + logging.info(f"Unathorized:{config.token}") + return True + + await update.message.answer("Error happaned!\nBot terminated!") + + await bot.send_message( + config.telegram_log_chat_id, + f"**Bot terminated**!\nException:{exception}", + parse_mode="Markdown" + ) + + logging.info(f"Bot terminated!Exception:{exception}") diff --git a/handlers/event.py b/handlers/event.py new file mode 100644 index 0000000..d5d88b4 --- /dev/null +++ b/handlers/event.py @@ -0,0 +1,29 @@ +from load import dp, bot, types + +# TODO: fix it +# import utils +# import config +# vt = utils.VirusTotalAPI(config.vt_api,True) +# @dp.message_handler(content_types=["document"],chat_type=[types.ChatType.SUPERGROUP]) +# async def file_handler(message:types.Message): +# file = await bot.get_file(message.document.file_id) +# +# await bot.send_message( +# message.chat.id, +# await vt.scan_file(file.file_path), +# parse_mode="Markdown" +# ) + +@dp.message_handler() +async def filter_link_shorts(message:types.Message): + link_shorters = open("txt/link_shorters.txt","r").read().split() + + for y in link_shorters: + for user_message in message.text.lower().split(): + if (y in user_message):await message.delete() + +# Joke +@dp.message_handler(content_types=types.ContentType.VOICE) +async def voice_message(message:types.Message): + photo = types.InputFile(path_or_bytesio="media/photo.jpg") + await message.answer_photo(photo) diff --git a/handlers/groups/__init__.py b/handlers/groups/__init__.py new file mode 100644 index 0000000..e09065e --- /dev/null +++ b/handlers/groups/__init__.py @@ -0,0 +1,2 @@ +from . import admin +from . import user diff --git a/handlers/groups/admin.py b/handlers/groups/admin.py new file mode 100644 index 0000000..139a69a --- /dev/null +++ b/handlers/groups/admin.py @@ -0,0 +1,423 @@ +from load import bot, dp, types +from aiogram.types.chat_permissions import ChatPermissions + +import config +import utils + +from load import database +from database.models import Member + +import re +import json + +from dataclasses import dataclass + + +# TODO:Automatic malware checking with VirusTotal(add skipping queue virustotal report) +# vt = utils.VirusTotalAPI(config.vt_api,True) + +def getArgument(arguments:list,index:int=0) -> str | None: + """ Get element from a list.If element not exist return None """ + if not (arguments): + return None + if (len(arguments) >= index): + return arguments[index] + else: + return None + +@dataclass +class CommandArguments: + user:Member | None + arguments:list + +async def getCommandArgs(message:types.Message) -> CommandArguments: + """ Describe user data and arguments from message """ + + #Example: + #1.!command @username ... (not reply) + #2.!command (not_reply) + #3.!command ... (not reply) + + arguments_list = message.text.split()[1:] + + is_reply = message.reply_to_message + + member = None + arguments = [] + + if (is_reply): + member = database.search_single_member(Member.user_id,message.reply_to_message) + arguments = arguments_list + else: + first_word = getArgument(arguments_list) + if (first_word): + if (first_word.isdigit()): + member = database.search_single_member(Member.user_id,first_word) + + if (first_word[0] == "@") : + member = database.search_single_member(Member.user_name,first_word) + + arguments = arguments_list[1:] + else: + arguments = arguments_list + + if (member is None) and (first_word): + await message.answer(f"❌ User {first_word} not exist.") + + return CommandArguments(member,arguments) + +def checkArg(message:str) -> bool | None: + """ Check if first argument in ["enable","on","true"] then return true """ + if (not message): + return None + + argument = message.split()[1] + + on = ['enable','on','true'] + off = ['disable','off','false'] + + return (argument in on) or (not argument in off) + +def delete_substring_from_string(string:str,substring:str) -> str: + string_list = string.split(substring) + return "".join(string_list).lstrip() + +# Filters: +# is_admin=True - Check admin permission, if user is admin, continue +# replied=True - If message is answer, continue + +@dp.message_handler(commands=["ban"],commands_prefix="!",hasRights=True) +async def ban_user(message: types.Message): + command = await getCommandArgs(message) + reason = getArgument(command.arguments) + + user = command.user + admin = message.from_user + + # If can't descibe user data + if (user is None): + await message.answer(( + "Usage:!ban @username reason=None" + "Reply to a message or use with a username.") + ) + return + + # Ban user and save (bool) + status = await bot.kick_chat_member(chat_id=message.chat.id, user_id=user.user_id, until_date=None) + + if status: + await message.answer(f"User [{user.first_name}](tg://user?id={user.user_id}) has been banned.", + parse_mode="Markdown") + + # Delete user from database + database.delete_user(user.user_id) + + # Open restrict + database.create_restriction(user.user_id, admin.id, "ban", reason) + +@dp.message_handler(commands=["unban"],commands_prefix="!",hasRights=True) +async def unban_user(message: types.Message): + command = await getCommandArgs(message) + user = command.user + + # If can't descibe user data + if (user is None): + await message.answer(( + "Usage:!unban @username reason=None\n" + "Reply to a message or use with username/id.") + ) + return + + # Unban user and set status (bool) + status = await bot.unban_chat_member(chat_id=message.chat.id, user_id=user.user_id) + + # add user to database + database.register_user(user.user_id, user.first_name) + + if status: + await message.answer(f"User [{user.first_name}](tg://user?id={user.user_id}) has been unbaned.", + parse_mode="Markdown") + +@dp.message_handler(commands=["kick"],commands_prefix="!",hasRights=True) +async def kick_user(message:types.Message): + command = await getCommandArgs(message) + arguments = command.arguments + + user = command.user + admin = message.from_user + + reason = getArgument(arguments) + + if (user is None): + await message.answer(( + "Usage:!kick @username reason=None\n" + "Reply to a message or use with a username/id.") + ) + return + + + status1 = await bot.kick_chat_member(chat_id=message.chat.id, user_id=user.user_id, until_date=None) + status2 = await bot.unban_chat_member(chat_id=message.chat.id, user_id=user.user_id) + + if (status1 and status2): + await message.answer(f"User [{user.first_name}](tg://user?id={user.user_id}) has been kicked.", + parse_mode="Markdown") + + database.create_restriction(user.user_id,admin.id,"kick",reason) + +@dp.message_handler(commands=["mute"],commands_prefix="!",hasRights=True) +async def mute_user(message:types.Message): + command = await getCommandArgs(message) + arguments = command.arguments + + user = command.user + admin = message.from_user + + if (user is None): + await message.answer(( + "Usage:!mute @username time\n" + "Reply to a message or use with a username/id.") + ) + return + + duration = re.findall(r"(\d+d|\d+h|\d+m|\d+s)",''.join(arguments)) + duration = " ".join(duration) + reason = delete_substring_from_string(" ".join(arguments),duration) + duration_timedelta = utils.parse_timedelta(duration) + + if not duration: + await message.answer(f"Error: \"{duration}\" — неверный формат времени. Examles: 3ч, 5м, 4h30s.") + return + + permissions = ChatPermissions(can_send_messages=False) + + status = await bot.restrict_chat_member( + chat_id=message.chat.id, + user_id=user.user_id, + until_date=duration_timedelta, + permissions=permissions + ) + + if status: + await message.answer(f"User **{user.first_name}** has been muted.", + parse_mode="Markdown") + + database.create_restriction(user.user_id,admin.id,"mute",reason) + +@dp.message_handler(commands=["umute"],commands_prefix="!",hasRights=True) +async def umute_user(message: types.Message): + # Get information + command = await getCommandArgs(message) + user = command.user + + # If can't + if (user is None): + await message.answer(( + "Usage:!unmute @username reason=None\n" + "Reply to a message or use with a username/id.") + ) + return + + # Get chat permissions + group_permissions = config.roles["group_permissions"] + + # Set permissions + permissions = ChatPermissions( + can_send_messages= group_permissions["can_send_messages"], + can_send_media_messages= group_permissions["can_send_media_messages"], + can_send_polls= group_permissions["can_send_polls"], + can_send_other_messages= group_permissions["can_send_other_messages"], + can_add_web_page_previews= group_permissions["can_add_web_page_previews"], + can_change_info= group_permissions["can_change_info"], + can_invite_users= group_permissions["can_invite_users"], + can_pin_messages= group_permissions["can_pin_messages"] + ) + + # Restrict user and save + status = await bot.restrict_chat_member( + chat_id=message.chat.id, + user_id=user.user_id, + permissions=permissions + ) + + if status: + await message.answer(f"User [{user.first_name}](tg://user?id={user.user_id}) has been unmuted.", + parse_mode="Markdown") + +@dp.message_handler(commands=["pin"],commands_prefix="!",hasRights=True) +async def pin_message(message:types.Message): + await bot.pin_chat_message(message.chat.id, message.reply_to_message.message_id) + +@dp.message_handler(commands=["ro"],commands_prefix="!",hasRights=True) +async def readonly_mode(message:types.Message): + check = checkArg(message.text) + + if (check is None): + await message.answer("!ro on/off alias:disable,enable,start,stop.") + return + + # Get chat permissions + group_permissions = config.roles["group_permissions"] + + # Set permissions + if (check): + chat_permissions = ChatPermissions( + can_send_messages=not check + ) + else: + chat_permissions = ChatPermissions( + can_send_messages=group_permissions['can_send_messages'], + can_send_media_messages=group_permissions["can_send_media_messages"], + can_send_other_messages=group_permissions['can_send_other_messages'], + can_send_polls=group_permissions['can_send_polls'], + can_invite_users=group_permissions['can_invite_users'], + can_change_info=group_permissions['can_change_info'], + can_add_web_page_previews=group_permissions['can_add_web_page_previews'], + can_pin_messages=group_permissions['can_pin_messages'] + ) + + status = await bot.set_chat_permissions(chat_id=message.chat.id, permissions=chat_permissions) + + if (status): + await message.answer(f"readonly - {check}") + +@dp.message_handler(commands=["media"],commands_prefix="!",hasRights=True) +async def media_content(message: types.Message): + check = checkArg(message.text) + + if (check is None): + await message.answer("!media on/off alias:disable,enable,start,stop.") + return + + # Get chat permissions + group_permissions = config.roles["group_permissions"] + + # Set permissions + chat_permissions = ChatPermissions( + can_send_messages=group_permissions['can_send_messages'], + can_send_media_messages=check, + can_send_other_messages=group_permissions['can_send_other_messages'], + can_send_polls=group_permissions['can_send_polls'], + can_invite_users=group_permissions['can_invite_users'], + can_change_info=group_permissions['can_change_info'], + can_add_web_page_previews=group_permissions['can_add_web_page_previews'], + can_pin_messages=group_permissions['can_pin_messages'] + ) + + # Set chat pemissions and save results + status = await bot.set_chat_permissions(chat_id=message.chat.id, permissions=chat_permissions) + + if status: + await message.answer(f"media - {check}.") + +@dp.message_handler(commands=["stickers"],commands_prefix="!",hasRights=True) +async def send_stickes(message: types.Message): + # Get arguments + check = checkArg(message.text) + + if (check is None): + await message.answer("!stickers on/off alias:disable,enable,start,stop") + return + + # Get chat permissions + group_permissions = config.roles["group_permissions"] + + # Set permissions. + chat_permissions = ChatPermissions( + can_send_messages=group_permissions['can_send_messages'], + can_send_media_messages=group_permissions['can_send_media_messages'], + can_send_other_messages=check, + can_send_polls=group_permissions['can_send_polls'], + can_invite_users=group_permissions['can_invite_users'], + can_change_info=group_permissions['can_change_info'], + can_add_web_page_previews=group_permissions['can_add_web_page_previews'], + can_pin_messages=group_permissions['can_pin_messages'] + ) + + # Start and save to satus (bool) + status = await bot.set_chat_permissions(chat_id=message.chat.id, permissions=chat_permissions) + + if status: + await message.answer(f"stickes - {check}.") + +@dp.message_handler(commands=["warn"],commands_prefix="!",hasRights=True) +async def warn_user(message: types.Message): + # Get information + command = await getCommandArgs(message) + reason = getArgument(command.arguments) + + user = command.user + admin = message.from_user + + if (user is None): + await message.answer(( + "Usage:!warn @username reason=None\n" + "Reply to a message or use with username/id.") + ) + return + + # Add warning + database.change_reports(user.user_id, delete=True) + + await message.answer(f"User [{user.first_name}](tg://user?id={user.user_id}) has gotten a warning.", + parse_mode="Markdown") + + database.create_restriction(user.user_id, admin.id, "warn", reason) + +@dp.message_handler(commands=["reload"],commands_prefix="!") +async def reload(message:types.Message): + await utils.check_user_data() + + group = await bot.get_chat(message.chat.id) + group_permissions = dict(group["permissions"]) + + with open("config/roles.json","r") as jsonfile: + data = json.load(jsonfile) + + if group_permissions.keys() != data["group_permissions"].keys(): + await message.answer("Add some permissions to roles.json") + return + + for permission in group_permissions.keys(): + data["group_permissions"][permission] = group_permissions[permission] + + with open("config/roles.json", "w") as jsonfile: + json.dump(data, jsonfile,indent=4) + + await message.answer(f"✅ The synchronization was successful.") + +@dp.message_handler(commands=["srole"],commands_prefix="!",hasRights=True) +async def set_role(message:types.Message): + command = await getCommandArgs(message) + new_role = getArgument(command.arguments) + + roles = config.roles + + user = command.user + admin = database.search_single_member(Member.user_id,message.from_user) + + if (admin is None): + return + + if (user is None) or (new_role is None): + await message.answer(""" + !srole @username/id role(owner,admin,helper,member) +Reply to a message or use with username.""") + return + + if not (new_role in roles["level"].keys()): + await message.answer(f"Role {new_role} not exists.") + return + + if (admin.user_id == user.user_id): + await message.answer("❌ You can't set role yourself.") + return + + if (roles['level'][new_role] > roles['level'][admin.role]): + await message.answer("Your rank is not high enough to change roles.") + return + + database.update_member_data(user.user_id,[Member.role],[new_role]) + + await message.answer(f"{new_role.capitalize()} role set for [{user.first_name}](tg://user?id={user.user_id}).", + parse_mode="Markdown") diff --git a/handlers/groups/user.py b/handlers/groups/user.py new file mode 100644 index 0000000..0ac8b53 --- /dev/null +++ b/handlers/groups/user.py @@ -0,0 +1,104 @@ +from load import bot, dp, types + +import config + +from load import database +from database.models import Member + + +@dp.message_handler(content_types=["new_chat_members"]) +async def welcome_message(message:types.Message): + # User + user = message.from_user + + exists = database.check_data_exists(Member.user_id,user.id) + + if (exists): + await message.answer("Спасибо что вы с нами.") + + if not (exists): + database.register_user(user.id,user.first_name,user.username) + # TODO: translate it + await message.answer(( + f"Привет,{user.first_name}\n" + "Просим ознакомится с [правилами](https://telegra.ph/Pravila-CHata-Open-Source-05-29)\n" + "Советы на 'хороший тон':\n" + "\t\t1.Формулируй свою мысль в 1-2 предложения\n" + "\t\t1.Не задавай [мета](nometa.xyz) вопросы\n"), + parse_mode="Markdown") + + + await message.delete() + +@dp.message_handler(commands=["leave"],chat_type=[types.ChatType.SUPERGROUP]) +async def leave_group(message:types.Message): + user = message.from_user + args = message.text.split() + + # TODO: translate it too + if (len(args) < 1) or not ( ' '.join(args[1:]) == "I UNDERSTAND" ): + await message.answer("Для того чтобы покинуть чат вам нужно ввести /leave I UNDERSTANT!") + return + + database.delete_user(user.id) + + # Ban user and save (bool) + status = await bot.kick_chat_member(chat_id=message.chat.id,user_id=user.id,until_date=None) + + if status: + await message.answer(f"User [{user.first_name}](tg://user?id={user.id}) has laved chat forever.", + parse_mode="Markdown") + +@dp.message_handler(commands=["start","help"],chat_type=[types.ChatType.SUPERGROUP]) +async def start_command_group(message:types.Message): + await message.answer(( + f"Hi,**{message.from_user.first_name}**!\n" + "My commands:\n" + " /help , /start - read the message.\n" + " /me , /bio - member information (if member group)."), + parse_mode="Markdown" + ) + +@dp.message_handler(commands=["bio","me"],chat_type=[types.ChatType.SUPERGROUP]) +async def get_information(message: types.Message): + user = database.search_single_member(Member.user_id,message.from_user.id) + + role_level = config.roles["level"] + + if (user is None): + await message.answer("❌Sorry,you not member group.") + return + + await message.answer(( + f"User:[{user.first_name}](tg://user?id={user.user_id})\n" + f"level:{role_level[user.role]}\n"), + parse_mode="Markdown" + ) + +@dp.message_handler(commands=["report"],replied=True,chat_type=[types.ChatType.SUPERGROUP]) +async def report(message: types.Message): + args = message.text.split() + + if (len(args) != 2): + await message.reply("Please,enter reason.") + return + + reported_user = message.reply_to_message.from_user + reporter_user = message.from_user + reason = args[1] + + # TODO: translate it + msg = ("Жалоба на: [{}](tg://user?id={})\nПожаловался:[{}](tg://user?id={})\nПричина: {}\n{}" + .format(reported_user['first_name'], + reported_user['id'], + reporter_user.first_name, + reporter_user.id, + reason, + message.reply_to_message.link("Link message", as_html=False) + )) + + await bot.send_message(config.telegram_log_chat_id, msg, parse_mode="Markdown") + +@dp.message_handler(content_types=["left_chat_member"]) +async def event_left_chat(message:types.Message): + await message.delete() diff --git a/handlers/private/__init__.py b/handlers/private/__init__.py new file mode 100644 index 0000000..f9b61db --- /dev/null +++ b/handlers/private/__init__.py @@ -0,0 +1 @@ +from . import user diff --git a/handlers/private/user.py b/handlers/private/user.py new file mode 100644 index 0000000..a71dfb7 --- /dev/null +++ b/handlers/private/user.py @@ -0,0 +1,107 @@ +from load import dp,types,database,bot +from database.models import Member + +from aiogram.types import KeyboardButton,ReplyKeyboardMarkup +from aiogram.types.reply_keyboard import ReplyKeyboardRemove + +import config +from keyboards.default import menu + +from aiogram.types import CallbackQuery +from aiogram.dispatcher.filters import Text + +from aiogram.dispatcher.storage import FSMContext +from states.report_message import States + +from keyboards.inline.report_button import report_button +from keyboards.inline.callback_data import report_callback + +@dp.message_handler(commands=["start","help"],chat_type=[types.ChatType.PRIVATE]) +async def start_command_private(message:types.Message): + await message.answer(( + "Hello,**{message.from_user.first_name}**!\n" + "\t\tMy commands:\n" + "\t\t/help , /start - read this message.") + ,parse_mode="Markdown",reply_markup=menu + ) + +# Keyboard +@dp.message_handler(Text(equals=["About Us"])) +async def about_us(message:types.Message): + await message.answer(( + "Moderator bot - an open source project for managing a Telegram group.\n\n" + "Possibilities:\n" + "1. Role system\n" + "2. Simple commands such as !ban, !mute\n" + "3. Convenient sticker/photo disabling with !stickers, !media\n" + "4. Users can report admins.\n" + "5. Admins can give warnings to users.\n" + "\nRelease version:2.5.2\n" + "[Github](https://github.com/hok7z/moderator-bot)"), + parse_mode="Markdown" + ) + + +@dp.message_handler(Text(equals=["Check restrictions"]),state=None) +async def check_for_restrict(message:types.Message): + user = message.from_user + restrictions = database.search_user_restriction(user_id=user.id) + + if (restrictions is None): + await message.answer("✅No restrictions.") + return + + for restriction in restrictions: + callback = report_callback.new(user_id=message.from_user.id) + markup = report_button("✉️ Report restriction",callback) + + await message.answer(f"Restriction\n{restriction.operation}\nReason:{restriction.reason}\nDate:{restriction.date}",reply_markup=markup) + + await States.state1.set() + +@dp.callback_query_handler(text_contains="report_restriction",state=States.state1) +async def report_restriction(call:CallbackQuery,state:FSMContext): + await call.answer(cache_time=60) + + # callback_data = call.data + # restriction_id = callback_data.split(":")[1] + + markup = ReplyKeyboardMarkup(resize_keyboard=True) + cancel = KeyboardButton("❌ Cancel") + markup.add(cancel) + + await state.update_data(restriction_id=restriction_id) + + await call.message.answer("Please,enter your report.",reply_markup=markup) + +@dp.message_handler(state=States.state2) +async def get_message_report(message: types.Message,state:FSMContext): + answer = message.text + + if not ("Cancel" in answer): + + restriction = database.search_user_restriction(message.from_user.id) + + if (restriction is None): + return + + #from_admin = restriction.from_admin + #to_user = restriction.to_user + + reason = restriction.reason + if (not reason): + reason = "No reason" + + await bot.send_message(config.telegram_log_chat_id,( + f"Report on restriction #{restriction_id}\n" + f"From admin:[{from_admin.first_name}](tg://user?id={from_admin.id})\n" + f"To user:[{from_admin.first_name}](tg://user?id={to_user.id})\n" + f"Reason:{reason}\n" + f"Message:{answer}" + ),parse_mode="Markdown") + + await message.answer("Report restriction sended",reply_markup=ReplyKeyboardRemove()) + else: + await message.answer("Operation cancaled",reply_markup=ReplyKeyboardRemove()) + + await state.finish() diff --git a/keyboards/__init__.py b/keyboards/__init__.py new file mode 100644 index 0000000..8abaca9 --- /dev/null +++ b/keyboards/__init__.py @@ -0,0 +1,2 @@ +from . import default +from . import inline diff --git a/keyboards/default/__init__.py b/keyboards/default/__init__.py new file mode 100644 index 0000000..cb8d856 --- /dev/null +++ b/keyboards/default/__init__.py @@ -0,0 +1,2 @@ +from .menu import menu +from .menu import cancel diff --git a/keyboards/default/menu.py b/keyboards/default/menu.py new file mode 100644 index 0000000..138210d --- /dev/null +++ b/keyboards/default/menu.py @@ -0,0 +1,19 @@ +from aiogram.types import ReplyKeyboardMarkup,KeyboardButton + +menu = ReplyKeyboardMarkup( + resize_keyboard=True, + keyboard=[ + [ + KeyboardButton("Check restrictions"), + KeyboardButton("About Us"), + ] +]) + +cancel = ReplyKeyboardMarkup( + resize_keyboard=True, + keyboard=[ + [ + KeyboardButton("❌Cancel") + ] + ] +) diff --git a/keyboards/inline/__init__.py b/keyboards/inline/__init__.py new file mode 100644 index 0000000..82b070f --- /dev/null +++ b/keyboards/inline/__init__.py @@ -0,0 +1 @@ +from . import report_button diff --git a/keyboards/inline/callback_data.py b/keyboards/inline/callback_data.py new file mode 100644 index 0000000..d3be68d --- /dev/null +++ b/keyboards/inline/callback_data.py @@ -0,0 +1,4 @@ +from aiogram.utils.callback_data import CallbackData + + +report_callback = CallbackData("report_restriction","user_id") diff --git a/keyboards/inline/report_button.py b/keyboards/inline/report_button.py new file mode 100644 index 0000000..ebb6365 --- /dev/null +++ b/keyboards/inline/report_button.py @@ -0,0 +1,8 @@ +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup + + +def report_button(text,callback_data): + markup = InlineKeyboardMarkup() + button = InlineKeyboardButton(text,callback_data=callback_data) + markup.insert(button) + return markup diff --git a/load.py b/load.py new file mode 100644 index 0000000..3a880e0 --- /dev/null +++ b/load.py @@ -0,0 +1,33 @@ +import asyncio + +from aiogram import Bot, Dispatcher +from aiogram import types +from aiogram.bot.api import TelegramAPIServer +from aiogram.contrib.fsm_storage.memory import MemoryStorage + +import config +import utils +import filters + +from database.database import Database + + +database = Database() + +loop = asyncio.new_event_loop() +asyncio.set_event_loop(loop) + +storage = MemoryStorage() + +tgc = utils.TelegramClientScrapper(config.api_id, config.api_hash, token=config.token, loop = loop) + +bot = Bot( + token=config.token, + server=TelegramAPIServer.from_base(config.telegram_api_server) +) + +dp = Dispatcher(bot, storage = storage) + +dp.filters_factory.bind(filters.IsAdminFilter) +dp.filters_factory.bind(filters.ReplayMessageFilter) +dp.filters_factory.bind(filters.UserHasRights) diff --git a/media/photo.jpg b/media/photo.jpg new file mode 100644 index 0000000..c907621 Binary files /dev/null and b/media/photo.jpg differ diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..c48862f --- /dev/null +++ b/poetry.lock @@ -0,0 +1,707 @@ +[[package]] +name = "aiogram" +version = "2.21" +description = "Is a pretty simple and fully asynchronous framework for Telegram Bot API" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +aiohttp = ">=3.8.0,<3.9.0" +Babel = ">=2.9.1,<2.10.0" +certifi = ">=2021.10.8" + +[package.extras] +fast = ["uvloop (>=0.16.0,<0.17.0)", "ujson (>=1.35)"] +proxy = ["aiohttp-socks (>=0.5.3,<0.6.0)"] + +[[package]] +name = "aiohttp" +version = "3.8.1" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotli", "cchardet"] + +[[package]] +name = "aioschedule" +version = "0.5.2" +description = "Job scheduling for humans." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "aiosignal" +version = "1.2.0" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "babel" +version = "2.9.1" +description = "Internationalization utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pytz = ">=2015.7" + +[[package]] +name = "certifi" +version = "2022.6.15" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "charset-normalizer" +version = "2.1.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "commonmark" +version = "0.9.1" +description = "Python parser for the CommonMark Markdown spec" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] + +[[package]] +name = "environs" +version = "9.5.0" +description = "simplified environment variable parsing" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +marshmallow = ">=3.0.0" +python-dotenv = "*" + +[package.extras] +dev = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "tox"] +django = ["dj-database-url", "dj-email-url", "django-cache-url"] +lint = ["flake8 (==4.0.1)", "flake8-bugbear (==21.9.2)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"] +tests = ["pytest", "dj-database-url", "dj-email-url", "django-cache-url"] + +[[package]] +name = "frozenlist" +version = "1.3.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "marshmallow" +version = "3.17.0" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["pytest", "pytz", "simplejson", "mypy (==0.961)", "flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "pre-commit (>=2.4,<3.0)", "tox"] +docs = ["sphinx (==4.5.0)", "sphinx-issues (==3.0.1)", "alabaster (==0.7.12)", "sphinx-version-warning (==1.1.2)", "autodocsumm (==0.2.8)"] +lint = ["mypy (==0.961)", "flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "pre-commit (>=2.4,<3.0)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "multidict" +version = "6.0.2" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "peewee" +version = "3.15.1" +description = "a little orm" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "psycopg2" +version = "2.9.3" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pyaes" +version = "1.6.1" +description = "Pure-Python Implementation of the AES block-cipher and common modes of operation" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyasn1" +version = "0.4.8" +description = "ASN.1 types and codecs" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pygments" +version = "2.12.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["railroad-diagrams", "jinja2"] + +[[package]] +name = "python-dotenv" +version = "0.20.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "pytz" +version = "2022.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "12.5.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.6.3,<4.0.0" + +[package.dependencies] +commonmark = ">=0.9.0,<0.10.0" +pygments = ">=2.6.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] + +[[package]] +name = "rsa" +version = "4.9" +description = "Pure-Python RSA implementation" +category = "main" +optional = false +python-versions = ">=3.6,<4" + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[package]] +name = "telethon" +version = "1.24.0" +description = "Full-featured Telegram client library for Python 3" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +pyaes = "*" +rsa = "*" + +[package.extras] +cryptg = ["cryptg"] + +[[package]] +name = "urllib3" +version = "1.26.11" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" + +[package.extras] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "yarl" +version = "1.8.1" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[metadata] +lock-version = "1.1" +python-versions = "^3.10" +content-hash = "cf2aa8bebb164e8dbd85f9e22232813ccaf44fdcd786cb900b459b0feeb738d6" + +[metadata.files] +aiogram = [ + {file = "aiogram-2.21-py3-none-any.whl", hash = "sha256:33ee61db550f6fc455e2d74d8911af31108e3c398eda03c2f91b0a7cb32a97d9"}, + {file = "aiogram-2.21.tar.gz", hash = "sha256:390ac56a629cd0d151d544e3b87d539ee49f734ccc7a1a7e375c33f436e556e0"}, +] +aiohttp = [ + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"}, + {file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"}, + {file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"}, + {file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"}, + {file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"}, + {file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"}, + {file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"}, + {file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"}, + {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, + {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, +] +aioschedule = [ + {file = "aioschedule-0.5.2.tar.gz", hash = "sha256:1fe8621d287f58cbba3d73695fbbd890355294ac0c01981a1fd1e4f0510fc744"}, +] +aiosignal = [ + {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, + {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, +] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +babel = [ + {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, + {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, +] +certifi = [] +charset-normalizer = [] +commonmark = [ + {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, + {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, +] +environs = [ + {file = "environs-9.5.0-py2.py3-none-any.whl", hash = "sha256:1e549569a3de49c05f856f40bce86979e7d5ffbbc4398e7f338574c220189124"}, + {file = "environs-9.5.0.tar.gz", hash = "sha256:a76307b36fbe856bdca7ee9161e6c466fd7fcffc297109a118c59b54e27e30c9"}, +] +frozenlist = [ + {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989"}, + {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204"}, + {file = "frozenlist-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5"}, + {file = "frozenlist-1.3.1-cp310-cp310-win32.whl", hash = "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be"}, + {file = "frozenlist-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db"}, + {file = "frozenlist-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c"}, + {file = "frozenlist-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792"}, + {file = "frozenlist-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2"}, + {file = "frozenlist-1.3.1-cp38-cp38-win32.whl", hash = "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845"}, + {file = "frozenlist-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc"}, + {file = "frozenlist-1.3.1-cp39-cp39-win32.whl", hash = "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b"}, + {file = "frozenlist-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189"}, + {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +marshmallow = [] +multidict = [ + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +peewee = [] +psycopg2 = [ + {file = "psycopg2-2.9.3-cp310-cp310-win32.whl", hash = "sha256:083707a696e5e1c330af2508d8fab36f9700b26621ccbcb538abe22e15485362"}, + {file = "psycopg2-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:d3ca6421b942f60c008f81a3541e8faf6865a28d5a9b48544b0ee4f40cac7fca"}, + {file = "psycopg2-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:9572e08b50aed176ef6d66f15a21d823bb6f6d23152d35e8451d7d2d18fdac56"}, + {file = "psycopg2-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:a81e3866f99382dfe8c15a151f1ca5fde5815fde879348fe5a9884a7c092a305"}, + {file = "psycopg2-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:cb10d44e6694d763fa1078a26f7f6137d69f555a78ec85dc2ef716c37447e4b2"}, + {file = "psycopg2-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4295093a6ae3434d33ec6baab4ca5512a5082cc43c0505293087b8a46d108461"}, + {file = "psycopg2-2.9.3-cp38-cp38-win32.whl", hash = "sha256:34b33e0162cfcaad151f249c2649fd1030010c16f4bbc40a604c1cb77173dcf7"}, + {file = "psycopg2-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:0762c27d018edbcb2d34d51596e4346c983bd27c330218c56c4dc25ef7e819bf"}, + {file = "psycopg2-2.9.3-cp39-cp39-win32.whl", hash = "sha256:8cf3878353cc04b053822896bc4922b194792df9df2f1ad8da01fb3043602126"}, + {file = "psycopg2-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c"}, + {file = "psycopg2-2.9.3.tar.gz", hash = "sha256:8e841d1bf3434da985cc5ef13e6f75c8981ced601fd70cc6bf33351b91562981"}, +] +pyaes = [ + {file = "pyaes-1.6.1.tar.gz", hash = "sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"}, +] +pyasn1 = [ + {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, + {file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"}, + {file = "pyasn1-0.4.8-py2.6.egg", hash = "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00"}, + {file = "pyasn1-0.4.8-py2.7.egg", hash = "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8"}, + {file = "pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"}, + {file = "pyasn1-0.4.8-py3.1.egg", hash = "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86"}, + {file = "pyasn1-0.4.8-py3.2.egg", hash = "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7"}, + {file = "pyasn1-0.4.8-py3.3.egg", hash = "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576"}, + {file = "pyasn1-0.4.8-py3.4.egg", hash = "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12"}, + {file = "pyasn1-0.4.8-py3.5.egg", hash = "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"}, + {file = "pyasn1-0.4.8-py3.6.egg", hash = "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359"}, + {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, + {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, +] +pygments = [ + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +python-dotenv = [ + {file = "python-dotenv-0.20.0.tar.gz", hash = "sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f"}, + {file = "python_dotenv-0.20.0-py3-none-any.whl", hash = "sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"}, +] +pytz = [ + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, +] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] +rich = [] +rsa = [] +telethon = [ + {file = "Telethon-1.24.0-py3-none-any.whl", hash = "sha256:04fdc5fa4ed3e886e6ecf4bad79205ab8880c6aefbd42c29c89c689a502aa816"}, + {file = "Telethon-1.24.0.tar.gz", hash = "sha256:818cb61281ed3f75ba4da9b68cb69486bed9474d2db4e0aa16e482053117452c"}, +] +urllib3 = [] +yarl = [ + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"}, + {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"}, + {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"}, + {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"}, + {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"}, + {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"}, + {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"}, + {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"}, + {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"}, + {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, + {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c7defa7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[tool.poetry] +name = "moderator_bot" +version = "0.1.0" +description = "Telegram bot for moderation of Telegram groups." +authors = ["hok7z "] +license = "GPL3" + +[tool.poetry.dependencies] +python = "^3.10" +aiogram = "^2.20" +peewee = "^3.14.10" +rich = "^12.4.4" +environs = "^9.5.0" +requests = "^2.27.1" +Telethon = "^1.24.0" +psycopg2 = "^2.9.3" +aioschedule = "^0.5.2" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/states/__init__.py b/states/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/states/report_message.py b/states/report_message.py new file mode 100644 index 0000000..d11ac1a --- /dev/null +++ b/states/report_message.py @@ -0,0 +1,6 @@ +from aiogram.dispatcher.filters.state import StatesGroup,State + + +class States(StatesGroup): + state1 = State() + state2 = State() diff --git a/txt/link_shorters.txt b/txt/link_shorters.txt new file mode 100644 index 0000000..9c00e3d --- /dev/null +++ b/txt/link_shorters.txt @@ -0,0 +1,231 @@ +bit.ly +goo.gl +tinyurl.com +is.gd +cli.gs +pic.gd +DwarfURL.com +ow.ly +yfrog.com +migre.me +ff.im +tiny.cc +url4.eu +tr.im +twit.ac +su.pr +twurl.nl +snipurl.com +BudURL.com +short.to +ping.fm +Digg.com +post.ly +Just.as +.tk +bkite.com +snipr.com +flic.kr +loopt.us +doiop.com +twitthis.com +htxt.it +AltURL.com +RedirX.com +DigBig.com +short.ie +u.mavrev.com +kl.am +wp.me +u.nu +rubyurl.com +om.ly +linkbee.com +Yep.it +posted.at +xrl.us +metamark.net +sn.im +hurl.ws +eepurl.com +idek.net +urlpire.com +chilp.it +moourl.com +snurl.com +xr.com +lin.cr +EasyURI.com +zz.gd +ur1.ca +URL.ie +adjix.com +twurl.cc +s7y.us shrinkify +EasyURL.net +atu.ca +sp2.ro +Profile.to +ub0.cc +minurl.fr +cort.as +fire.to +2tu.us +twiturl.de +to.ly +BurnURL.com +nn.nf +clck.ru +notlong.com +thrdl.es +spedr.com +vl.am +miniurl.com +virl.com +PiURL.com +1url.com +gri.ms +tr.my +Sharein.com +urlzen.com +fon.gs +Shrinkify.com +ri.ms +b23.ru +Fly2.ws +xrl.in +Fhurl.com +wipi.es +korta.nu +shortna.me +fa.b +WapURL.co.uk +urlcut.com +6url.com +abbrr.com +SimURL.com +klck.me +x.se +2big.at +url.co.uk +ewerl.com +inreply.to +TightURL.com +a.gg +tinytw.it +zi.pe +riz.gd +hex.io +fwd4.me +bacn.me +shrt.st +ln-s.ru +tiny.pl +o-x.fr +StartURL.com +jijr.com +shorl.com +icanhaz.com +updating.me +kissa.be +hellotxt.com +pnt.me +nsfw.in +xurl.jp +yweb.com +urlkiss.com +QLNK.net +w3t.org +lt.tl +twirl.at +zipmyurl.com +urlot.com +a.nf +hurl.me +URLHawk.com +Tnij.org +4url.cc +firsturl.de +Hurl.it +sturly.com +shrinkster.com +ln-s.net +go2cut.com +liip.to +shw.me +XeeURL.com +liltext.com +lnk.gd +xzb.cc +linkbun.ch +href.in +urlbrief.com +2ya.com +safe.mn +shrunkin.com +bloat.me +krunchd.com +minilien.com +ShortLinks.co.uk +qicute.com +rb6.me +urlx.ie +pd.am +go2.me +tinyarro.ws +tinyvid.io +lurl.no +ru.ly +lru.jp +rickroll.it +togoto.us +ClickMeter.com +hugeurl.com +tinyuri.ca +shrten.com +shorturl.com +Quip-Art.com +urlao.com +a2a.me +tcrn.ch +goshrink.com +DecentURL.com +decenturl.com +zi.ma +1link.in +sharetabs.com +shoturl.us +fff.to +hover.com +lnk.in +jmp2.net +dy.fi +urlcover.com +2pl.us +tweetburner.com +u6e.de +xaddr.com +gl.am +dfl8.me +go.9nl.com +gurl.es +C-O.IN +TraceURL.com +liurl.cn +MyURL.in +urlenco.de +ne1.net +buk.me +rsmonkey.com +cuturl.com +turo.us +sqrl.it +iterasi.net +tiny123.com +EsyURL.com +urlx.org +IsCool.net +twitterpan.com +GoWat.ch +poprl.com +njx.me diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..833675a --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1,9 @@ +from .notify_start import notify_started_bot +from .default_commands import set_default_commands + +from .update_user_data import check_user_data + +from .telegram_client import TelegramClientScrapper +from .parse_timedelta import parse_timedelta + +from .virustotal import VirusTotalAPI diff --git a/utils/default_commands.py b/utils/default_commands.py new file mode 100644 index 0000000..94567f4 --- /dev/null +++ b/utils/default_commands.py @@ -0,0 +1,6 @@ +async def set_default_commands(dp): + from load import types + await dp.bot.set_my_commands([ + types.BotCommand("start","Start bot"), + types.BotCommand("help","Help") + ]) diff --git a/utils/notify_start.py b/utils/notify_start.py new file mode 100644 index 0000000..6697043 --- /dev/null +++ b/utils/notify_start.py @@ -0,0 +1,4 @@ +import config + +async def notify_started_bot(bot): + await bot.send_message(config.telegram_log_chat_id,"Bot started!") diff --git a/utils/parse_timedelta.py b/utils/parse_timedelta.py new file mode 100644 index 0000000..3491762 --- /dev/null +++ b/utils/parse_timedelta.py @@ -0,0 +1,12 @@ +import re +import datetime as dt +from typing import Union + +def parse_timedelta(specification: str) -> Union[None, dt.timedelta]: + specification = specification.strip().replace(' ', '') + match = re.fullmatch(r'(?:(\d+)(?:d|д))?(?:(\d+)(?:h|ч))?(?:(\d+)(?:m|м))?(?:(\d+)(?:s|с))?', specification) + if match: + units = [(0 if i is None else int(i)) for i in match.groups()] + return dt.timedelta(days=units[0], hours=units[1], minutes=units[2], seconds=units[3]) + else: + return None diff --git a/utils/telegram_client.py b/utils/telegram_client.py new file mode 100644 index 0000000..74477d5 --- /dev/null +++ b/utils/telegram_client.py @@ -0,0 +1,61 @@ +from telethon import TelegramClient +from telethon.errors import SessionPasswordNeededError +from telethon.tl.functions.channels import GetParticipantsRequest +from telethon.tl.types import ChannelParticipantsSearch +from telethon.tl.types import PeerChannel + + +class TelegramClientScrapper: + def __init__(self, api_id, api_hash, phone=None, token=None, loop=None): + self.api_id = api_id + self.api_hash = api_hash + self.phone = phone + self.loop = loop + self.token = token + + async def _connect(self): + self.client = TelegramClient("session", self.api_id, self.api_hash, loop=self.loop) + await self.client.start(bot_token=self.token) + if not await self.client.is_user_authorized(): + await self.client.send_code_request(self.phone) + try: + await self.client.sign_in(self.phone, input("Enter you just recieved:")) + except SessionPasswordNeededError: + await self.client.sign_in(password=input("Enter password:")) + + async def get_group_users(self, group_id): + + chat_entity = PeerChannel(int(group_id)) + + offset = 0 + limit = 100 + list_participants = [] + + + while True: + participants = await self.client(GetParticipantsRequest( + chat_entity, ChannelParticipantsSearch(''), offset, limit, + hash=0 + )) + + if (not participants.users): + break + + list_participants.extend(participants.users) + offset += len(participants.users) + + participants_details = [] + for participant in list_participants: + is_bot = participant.bot + user_name = participant.username + if (user_name): + user_name = f"@{user_name}" + + if (not is_bot): + participants_details.append({ + "id": participant.id, + "first_name": participant.first_name, + "user_name":user_name + }) + + return participants_details diff --git a/utils/update_user_data.py b/utils/update_user_data.py new file mode 100644 index 0000000..7ca9083 --- /dev/null +++ b/utils/update_user_data.py @@ -0,0 +1,37 @@ +from database.models import Member +from config import group_id + +async def __is_group_owner(user_id): + from load import bot + member = await bot.get_chat_member(group_id,user_id) + return member.is_chat_owner() + +async def check_user_data(): + """Check user data in database and update it""" + from load import tgc,database + users = await tgc.get_group_users(group_id) + + for user in users: + user_exists = database.check_data_exists(Member.user_id,user["id"]) + + role = "member" + if (await __is_group_owner(user["id"])):role = "owner" + + if (not user_exists): + user_name = user["user_name"] + + if (user_name): + user_name = f"@{user_name}" + + database.register_user( + user["id"], + user["first_name"], + user["user_name"], + role, + ) + + else: + database.update_member_data(user["id"], + [Member.first_name,Member.user_name], + [user["first_name",user["user_name"]]] + ) diff --git a/utils/virustotal.py b/utils/virustotal.py new file mode 100644 index 0000000..32b7700 --- /dev/null +++ b/utils/virustotal.py @@ -0,0 +1,77 @@ +import io +from typing import Union,Any +import aiohttp + +# TODO: skip queue virustotal +class VirusTotalAPI: + def __init__(self,apikey:str,local_telegram_api:bool): + self.apikey = apikey + self.local_telegram_api = local_telegram_api + + async def __download_file(self,filepath:str, + *args,**kw) -> Union[io.BytesIO,Any]: + + if ( self.local_telegram_api ): + with open(filepath,'rb') as bf: + return io.BytesIO(bf.read()) + else: + from load import bot + return await bot.download_file(filepath, + *args,**kw) + + async def __file_scan(self,filepath) -> None: + file = await self.__download_file(filepath) + + url = "https://www.virustotal.com/vtapi/v2/file/scan" + params = {"apikey":self.apikey,"file":file} + + async with aiohttp.ClientSession() as session: + response = await session.post(url,data=params) + response = await response.json() + + return response["sha1"] + + async def __file_report(self,resource) -> dict: + url = "https://www.virustotal.com/vtapi/v2/file/report" + params = {"apikey":self.apikey,"resource":resource} + + async with aiohttp.ClientSession() as session: + response = await session.get(url,params=params) + response = await response.json() + + return response + + def format_output(self,file_report:dict) -> str: + """Format file_report + File Analys + Status:Infected/Clear + Positives:positives/total percent% + File Report + """ + + total = file_report["total"] + positives = file_report["positives"] + permalink = file_report["permalink"] + percent = round(positives/total*100) + + if (percent >= 40): + status = "Infected ☣️" + else: + status = "Clear ✅" + + output = ( + ( + "File Analys\n" + f"Detected:{positives}/{total} %{percent}\n" + f"Status:{status}\n" + f"[File Report]({permalink})\n" + ) + ) + + return output + + async def scan_file(self,filepath:str) -> str: + resource = await self.__file_scan(filepath) + file_report = await self.__file_report(resource) + + return file_report