From c4966f772a75bb39a294cea85ba3e0a856352110 Mon Sep 17 00:00:00 2001 From: xditya <58950863+xditya@users.noreply.github.com> Date: Mon, 22 Feb 2021 06:19:43 +0530 Subject: [PATCH] Initial Commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Aditya Co-Authored-By: Amit Sharma <48654350+buddhhu@users.noreply.github.com> Co-Authored-By: hellboi_atul <68107352+hellboi-atul@users.noreply.github.com> Co-Authored-By: Sρι∂у <68327188+sppidy@users.noreply.github.com> Co-Authored-By: Anonymous <69723581+New-dev0@users.noreply.github.com> Co-Authored-By: Danish <72792730+1Danish-00@users.noreply.github.com> Co-Authored-By: Arnab Paryali Co-Authored-By: Programming Error <75001577+programmingerror@users.noreply.github.com> --- .env.sample | 19 + .gitignore | 13 + Dockerfile | 11 + LICENSE | 661 +++++++++++++++++++++++++++++ README.md | 53 +++ app.json | 53 +++ assistant/__init__.py | 20 + assistant/api_setter.py | 43 ++ assistant/customvars.py | 94 ++++ assistant/inlinestuff.py | 286 +++++++++++++ assistant/othervars.py | 120 ++++++ assistant/ping.py | 20 + assistant/pmbot/__init__.py | 6 + assistant/pmbot/banuser.py | 40 ++ assistant/pmbot/incoming.py | 32 ++ assistant/pmbot/outgoing.py | 43 ++ assistant/start.py | 113 +++++ assistant/ytdl.py | 197 +++++++++ heroku.yml | 3 + plugins/__init__.py | 98 +++++ plugins/_help.py | 66 +++ plugins/_inline.py | 503 ++++++++++++++++++++++ plugins/_tagnotif.py | 51 +++ plugins/_wspr.py | 169 ++++++++ plugins/admintools.py | 420 ++++++++++++++++++ plugins/afk.py | 252 +++++++++++ plugins/bot.py | 232 ++++++++++ plugins/carbon.py | 235 ++++++++++ plugins/chats.py | 117 +++++ plugins/core.py | 175 ++++++++ plugins/github.py | 70 +++ plugins/google.py | 120 ++++++ plugins/imagetools.py | 468 ++++++++++++++++++++ plugins/pdftools.py | 295 +++++++++++++ plugins/pmpermit.py | 258 +++++++++++ plugins/profile.py | 163 +++++++ plugins/redis.py | 115 +++++ plugins/specialtools.py | 246 +++++++++++ plugins/stickertools.py | 524 +++++++++++++++++++++++ plugins/sudo.py | 194 +++++++++ plugins/superfban.py | 328 ++++++++++++++ plugins/tools.py | 411 ++++++++++++++++++ plugins/updater.py | 178 ++++++++ plugins/uploads_files.py | 175 ++++++++ plugins/utilities.py | 583 +++++++++++++++++++++++++ plugins/words.py | 145 +++++++ requirements.txt | 31 ++ resources/downloads/.atoz | 1 + resources/extras/logo_rdm.png | Bin 0 -> 14068 bytes resources/extras/redistut.md | 16 + resources/extras/thumb.jpg | Bin 0 -> 43352 bytes resources/extras/ultroid.jpg | Bin 0 -> 52867 bytes resources/extras/ultroid_blank.png | Bin 0 -> 889 bytes resources/fonts/2.ttf | Bin 0 -> 21872 bytes resources/fonts/default.ttf | Bin 0 -> 35340 bytes resources/session/session.sh | 32 ++ resources/session/ssgen.py | 24 ++ resources/startup/deploy.sh | 50 +++ resources/startup/startup.sh | 19 + sessiongen | 7 + 60 files changed, 8598 insertions(+) create mode 100644 .env.sample create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app.json create mode 100644 assistant/__init__.py create mode 100644 assistant/api_setter.py create mode 100644 assistant/customvars.py create mode 100644 assistant/inlinestuff.py create mode 100644 assistant/othervars.py create mode 100644 assistant/ping.py create mode 100644 assistant/pmbot/__init__.py create mode 100644 assistant/pmbot/banuser.py create mode 100644 assistant/pmbot/incoming.py create mode 100644 assistant/pmbot/outgoing.py create mode 100644 assistant/start.py create mode 100644 assistant/ytdl.py create mode 100644 heroku.yml create mode 100644 plugins/__init__.py create mode 100644 plugins/_help.py create mode 100644 plugins/_inline.py create mode 100644 plugins/_tagnotif.py create mode 100644 plugins/_wspr.py create mode 100644 plugins/admintools.py create mode 100644 plugins/afk.py create mode 100644 plugins/bot.py create mode 100644 plugins/carbon.py create mode 100644 plugins/chats.py create mode 100644 plugins/core.py create mode 100644 plugins/github.py create mode 100644 plugins/google.py create mode 100644 plugins/imagetools.py create mode 100644 plugins/pdftools.py create mode 100644 plugins/pmpermit.py create mode 100644 plugins/profile.py create mode 100644 plugins/redis.py create mode 100644 plugins/specialtools.py create mode 100644 plugins/stickertools.py create mode 100644 plugins/sudo.py create mode 100644 plugins/superfban.py create mode 100644 plugins/tools.py create mode 100644 plugins/updater.py create mode 100644 plugins/uploads_files.py create mode 100644 plugins/utilities.py create mode 100644 plugins/words.py create mode 100644 requirements.txt create mode 100644 resources/downloads/.atoz create mode 100644 resources/extras/logo_rdm.png create mode 100644 resources/extras/redistut.md create mode 100644 resources/extras/thumb.jpg create mode 100644 resources/extras/ultroid.jpg create mode 100644 resources/extras/ultroid_blank.png create mode 100644 resources/fonts/2.ttf create mode 100644 resources/fonts/default.ttf create mode 100644 resources/session/session.sh create mode 100644 resources/session/ssgen.py create mode 100644 resources/startup/deploy.sh create mode 100644 resources/startup/startup.sh create mode 100644 sessiongen diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000000..ab344aa2f6 --- /dev/null +++ b/.env.sample @@ -0,0 +1,19 @@ +# Don't use quotes( " and ' ) +# e.g : HNDLR=. +# Leave ENV as it is. + +ENV=True +API_ID= +API_HASH=abcdefgh +SESSION= +HNDLR= +BOT_USERNAME= +BOT_TOKEN= +LOG_CHANNEL= +REDIS_URI= +REDIS_PASSWORD= +HEROKU_API= +HEROKU_APP_NAME= +SUDO=True +MSG_FRWD=True +I_DEV=False \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..5f03c411e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.env +venv/ +__pycache__ +BOT_TOKEN.session-journal +BOT_TOKEN.session +*.mp3 +*.webm +*.webp +*.mp4 +*.tgs +logs-ultroid.txt +.vscode/* +ultroid-log.txt \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..6c268104dc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in . + +FROM python:3.9.2 +RUN chmod +x /usr/local/bin/* +RUN wget https://del.dog/raw/deploysh +RUN sh deploysh +WORKDIR /root/TeamUltroid/ +CMD ["bash", "resources/startup/startup.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..29ebfa545f --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000..6fb600f551 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# Ultroid - UserBot +A stable pluggable Telegram userbot, based on Telethon. + +

+ TeamUltroid +

+ +[![Stars](https://img.shields.io/github/stars/TeamUltroid/Ultroid?style=social)](https://github.com/TeamUltroid/Ultroid/stargazers) +[![Forks](https://img.shields.io/github/forks/TeamUltroid/Ultroid?style=social)](https://github.com/TeamUltroid/Ultroid/fork) +[![Python Version](https://img.shields.io/badge/Python-v3.9-blue)](https://www.python.org/) +[![Contributors](https://img.shields.io/github/contributors/TeamUltroid/Ultroid)](https://github.com/TeamUltroid/Ultroid/graphs/contributors) +[![License](https://img.shields.io/badge/License-AGPL-blue)](https://github.com/TeamUltroid/Ultroid/blob/main/LICENSE) +[![Size](https://img.shields.io/github/repo-size/TeamUltroid/Ultroid)](https://github.com/TeamUltroid/Ultroid/) + +
+More Info +
+ Documentation soon..
+
+ +# Deploy +- [Heroku](https://github.com/TeamUltroid/Ultroid#Deploy-to-Heroku) +- [Local Machine](https://github.com/TeamUltroid/Ultroid#Deploy-Locally) + +## Deploy to Heroku +- Get your `API_ID` and `API_HASH` from [here](https://my.telegram.org/) and click the below button!
+ +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) + +## Deploy Locally +- Get your `API_ID` and `API_HASH` from [here](https://my.telegram.org/) +- Get your `REDIS_URI` and `REDIS_PASSWORD` from [here](https://redislabs.com), tutorial [here](./resources/extras/redistut.md). +- Clone the repository:
+`git clone https://github.com/TeamUltroid/Ultroid.git` +- Go to the cloned folder:
+`cd Ultroid` +- Create a virtual env:
+`virtualenv -p /usr/bin/python3 venv` +`. ./venv/bin/activate` +- Install the requirements:
+`pip install -r requirements.txt` +- Generate your `SESSION`: +`bash sessiongen` +- Fill your details in a `.env` file, as given in [`.env.sample`](https://github.com/TeamUltroid/Ultroid/blob/main/.env.sample). +(You can either edit and rename the file or make a new file.) +- Run the bot: +`bash resources/startup/startup.sh` + +Made with 💕 by [@TeamUltroid](https://t.me/TeamUltroid).
+ +# Credits +* [Lonami](https://github.com/LonamiWebs/) for [Telethon](https://github.com/LonamiWebs/Telethon) + diff --git a/app.json b/app.json new file mode 100644 index 0000000000..ac570caa52 --- /dev/null +++ b/app.json @@ -0,0 +1,53 @@ +{ + "name": "Ultroid UserBot", + "description": "Pluggable telegram userbot, made in python using Telethon.", + "logo": "https://telegra.ph/file/1518e15421d21c5afd9af.jpg", + "keywords": ["Telethon", "telegram", "userbot", "python"], + "repository": "https://github.com/TeamUltroid/Ultroid", + "website": "https://ultroid.now.sh/", + "success_url": "https://t.me/TheUltroid", + "stack": "container", + "env": { + "API_ID": { + "description": "You api id, from my.telegram.org or @ScrapperRoBot.", + "value": "" + }, + "API_HASH": { + "description": "You api hash, from my.telegram.org or @ScrapperRoBot.", + "value": "" + }, + "BOT_USERNAME": { + "description": "Make a bot from @BotFather, and enter it's username here, with '@'", + "value": "" + }, + "BOT_TOKEN": { + "description": "Make a bot from @BotFather, and enter it's api token here.", + "value": "" + }, + "SESSION": { + "description": "Your session string. Can be added now, or after deploy. (The bot will NOT work without a session string!!)", + "value": "" + }, + "REDIS_URI": { + "description": "Redis endpoint URL, from redislabs.com", + "value": "" + }, + "REDIS_PASSWORD": { + "description": "Redis endpoint password, from redislabs.com", + "value": "" + }, + "HEROKU_API": { + "description": "Heroku API token. Needed if deploying on heroku ONLY.", + "value": "", + "required": false + }, + "HEROKU_APP_NAME": { + "description": "Name of your heroku app, given in the first blank on this page. To be added if deploying to heroku ONLY.", + "value": "" + }, + "LOG_CHANNEL": { + "description": "Create a private group. Add @missrose_bot and your BOT_USERNAME bot. Do /id. Paste that here", + "value": "" + } + } +} diff --git a/assistant/__init__.py b/assistant/__init__.py new file mode 100644 index 0000000000..41dbcf4dfe --- /dev/null +++ b/assistant/__init__.py @@ -0,0 +1,20 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from pyUltroid import * +from pyUltroid.dB.database import Var +from telethon import Button, custom + +OWNER_NAME = ultroid_bot.me.first_name +OWNER_ID = ultroid_bot.me.id + + +async def setit(event, name, value): + try: + udB.set(name, value) + except BaseException: + return await event.edit("`Something Went Wrong`") diff --git a/assistant/api_setter.py b/assistant/api_setter.py new file mode 100644 index 0000000000..b967ca4442 --- /dev/null +++ b/assistant/api_setter.py @@ -0,0 +1,43 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import * + +# main menu for api setting + + +@callback("apiset") +@owner +async def apiset(event): + await event.edit( + "Choose which API you want to set.", + buttons=[[Button.inline("Remove.bg", data="rmbg")]], + ) + + +# remove.bg api + + +@callback("rmbg") +@owner +async def rmbgapi(event): + await event.delete() + pru = event.sender_id + var = "RMBG_API" + name = "Remove.bg API Key" + async with event.client.conversation(pru) as conv: + await conv.send_message( + "**remove.bg API**\nEnter your API key from remove.bg.\n\nUse /cancel to terminate the operation." + ) + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + return await conv.send_message("Cancelled!!") + else: + await setit(event, var, themssg) + await conv.send_message("{} changed to {}".format(name, themssg)) diff --git a/assistant/customvars.py b/assistant/customvars.py new file mode 100644 index 0000000000..d0a9fcb3a3 --- /dev/null +++ b/assistant/customvars.py @@ -0,0 +1,94 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import os + +from telegraph import Telegraph, upload_file + +from . import * + +# --------------------------------------------------------------------# +telegraph = Telegraph() +r = telegraph.create_account(short_name="Ultroid") +auth_url = r["auth_url"] +# --------------------------------------------------------------------# + + +@callback("alvcstm") +@owner +async def alvcs(event): + await event.edit( + "Customise your {}alive. Choose from the below options -".format(Var.HNDLR), + buttons=[ + [Button.inline("Alive Text", data="alvtx")], + [Button.inline("Alive Media", data="alvmed")], + [Button.inline("Delete Alive Media", data="delmed")], + ], + ) + + +@callback("alvtx") +@owner +async def name(event): + await event.delete() + pru = event.sender_id + var = "ALIVE_TEXT" + name = "Alive Text" + async with event.client.conversation(pru) as conv: + await conv.send_message( + "**Alive Text**\nEnter the new alive text.\n\nUse /cancel to terminate the operation." + ) + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + return await conv.send_message("Cancelled!!") + else: + await setit(event, var, themssg) + await conv.send_message("{} changed to {}".format(name, themssg)) + + +@callback("alvmed") +@owner +async def media(event): + await event.delete() + pru = event.sender_id + var = "ALIVE_PIC" + name = "Alive Media" + async with event.client.conversation(pru) as conv: + await conv.send_message( + "**Alive Media**\nSend me a pic/gif/bot api id of sticker to set as alive media.\n\nUse /cancel to terminate the operation." + ) + response = await conv.get_response() + try: + themssg = response.message.message + if themssg == "/cancel": + return await conv.send_message("Operation cancelled!!") + except BaseException: + pass + media = await event.client.download_media(response, "alvpc") + if not (response.text).startswith("/") and not response.text == "": + url = response.text + else: + try: + x = upload_file(media) + url = f"https://telegra.ph/{x[0]}" + os.remove(media) + except BaseException: + return await conv.send_message("Terminated.") + await setit(event, var, url) + await conv.send_message("{} has been set.".format(name)) + + +@callback("delmed") +@owner +async def dell(event): + try: + udB.delete("ALIVE_PIC") + return await event.edit("Done!") + except BaseException: + return await event.edit("Something went wrong...") diff --git a/assistant/inlinestuff.py b/assistant/inlinestuff.py new file mode 100644 index 0000000000..52401bf1e5 --- /dev/null +++ b/assistant/inlinestuff.py @@ -0,0 +1,286 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import random +import re +from urllib.request import urlopen + +import play_scraper +import requests +from bs4 import BeautifulSoup +from search_engine_parser import GoogleSearch, YahooSearch +from telethon import Button +from telethon.tl.types import InputWebDocument as wb + +gugirl = "https://telegra.ph/file/0df54ae4541abca96aa11.jpg" +yeah = "https://telegra.ph/file/e3c67885e16a194937516.jpg" +ps = "https://telegra.ph/file/de0b8d9c858c62fae3b6e.jpg" + + +@in_pattern("go") +@in_owner +async def gsearch(q_event): + try: + match = q_event.text.split(" ", maxsplit=1)[1] + except IndexError: + kkkk = q_event.builder.article( + title="Search Something", + thumb=wb( + gugirl, + 0, + "image/jpeg", + []), + text="**Gᴏᴏɢʟᴇ Sᴇᴀʀᴄʜ**\n\nYou didn't search anything", + buttons=Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", + query="go ", + same_peer=True), + ) + await q_event.answer([kkkk]) + searcher = [] + page = re.findall(r"page=\d+", match) + try: + page = page[0] + page = page.replace("page=", "") + match = match.replace("page=" + page[0], "") + except IndexError: + page = 1 + search_args = (str(match), int(page)) + gsearch = GoogleSearch() + gresults = await gsearch.async_search(*search_args) + msg = "" + for i in range(len(gresults["links"])): + try: + title = gresults["titles"][i] + link = gresults["links"][i] + desc = gresults["descriptions"][i] + msg += f"👉[{title}]({link})\n`{desc}`\n\n" + searcher.append( + await q_event.builder.article( + title=title, + description=desc, + thumb=wb(gugirl, 0, "image/jpeg", []), + text=f"**Gᴏᴏɢʟᴇ Sᴇᴀʀᴄʜ**\n\n**••Tɪᴛʟᴇ••**\n`{title}`\n\n**••Dᴇsᴄʀɪᴘᴛɪᴏɴ••**\n`{desc}`", + link_preview=False, + buttons=[ + [Button.url("Lɪɴᴋ", url=f"{link}")], + [ + Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", query="go ", same_peer=True + ), + Button.switch_inline( + "Sʜᴀʀᴇ", query=f"go {match}", same_peer=False + ), + ], + ], + ) + ) + except IndexError: + break + await q_event.answer(searcher) + + +@in_pattern("yahoo") +@in_owner +async def gsearch(q_event): + try: + match = q_event.text.split(" ", maxsplit=1)[1] + except IndexError: + kkkk = q_event.builder.article( + title="Search Something", + thumb=wb(yeah, 0, "image/jpeg", []), + text="**Yᴀʜᴏᴏ Sᴇᴀʀᴄʜ**\n\nYou didn't search anything", + buttons=Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", query="yahoo ", same_peer=True + ), + ) + await q_event.answer([kkkk]) + searcher = [] + page = re.findall(r"page=\d+", match) + try: + page = page[0] + page = page.replace("page=", "") + match = match.replace("page=" + page[0], "") + except IndexError: + page = 1 + search_args = (str(match), int(page)) + gsearch = YahooSearch() + gresults = await gsearch.async_search(*search_args) + msg = "" + for i in range(len(gresults["links"])): + try: + title = gresults["titles"][i] + link = gresults["links"][i] + desc = gresults["descriptions"][i] + msg += f"👉[{title}]({link})\n`{desc}`\n\n" + searcher.append( + await q_event.builder.article( + title=title, + description=desc, + thumb=wb(yeah, 0, "image/jpeg", []), + text=f"**Yᴀʜᴏᴏ Sᴇᴀʀᴄʜ**\n\n**••Tɪᴛʟᴇ••**\n`{title}`\n\n**••Dᴇsᴄʀɪᴘᴛɪᴏɴ••**\n`{desc}`", + link_preview=False, + buttons=[ + [Button.url("Lɪɴᴋ", url=f"{link}")], + [ + Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", query="yahoo ", same_peer=True + ), + Button.switch_inline( + "Sʜᴀʀᴇ", query=f"yahoo {match}", same_peer=False + ), + ], + ], + ) + ) + except IndexError: + break + await q_event.answer(searcher) + + +@in_pattern("app") +@in_owner +async def _(e): + try: + f = e.text.split(" ", maxsplit=1)[1] + except IndexError: + kkkk = e.builder.article( + title="Search Something", + thumb=wb( + ps, + 0, + "image/jpeg", + []), + text="**Pʟᴀʏ Sᴛᴏʀᴇ**\n\nYou didn't search anything", + buttons=Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", + query="app ", + same_peer=True), + ) + await e.answer([kkkk]) + foles = [] + aap = play_scraper.search(f) + for z in aap: + name = z["title"] + desc = z["description"] + price = z["price"] + dev = z["developer"] + icon = z["icon"] + url = z["url"] + ids = z["app_id"] + text = f"**••Aᴘᴘ Nᴀᴍᴇ••** [{name}]({icon})\n" + text += f"**••Dᴇᴠᴇʟᴏᴘᴇʀ••** `{dev}`\n" + text += f"**••Pʀɪᴄᴇ••** `{price}`\n\n" + text += f"**••Dᴇsᴄʀɪᴘᴛɪᴏɴ••**\n`{desc}`" + foles.append( + await e.builder.article( + title=name, + description=ids, + thumb=wb(ps, 0, "image/jpeg", []), + text=text, + link_preview=True, + buttons=[ + [Button.url("Lɪɴᴋ", url=f"https://play.google.com{url}")], + [ + Button.switch_inline( + "Mᴏʀᴇ Aᴘᴘs", + query="app ", + same_peer=True, + ), + Button.switch_inline( + "Sʜᴀʀᴇ", + query=f"app {f}", + same_peer=False, + ), + ], + ], + ), + ) + await e.answer(foles) + + +@in_pattern("mods") +@in_owner +async def _(e): + try: + quer = e.text.split(" ", maxsplit=1)[1] + except IndexError: + kkkk = e.builder.article( + title="Search Something", + text="**Mᴏᴅᴅᴇᴅ Aᴘᴘs**\n\nYou didn't search anything", + buttons=Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", + query="mods ", + same_peer=True), + ) + await e.answer([kkkk]) + page = 1 + start = (page - 1) * 3 + 1 + urd = random.randrange(1, 3) + if urd == 1: + da = "AIzaSyAyDBsY3WRtB5YPC6aB_w8JAy6ZdXNc6FU" + if urd == 2: + da = "AIzaSyBF0zxLlYlPMp9xwMQqVKCQRq8DgdrLXsg" + if urd == 3: + da = "AIzaSyDdOKnwnPwVIQ_lbH5sYE4FoXjAKIQV0DQ" + url = f"https://www.googleapis.com/customsearch/v1?key={da}&cx=25b3b50edb928435b&q={quer}&start={start}" + data = requests.get(url).json() + search_items = data.get("items") + play_scraper.search(quer) + modss = [] + for a in search_items: + title = a.get("title") + desc = a.get("snippet") + link = a.get("link") + text = f"**••Tɪᴛʟᴇ••** `{title}`\n\n" + text += f"**Dᴇsᴄʀɪᴘᴛɪᴏɴ** `{desc}`" + modss.append( + await e.builder.article( + title=title, + description=desc, + text=text, + link_preview=True, + buttons=[ + [Button.url("Dᴏᴡɴʟᴏᴀᴅ", url=f"{link}")], + [ + Button.switch_inline( + "Mᴏʀᴇ Mᴏᴅs", query="mods ", same_peer=True + ), + Button.switch_inline( + "Sʜᴀʀᴇ", query=f"mods {quer}", same_peer=False + ), + ], + ], + ) + ) + await e.answer(modss) + + +@in_pattern("clipart") +@in_owner +async def clip(e): + try: + quer = e.text.split(" ", maxsplit=1)[1] + except IndexError: + kkkk = e.builder.article( + title="Search Something", + text="**Cʟɪᴘᴀʀᴛ Sᴇᴀʀᴄʜ**\n\nYou didn't search anything", + buttons=Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", query="clipart ", same_peer=True + ), + ) + await e.answer([kkkk]) + quer = quer.replace(" ", "+") + sear = f"https://clipartix.com/search/{quer}" + html = urlopen(sear) + bs = BeautifulSoup(html, "lxml", from_encoding="utf-8") + resul = bs.find_all("img", "attachment-full size-full") + buil = e.builder + hm = [] + for res in resul: + hm += [buil.photo(include_media=True, file=res["src"])] + await e.answer(hm, gallery=True) diff --git a/assistant/othervars.py b/assistant/othervars.py new file mode 100644 index 0000000000..bf89c8a1b4 --- /dev/null +++ b/assistant/othervars.py @@ -0,0 +1,120 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from . import * + +# main menuu for setting other vars + + +@callback("otvars") +@owner +async def otvaar(event): + await event.edit( + "Other Variables to set for @TheUltroid:", + buttons=[ + [Button.inline("Tag Logger", data="taglog")], + [Button.inline("PM Permit", data="pmset")], + [Button.inline("SuperFban", data="sfban")] + ], + ) + + +@callback("taglog") +@owner +async def tagloggerr(event): + await event.delete() + pru = event.sender_id + var = "TAG_LOG" + name = "Tag Log Group" + async with event.client.conversation(pru) as conv: + await conv.send_message( + f"Make a group, add your assistant and make it admin.\nGet the `{hndlr}id` of that group and send it here for tag logs.\n\nUse /cancel to cancel." + ) + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + return await conv.send_message("Cancelled!!") + else: + await setit(event, var, themssg) + await conv.send_message("{} changed to {}".format(name, themssg)) + + +@callback("pmset") +@owner +async def pmset(event): + await event.edit( + "PMPermit Settings:", + buttons=[ + [Button.inline("Turn PMPermit On", data="pmon")], + [Button.inline("Turn PMPermit Off", data="pmoff")], + ], + ) + + +@callback("pmon") +@owner +async def pmonn(event): + var = "PMSETTING" + await setit(event, var, "True") + await event.edit(f"Done! PMPermit has been turned on!! Please `{hndlr}restart`") + + +@callback("pmoff") +@owner +async def pmofff(event): + var = "PMSETTING" + await setit(event, var, "False") + await event.edit(f"Done! PMPermit has been turned off!! Please `{hndlr}restart`") + + +@callback("sfban") +@owner +async def sfban(event): + await event.edit("SuperFban Settings:", + buttons=[ + [Button.inline("FBan Group", data="sfgrp")], + [Button.inline("Exclude Feds", data="sfexf")] + ]) + + +@callback("sfgrp") +@owner +async def sfgrp(event): + await event.delete() + name = "FBan Group ID" + var = "FBAN_GROUP_ID" + pru = event.sender_id + async with asst.conversation(pru) as conv: + await conv.send_message(f"Make a group, add @MissRose_Bot, send `{hndlr}id`, copy that and send it here.\nUse /cancel to go back.") + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + return await conv.send_message("Cancelled!!") + else: + await setit(event, var, themssg) + await conv.send_message("{} changed to {}".format(name, themssg)) + + +@callback("sfexf") +@owner +async def sfexf(event): + await event.delete() + name = "Excluded Feds" + var = "EXCLUDE_FED" + pru = event.sender_id + async with asst.conversation(pru) as conv: + await conv.send_message(f"Send the Fed IDs you want to exclude in the ban. Split by a space.\neg`id1 id2 id3`\nSet is as `None` if you dont want any.\nUse /cancel to go back.") + response = conv.wait_event(events.NewMessage(chats=pru)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + return await conv.send_message("Cancelled!!") + else: + await setit(event, var, themssg) + await conv.send_message("{} changed to {}".format(name, themssg)) diff --git a/assistant/ping.py b/assistant/ping.py new file mode 100644 index 0000000000..1b4bbddb64 --- /dev/null +++ b/assistant/ping.py @@ -0,0 +1,20 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from datetime import datetime + + +@asst_cmd("ping") +@owner +async def _(event): + start = datetime.now() + end = datetime.now() + ms = (end - start).microseconds / 1000 + await asst.send_message( + event.chat_id, + f"**Pong!!**\n `{ms}ms`", + ) diff --git a/assistant/pmbot/__init__.py b/assistant/pmbot/__init__.py new file mode 100644 index 0000000000..b8e9749d48 --- /dev/null +++ b/assistant/pmbot/__init__.py @@ -0,0 +1,6 @@ +from pyUltroid.functions.asst_fns import * + +from .. import * + +OWNER_NAME = ultroid_bot.me.first_name +OWNER_ID = ultroid_bot.me.id diff --git a/assistant/pmbot/banuser.py b/assistant/pmbot/banuser.py new file mode 100644 index 0000000000..b77cc83721 --- /dev/null +++ b/assistant/pmbot/banuser.py @@ -0,0 +1,40 @@ +from . import * + + +@asst_cmd("ban") +async def banhammer(event): + x = await event.get_reply_message() + if x is None: + return await event.edit("Please reply to someone to ban him.") + if x.fwd_from: + target = x.fwd_from.from_id.user_id + else: + # this is a weird way of doing it + return + if not is_blacklisted(target): + blacklist_user(target) + await asst.send_message(event.chat_id, f"#BAN\nUser - {target}") + await asst.send_message( + target, + "`GoodBye! You have been banned.`\n**Further messages you send will not be forwarded.**", + ) + else: + return await asst.send_message(event.chat_id, f"User is already banned!") + + +@asst_cmd("unban") +async def banhammer(event): + x = await event.get_reply_message() + if x is None: + return await event.edit("Please reply to someone to ban him.") + if x.fwd_from: + target = x.fwd_from.from_id.user_id + else: + # this is a weird way of doing it + return + if is_blacklisted(target): + rem_blacklist(target) + await asst.send_message(event.chat_id, f"#UNBAN\nUser - {target}") + await asst.send_message(target, "`Congrats! You have been unbanned.`") + else: + return await asst.send_message(event.chat_id, f"User was never banned!") diff --git a/assistant/pmbot/incoming.py b/assistant/pmbot/incoming.py new file mode 100644 index 0000000000..fa94ff21da --- /dev/null +++ b/assistant/pmbot/incoming.py @@ -0,0 +1,32 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +# https://github.com/xditya/TeleBot/blob/master/telebot/plugins/mybot/pmbot/incoming.py +""" +Incoming Message(s) forwarder. +""" + +from telethon import events + +from . import * + +# if incoming + + +@asst.on(events.NewMessage(func=lambda e: e.is_private)) +async def on_new_mssg(event): + incoming = event.raw_text + who = event.sender_id + if is_blacklisted(who): + return + # doesn't reply to that user anymore + if incoming.startswith("/"): + pass + elif who == OWNER_ID: + return + else: + await event.forward_to(OWNER_ID) diff --git a/assistant/pmbot/outgoing.py b/assistant/pmbot/outgoing.py new file mode 100644 index 0000000000..9a2c5f2d81 --- /dev/null +++ b/assistant/pmbot/outgoing.py @@ -0,0 +1,43 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +# https://github.com/xditya/TeleBot/blob/master/telebot/plugins/mybot/pmbot/outgoing.py + +from telethon import events +from telethon.utils import pack_bot_file_id + +from . import * + +# outgoing + + +@asst.on(events.NewMessage(func=lambda e: e.is_private)) +async def on_out_mssg(event): + x = await event.get_reply_message() + if x is None: + return + to_send = event.raw_text + who = event.sender_id + if x.fwd_from: + to_user = x.fwd_from.sender_id.user_id + else: + # this is a weird way of doing it + return + if who == OWNER_ID: + if to_send.startswith("/"): + return + if event.text is not None and event.media: + # if sending media + bot_api_file_id = pack_bot_file_id(event.media) + await asst.send_file( + to_user, + file=bot_api_file_id, + caption=event.text, + reply_to=x.reply_to_msg_id, + ) + else: + await asst.send_message(to_user, to_send, reply_to=x.reply_to_msg_id) diff --git a/assistant/start.py b/assistant/start.py new file mode 100644 index 0000000000..09f8e04dcc --- /dev/null +++ b/assistant/start.py @@ -0,0 +1,113 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from datetime import datetime + +from pyUltroid.functions.asst_fns import * +from telethon import Button, custom, events + +from plugins import * + +from . import * + + +@asst_cmd("start") +async def assistant(event): + if event.is_group and event.sender_id in sed: + return await eor(event, "`I dont work in groups`") + else: + if not is_added(event.sender_id) and event.sender_id not in sed: + add_user(event.sender_id) + await asst.send_message( + OWNER_ID, + f"Bot started by [{event.sender_id}](tg://user?id={event.sender_id})", + ) + ok = "" + if Var.MSG_FRWD is True: + ok = "You can contact me using this bot!!" + if event.is_private and event.sender_id in sed: + return + await event.reply( + f"Hey there, this is Ultroid Assistant of {OWNER_NAME}!\n\n{ok}", + buttons=[Button.url("Know More", url="https://t.me/TeamUltroid")], + ) + + +@asst_cmd("start") +@owner +async def ultroid(event): + if event.is_group: + return + await asst.send_message( + event.chat_id, + f"Hi {OWNER_NAME}. Please browse through the options", + buttons=[ + [custom.Button.inline("Settings ⚙️", data="setter")], + [custom.Button.inline("Stats", data="stat")], + [custom.Button.inline("Broadcast", data="bcast")], + ], + ) + + +@callback("stat") +@owner +async def botstat(event): + ok = len(get_all_users()) + msg = """Ultroid Assistant - Stats +Total Users - {}""".format( + ok + ) + await event.answer(msg, cache_time=0, alert=True) + + +@callback("bcast") +@owner +async def bdcast(event): + ok = get_all_users() + await event.edit(f"Broadcast to {len(ok)} users.") + async with event.client.conversation(OWNER_ID) as conv: + await conv.send_message( + "Enter your broadcast message.\nUse /cancel to stop the broadcast." + ) + response = conv.wait_event(events.NewMessage(chats=OWNER_ID)) + response = await response + themssg = response.message.message + if themssg == "/cancel": + return await conv.send_message("Cancelled!!") + else: + success = 0 + fail = 0 + await conv.send_message(f"Starting a broadcast to {len(ok)} users...") + start = datetime.now() + for i in ok: + try: + await asst.send_message(int(i), f"{themssg}") + success += 1 + except BaseException: + fail += 1 + end = datetime.now() + time_taken = (end - start).seconds + await conv.send_message( + f""" +Broadcast completed in {time_taken} seconds. +Total Users in Bot - {len(ok)} +Sent to {success} users. +Failed for {fail} user(s).""" + ) + + +@callback("setter") +@owner +async def setting(event): + await event.edit( + "Choose from the below options -", + buttons=[ + [custom.Button.inline("Alive Customisation", data="alvcstm")], + [custom.Button.inline("API Keys", data="apiset")], + [custom.Button.inline("Other Vars.", data="otvars")], + ], + ) diff --git a/assistant/ytdl.py b/assistant/ytdl.py new file mode 100644 index 0000000000..05af667275 --- /dev/null +++ b/assistant/ytdl.py @@ -0,0 +1,197 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + + +import asyncio +import os +import re +import time + +from pyUltroid.functions.all import * +from telethon import Button +from telethon.tl.types import DocumentAttributeAudio, InputWebDocument as wb +from youtube_dl import YoutubeDL +from youtubesearchpython import VideosSearch + +ytt = "https://telegra.ph/file/afd04510c13914a06dd03.jpg" + + +@in_pattern("yt") +@in_owner +async def _(event): + try: + string = event.text.split(" ", maxsplit=1)[1] + except IndexError: + fuk = event.builder.article( + title="Search Something", + thumb=wb(ytt, 0, "image/jpeg", []), + text="**YᴏᴜTᴜʙᴇ Sᴇᴀʀᴄʜ**\n\nYou didn't search anything", + buttons=Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", + query="yt ", + same_peer=True, + ), + ) + await event.answer([fuk]) + return + results = [] + search = VideosSearch(string, limit=10) + nub = search.result() + nibba = nub["result"] + for v in nibba: + link = v["link"] + title = v["title"] + ids = v["id"] + duration = v["duration"] + thumb = f"https://img.youtube.com/vi/{ids}/hqdefault.jpg" + text = f"**•Tɪᴛʟᴇ•** `{title}`\n\n**••[Lɪɴᴋ]({link})••**\n\n**••Dᴜʀᴀᴛɪᴏɴ••** `{duration}`\n\n\n" + desc = f"Title : {title}\nDuration : {duration}" + results.append( + await event.builder.document( + file=thumb, + title=title, + description=desc, + text=text, + include_media=True, + buttons=[ + [ + Button.inline("Audio", data=f"audio{link}"), + Button.inline("Video", data=f"video{link}"), + ], + [ + Button.switch_inline( + "Sᴇᴀʀᴄʜ Aɢᴀɪɴ", query="yt ", same_peer=True + ), + Button.switch_inline( + "Sʜᴀʀᴇ", query=f"yt {string}", same_peer=False + ), + ], + ], + ) + ) + await event.answer(results) + + +@callback(re.compile("audio(.*)")) +@owner +async def _(sur): + url = sur.pattern_match.group(1).decode("UTF-8") + getter = sur.sender_id + opts = { + "format": "bestaudio", + "addmetadata": True, + "key": "FFmpegMetadata", + "writethumbnail": True, + "prefer_ffmpeg": True, + "geo_bypass": True, + "nocheckcertificate": True, + "postprocessors": [ + { + "key": "FFmpegExtractAudio", + "preferredcodec": "mp3", + "preferredquality": "320", + } + ], + "outtmpl": "%(id)s.mp3", + "quiet": True, + "logtostderr": False, + } + song = True + await dler(sur) + with YoutubeDL(opts) as ytdl: + ytdl_data = ytdl.extract_info(url) + + jpg = f"{ytdl_data['id']}.mp3.jpg" + png = f"{ytdl_data['id']}.mp3.png" + webp = f"{ytdl_data['id']}.mp3.webp" + dir = os.listdir() + + if jpg in dir: + thumb = jpg + elif png in dir: + thumb = png + elif webp in dir: + thumb = webp + else: + thumb = None + + c_time = time.time() + if song: + await sur.edit( + f"`Preparing to upload song:`\ + \n**{ytdl_data['title']}**\ + \nby *{ytdl_data['uploader']}*" + ) + await asst.send_file( + getter, + f"{ytdl_data['id']}.mp3", + thumb=thumb, + caption=f"**{ytdl_data['title']}\n{convert(ytdl_data['duration'])}\n{ytdl_data['uploader']}**", + supports_streaming=True, + attributes=[ + DocumentAttributeAudio( + duration=int(ytdl_data["duration"]), + title=str(ytdl_data["title"]), + performer=str(ytdl_data["uploader"]), + ) + ], + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress(d, t, sur, c_time, "Uploading..", f"{ytdl_data['title']}.mp3") + ), + ) + os.system(f"rm {ytdl_data['id']}.mp*") + await sur.edit( + f"Get Your requested file **{ytdl_data['title']}** from here {Var.BOT_USERNAME} ", + buttons=Button.switch_inline("Search More", query="yt ", same_peer=True), + ) + + +@callback(re.compile("video(.*)")) +@owner +async def _(fuk): + url = fuk.pattern_match.group(1).decode("UTF-8") + getter = fuk.sender_id + opts = { + "format": "best", + "addmetadata": True, + "key": "FFmpegMetadata", + "writethumbnail": True, + "prefer_ffmpeg": True, + "geo_bypass": True, + "nocheckcertificate": True, + "postprocessors": [{"key": "FFmpegVideoConvertor", "preferedformat": "mp4"}], + "outtmpl": "%(id)s.mp4", + "logtostderr": False, + "quiet": True, + } + video = True + await dler(fuk) + with YoutubeDL(opts) as ytdl: + ytdl_data = ytdl.extract_info(url) + + c_time = time.time() + if video: + await fuk.edit( + f"`Preparing to upload video:`\ + \n**{ytdl_data['title']}**\ + \nby *{ytdl_data['uploader']}*" + ) + await asst.send_file( + getter, + f"{ytdl_data['id']}.mp4", + thumb=f"./resources/extras/ultroid.jpg", + caption=f"**{ytdl_data['title']}\n{convert(ytdl_data['duration'])}\n{ytdl_data['uploader']}**", + supports_streaming=True, + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress(d, t, fuk, c_time, "Uploading..", f"{ytdl_data['title']}.mp4") + ), + ) + os.remove(f"{ytdl_data['id']}.mp4") + await fuk.edit( + f"Get Your requested file **{ytdl_data['title']}** from here {Var.BOT_USERNAME} ", + buttons=Button.switch_inline("Search More", query="yt ", same_peer=True), + ) diff --git a/heroku.yml b/heroku.yml new file mode 100644 index 0000000000..09b09fc793 --- /dev/null +++ b/heroku.yml @@ -0,0 +1,3 @@ +build: + docker: + worker: Dockerfile diff --git a/plugins/__init__.py b/plugins/__init__.py new file mode 100644 index 0000000000..f562b1253a --- /dev/null +++ b/plugins/__init__.py @@ -0,0 +1,98 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import time + +from pyUltroid import * +from pyUltroid.dB.core import * +from pyUltroid.functions import * +from pyUltroid.functions.all import * +from pyUltroid.functions.google_image import googleimagesdownload +from pyUltroid.functions.sudos import * +from pyUltroid.utils import * + +start_time = time.time() +ultroid_version = "v0.0.1" +OWNER_NAME = ultroid_bot.me.first_name +OWNER_ID = ultroid_bot.me.id +DEVLIST = [ + "1259468938", + "1452145387", + "719195224", + "1318486004", + "1289422521", + "1322549723", + "611816596", + "1003250439", + "1152902819", + "716243352", + "1444249738", + "559661211", + "881536550", + "630654925", +] + +# sudo +ok = udB.get("SUDOS") +if ok: + SUDO_USERS = set(int(x) for x in ok.split()) +else: + SUDO_USERS = "" + +if SUDO_USERS: + sudos = list(SUDO_USERS) +else: + sudos = "" + +on = Var.SUDO + +if Var.SUDO: + sed = [ultroid_bot.uid, *sudos] +else: + sed = [ultroid_bot.uid] + + +def grt(seconds: int) -> str: + count = 0 + up_time = "" + time_list = [] + time_suffix_list = ["s", "m", "h", "days"] + + while count < 4: + count += 1 + if count < 3: + remainder, result = divmod(seconds, 60) + else: + remainder, result = divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + up_time += time_list.pop() + ", " + + time_list.reverse() + up_time += ":".join(time_list) + + return up_time + + +KANGING_STR = [ + "Using Witchery to kang this sticker...", + "Plagiarising hehe...", + "Inviting this sticker over to my pack...", + "Kanging this sticker...", + "Hey that's a nice sticker!\nMind if I kang?!..", + "Hehe me stel ur stiker...", + "Ay look over there (☉。☉)!→\nWhile I kang this...", + "Roses are red violets are blue, kanging this sticker so my pack looks cool", + "Imprisoning this sticker...", + "Mr.Steal-Your-Sticker is stealing this sticker... ", +] diff --git a/plugins/_help.py b/plugins/_help.py new file mode 100644 index 0000000000..9025b6f455 --- /dev/null +++ b/plugins/_help.py @@ -0,0 +1,66 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from pyUltroid.dB.database import Var +from support import * +from telethon.errors.rpcerrorlist import BotInlineDisabledError as dis +from telethon.errors.rpcerrorlist import BotMethodInvalidError as bmi +from telethon.errors.rpcerrorlist import BotResponseTimeoutError as rep + +from . import * + + +@ultroid_cmd( + pattern="help ?(.*)", +) +async def ult(ult): + plug = ult.pattern_match.group(1) + tgbot = Var.BOT_USERNAME + if plug: + try: + if plug in HELP: + output = "**Plugin** - `{}`\n".format(plug) + for i in HELP[plug]: + output += i + output += "\n© @TheUltroid" + await eor(ult, output) + elif plug in CMD_HELP: + kk = f"Plugin Name-{plug}\n\n✘ Commands Available-\n\n" + kk += str(CMD_HELP[plug]) + await eor(ult, kk) + else: + try: + x = f"Plugin Name-{plug}\n\n✘ Commands Available-\n\n" + for d in LIST[plug]: + x += Var.HNDLR + d + x += "\n" + await eor(ult, x) + except BaseException: + await eod(ult, f"`{plug}` is not a valid plugin!", time=5) + except BaseException: + await eor(ult, "Error 🤔 occured.") + else: + try: + results = await ultroid_bot.inline_query(tgbot, "ultd") + except rep: + return await eor( + ult, + "`The bot did not respond to the inline query.\nConsider using {}restart`".format( + Var.HNDLR + ), + ) + except dis: + return await eor( + ult, "`Please turn on inline mode for your bot from` @Botfather." + ) + except bmi: + return await eor( + ult, + f"Hey, \nYou are on Bot Mode. \nBot Mode Users Cant Get Help Directly ... \nInstead Copy Paste The Following in The Chat and Click The Pop Up \n\n `@{tgbot} ultd`", + ) + await results[0].click(ult.chat_id, reply_to=ult.reply_to_msg_id, hide_via=True) + await ult.delete() diff --git a/plugins/_inline.py b/plugins/_inline.py new file mode 100644 index 0000000000..3b3da725f7 --- /dev/null +++ b/plugins/_inline.py @@ -0,0 +1,503 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import os +import random +import re +import time +from datetime import datetime +from math import ceil +from platform import python_version as pyver + +from git import Repo +from support import * +from telethon import Button, __version__ +from telethon.tl.types import InputWebDocument + +from . import * + +# ================================================# +notmine = "This bot is for {}".format(OWNER_NAME) +ULTROID_PIC = "https://telegra.ph/file/11245cacbffe92e5d5b14.jpg" +helps = """ +[Uʟᴛʀᴏɪᴅ Sᴜᴘᴘᴏʀᴛ](t.me/ultroidsupport) + +**Hᴇʟᴘ Mᴇɴᴜ Oғ {}. + +Pʟᴜɢɪɴs ~ {}** +""" + + +if Var.ADDONS: + zhelps = """ +[Uʟᴛʀᴏɪᴅ Sᴜᴘᴘᴏʀᴛ](t.me/ultroidsupport) + +**Hᴇʟᴘ Mᴇɴᴜ Oғ {}. + +Aᴅᴅᴏɴs ~ {}** +""" +else: + zhelps = """ +[Uʟᴛʀᴏɪᴅ Sᴜᴘᴘᴏʀᴛ](t.me/ultroidsupport) + +**Hᴇʟᴘ Mᴇɴᴜ Oғ {}. + +Aᴅᴅᴏɴs ~ {} + +Gᴏ Aɴᴅ Aᴅᴅ ADDON Vᴀʀ Wɪᴛʜ Vᴀʟᴜᴇ Tʀᴜᴇ** +""" +# ============================================# + + +@inline +@in_owner +async def e(o): + if len(o.text) == 0: + b = o.builder + uptime = grt((time.time() - start_time)) + ALIVEMSG = """ +**The Ultroid Userbot...**\n\n +✵ **Owner** - `{}` +✵ **Ultroid** - `{}` +✵ **UpTime** - `{}` +✵ **Python** - `{}` +✵ **Telethon** - `{}` +✵ **Branch** - `{}` +""".format( + OWNER_NAME, + ultroid_version, + uptime, + pyver(), + __version__, + Repo().active_branch, + ) + res = [ + b.article( + title="Ultroid Userbot", + url="https://t.me/TeamUltroid", + description="Userbot | Telethon ", + text=ALIVEMSG, + thumb=InputWebDocument(ULTROID_PIC, 0, "image/jpeg", []), + ) + ] + await o.answer(res, switch_pm=f"👥 ULTROID PORTAL", switch_pm_param="start") + + +if Var.BOT_USERNAME is not None and asst is not None: + + @inline + @in_owner + async def inline_handler(event): + builder = event.builder + result = None + query = event.text + if event.query.user_id in sed and query.startswith("ultd"): + result = builder.article( + title="Help Menu", + description="Help Menu - UserBot | Telethon ", + url="https://t.me/TheUltroid", + thumb=InputWebDocument(ULTROID_PIC, 0, "image/jpeg", []), + text=f"** Bᴏᴛ Oғ {OWNER_NAME}\n\nMᴀɪɴ Mᴇɴᴜ\n\nPʟᴜɢɪɴs ~ {len(PLUGINS) - 4}\nAᴅᴅᴏɴs ~ {len(ADDONS)}**", + buttons=[ + [ + Button.inline("• Pʟᴜɢɪɴs", data="hrrrr"), + Button.inline("• Aᴅᴅᴏɴs", data="frrr"), + ], + [Button.inline("Oᴡɴᴇʀ•ᴛᴏᴏʟꜱ", data="ownr")], + [Button.inline("Iɴʟɪɴᴇ•Pʟᴜɢɪɴs", data="inlone")], + [Button.inline("••Cʟᴏꜱᴇ••", data="close")], + ], + ) + await event.answer([result] if result else None) + elif event.query.user_id in sed and query.startswith("paste"): + ok = query.split("-")[1] + link = f"https://nekobin.com/{ok}" + link_raw = f"https://nekobin.com/raw/{ok}" + result = builder.article( + title="Paste", + text="Pᴀsᴛᴇᴅ Tᴏ Nᴇᴋᴏʙɪɴ!", + buttons=[ + [ + Button.url("NekoBin", url=f"{link}"), + Button.url("Raw", url=f"{link_raw}"), + ] + ], + ) + await event.answer([result] if result else None) + + @inline + @in_owner + @callback("ownr") + @owner + async def setting(event): + await event.edit( + buttons=[ + [ + Button.inline("•Pɪɴɢ•", data="pkng"), + Button.inline("•Uᴘᴛɪᴍᴇ•", data="upp"), + ], + [Button.inline("•Rᴇsᴛᴀʀᴛ•", data="rstrt")], + [Button.inline("<- Bᴀᴄᴋ", data="open")], + ], + ) + + @callback("pkng") + async def _(event): + start = datetime.now() + end = datetime.now() + ms = (end - start).microseconds / 1000 + pin = f"🌋Pɪɴɢ = {ms}ms" + await event.answer(pin, cache_time=0, alert=True) + + @callback("upp") + async def _(event): + uptime = grt((time.time() - start_time)) + pin = f"🙋Uᴘᴛɪᴍᴇ = {uptime}" + await event.answer(pin, cache_time=0, alert=True) + + @callback("inlone") + @owner + async def _(e): + button = [ + [ + Button.switch_inline( + "Sᴇɴᴅ Oғғɪᴄɪᴀʟ Pʟᴜɢɪɴs", + query="send all", + same_peer=True, + ) + ], + [ + Button.switch_inline( + "Pʟᴀʏ Sᴛᴏʀᴇ Aᴘᴘs", + query="app telegram", + same_peer=True, + ) + ], + [ + Button.switch_inline( + "Mᴏᴅᴅᴇᴅ Aᴘᴘs", + query="mods minecraft", + same_peer=True, + ) + ], + [ + Button.switch_inline( + "Sᴇᴀʀᴄʜ Oɴ Gᴏᴏɢʟᴇ", + query="go TeamUltroid", + same_peer=True, + ) + ], + [ + Button.switch_inline( + "Sᴇᴀʀᴄʜ Oɴ Yᴀʜᴏᴏ", + query="yahoo TeamUltroid", + same_peer=True, + ) + ], + [ + Button.switch_inline( + "YᴏᴜTᴜʙᴇ Dᴏᴡɴʟᴏᴀᴅᴇʀ", + query="yt How to Deploy Ultroid Userbot", + same_peer=True, + ) + ], + [ + Button.switch_inline( + "CʟɪᴘAʀᴛ Sᴇᴀʀᴄʜ", + query="clipart frog", + same_peer=True, + ) + ], + [ + Button.inline( + "<- Bᴀᴄᴋ", + data="open", + ) + ], + ] + await e.edit(buttons=button, link_preview=False) + + @callback("hrrrr") + @owner + async def on_plug_in_callback_query_handler(event): + xhelps = helps.format(OWNER_NAME, len(PLUGINS) - 4) + buttons = paginate_help(0, PLUGINS, "helpme") + await event.edit(f"{xhelps}", buttons=buttons, link_preview=False) + + @callback("frrr") + @owner + async def addon(event): + halp = zhelps.format(OWNER_NAME, len(ADDONS)) + if len(ADDONS) > 0: + buttons = paginate_addon(0, ADDONS, "addon") + await event.edit(f"{halp}", buttons=buttons, link_preview=False) + else: + await event.answer( + "• Iɴsᴛᴀʟʟ A Pʟᴜɢɪɴ Mᴀɴᴜᴀʟʟʏ Oʀ Aᴅᴅ Vᴀʀ ADDON Wɪᴛʜ Vᴀʟᴜᴇ Tʀᴜᴇ", + cache_time=0, + alert=True, + link_preview=False, + ) + + @callback("rstrt") + @owner + async def rrst(ult): + await restart(ult) + + @callback( + re.compile( + rb"helpme_next\((.+?)\)", + ), + ) + @owner + async def on_plug_in_callback_query_handler(event): + current_page_number = int(event.data_match.group(1).decode("UTF-8")) + buttons = paginate_help(current_page_number + 1, PLUGINS, "helpme") + await event.edit(buttons=buttons, link_preview=False) + + @callback( + re.compile( + rb"helpme_prev\((.+?)\)", + ), + ) + @owner + async def on_plug_in_callback_query_handler(event): + current_page_number = int(event.data_match.group(1).decode("UTF-8")) + buttons = paginate_help(current_page_number - 1, PLUGINS, "helpme") + await event.edit(buttons=buttons, link_preview=False) + + @callback( + re.compile( + rb"addon_next\((.+?)\)", + ), + ) + @owner + async def on_plug_in_callback_query_handler(event): + current_page_number = int(event.data_match.group(1).decode("UTF-8")) + buttons = paginate_addon(current_page_number + 1, ADDONS, "addon") + await event.edit(buttons=buttons, link_preview=False) + + @callback( + re.compile( + rb"addon_prev\((.+?)\)", + ), + ) + @owner + async def on_plug_in_callback_query_handler(event): + current_page_number = int(event.data_match.group(1).decode("UTF-8")) + buttons = paginate_addon(current_page_number - 1, ADDONS, "addon") + await event.edit(buttons=buttons, link_preview=False) + + @callback("back") + @owner + async def backr(event): + xhelps = helps.format(OWNER_NAME, len(PLUGINS) - 4) + current_page_number = 0 + buttons = paginate_help(current_page_number, PLUGINS, "helpme") + await event.edit(f"{xhelps}", buttons=buttons, link_preview=False) + + @callback("buck") + @owner + async def backr(event): + xhelps = zhelps.format(OWNER_NAME, len(ADDONS)) + current_page_number = 0 + buttons = paginate_addon(current_page_number, ADDONS, "addon") + await event.edit(f"{xhelps}", buttons=buttons, link_preview=False) + + @callback("open") + @owner + async def opner(event): + buttons = [ + [ + Button.inline("• Pʟᴜɢɪɴs ", data="hrrrr"), + Button.inline("• Aᴅᴅᴏɴs", data="frrr"), + ], + [Button.inline("Oᴡɴᴇʀ•Tᴏᴏʟꜱ", data="ownr")], + [Button.inline("Iɴʟɪɴᴇ•Pʟᴜɢɪɴs", data="inlone")], + [Button.inline("••Cʟᴏꜱᴇ••", data="close")], + ] + await event.edit( + f"** Bᴏᴛ Oғ {OWNER_NAME}\n\nMᴀɪɴ Mᴇɴᴜ\n\nOꜰꜰɪᴄɪᴀʟ Pʟᴜɢɪɴs ~ {len(PLUGINS) - 4}\nUɴᴏꜰꜰɪᴄɪᴀʟ Pʟᴜɢɪɴs ~ {len(ADDONS)}**", + buttons=buttons, + link_preview=False, + ) + + @callback("close") + @owner + async def on_plug_in_callback_query_handler(event): + await event.edit( + "**Mᴇɴᴜ Hᴀs Bᴇᴇɴ Cʟᴏsᴇᴅ**", + buttons=Button.inline("Oᴘᴇɴ Mᴀɪɴ Mᴇɴᴜ Aɢᴀɪɴ", data="open"), + ) + + @callback( + re.compile( + b"us_plugin_(.*)", + ), + ) + @owner + async def on_plug_in_callback_query_handler(event): + plugin_name = event.data_match.group(1).decode("UTF-8") + help_string = f"Plugin Name - `{plugin_name}`\n" + try: + for i in HELP[plugin_name]: + help_string += i + except BaseException: + pass + if help_string == "": + reply_pop_up_alert = "{} has no detailed help...".format(plugin_name) + else: + reply_pop_up_alert = help_string + reply_pop_up_alert += "\n© @TheUltroid" + try: + if event.query.user_id in sed: + await event.edit( + reply_pop_up_alert, + buttons=[ + Button.inline("<- Bᴀᴄᴋ", data="back"), + Button.inline("••Cʟᴏꜱᴇ••", data="close"), + ], + ) + else: + reply_pop_up_alert = notmine + await event.answer(reply_pop_up_alert, cache_time=0) + except BaseException: + halps = "Do .help {} to get the list of commands.".format(plugin_name) + await event.edit(halps) + + @callback( + re.compile( + b"add_plugin_(.*)", + ), + ) + @owner + async def on_plug_in_callback_query_handler(event): + plugin_name = event.data_match.group(1).decode("UTF-8") + help_string = "" + try: + for i in HELP[plugin_name]: + help_string += i + except BaseException: + try: + for u in CMD_HELP[plugin_name]: + help_string = ( + f"Plugin Name-{plugin_name}\n\n✘ Commands Available-\n\n" + ) + help_string += str(CMD_HELP[plugin_name]) + except BaseException: + try: + if plugin_name in LIST: + help_string = ( + f"Plugin Name-{plugin_name}\n\n✘ Commands Available-\n\n" + ) + for d in LIST[plugin_name]: + help_string += Var.HNDLR + d + help_string += "\n" + except BaseException: + pass + if help_string == "": + reply_pop_up_alert = "{} has no detailed help...".format(plugin_name) + else: + reply_pop_up_alert = help_string + reply_pop_up_alert += "\n© @TheUltroid" + try: + if event.query.user_id in sed: + await event.edit( + reply_pop_up_alert, + buttons=[ + Button.inline("<- Bᴀᴄᴋ", data="buck"), + Button.inline("••Cʟᴏꜱᴇ••", data="close"), + ], + ) + else: + reply_pop_up_alert = notmine + await event.answer(reply_pop_up_alert, cache_time=0) + except BaseException: + halps = "Do .help {} to get the list of commands.".format(plugin_name) + await event.edit(halps) + + +def paginate_help(page_number, loaded_plugins, prefix): + number_of_rows = 5 + number_of_cols = 2 + multi = os.environ.get("EMOJI_TO_DESPLAY_IN_HELP", "✘") + mult2i = os.environ.get("EMOJI2_TO_DESPLAY_IN_HELP", "✘") + helpable_plugins = [] + for p in loaded_plugins: + if not p.startswith("_"): + helpable_plugins.append(p) + helpable_plugins = sorted(helpable_plugins) + modules = [ + Button.inline( + "{} {} {}".format( + random.choice(list(multi)), x, random.choice(list(mult2i)) + ), + data="us_plugin_{}".format(x), + ) + for x in helpable_plugins + ] + pairs = list(zip(modules[::number_of_cols], modules[1::number_of_cols])) + if len(modules) % number_of_cols == 1: + pairs.append((modules[-1],)) + max_num_pages = ceil(len(pairs) / number_of_rows) + modulo_page = page_number % max_num_pages + if len(pairs) > number_of_rows: + pairs = pairs[ + modulo_page * number_of_rows : number_of_rows * (modulo_page + 1) + ] + [ + ( + Button.inline( + "<- Pʀᴇᴠɪᴏᴜs", data="{}_prev({})".format(prefix, modulo_page) + ), + Button.inline("-Bᴀᴄᴋ-", data="open"), + Button.inline( + "Nᴇxᴛ ->", data="{}_next({})".format(prefix, modulo_page) + ), + ) + ] + return pairs + + +def paginate_addon(page_number, loaded_plugins, prefix): + number_of_rows = 5 + number_of_cols = 2 + multi = os.environ.get("EMOJI_TO_DESPLAY_IN_HELP", "✘") + mult2i = os.environ.get("EMOJI2_TO_DESPLAY_IN_HELP", "✘") + helpable_plugins = [] + for p in loaded_plugins: + if not p.startswith("_"): + helpable_plugins.append(p) + helpable_plugins = sorted(helpable_plugins) + modules = [ + Button.inline( + "{} {} {}".format( + random.choice(list(multi)), x, random.choice(list(mult2i)) + ), + data="add_plugin_{}".format(x), + ) + for x in helpable_plugins + ] + pairs = list(zip(modules[::number_of_cols], modules[1::number_of_cols])) + if len(modules) % number_of_cols == 1: + pairs.append((modules[-1],)) + max_num_pages = ceil(len(pairs) / number_of_rows) + modulo_page = page_number % max_num_pages + if len(pairs) > number_of_rows: + pairs = pairs[ + modulo_page * number_of_rows : number_of_rows * (modulo_page + 1) + ] + [ + ( + Button.inline( + "<- Pʀᴇᴠɪᴏᴜs", data="{}_prev({})".format(prefix, modulo_page) + ), + Button.inline("-Bᴀᴄᴋ-", data="open"), + Button.inline( + "Nᴇxᴛ ->", data="{}_next({})".format(prefix, modulo_page) + ), + ) + ] + return pairs diff --git a/plugins/_tagnotif.py b/plugins/_tagnotif.py new file mode 100644 index 0000000000..51390b241e --- /dev/null +++ b/plugins/_tagnotif.py @@ -0,0 +1,51 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from telethon import custom, events +from telethon.tl.types import Channel +from telethon.utils import get_display_name + +from . import * + + +@ultroid_bot.on( + events.NewMessage( + incoming=True, + func=lambda e: (e.mentioned), + ) +) +async def all_messages_catcher(event): + if udB.get("TAG_LOG") is not None: + NEEDTOLOG = int(udB.get("TAG_LOG")) + await event.forward_to(NEEDTOLOG) + ammoca_message = "" + who_ = await event.client.get_entity(event.sender_id) + if who_.bot or who_.verified or who_.support: + return + who_m = f"[{get_display_name(who_)}](tg://user?id={who_.id})" + where_ = await event.client.get_entity(event.chat_id) + where_m = get_display_name(where_) + button_text = "📨 Go to Message " + if isinstance(where_, Channel): + message_link = f"https://t.me/c/{where_.id}/{event.id}" + chat_link = f"https://t.me/c/{where_.id}" + else: + message_link = f"tg://openmessage?chat_id={where_.id}&message_id={event.id}" + chat_link = f"tg://openmessage?chat_id={where_.id}" + ammoca_message += f"{who_m} tagged you in [{where_m}]({chat_link})" + try: + await asst.send_message( + entity=NEEDTOLOG, + message=ammoca_message, + link_preview=False, + buttons=[[custom.Button.url(button_text, message_link)]], + silent=True, + ) + except BaseException: + pass + else: + return diff --git a/plugins/_wspr.py b/plugins/_wspr.py new file mode 100644 index 0000000000..bc93db0ae3 --- /dev/null +++ b/plugins/_wspr.py @@ -0,0 +1,169 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import re + +from telethon import Button +from telethon.errors.rpcerrorlist import BotInlineDisabledError as dis +from telethon.errors.rpcerrorlist import BotResponseTimeoutError as rep +from telethon.errors.rpcerrorlist import MessageNotModifiedError as np +from telethon.tl.functions.users import GetFullUserRequest as gu +from telethon.tl.types import UserStatusEmpty as mt +from telethon.tl.types import UserStatusLastMonth as lm +from telethon.tl.types import UserStatusLastWeek as lw +from telethon.tl.types import UserStatusOffline as off +from telethon.tl.types import UserStatusOnline as on +from telethon.tl.types import UserStatusRecently as rec + +snap = {} +buddhhu = [] + + +@ultroid_cmd( + pattern="wspr ?(.*)", +) +async def _(e): + if e.reply_to_msg_id: + okk = (await e.get_reply_message()).sender_id + try: + zyx = await ultroid_bot(gu(id=okk)) + put = zyx.user.username + except ValueError as ex: + return await eor(e, str(ex)) + except AttributeError: + return await eor(e, "No username of replied user wad found") + else: + put = e.pattern_match.group(1) + if put: + try: + results = await ultroid_bot.inline_query(Var.BOT_USERNAME, f"msg {put}") + except rep: + return await eor( + e, + "`The bot did not respond to the inline query.\nConsider using {}restart`".format( + Var.HNDLR + ), + ) + except dis: + return await eor( + e, "`Please turn on inline mode for your bot from` @Botfather." + ) + await results[0].click(e.chat_id, reply_to=e.reply_to_msg_id, hide_via=True) + await e.delete() + else: + await eor(e, "Add some id or username too") + + +@in_pattern("msg") +async def _(e): + vvv = e.text + zzz = vvv.split(" ", maxsplit=1) + try: + ggg = zzz[1] + sed = ggg.split(" wspr ", maxsplit=1) + query = sed[0] + except IndexError: + return + meme = e.query.user_id + try: + desc = sed[1] + except IndexError: + desc = "Touch me" + if "wspr" not in vvv: + try: + logi = await ultroid_bot(gu(id=query)) + name = logi.user.first_name + ids = logi.user.id + username = logi.user.username + x = logi.user.status + bio = logi.about + if isinstance(x, on): + status = "Online" + if isinstance(x, off): + status = "Offline" + if isinstance(x, rec): + status = "Last Seen Recently" + if isinstance(x, lm): + status = "Last seen months ago" + if isinstance(x, lw): + status = "Last seen weeks ago" + if isinstance(x, mt): + status = "Can't Tell" + text = f"**Name:** `{name}`\n" + text += f"**Id:** `{ids}`\n" + text += f"**Username:** `{username}`\n" + text += f"**Status:** `{status}`\n" + text += f"**About:** `{bio}`" + button = [ + Button.url("Private", url=f"t.me/{username}"), + Button.switch_inline( + "Secret msg", query=f"msg {query} wspr ", same_peer=True + ), + ] + sur = e.builder.article( + title=f"{name}", + description=desc, + text=text, + buttons=button, + ) + except BaseException: + name = f"User {query} Not Found\nSearch Again" + sur = e.builder.article( + title=name, + text=name, + ) + else: + try: + logi = await ultroid_bot.get_entity(query) + button = [ + Button.inline("Secret Msg", data=f"dd_{logi.id}"), + Button.inline("Delete Msg", data=f"del"), + ] + sur = e.builder.article( + title=f"{logi.first_name}", + description=desc, + text=f"@{logi.username} secret msg for you.\nDelete your msg after reading.\nOr the next msg will not be updated.", + buttons=button, + ) + buddhhu.append(meme) + buddhhu.append(logi.id) + snap.update({logi.id: desc}) + except ValueError: + sur = e.builder.article( + title="Type ur msg", text=f"You Didn't Type Your Msg" + ) + await e.answer([sur]) + + +@callback( + re.compile( + "dd_(.*)", + ), +) +async def _(e): + ids = int(e.pattern_match.group(1).decode("UTF-8")) + if e.sender_id in buddhhu: + await e.answer(snap[ids], alert=True) + else: + await e.answer("Not For You", alert=True) + + +@callback("del") +async def _(e): + if e.sender_id in buddhhu: + for k in buddhhu: + try: + del snap[k] + buddhhu.clear() + except KeyError: + pass + try: + await e.edit("Msg deleted") + except np: + pass + else: + await e.answer("You Can't do this", alert=True) diff --git a/plugins/admintools.py b/plugins/admintools.py new file mode 100644 index 0000000000..8178baf7bd --- /dev/null +++ b/plugins/admintools.py @@ -0,0 +1,420 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}promote ` + Promote the user in the chat. + +• `{i}demote ` + Demote the user in the chat. + +• `{i}ban ` + Ban the user from the chat. + +• `{i}unban ` + Unban the user from the chat. + +• `{i}kick ` + Kick the user from the chat. + +• `{i}pin ` + Pin the message in the chat. + +• `{i}unpin (all) ` + Unpin the message(s) in the chat. + +• `{i}purge ` + Purge all messages from the replied message. + +• `{i}purgeall ` + Delete all msgs of replied user. + Delete all msgs of input user + +• `{i}del ` + Delete the replied message. + +• `{i}edit ` + Edit your last message. +""" + +import asyncio + +from telethon.errors import BadRequestError +from telethon.errors.rpcerrorlist import UserIdInvalidError +from telethon.tl.functions.channels import EditAdminRequest, EditBannedRequest +from telethon.tl.types import ChatAdminRights, ChatBannedRights + +from . import * + + +@ultroid_cmd( + pattern="promote ?(.*)", + groups_only=True, +) +async def prmte(ult): + xx = await eor(ult, "`Processing...`") + chat = await ult.get_chat() + isAdmin = chat.admin_rights + isCreator = chat.creator + if not isAdmin and not isCreator: + return await xx.edit("`Hmm, I'm not an admin here...`") + await xx.edit("`Promoting...`") + user, rank = await get_user_info(ult) + if not rank: + rank = "Admin" + if not user: + return await xx.edit("`Reply to a user to promote him!`") + try: + await ultroid_bot( + EditAdminRequest( + ult.chat_id, + user.id, + ChatAdminRights( + add_admins=False, + invite_users=True, + change_info=False, + ban_users=True, + delete_messages=True, + pin_messages=True, + ), + rank, + ) + ) + await xx.edit( + f"[{user.first_name}](tg://user?id={user.id}) `is now an admin in {ult.chat.title} with title {rank}.`" + ) + except BadRequestError: + return await xx.edit("`I don't have the right to promote you.`") + await asyncio.sleep(5) + await xx.delete() + + +@ultroid_cmd( + pattern="demote ?(.*)", + groups_only=True, +) +async def dmote(ult): + xx = await eor(ult, "`Processing...`") + chat = await ult.get_chat() + isAdmin = chat.admin_rights + isCreator = chat.creator + if not isAdmin and not isCreator: + return await xx.edit("`Hmm, I'm not an admin here...`") + await xx.edit("`Demoting...`") + user, rank = await get_user_info(ult) + if not rank: + rank = "Not Admin" + if not user: + return await xx.edit("`Reply to a user to demote him!`") + try: + await ultroid_bot( + EditAdminRequest( + ult.chat_id, + user.id, + ChatAdminRights( + add_admins=None, + invite_users=None, + change_info=None, + ban_users=None, + delete_messages=None, + pin_messages=None, + ), + rank, + ) + ) + await xx.edit( + f"[{user.first_name}](tg://user?id={user.id}) `is no longer an admin in {ult.chat.title}`" + ) + except BadRequestError: + return await xx.edit("`I don't have the right to demote you.`") + await asyncio.sleep(5) + await xx.delete() + + +@ultroid_cmd( + pattern="ban ?(.*)", + groups_only=True, +) +async def bban(ult): + xx = await eor(ult, "`Processing...`") + chat = await ult.get_chat() + isAdmin = chat.admin_rights + isCreator = chat.creator + if not isAdmin and not isCreator: + return await xx.edit("`Hmm, I'm not an admin here...`") + user, reason = await get_user_info(ult) + if not user: + return await xx.edit("`Reply to a user or give username to ban him!`") + await xx.edit("`Getting user info...`") + try: + await ultroid_bot( + EditBannedRequest( + ult.chat_id, + user.id, + ChatBannedRights( + until_date=None, + view_messages=True, + send_messages=True, + send_media=True, + send_stickers=True, + send_gifs=True, + send_games=True, + send_inline=True, + embed_links=True, + ), + ) + ) + except BadRequestError: + return await xx.edit("`I don't have the right to ban a user.`") + except UserIdInvalidError: + await xx.edit("`I couldn't get who he is!`") + try: + reply = await ult.get_reply_message() + if reply: + await reply.delete() + except BadRequestError: + return await xx.edit( + f"[{user.first_name}](tg://user?id={user.id}) **was banned by** [{OWNER_NAME}](tg://user?id={OWNER_ID}) **in** `{ult.chat.title}`\n**Reason**: `{reason}`\n**Messages Deleted**: `False`" + ) + if reason: + await xx.edit( + f"[{user.first_name}](tg://user?id={user.id}) **was banned by** [{OWNER_NAME}](tg://user?id={OWNER_ID}) **in** `{ult.chat.title}`\n**Reason**: `{reason}`" + ) + else: + await xx.edit( + f"[{user.first_name}](tg://user?id={user.id}) **was banned by** [{OWNER_NAME}](tg://user?id={OWNER_ID}) **in** `{ult.chat.title}`" + ) + + +@ultroid_cmd( + pattern="unban ?(.*)", + groups_only=True, +) +async def uunban(ult): + xx = await eor(ult, "`Processing...`") + chat = await ult.get_chat() + isAdmin = chat.admin_rights + isCreator = chat.creator + if not isAdmin and not isCreator: + return await xx.edit("`Hmm, I'm not an admin here...`") + user, reason = await get_user_info(ult) + if not user: + return await xx.edit("`Reply to a user or give username to unban him!`") + await xx.edit("`Getting user info...`") + try: + await ultroid_bot( + EditBannedRequest( + ult.chat_id, + user.id, + ChatBannedRights( + until_date=None, + view_messages=None, + send_messages=None, + send_media=None, + send_stickers=None, + send_gifs=None, + send_games=None, + send_inline=None, + embed_links=None, + ), + ) + ) + except BadRequestError: + return await xx.edit("`I don't have the right to unban a user.`") + except UserIdInvalidError: + await xx.edit("`I couldn't get who he is!`") + if reason: + await xx.edit( + f"[{user.first_name}](tg://user?id={user.id}) **was unbanned by** [{OWNER_NAME}](tg://user?id={OWNER_ID}) **in** `{ult.chat.title}`\n**Reason**: `{reason}`" + ) + else: + await xx.edit( + f"[{user.first_name}](tg://user?id={user.id}) **was unbanned by** [{OWNER_NAME}](tg://user?id={OWNER_ID}) **in** `{ult.chat.title}`" + ) + + +@ultroid_cmd( + pattern="kick ?(.*)", + groups_only=True, +) +async def kck(ult): + xx = await eor(ult, "`Processing...`") + chat = await ult.get_chat() + isAdmin = chat.admin_rights + isCreator = chat.creator + if not isAdmin and not isCreator: + return await xx.edit("`Hmm, I'm not an admin here...`") + user, reason = await get_user_info(ult) + if not user: + return await xx.edit("`Kick? Whom? I couldn't get his info...`") + await xx.edit("`Kicking...`") + try: + await ultroid_bot.kick_participant(ult.chat_id, user.id) + await asyncio.sleep(0.5) + except BadRequestError: + return await xx.edit("`I don't have the right to kick a user.`") + except Exception as e: + return await xx.edit( + f"`I don't have the right to kick a user.`\n\n**ERROR**:\n`{str(e)}`" + ) + if reason: + await xx.edit( + f"[{user.first_name}](tg://user?id={user.id})` was kicked by` [{OWNER_NAME}](tg://user?id={OWNER_ID}) `in {ult.chat.title}`\n**Reason**: `{reason}`" + ) + else: + await xx.edit( + f"[{user.first_name}](tg://user?id={user.id})` was kicked by` [{OWNER_NAME}](tg://user?id={OWNER_ID}) `in {ult.chat.title}`" + ) + + +@ultroid_cmd( + pattern="pin($| (.*))", +) +async def pin(msg): + x = await eor(msg, "`Processing...`") + if not msg.is_private: + # for pin(s) in private messages + await msg.get_chat() + cht = await ultroid_bot.get_entity(msg.chat_id) + xx = msg.reply_to_msg_id + if not msg.is_reply: + return await x.edit("`Reply to a message to pin it.`") + ch = msg.pattern_match.group(1) + slnt = False + if ch == "loud": + slnt = True + try: + await ultroid_bot.pin_message(msg.chat_id, xx, notify=slnt) + except BadRequestError: + return await x.edit("`Hmm, I'm have no rights here...`") + except Exception as e: + return await x.edit(f"**ERROR:**`{str(e)}`") + await x.edit(f"`Pinned` [this message](https://t.me/c/{cht.id}/{xx})!") + + +@ultroid_cmd( + pattern="unpin($| (.*))", +) +async def unp(ult): + xx = await eor(ult, "`Processing...`") + if not ult.is_private: + # for (un)pin(s) in private messages + await ult.get_chat() + ch = (ult.pattern_match.group(1)).strip() + msg = ult.reply_to_msg_id + if msg and not ch: + try: + await ultroid_bot.unpin_message(ult.chat_id, msg) + except BadRequestError: + return await xx.edit("`Hmm, I'm have no rights here...`") + except Exception as e: + return await xx.edit(f"**ERROR:**\n`{str(e)}`") + elif ch == "all": + try: + await ultroid_bot.unpin_message(ult.chat_id) + except BadRequestError: + return await xx.edit("`Hmm, I'm have no rights here...`") + except Exception as e: + return await xx.edit(f"**ERROR:**`{str(e)}`") + else: + return await xx.edit(f"Either reply to a message, or, use `{hndlr}unpin all`") + if not msg and ch != "all": + return await xx.edit(f"Either reply to a message, or, use `{hndlr}unpin all`") + await xx.edit("`Unpinned!`") + + +@ultroid_cmd( + pattern="purge$", +) +async def fastpurger(purg): + chat = await purg.get_input_chat() + msgs = [] + count = 0 + if not purg.reply_to_msg_id: + return await eod(purg, "`Reply to a message to purge from.`", time=10) + async for msg in ultroid_bot.iter_messages(chat, min_id=purg.reply_to_msg_id): + msgs.append(msg) + count = count + 1 + msgs.append(purg.reply_to_msg_id) + if len(msgs) == 100: + await ultroid_bot.delete_messages(chat, msgs) + msgs = [] + + if msgs: + await ultroid_bot.delete_messages(chat, msgs) + done = await ultroid_bot.send_message( + purg.chat_id, + "__Fast purge complete!__\n**Purged** `" + str(count) + "` **messages.**", + ) + await asyncio.sleep(5) + await done.delete() + + +@ultroid_cmd( + pattern="purgeall ?(.*)", +) +async def _(e): + input = e.pattern_match.group(1) + xx = await eor(e, "`Processing...`") + if e.reply_to_msg_id: + input = (await e.get_reply_message()).sender_id + if input: + try: + nos = 0 + async for x in e.client.iter_messages(e.chat_id, from_user=input): + await e.client.delete_messages(e.chat_id, x) + nos += 1 + await e.client.send_message( + e.chat_id, f"**Purged {nos} msgs of {input} from here**" + ) + except ValueError: + return await eod(xx, str(er), time=5) + else: + return await eod( + xx, + "Reply to someone's msg or give their id to delete all msgs from this chat", + time=10, + ) + + +@ultroid_cmd( + pattern="del$", +) +async def delete_it(delme): + msg_src = await delme.get_reply_message() + if delme.reply_to_msg_id: + try: + await msg_src.delete() + await delme.delete() + except BaseException: + await eod( + delme, + f"Couldn't delete the message.\n\n**ERROR:**\n`{str(e)}`", + time=10, + ) + + +@ultroid_cmd( + pattern="edit", +) +async def editer(edit): + message = edit.text + chat = await edit.get_input_chat() + self_id = await ultroid_bot.get_peer_id("me") + string = str(message[6:]) + i = 1 + async for message in ultroid_bot.iter_messages(chat, self_id): + if i == 2: + await message.edit(string) + await edit.delete() + break + i = i + 1 + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/afk.py b/plugins/afk.py new file mode 100644 index 0000000000..fe0b872382 --- /dev/null +++ b/plugins/afk.py @@ -0,0 +1,252 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}afk ` + afk means away from keyboard, + After u active this if Someting tag or msg u then It auto Reply Him/her, + (Note : By Reply To any media U can set media afk too). + +""" + +import asyncio +from datetime import datetime + +from telethon import events +from telethon.tl import functions, types + +from . import * + +global USER_AFK +global afk_time +global last_afk_message +global last_afk_msg +global afk_start +global afk_end +USER_AFK = {} +afk_time = None +last_afk_message = {} +last_afk_msg = {} +afk_start = {} + +LOG = Var.LOG_CHANNEL + + +@ultroid_bot.on(events.NewMessage(outgoing=True)) +@ultroid_bot.on(events.MessageEdited(outgoing=True)) +async def set_not_afk(event): + global USER_AFK + global afk_time + global last_afk_message + global afk_start + global afk_end + back_alive = datetime.now() + afk_end = back_alive.replace(microsecond=0) + if afk_start != {}: + total_afk_time = str((afk_end - afk_start)) + current_message = event.message.message + if "afk" not in current_message and "yes" in USER_AFK: + try: + if pic.endswith((".tgs", ".webp")): + shite = await ultroid_bot.send_message(event.chat_id, file=pic) + shites = await ultroid_bot.send_message( + event.chat_id, + "`No Longer Afk`\n\nWas afk for~`" + total_afk_time + "`", + ) + else: + shite = await ultroid_bot.send_message( + event.chat_id, + "`No Longer Afk`\n\nWas afk for~`" + total_afk_time + "`", + file=pic, + ) + except BaseException: + shite = await ultroid_bot.send_message( + event.chat_id, "`No Longer Afk`\nWas afk for" + total_afk_time + "`" + ) + try: + try: + if pic.endswith((".tgs", ".webp")): + await ultroid_bot.send_message(LOG, file=pic) + await ultroid_bot.send_message( + LOG, + "#AFKFALSE \nSet AFK mode to False\n" + + "Back alive!\nNo Longer afk.\n Was afk for`" + + total_afk_time + + "`", + ) + else: + await ultroid_bot.send_message( + LOG, + "#AFKFALSE \nSet AFK mode to False\n" + + "Back alive!\nNo Longer afk.\n Was afk for`" + + total_afk_time + + "`", + file=pic, + ) + except BaseException: + await ultroid_bot.send_message( + LOG, + "#AFKFALSE \nSet AFK mode to False\n" + + "Back alive!\nNo Longer afk.\n Was afk for`" + + total_afk_time + + "`", + ) + except BaseException: + pass + await asyncio.sleep(3) + await shite.delete() + try: + await shites.delete() + except BaseException: + pass + USER_AFK = {} + afk_time = None + + +@ultroid_bot.on( + events.NewMessage(incoming=True, func=lambda e: bool(e.mentioned or e.is_private)) +) +async def on_afk(event): + if event.fwd_from: + return + global USER_AFK + global afk_time + global last_afk_message + global afk_start + global afk_end + back_alivee = datetime.now() + afk_end = back_alivee.replace(microsecond=0) + if afk_start != {}: + total_afk_time = str((afk_end - afk_start)) + current_message_text = event.message.message.lower() + if "afk" in current_message_text: + return False + if USER_AFK and not (await event.get_sender()).bot: + msg = None + if reason: + message_to_reply = ( + f"__Master #AFK since__ `{total_afk_time}`\n\n" + + f"__" + + f"\n\n**Reason:- **{reason}" + ) + else: + message_to_reply = f"__Master #AFK since__ `{total_afk_time}`\n\n" + f"__" + try: + if pic.endswith((".tgs", ".webp")): + msg = await event.reply(file=pic) + msgs = await event.reply(message_to_reply) + else: + msg = await event.reply(message_to_reply, file=pic) + except BaseException: + msg = await event.reply(message_to_reply) + await asyncio.sleep(2.5) + if event.chat_id in last_afk_message: + await last_afk_message[event.chat_id].delete() + try: + if event.chat_id in last_afk_msg: + await last_afk_msg[event.chat_id].delete() + except BaseException: + pass + last_afk_message[event.chat_id] = msg + try: + if msgs: + last_afk_msg[event.chat_id] = msgs + except BaseException: + pass + + +@ultroid_cmd(pattern=r"afk ?(.*)") +async def _(event): + if event.fwd_from: + return + reply = await event.get_reply_message() + global USER_AFK + global afk_time + global last_afk_message + global last_afk_msg + global afk_start + global afk_end + global reason + global pic + USER_AFK = {} + afk_time = None + last_afk_message = {} + last_afk_msg = {} + afk_end = {} + start_1 = datetime.now() + afk_start = start_1.replace(microsecond=0) + reason = event.pattern_match.group(1) + if reply: + pic = await event.client.download_media(reply) + else: + pic = None + if not USER_AFK: + last_seen_status = await ultroid_bot( + functions.account.GetPrivacyRequest(types.InputPrivacyKeyStatusTimestamp()) + ) + if isinstance(last_seen_status.rules, types.PrivacyValueAllowAll): + afk_time = datetime.datetime.now() + USER_AFK = f"yes: {reason} {pic}" + if reason: + try: + if pic.endswith((".tgs", ".webp")): + await ultroid_bot.send_message(event.chat_id, file=pic) + await ultroid_bot.send_message( + event.chat_id, f"Afk __because ~ {reason}__" + ) + else: + await ultroid_bot.send_message( + event.chat_id, f"Afk __because ~ {reason}__", file=pic + ) + except BaseException: + await ultroid_bot.send_message( + event.chat_id, f"Afk __because ~ {reason}__" + ) + else: + try: + if pic.endswith((".tgs", ".webp")): + await ultroid_bot.send_message(event.chat_id, file=pic) + await ultroid_bot.send_message( + event.chat_id, f"**I am Going afk!**" + ) + else: + await ultroid_bot.send_message( + event.chat_id, f"**I am Going afk!**", file=pic + ) + except BaseException: + await ultroid_bot.send_message(event.chat_id, f"**I am Going afk!**") + await event.delete() + try: + if reason and pic: + if pic.endswith((".tgs", ".webp")): + await ultroid_bot.send_message(LOG, file=pic) + await ultroid_bot.send_message( + LOG, f"AFK mode to On and Reason is {reason}" + ) + else: + await ultroid_bot.send_message( + LOG, f"AFK mode to On and Reason is {reason}", file=pic + ) + elif reason: + await ultroid_bot.send_message( + LOG, f"AFK mode to On and Reason is {reason}" + ) + elif pic: + if pic.endswith((".tgs", ".webp")): + await ultroid_bot.send_message(LOG, file=pic) + await ultroid_bot.send_message(LOG, f"AFK mode to On") + else: + await ultroid_bot.send_message(LOG, f"AFK mode to On", file=pic) + else: + await ultroid_bot.send_message(LOG, f"AFK mode to On") + except Exception as e: + logger.warn(str(e)) + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/bot.py b/plugins/bot.py new file mode 100644 index 0000000000..56b173963f --- /dev/null +++ b/plugins/bot.py @@ -0,0 +1,232 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available + +• `{i}alive` + Check if your bot is working. + +• `{i}ping` + Check Ultroid's response time. + +• `{i}cmds` + View all plugin names. + +• `{i}restart` + To restart your bot. + +• `{i}logs` + Get the last 100 lines from heroku logs. + +• `{i}usage` + Get app usage details. + +• `{i}shutdown` + Turn off your bot. +""" + +import asyncio +import math +import os +import shutil +import time +from datetime import datetime as dt +from platform import python_version as pyver + +import heroku3 +import psutil +import requests +from git import Repo +from telethon import __version__ + +from . import * + +HEROKU_API = None +HEROKU_APP_NAME = None + +try: + if Var.HEROKU_API and Var.HEROKU_APP_NAME: + HEROKU_API = Var.HEROKU_API + HEROKU_APP_NAME = Var.HEROKU_APP_NAME + Heroku = heroku3.from_key(Var.HEROKU_API) + heroku_api = "https://api.heroku.com" + app = Heroku.app(Var.HEROKU_APP_NAME) +except BaseException: + HEROKU_API = None + HEROKU_APP_NAME = None + + +@ultroid_cmd( + pattern="alive$", +) +async def lol(ult): + pic = udB.get("ALIVE_PIC") + uptime = grt((time.time() - start_time)) + header = udB.get("ALIVE_TEXT") if udB.get("ALIVE_TEXT") else "Hey, I am alive." + als = """ +**The Ultroid Userbot...** + +**{}** + +✵ **Owner** - `{}` +✵ **Ultroid** - `{}` +✵ **UpTime** - `{}` +✵ **Python** - `{}` +✵ **Telethon** - `{}` +✵ **Branch** - `{}` +""".format( + header, + OWNER_NAME, + ultroid_version, + uptime, + pyver(), + __version__, + Repo().active_branch, + ) + if pic is None: + await ult.edit(als) + elif pic is not None and "telegra" in pic: + await ult.delete() + await ult.reply(als, file=pic) + else: + await ult.delete() + await ultroid_bot.send_message(ult.chat_id, file=pic) + await ultroid_bot.send_message(ult.chat_id, als) + + +@ultroid_cmd( + pattern="ping$", +) +async def _(event): + start = dt.now() + x = await eor(event, "`Pong !`") + if event.fwd_from: + return + end = dt.now() + ms = (end - start).microseconds / 1000 + uptime = grt((time.time() - start_time)) + await x.edit(f"**Pong !!** `{ms}ms`\n**Uptime** - `{uptime}`") + + +@ultroid_cmd( + pattern="cmds$", +) +async def cmds(event): + await allcmds(event) + + +@ultroid_cmd( + pattern="restart$", +) +async def restartbt(ult): + await restart(ult) + + +@ultroid_cmd( + pattern="logs$", +) +async def _(ult): + xx = await eor(ult, "`Processing...`") + if HEROKU_API is None and HEROKU_APP_NAME is None: + return await xx.edit("Please set `HEROKU_APP_NAME` and `HEROKU_API` in vars.") + await xx.edit("`Downloading Logs...`") + with open("logs-ultroid.txt", "w") as log: + log.write(app.get_log()) + ok = app.get_log() + message = ok + url = "https://del.dog/documents" + r = requests.post(url, data=message.encode("UTF-8")).json() + url = f"https://del.dog/{r['key']}" + await ult.client.send_file( + ult.chat_id, + "logs-ultroid.txt", + reply_to=ult.id, + caption=f"**Heroku** Ultroid Logs.\nPasted [here]({url}) too!", + ) + await xx.edit("`Uploading...`") + await asyncio.sleep(1) + await xx.delete() + return os.remove("logs-ultroid.txt") + + +@ultroid_cmd( + pattern="usage$", +) +async def dyno_usage(dyno): + dyn = await eor(dyno, "`Processing...`") + useragent = ( + "Mozilla/5.0 (Linux; Android 10; SM-G975F) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/80.0.3987.149 Mobile Safari/537.36" + ) + user_id = Heroku.account().id + headers = { + "User-Agent": useragent, + "Authorization": f"Bearer {Var.HEROKU_API}", + "Accept": "application/vnd.heroku+json; version=3.account-quotas", + } + path = "/accounts/" + user_id + "/actions/get-quota" + r = requests.get(heroku_api + path, headers=headers) + if r.status_code != 200: + return await dyno.edit( + "`Error: something bad happened`\n\n" f">.`{r.reason}`\n" + ) + result = r.json() + quota = result["account_quota"] + quota_used = result["quota_used"] + remaining_quota = quota - quota_used + percentage = math.floor(remaining_quota / quota * 100) + minutes_remaining = remaining_quota / 60 + hours = math.floor(minutes_remaining / 60) + minutes = math.floor(minutes_remaining % 60) + App = result["apps"] + try: + App[0]["quota_used"] + except IndexError: + AppQuotaUsed = 0 + AppPercentage = 0 + else: + AppQuotaUsed = App[0]["quota_used"] / 60 + AppPercentage = math.floor(App[0]["quota_used"] * 100 / quota) + AppHours = math.floor(AppQuotaUsed / 60) + AppMinutes = math.floor(AppQuotaUsed % 60) + total, used, free = shutil.disk_usage(".") + cpuUsage = psutil.cpu_percent() + memory = psutil.virtual_memory().percent + disk = psutil.disk_usage("/").percent + upload = humanbytes(psutil.net_io_counters().bytes_sent) + down = humanbytes(psutil.net_io_counters().bytes_recv) + TOTAL = humanbytes(total) + USED = humanbytes(used) + FREE = humanbytes(free) + return await eod( + dyn, + "**⚙️ Dyno Usage ⚙️**:\n\n" + + f" -> `Dyno usage for` **{Var.HEROKU_APP_NAME}**:\n" + + f" • `{AppHours}`**h** `{AppMinutes}`**m** " + + f"**|** [`{AppPercentage}`**%**]" + + "\n\n" + + " -> `Dyno hours quota remaining this month`:\n" + + f" • `{hours}`**h** `{minutes}`**m** " + + f"**|** [`{percentage}`**%**]\n\n" + + f"**Total Disk Space: {TOTAL}\n\n**" + + f"**Used: {USED} Free: {FREE}\n\n**" + + f"**📊Data Usage📊\n\nUpload: {upload}\nDown: {down}\n\n**" + + f"**CPU: {cpuUsage}%\nRAM: {memory}%\nDISK: {disk}%**", + ) + + +@ultroid_cmd( + pattern="shutdown$", +) +async def shht(event): + await eor(event, "GoodBye {}.\n`Shutting down...`".format(OWNER_NAME)) + await ultroid_bot.disconnect() + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/carbon.py b/plugins/carbon.py new file mode 100644 index 0000000000..e6c336996c --- /dev/null +++ b/plugins/carbon.py @@ -0,0 +1,235 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}carbon ` + Carbonise the text with default settings. +• `{i}rcarbon ` + Carbonise the text, with random bg colours. +""" + +import os +import random + +from carbonnow import Carbon + +from . import * + +all_col = [ + "Black", + "Navy", + "DarkBlue", + "MediumBlue", + "Blue", + "DarkGreen", + "Green", + "Teal", + "DarkCyan", + "DeepSkyBlue", + "DarkTurquoise", + "MediumSpringGreen", + "Lime", + "SpringGreen", + "Aqua", + "Cyan", + "MidnightBlue", + "DodgerBlue", + "LightSeaGreen", + "ForestGreen", + "SeaGreen", + "DarkSlateGray", + "DarkSlateGrey", + "LimeGreen", + "MediumSeaGreen", + "Turquoise", + "RoyalBlue", + "SteelBlue", + "DarkSlateBlue", + "MediumTurquoise", + "Indigo ", + "DarkOliveGreen", + "CadetBlue", + "CornflowerBlue", + "RebeccaPurple", + "MediumAquaMarine", + "DimGray", + "DimGrey", + "SlateBlue", + "OliveDrab", + "SlateGray", + "SlateGrey", + "LightSlateGray", + "LightSlateGrey", + "MediumSlateBlue", + "LawnGreen", + "Chartreuse", + "Aquamarine", + "Maroon", + "Purple", + "Olive", + "Gray", + "Grey", + "SkyBlue", + "LightSkyBlue", + "BlueViolet", + "DarkRed", + "DarkMagenta", + "SaddleBrown", + "DarkSeaGreen", + "LightGreen", + "MediumPurple", + "DarkViolet", + "PaleGreen", + "DarkOrchid", + "YellowGreen", + "Sienna", + "Brown", + "DarkGray", + "DarkGrey", + "LightBlue", + "GreenYellow", + "PaleTurquoise", + "LightSteelBlue", + "PowderBlue", + "FireBrick", + "DarkGoldenRod", + "MediumOrchid", + "RosyBrown", + "DarkKhaki", + "Silver", + "MediumVioletRed", + "IndianRed ", + "Peru", + "Chocolate", + "Tan", + "LightGray", + "LightGrey", + "Thistle", + "Orchid", + "GoldenRod", + "PaleVioletRed", + "Crimson", + "Gainsboro", + "Plum", + "BurlyWood", + "LightCyan", + "Lavender", + "DarkSalmon", + "Violet", + "PaleGoldenRod", + "LightCoral", + "Khaki", + "AliceBlue", + "HoneyDew", + "Azure", + "SandyBrown", + "Wheat", + "Beige", + "WhiteSmoke", + "MintCream", + "GhostWhite", + "Salmon", + "AntiqueWhite", + "Linen", + "LightGoldenRodYellow", + "OldLace", + "Red", + "Fuchsia", + "Magenta", + "DeepPink", + "OrangeRed", + "Tomato", + "HotPink", + "Coral", + "DarkOrange", + "LightSalmon", + "Orange", + "LightPink", + "Pink", + "Gold", + "PeachPuff", + "NavajoWhite", + "Moccasin", + "Bisque", + "MistyRose", + "BlanchedAlmond", + "PapayaWhip", + "LavenderBlush", + "SeaShell", + "Cornsilk", + "LemonChiffon", + "FloralWhite", + "Snow", + "Yellow", + "LightYellow", + "Ivory", + "White", +] + + +@ultroid_cmd( + pattern="carbon", +) +async def crbn(event): + xxxx = await eor(event, "Processing") + if event.reply_to_msg_id: + temp = await event.get_reply_message() + if temp.media: + b = await ultroid_bot.download_media(temp) + a = open(b, "r") + code = a.read() + a.close() + os.remove(b) + else: + code = temp.message + else: + code = event.text.split(" ", maxsplit=1)[1] + carbon = Carbon(code=code) + xx = await carbon.save("ultroid_carbon") + await xxxx.delete() + await ultroid_bot.send_file( + event.chat_id, + xx, + caption=f"Carbonised by [{OWNER_NAME}](tg://user?id={OWNER_ID})", + force_document=True, + ) + os.remove(xx) + + +@ultroid_cmd( + pattern="rcarbon", +) +async def crbn(event): + xxxx = await eor(event, "Processing") + if event.reply_to_msg_id: + temp = await event.get_reply_message() + if temp.media: + b = await ultroid_bot.download_media(temp) + a = open(b, "r") + code = a.read() + a.close() + os.remove(b) + else: + code = temp.message + else: + code = event.text.split(" ", maxsplit=1)[1] + col = random.choice(all_col) + carbon = Carbon(code=code, background=col) + xx = await carbon.save("ultroid_carbon") + await xxxx.delete() + await ultroid_bot.send_file( + event.chat_id, + xx, + caption=f"Carbonised by [{OWNER_NAME}](tg://user?id={OWNER_ID})", + force_document=True, + ) + os.remove(xx) + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/chats.py b/plugins/chats.py new file mode 100644 index 0000000000..f0a57d060c --- /dev/null +++ b/plugins/chats.py @@ -0,0 +1,117 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}delchat` + Delete the group this cmd is used in. + +• `{i}getlink` + Get link of group this cmd is used in. + +• `{i}create (b|g|c) ` + Create group woth a specific name. + b - megagroup/supergroup + g - small group + c - channel +""" + + +from telethon.errors import ChatAdminRequiredError as no_admin +from telethon.tl import functions + +from . import * + + +@ultroid_cmd( + pattern="delchat$", + groups_only=True, +) +async def _(e): + xx = await eor(e, "`Processing...`") + try: + await e.client(functions.channels.DeleteChannelRequest(e.chat_id)) + except TypeError: + return await eod(xx, "`Cant delete this chat`", time=10) + except no_admin: + return await eod(xx, "`I m not an admin`", time=10) + await e.client.send_message(Var.LOG_CHANNEL, f"#Deleted\nDeleted {e.chat_id}") + + +@ultroid_cmd( + pattern="getlink$", + groups_only=True, +) +async def _(e): + xx = await eor(e, "`Processing...`") + try: + r = await e.client( + functions.messages.ExportChatInviteRequest(e.chat_id), + ) + except no_admin: + return await eod(xx, "`I m not an admin`", time=10) + await eod(xx, f"Link:- {r.link}") + + +@ultroid_cmd( + pattern="create (b|g|c)(?: |$)(.*)", +) +async def _(e): + type_of_group = e.pattern_match.group(1) + group_name = e.pattern_match.group(2) + xx = await eor(e, "`Processing...`") + if type_of_group == "b": + try: + r = await e.client( + functions.messages.CreateChatRequest( + users=["@missrose_bot"], + title=group_name, + ) + ) + created_chat_id = r.chats[0].id + await e.client( + functions.messages.DeleteChatUserRequest( + chat_id=created_chat_id, + user_id="@missrose_bot", + ) + ) + result = await e.client( + functions.messages.ExportChatInviteRequest( + peer=created_chat_id, + ) + ) + await xx.edit( + f"Your [{group_name}]({result.link}) Group Made Boss!", + link_preview=False, + ) + except Exception as ex: + await xx.edit(str(ex)) + elif type_of_group == "g" or type_of_group == "c": + try: + r = await e.client( + functions.channels.CreateChannelRequest( + title=group_name, + about="Join @TeamUltroid", + megagroup=False if type_of_group == "c" else True, + ) + ) + created_chat_id = r.chats[0].id + result = await e.client( + functions.messages.ExportChatInviteRequest( + peer=created_chat_id, + ) + ) + await xx.edit( + f"Your [{group_name}]({result.link}) Group/Channel Has been made Boss!", + link_preview=False, + ) + except Exception as ex: + await xx.edit(str(ex)) + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/core.py b/plugins/core.py new file mode 100644 index 0000000000..d068598299 --- /dev/null +++ b/plugins/core.py @@ -0,0 +1,175 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}install ` + To install the plugin. + +• `{i}uninstall ` + To unload and remove the plugin. + +• `{i}load ` + To load unloaded unofficial plugin. + +• `{i}unload ` + To unload unofficial plugin. + +• `{i}help ` + Shows you a help menu (like this) for every plugin. +""" + +import os + +from telethon.tl.custom import Button + +from . import * + + +@in_pattern( + "send (.*)", +) +@in_owner +async def inline_handler(event): + builder = event.builder + input_str = event.pattern_match.group(1) + plug = [*PLUGINS] + plugs = [] + if input_str == "all": + for i in plug: + try: + plugs.append( + await event.builder.document( + f"./plugins/{i}.py", + title=f"{i}.py", + description=f"Module Found", + text=f"{i}.py use .paste to paste in neko and raw..", + buttons=[ + [ + Button.switch_inline( + "Search Again..?", query="send all", same_peer=True + ) + ] + ], + ) + ) + except BaseException: + pass + await event.answer(plugs) + else: + try: + ultroid = builder.document( + f"./plugins/{input_str}.py", + title=f"{input_str}.py", + description=f"Module {input_str} Found", + text=f"{input_str}.py use .paste to paste in neko and raw..", + buttons=[ + [ + Button.switch_inline( + "Search Again..?", query="send ", same_peer=True + ) + ] + ], + ) + await event.answer([ultroid]) + return + except BaseException: + ultroidcode = builder.article( + title=f"Module {input_str}.py Not Found", + description=f"No Such Module", + text=f"No Module Named {input_str}.py", + buttons=[ + [ + Button.switch_inline( + "Search Again", query="send ", same_peer=True + ) + ] + ], + ) + await event.answer([ultroidcode]) + return + + +@ultroid_cmd( + pattern="install$", +) +async def install(event): + await safeinstall(event) + + +@ultroid_cmd( + pattern=r"unload (?P\w+)$", +) +async def unload(event): + shortname = event.pattern_match["shortname"] + if not shortname: + await eor(event, "`Give name of plugin which u want to unload`") + return + lsd = os.listdir("addons") + lst = os.listdir("plugins") + zym = shortname + ".py" + if zym in lsd: + try: + un_plug(shortname) + await eod(event, f"**Uɴʟᴏᴀᴅᴇᴅ** `{shortname}` **Sᴜᴄᴄᴇssғᴜʟʟʏ.**", time=3) + except BaseException: + pass + elif zym in lst: + return await eod(event, "**Yᴏᴜ Cᴀɴ'ᴛ Uɴʟᴏᴀᴅ Oғғɪᴄɪᴀʟ Pʟᴜɢɪɴs**", time=3) + else: + return await eod(event, f"**Nᴏ Pʟᴜɢɪɴ Nᴀᴍᴇᴅ** `{shortname}`", time=3) + + +@ultroid_cmd( + pattern=r"uninstall (?P\w+)$", +) +async def uninstall(event): + shortname = event.pattern_match["shortname"] + if not shortname: + await eor(event, "`Give name of plugin which u want to uninstall`") + return + lsd = os.listdir("addons") + lst = os.listdir("plugins") + zym = shortname + ".py" + if zym in lsd: + try: + un_plug(shortname) + await eod(event, f"**Uɴɪɴsᴛᴀʟʟᴇᴅ** `{shortname}` **Sᴜᴄᴄᴇssғᴜʟʟʏ.**", time=3) + os.remove(f"addons/{shortname}.py") + except BaseException: + pass + elif zym in lst: + return await eod(event, "**Yᴏᴜ Cᴀɴ'ᴛ Uɴɪɴsᴛᴀʟʟ Oғғɪᴄɪᴀʟ Pʟᴜɢɪɴs**", time=3) + else: + return await eod(event, f"**Nᴏ Pʟᴜɢɪɴ Nᴀᴍᴇᴅ** `{shortname}`", time=3) + + +@ultroid_cmd( + pattern=r"load (?P\w+)$", +) +async def load(event): + shortname = event.pattern_match["shortname"] + if not shortname: + await eor(event, "`Give name of plugin which u want to load`") + return + try: + try: + un_plug(shortname) + except BaseException: + pass + load_addons(shortname) + await eod(event, f"**Sᴜᴄᴄᴇssғᴜʟʟʏ Lᴏᴀᴅᴇᴅ** `{shortname}`", time=3) + except Exception as e: + await eod( + event, + f"**Could not load** `{shortname}` **because of the following error.**\n`{str(e)}`", + time=3, + ) + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/github.py b/plugins/github.py new file mode 100644 index 0000000000..3ad0a82dc6 --- /dev/null +++ b/plugins/github.py @@ -0,0 +1,70 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}github ` + Get full information of the users github profile. +""" + + +import requests + +from . import * + + +@ultroid_cmd( + pattern="github (.*)", +) +async def gitsearch(event): + xx = await eor(event, "`Searching...`") + try: + usrname = event.pattern_match.group(1) + except BaseException: + return await xx.edit("`Search for whom? Give me a user name!!`") + url = f"https://api.github.com/users/{usrname}" + ult = requests.get(url).json() + try: + uname = ult["login"] + uid = ult["id"] + upic = ult["avatar_url"] + ulink = ult["html_url"] + uacc = ult["name"] + ucomp = ult["company"] + ublog = ult["blog"] + ulocation = ult["location"] + ubio = ult["bio"] + urepos = ult["public_repos"] + ufollowers = ult["followers"] + ufollowing = ult["following"] + except BaseException: + return await xx.edit("`No such user found...`") + fullusr = f""" +**[GITHUB]({ulink})** + +**Name** - {uacc} +**UserName** - {uname} +**ID** - {uid} +**Company** - {ucomp} +**Blog** - {ublog} +**Location** - {ulocation} +**Bio** - {ubio} +**Repos** - {urepos} +**Followers** - {ufollowers} +**Following** - {ufollowing} +""" + await xx.delete() + await ultroid_bot.send_file( + event.chat_id, + upic, + caption=fullusr, + link_preview=False, + ) + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/google.py b/plugins/google.py new file mode 100644 index 0000000000..057433320a --- /dev/null +++ b/plugins/google.py @@ -0,0 +1,120 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}google ` + For doing google search. + +• `{i}img ` + For doing Images search. + +• `{i}reverse ` + Reply an Image or sticker to find its sauce. +""" + +import os +from shutil import rmtree + +import requests +from bs4 import BeautifulSoup as bs +from PIL import Image +from search_engine_parser import * + +from . import * + + +@ultroid_cmd(pattern="google ?(.*)") +async def google(event): + inp = event.pattern_match.group(1) + if not inp: + return await event.edit("Give something to search") + x = await eor(event, "`searching..`") + gs = GoogleSearch() + res = await gs.async_search(f"{inp}") + out = "" + for i in range(len(res["links"])): + text = res["titles"][i] + url = res["links"][i] + des = res["descriptions"][i] + out += f" 👉🏻 [{text}]({url})\n`{des}`\n\n" + await x.edit( + f"**Google Search Query:**\n`{inp}`\n\n**Results:**\n{out}", link_preview=False + ) + + +@ultroid_cmd(pattern="img ?(.*)") +async def goimg(event): + query = event.pattern_match.group(1) + if not query: + return await eor(event, "`Give something to search") + nn = await eor(event, "`Processing Keep Patience...`") + if ";" in query: + try: + lmt = int(query.split(";")[1]) + except BaseExceptaion: + lmt = 5 + else: + lmt = 5 + gi = googleimagesdownload() + args = { + "keywords": query, + "limit": lmt, + "format": "jpg", + "output_directory": "./resources/downloads/", + } + pth = gi.download(args) + ok = pth[0][query] + await event.client.send_file(event.chat_id, ok, album=True) + rmtree(f"./resources/downloads/{query}/") + await nn.delete() + + +@ultroid_cmd(pattern="reverse") +async def reverse(event): + reply = await event.get_reply_message() + if not reply: + return await eor(event, "`Reply to any Image`") + ult = await eor(event, "`Processing...`") + dl = await bot.download_media(reply) + img = Image.open(dl) + x, y = img.size + file = {"encoded_image": (dl, open(dl, "rb"))} + grs = requests.post( + "https://www.google.com/searchbyimage/upload", files=file, allow_redirects=False + ) + loc = grs.headers.get("Location") + response = requests.get( + loc, + headers={ + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0" + }, + ) + xx = bs(response.text, "html.parser") + div = xx.find("div", {"class": "r5a77d"}) + alls = div.find("a") + link = alls["href"] + text = alls.text + await ult.edit(f"`Dimension ~ {x} : {y}`\nSauce ~ [{text}](google.com{link})") + gi = googleimagesdownload() + args = { + "keywords": text, + "limit": 2, + "format": "jpg", + "output_directory": "./resources/downloads/", + } + pth = gi.download(args) + ok = pth[0][text] + await event.client.send_file( + event.chat_id, ok, album=True, caption="Similar Images Realted to Search" + ) + rmtree(f"./resources/downloads/{text}/") + os.remove(dl) + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/imagetools.py b/plugins/imagetools.py new file mode 100644 index 0000000000..81150ad0af --- /dev/null +++ b/plugins/imagetools.py @@ -0,0 +1,468 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}toon ` + To make it toon. + +• `{i}danger ` + To make it look Danger. + +• `{i}grey ` + To make it black nd white. + +• `{i}negative ` + To make negative image. + +• `{i}blur ` + To make it blurry. + +• `{i}quad ` + create a Vortex. + +• `{i}mirror ` + To create mirror pic. + +• `{i}flip ` + To make it flip. + +• `{i}sketch ` + To draw its sketch. + +• `{i}blue ` + just cool. +""" + +import asyncio +import os + +import cv2 +import numpy as np +from PIL import Image +from telegraph import upload_file as upf +from validators.url import url + +from . import * + + +@ultroid_cmd( + pattern="sketch$", +) +async def sketch(e): + ureply = await e.get_reply_message() + xx = await eor(e, "`...`") + if not (ureply and (ureply.media)): + await xx.edit("`Reply to any media`") + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + await xx.edit("`Ooo Animated Sticker 👀...`") + cmd = ["lottie_convert.py", ultt, "ult.png"] + file = "ult.png" + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + else: + await xx.edit("`Processing...`") + img = cv2.VideoCapture(ultt) + heh, lol = img.read() + cv2.imwrite("ult.png", lol) + file = "ult.png" + img = cv2.imread(file) + gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + inverted_gray_image = 255 - gray_image + blurred_img = cv2.GaussianBlur(inverted_gray_image, (21, 21), 0) + inverted_blurred_img = 255 - blurred_img + pencil_sketch_IMG = cv2.divide(gray_image, inverted_blurred_img, scale=256.0) + cv2.imwrite("ultroid.png", pencil_sketch_IMG) + await e.client.send_file(e.chat_id, file="ultroid.png") + await xx.delete() + os.remove(file) + os.remove("ultroid.png") + + +@ultroid_cmd( + pattern="grey$", +) +async def ultd(event): + ureply = await event.get_reply_message() + if not (ureply and (ureply.media)): + await eor(event, "`Reply to any media`") + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + xx = await eor(event, "`Ooo Animated Sticker 👀...`") + cmd = ["lottie_convert.py", ultt, "ult.png"] + file = "ult.png" + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + else: + xx = await eor(event, "`Processing...`") + img = cv2.VideoCapture(ultt) + heh, lol = img.read() + cv2.imwrite("ult.png", lol) + file = "ult.png" + ult = cv2.imread(file) + ultroid = cv2.cvtColor(ult, cv2.COLOR_BGR2GRAY) + cv2.imwrite("ult.jpg", ultroid) + await event.client.send_file( + event.chat_id, "ult.jpg", force_document=False, reply_to=event.reply_to_msg_id + ) + await xx.delete() + os.remove("ult.png") + os.remove("ult.jpg") + os.remove(ultt) + + +@ultroid_cmd( + pattern="blur$", +) +async def ultd(event): + ureply = await event.get_reply_message() + if not (ureply and (ureply.media)): + await eor(event, "`Reply to any media`") + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + xx = await eor(event, "`Ooo Animated Sticker 👀...`") + cmd = ["lottie_convert.py", ultt, "ult.png"] + file = "ult.png" + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + else: + xx = await eor(event, "`Processing...`") + img = cv2.VideoCapture(ultt) + heh, lol = img.read() + cv2.imwrite("ult.png", lol) + file = "ult.png" + ult = cv2.imread(file) + ultroid = cv2.GaussianBlur(ult, (35, 35), 0) + cv2.imwrite("ult.jpg", ultroid) + await event.client.send_file( + event.chat_id, "ult.jpg", force_document=False, reply_to=event.reply_to_msg_id + ) + await xx.delete() + os.remove("ult.png") + os.remove("ult.jpg") + os.remove(ultt) + + +@ultroid_cmd( + pattern="negative$", +) +async def ultd(event): + ureply = await event.get_reply_message() + xx = await eor(event, "`...`") + if not (ureply and (ureply.media)): + await xx.edit("`Reply to any media`") + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + await xx.edit("`Ooo Animated Sticker 👀...`") + cmd = ["lottie_convert.py", ultt, "ult.png"] + file = "ult.png" + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + else: + await xx.edit("`Processing...`") + img = cv2.VideoCapture(ultt) + heh, lol = img.read() + cv2.imwrite("ult.png", lol) + file = "ult.png" + ult = cv2.imread(file) + ultroid = cv2.bitwise_not(ult) + cv2.imwrite("ult.jpg", ultroid) + await event.client.send_file( + event.chat_id, "ult.jpg", force_document=False, reply_to=event.reply_to_msg_id + ) + await xx.delete() + os.remove("ult.png") + os.remove("ult.jpg") + os.remove(ultt) + + +@ultroid_cmd( + pattern="mirror$", +) +async def ultd(event): + ureply = await event.get_reply_message() + xx = await eor(event, "`...`") + if not (ureply and (ureply.media)): + await xx.edit("`Reply to any media`") + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + await xx.edit("`Ooo Animated Sticker 👀...`") + cmd = ["lottie_convert.py", ultt, "ult.png"] + file = "ult.png" + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + else: + await xx.edit("`Processing...`") + img = cv2.VideoCapture(ultt) + heh, lol = img.read() + cv2.imwrite("ult.png", lol) + file = "ult.png" + ult = cv2.imread(file) + ish = cv2.flip(ult, 1) + ultroid = cv2.hconcat([ult, ish]) + cv2.imwrite("ult.jpg", ultroid) + await event.client.send_file( + event.chat_id, "ult.jpg", force_document=False, reply_to=event.reply_to_msg_id + ) + await xx.delete() + os.remove("ult.png") + os.remove("ult.jpg") + os.remove(ultt) + + +@ultroid_cmd( + pattern="flip$", +) +async def ultd(event): + ureply = await event.get_reply_message() + xx = await eor(event, "`...`") + if not (ureply and (ureply.media)): + await xx.edit("`Reply to any media`") + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + await xx.edit("`Ooo Animated Sticker 👀...`") + cmd = ["lottie_convert.py", ultt, "ult.png"] + file = "ult.png" + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + else: + await xx.edit("`Processing...`") + img = cv2.VideoCapture(ultt) + heh, lol = img.read() + cv2.imwrite("ult.png", lol) + file = "ult.png" + ult = cv2.imread(file) + trn = cv2.flip(ult, 1) + ish = cv2.rotate(trn, cv2.ROTATE_180) + ultroid = cv2.vconcat([ult, ish]) + cv2.imwrite("ult.jpg", ultroid) + await event.client.send_file( + event.chat_id, "ult.jpg", force_document=False, reply_to=event.reply_to_msg_id + ) + await xx.delete() + os.remove("ult.png") + os.remove("ult.jpg") + os.remove(ultt) + + +@ultroid_cmd( + pattern="quad$", +) +async def ultd(event): + ureply = await event.get_reply_message() + xx = await eor(event, "`...`") + if not (ureply and (ureply.media)): + await xx.edit("`Reply to any media`") + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + await xx.edit("`Ooo Animated Sticker 👀...`") + cmd = ["lottie_convert.py", ultt, "ult.png"] + file = "ult.png" + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + else: + await xx.edit("`Processing...`") + img = cv2.VideoCapture(ultt) + heh, lol = img.read() + cv2.imwrite("ult.png", lol) + file = "ult.png" + ult = cv2.imread(file) + roid = cv2.flip(ult, 1) + mici = cv2.hconcat([ult, roid]) + fr = cv2.flip(mici, 1) + trn = cv2.rotate(fr, cv2.ROTATE_180) + ultroid = cv2.vconcat([mici, trn]) + cv2.imwrite("ult.jpg", ultroid) + await event.client.send_file( + event.chat_id, "ult.jpg", force_document=False, reply_to=event.reply_to_msg_id + ) + await xx.delete() + os.remove("ult.png") + os.remove("ult.jpg") + os.remove(ultt) + + +@ultroid_cmd( + pattern="toon$", +) +async def ultd(event): + ureply = await event.get_reply_message() + xx = await eor(event, "`...`") + if not (ureply and (ureply.media)): + await xx.edit("`Reply to any media`") + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + await xx.edit("`Ooo Animated Sticker 👀...`") + cmd = ["lottie_convert.py", ultt, "ult.png"] + file = "ult.png" + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + else: + await xx.edit("`Processing...`") + img = cv2.VideoCapture(ultt) + heh, lol = img.read() + cv2.imwrite("ult.png", lol) + file = "ult.png" + ult = cv2.imread(file) + height, width, channels = ult.shape + samples = np.zeros([height * width, 3], dtype=np.float32) + count = 0 + for x in range(height): + for y in range(width): + samples[count] = ult[x][y] + count += 1 + compactness, labels, centers = cv2.kmeans( + samples, + 12, + None, + (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10000, 0.0001), + 5, + cv2.KMEANS_PP_CENTERS, + ) + centers = np.uint8(centers) + ish = centers[labels.flatten()] + ultroid = ish.reshape((ult.shape)) + cv2.imwrite("ult.jpg", ultroid) + await event.client.send_file( + event.chat_id, "ult.jpg", force_document=False, reply_to=event.reply_to_msg_id + ) + await xx.delete() + os.remove("ult.png") + os.remove("ult.jpg") + os.remove(ultt) + + +@ultroid_cmd( + pattern="danger$", +) +async def ultd(event): + ureply = await event.get_reply_message() + xx = await eor(event, "`...`") + if not (ureply and (ureply.media)): + await xx.edit("`Reply to any media`") + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + await xx.edit("`Ooo Animated Sticker 👀...`") + cmd = ["lottie_convert.py", ultt, "ult.png"] + file = "ult.png" + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + else: + await xx.edit("`Processing...`") + img = cv2.VideoCapture(ultt) + heh, lol = img.read() + cv2.imwrite("ult.png", lol) + file = "ult.png" + ult = cv2.imread(file) + dan = cv2.cvtColor(ult, cv2.COLOR_BGR2RGB) + ultroid = cv2.cvtColor(dan, cv2.COLOR_HSV2BGR) + cv2.imwrite("ult.jpg", ultroid) + await event.client.send_file( + event.chat_id, "ult.jpg", force_document=False, reply_to=event.reply_to_msg_id + ) + await xx.delete() + os.remove("ult.png") + os.remove("ult.jpg") + os.remove(ultt) + + +@ultroid_cmd( + pattern="blue$", +) +async def ultd(event): + ureply = await event.get_reply_message() + xx = await eor(event, "`...`") + if not (ureply and (ureply.media)): + await xx.edit("`Reply to any media`") + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + await xx.edit("`Ooo Animated Sticker 👀...`") + cmd = ["lottie_convert.py", ultt, "ult.png"] + file = "ult.png" + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + else: + await xx.edit("`Processing...`") + img = cv2.VideoCapture(ultt) + heh, lol = img.read() + cv2.imwrite("ult.png", lol) + file = "ult.png" + got = upf(file) + lnk = f"https://telegra.ph{got[0]}" + r = requests.get( + f"https://nekobot.xyz/api/imagegen?type=blurpify&image={lnk}" + ).json() + ms = r.get("message") + utd = url(ms) + if not utd: + return + with open("ult.png", "wb") as f: + f.write(requests.get(ms).content) + img = Image.open("ult.png").convert("RGB") + img.save("ult.webp", "webp") + await event.client.send_file( + event.chat_id, "ult.webp", force_document=False, reply_to=event.reply_to_msg_id + ) + await xx.delete() + os.remove("ult.png") + os.remove("ult.webp") + os.remove(ultt) + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/pdftools.py b/plugins/pdftools.py new file mode 100644 index 0000000000..05a95ec404 --- /dev/null +++ b/plugins/pdftools.py @@ -0,0 +1,295 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}pdf ` + Extract nd Send page as a Image.(note-: For Extraction all pages just use .pdf) + +• `{i}pdtext ` + Extract Text From the Pdf.(note-: For Extraction all text just use .pdtext) + +• `{i}pdscan ` + It scan, crop nd send img as pdf. + +• `{i}pdsave ` + It scan, crop nd save file to merge u can merge many pages as a single pdf. + +• `{i}pdsend ` + Merge nd send the Pdf to collected from .pdsave. +""" + +import os +import shutil + +import cv2 +import imutils +import numpy as np +import PIL +from imutils.perspective import four_point_transform +from PyPDF2 import PdfFileMerger, PdfFileReader, PdfFileWriter +from skimage.filters import threshold_local + +from . import * + +if not os.path.exists("pdf/"): + os.makedirs("pdf/") + + +@ultroid_cmd( + pattern="pdf ?(.*)", +) +async def pdfseimg(event): + ok = await event.get_reply_message() + msg = event.pattern_match.group(1) + if not ok and ok.document and ok.document.mime_type == "application/pdf": + await eor(event, "`Reply The pdf u Want to Download..`") + return + xx = await eor(event, "Processing...") + if not msg: + d = os.path.join("pdf/", "hehe.pdf") + await event.client.download_media(ok, d) + pdfp = "pdf/hehe.pdf" + pdfp.replace(".pdf", "") + pdf = PdfFileReader(pdfp) + for num in range(pdf.numPages): + pw = PdfFileWriter() + pw.addPage(pdf.getPage(num)) + with open(os.path.join("pdf/ult{}.png".format(num + 1)), "wb") as f: + pw.write(f) + os.remove(pdfp) + a = os.listdir("pdf/") + for z in a: + lst = [f"pdf/{z}"] + await event.client.send_file(event.chat_id, lst, album=True) + shutil.rmtree("pdf") + os.makedirs("pdf/") + await xx.delete() + if msg: + o = int(msg) - 1 + d = os.path.join("pdf/", "hehe.pdf") + await event.client.download_media(ok, d) + pdfp = "pdf/hehe.pdf" + pdfp.replace(".pdf", "") + pdf = PdfFileReader(pdfp) + pw = PdfFileWriter() + pw.addPage(pdf.getPage(o)) + with open(os.path.join("ult.png"), "wb") as f: + pw.write(f) + os.remove(pdfp) + await event.client.send_file( + event.chat_id, "ult.png", reply_to=event.reply_to_msg_id + ) + os.remove("ult.png") + await xx.delete() + + +@ultroid_cmd( + pattern="pdtext ?(.*)", +) +async def pdfsetxt(event): + ok = await event.get_reply_message() + msg = event.pattern_match.group(1) + if not ok and ok.document and ok.document.mime_type == "application/pdf": + await eor(event, "`Reply The pdf u Want to Download..`") + return + xx = await eor(event, "`Processing...`") + if not msg: + dl = await event.client.download_media(ok) + pdf = PdfFileReader(dl) + text = f"{dl.split('.')[0]}.txt" + with open(text, "w") as f: + for page_num in range(pdf.numPages): + pageObj = pdf.getPage(page_num) + txt = pageObj.extractText() + f.write("Page {0}\n".format(page_num + 1)) + f.write("".center(100, "-")) + f.write(txt) + await event.client.send_file( + event.chat_id, text, reply_to=event.reply_to_msg_id + ) + os.remove(text) + os.remove(dl) + await xx.delete() + return + if "_" in msg: + u, d = msg.split("_") + dl = await event.client.download_media(ok) + a = PdfFileReader(dl) + str = "" + for i in range(int(u) - 1, int(d)): + str += a.getPage(i).extractText() + text = f"{dl.split('.')[0]} {msg}.txt" + with open(text, "w") as f: + f.write(str) + await event.client.send_file( + event.chat_id, text, reply_to=event.reply_to_msg_id + ) + os.remove(text) + os.remove(dl) + else: + u = int(msg) - 1 + dl = await event.client.download_media(ok) + a = PdfFileReader(dl) + str = a.getPage(u).extractText() + text = f"{dl.split('.')[0]} Pg-{msg}.txt" + with open(text, "w") as f: + f.write(str) + await event.client.send_file( + event.chat_id, text, reply_to=event.reply_to_msg_id + ) + os.remove(text) + os.remove(dl) + await xx.delete() + + +@ultroid_cmd( + pattern="pdscan ?(.*)", +) +async def imgscan(event): + ok = await event.get_reply_message() + if not (ok and (ok.media)): + await eor(event, "`Reply The pdf u Want to Download..`") + return + ultt = await ok.download_media() + if not ultt.endswith(("png", "jpg", "jpeg", "webp")): + await eor(event, "`Reply to a Image only...`") + os.remove(ultt) + return + xx = await eor(event, "`Processing...`") + image = cv2.imread(ultt) + original_image = image.copy() + ratio = image.shape[0] / 500.0 + image = imutils.resize(image, height=500) + image_yuv = cv2.cvtColor(image, cv2.COLOR_BGR2YUV) + image_y = np.zeros(image_yuv.shape[0:2], np.uint8) + image_y[:, :] = image_yuv[:, :, 0] + image_blurred = cv2.GaussianBlur(image_y, (3, 3), 0) + edges = cv2.Canny(image_blurred, 50, 200, apertureSize=3) + contours, hierarchy = cv2.findContours( + edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE + ) + polygons = [] + for cnt in contours: + hull = cv2.convexHull(cnt) + polygons.append(cv2.approxPolyDP(hull, 0.01 * cv2.arcLength(hull, True), False)) + sortedPoly = sorted(polygons, key=cv2.contourArea, reverse=True) + cv2.drawContours(image, sortedPoly[0], -1, (0, 0, 255), 5) + simplified_cnt = sortedPoly[0] + if len(simplified_cnt) == 4: + cropped_image = four_point_transform( + original_image, simplified_cnt.reshape(4, 2) * ratio + ) + gray_image = cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY) + T = threshold_local(gray_image, 11, offset=10, method="gaussian") + ok = (gray_image > T).astype("uint8") * 255 + if len(simplified_cnt) != 4: + ok = cv2.detailEnhance(original_image, sigma_s=10, sigma_r=0.15) + cv2.imwrite("o.png", ok) + image1 = PIL.Image.open("o.png") + im1 = image1.convert("RGB") + scann = f"Scanned {ultt.split('.')[0]}.pdf" + im1.save(scann) + await event.client.send_file(event.chat_id, scann, reply_to=event.reply_to_msg_id) + await xx.delete() + os.remove(ultt) + os.remove("o.png") + os.remove(scann) + + +@ultroid_cmd( + pattern="pdsave ?(.*)", +) +async def savepdf(event): + ok = await event.get_reply_message() + if not (ok and (ok.media)): + await eor( + event, "`Reply to Images/pdf which u want to merge as a single pdf..`" + ) + return + ultt = await ok.download_media() + if ultt.endswith(("png", "jpg", "jpeg", "webp")): + xx = await eor(event, "`Processing...`") + image = cv2.imread(ultt) + original_image = image.copy() + ratio = image.shape[0] / 500.0 + image = imutils.resize(image, height=500) + image_yuv = cv2.cvtColor(image, cv2.COLOR_BGR2YUV) + image_y = np.zeros(image_yuv.shape[0:2], np.uint8) + image_y[:, :] = image_yuv[:, :, 0] + image_blurred = cv2.GaussianBlur(image_y, (3, 3), 0) + edges = cv2.Canny(image_blurred, 50, 200, apertureSize=3) + contours, hierarchy = cv2.findContours( + edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE + ) + polygons = [] + for cnt in contours: + hull = cv2.convexHull(cnt) + polygons.append( + cv2.approxPolyDP(hull, 0.01 * cv2.arcLength(hull, True), False) + ) + sortedPoly = sorted(polygons, key=cv2.contourArea, reverse=True) + cv2.drawContours(image, sortedPoly[0], -1, (0, 0, 255), 5) + simplified_cnt = sortedPoly[0] + if len(simplified_cnt) == 4: + cropped_image = four_point_transform( + original_image, simplified_cnt.reshape(4, 2) * ratio + ) + gray_image = cv2.cvtColor(cropped_image, cv2.COLOR_BGR2GRAY) + T = threshold_local(gray_image, 11, offset=10, method="gaussian") + ok = (gray_image > T).astype("uint8") * 255 + if len(simplified_cnt) != 4: + ok = cv2.detailEnhance(original_image, sigma_s=10, sigma_r=0.15) + cv2.imwrite("o.png", ok) + image1 = PIL.Image.open("o.png") + im1 = image1.convert("RGB") + a = dani_ck("pdf/scan.pdf") + im1.save(a) + await xx.edit( + f"Done, Now Reply Another Image/pdf if completed then use {hndlr}pdsend to merge nd send all as pdf", + ) + os.remove("o.png") + elif ultt.endswith(".pdf"): + a = dani_ck("pdf/scan.pdf") + await ultroid_bot.download_media(ok, a) + await eor( + event, + f"Done, Now Reply Another Image/pdf if completed then use {hndlr}pdsend to merge nd send all as pdf", + ) + else: + await eor(event, "`Reply to a Image/pdf only...`") + os.remove(ultt) + + +@ultroid_cmd( + pattern="pdsend ?(.*)", +) +async def sendpdf(event): + if not os.path.exists("pdf/scan.pdf"): + await eor( + event, + "first select pages by replying .pdsave of which u want to make multi page pdf file", + ) + return + msg = event.pattern_match.group(1) + if msg: + ok = f"{msg}.pdf" + else: + ok = "My PDF File.pdf" + merger = PdfFileMerger() + for item in os.listdir("pdf/"): + if item.endswith("pdf"): + merger.append(f"pdf/{item}") + merger.write(ok) + await event.client.send_file(event.chat_id, ok, reply_to=event.reply_to_msg_id) + os.remove(ok) + shutil.rmtree("pdf/") + os.makedirs("pdf/") + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/pmpermit.py b/plugins/pmpermit.py new file mode 100644 index 0000000000..6102b44ed2 --- /dev/null +++ b/plugins/pmpermit.py @@ -0,0 +1,258 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from pyUltroid.functions.pmpermit_db import * +from telethon import events +from telethon.tl.functions.contacts import BlockRequest, UnblockRequest +from telethon.tl.functions.messages import ReportSpamRequest + +from . import * + +# ========================= CONSTANTS ============================= +COUNT_PM = {} +LASTMSG = {} +PMPIC = "https://telegra.ph/file/94f6a4aeb21ce2d58dd41.jpg" +UNAPPROVED_MSG = """ +**PMSecurity of {}!** +Please wait for me to respnd or you will be blocked and reported as spam!! + +You have {}/{} warnings!""" +WARNS = 3 +NO_REPLY = "Reply to someone's msg or try this commmand in private." +PMCMDS = [ + f"{hndlr}a", + f"{hndlr}approve", + f"{hndlr}da", + f"{hndlr}disapprove", + f"{hndlr}block", + f"{hndlr}unblock", +] +# ================================================================= + +sett = udB.get("PMSETTING") +if sett is None: + sett = True +if sett == "True" and sett != "False": + + @ultroid_bot.on(events.NewMessage(outgoing=True, func=lambda e: e.is_private)) + async def autoappr(event): + chat = await event.get_chat() + mssg = event.text + if mssg in PMCMDS: # do not approve if outgoing is a command. + return + if not is_approved(chat.id) and chat.id not in COUNT_PM: + approve_user(chat.id) + if Var.LOG_CHANNEL: + name = await event.client.get_entity(chat.id) + name0 = str(name.first_name) + await event.client.send_message( + Var.LOG_CHANNEL, + f"#AutoApproved\nUser - [{name0}](tg://user?id={chat.id})", + ) + + @ultroid_bot.on(events.NewMessage(incoming=True)) + async def permitpm(event): + if event.is_private: + user = await event.get_chat() + if user.bot: + return + apprv = is_approved(user.id) + if not apprv and event.text != UNAPPROVED_MSG: + try: + wrn = COUNT_PM[user.id] + except KeyError: + wrn = 0 + if user.id in LASTMSG: + prevmsg = LASTMSG[user.id] + if event.text != prevmsg: + async for message in event.client.iter_messages( + user.id, from_user="me", search=UNAPPROVED_MSG + ): + await message.delete() + await event.client.send_file( + user.id, + PMPIC, + caption=UNAPPROVED_MSG.format(OWNER_NAME, wrn, WARNS), + ) + elif event.text == prevmsg: + async for message in event.client.iter_messages( + user.id, from_user="me", search=UNAPPROVED_MSG + ): + await message.delete() + await event.client.send_file( + user.id, + PMPIC, + caption=UNAPPROVED_MSG.format(OWNER_NAME, wrn, WARNS), + ) + LASTMSG.update({user.id: event.text}) + else: + await event.client.send_file( + user.id, + PMPIC, + caption=UNAPPROVED_MSG.format(OWNER_NAME, wrn, WARNS), + ) + LASTMSG.update({user.id: event.text}) + if user.id not in COUNT_PM: + COUNT_PM.update({user.id: 1}) + else: + COUNT_PM[user.id] = COUNT_PM[user.id] + 1 + if COUNT_PM[user.id] > WARNS: + await event.respond( + "`You were spamming my Master's PM, which I didn't like.`\n`You have been BLOCKED and reported as SPAM, until further notice.`" + ) + try: + del COUNT_PM[user.id] + del LASTMSG[user.id] + except KeyError: + if Var.LOG_CHANNEL: + await event.client.send_message( + Var.LOG_CHANNEL, + "PMPermit is messed! Pls restart the bot!!", + ) + return LOGS.info("COUNT_PM is messed.") + await event.client(BlockRequest(user.id)) + await event.client(ReportSpamRequest(peer=user.id)) + if Var.LOG_CHANNEL: + name = await event.client.get_entity(user.id) + name0 = str(name.first_name) + await event.client.send_message( + Var.LOG_CHANNEL, + f"[{name0}](tg://user?id={user.id}) was blocked for spamming.", + ) + + @ultroid_cmd(pattern="(a|approve)(?: |$)") + async def approvepm(apprvpm): + if apprvpm.reply_to_msg_id: + reply = await apprvpm.get_reply_message() + replied_user = await apprvpm.client.get_entity(reply.sender_id) + aname = replied_user.id + name0 = str(replied_user.first_name) + uid = replied_user.id + if not is_approved(uid): + approve_user(uid) + await apprvpm.edit(f"[{name0}](tg://user?id={uid}) `approved to PM!`") + await asyncio.sleep(3) + await apprvpm.delete() + else: + await apprvpm.edit("`User may already be approved.`") + await asyncio.sleep(5) + await apprvpm.delete() + elif apprvpm.is_private: + user = await apprvpm.get_chat() + aname = await apprvpm.client.get_entity(user.id) + name0 = str(aname.first_name) + uid = user.id + if not is_approved(uid): + approve_user(uid) + await apprvpm.edit(f"[{name0}](tg://user?id={uid}) `approved to PM!`") + async for message in apprvpm.client.iter_messages( + user.id, from_user="me", search=UNAPPROVED_MSG + ): + await message.delete() + await asyncio.sleep(3) + await apprvpm.delete() + if Var.LOG_CHANNEL: + await apprvpm.client.send_message( + Var.LOG_CHANNEL, + f"#APPROVED\nUser: [{name0}](tg://user?id={uid})", + ) + else: + await apprvpm.edit("`User may already be approved.`") + await asyncio.sleep(5) + await apprvpm.delete() + if Var.LOG_CHANNEL: + await apprvpm.client.send_message( + Var.LOG_CHANNEL, + f"#APPROVED\nUser: [{name0}](tg://user?id={uid})", + ) + else: + await apprvpm.edit(NO_REPLY) + + @ultroid_cmd(pattern="(da|disapprove)(?: |$)") + async def disapprovepm(e): + if e.reply_to_msg_id: + reply = await e.get_reply_message() + replied_user = await e.client.get_entity(reply.sender_id) + aname = replied_user.id + name0 = str(replied_user.first_name) + if is_approved(replied_user.id): + disapprove_user(replied_user.id) + await e.edit( + f"[{name0}](tg://user?id={replied_user.id}) `Disaproved to PM!`" + ) + await asyncio.sleep(5) + await e.delete() + else: + await e.edit( + f"[{name0}](tg://user?id={replied_user.id}) was never approved!" + ) + await asyncio.sleep(5) + await e.delete() + elif e.is_private: + bbb = await e.get_chat() + aname = await e.client.get_entity(bbb.id) + name0 = str(aname.first_name) + if is_approved(bbb.id): + disapprove_user(bbb.id) + await e.edit(f"[{name0}](tg://user?id={bbb.id}) `Disaproved to PM!`") + await asyncio.sleep(5) + await e.delete() + if Var.LOG_CHANNEL: + await e.client.send_message( + Var.LOG_CHANNEL, + f"[{name0}](tg://user?id={bbb.id}) was disapproved to PM you.", + ) + else: + await e.edit(f"[{name0}](tg://user?id={bbb.id}) was never approved!") + await asyncio.sleep(5) + await e.delete() + else: + await e.edit(NO_REPLY) + + @ultroid_cmd(pattern="block$") + async def blockpm(block): + if block.reply_to_msg_id: + reply = await block.get_reply_message() + replied_user = await block.client.get_entity(reply.sender_id) + aname = replied_user.id + name0 = str(replied_user.first_name) + await block.client(BlockRequest(replied_user.id)) + await block.edit("`You've been blocked!`") + uid = replied_user.id + elif block.is_private: + bbb = await block.get_chat() + await block.client(BlockRequest(bbb.id)) + aname = await block.client.get_entity(bbb.id) + await block.edit("`You've been blocked!`") + name0 = str(aname.first_name) + uid = bbb.id + else: + await block.edit(NO_REPLY) + try: + disapprove_user(uid) + except AttributeError: + pass + if Var.LOG_CHANNEL: + await block.client.send_message( + Var.LOG_CHANNEL, f"#BLOCKED\nUser: [{name0}](tg://user?id={uid})" + ) + + @ultroid_cmd(pattern="unblock$") + async def unblockpm(unblock): + if unblock.reply_to_msg_id: + reply = await unblock.get_reply_message() + replied_user = await unblock.client.get_entity(reply.sender_id) + name0 = str(replied_user.first_name) + await unblock.client(UnblockRequest(replied_user.id)) + await unblock.edit("`You have been unblocked.`") + else: + await unblock.edit(NO_REPLY) + if Var.LOG_CHANNEL: + await unblock.client.send_message( + Var.LOG_CHANNEL, + f"[{name0}](tg://user?id={replied_user.id}) was unblocked!.", + ) diff --git a/plugins/profile.py b/plugins/profile.py new file mode 100644 index 0000000000..7364ae3028 --- /dev/null +++ b/plugins/profile.py @@ -0,0 +1,163 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}setname ` + Change your profile name. + +• `{i}setbio ` + Change your profile bio. + +• `{i}setpic ` + Change your profile pic. + +• `{i}delpfp (optional)` + Delete one profile pic, if no value given, else delete n number of pics. + +• `{i}gpoto ` + Upload the photo of Chat/User if Available. +""" + +import asyncio +import os + +from telethon.tl import functions +from telethon.tl.functions.photos import DeletePhotosRequest, GetUserPhotosRequest +from telethon.tl.types import InputPhoto + +from . import * + +TMP_DOWNLOAD_DIRECTORY = "resources/downloads/" + +# bio changer + + +@ultroid_cmd( + pattern="setbio ?(.*)", +) +async def _(ult): + ok = await eor(ult, "...") + set = ult.pattern_match.group(1) + try: + await ultroid_bot(functions.account.UpdateProfileRequest(about=set)) + await ok.edit("Profile bio changed to\n`{}`".format(set)) + except Exception as ex: + await ok.edit("Error occured.\n`{}`".format(str(ex))) + await asyncio.sleep(10) + await ok.delete() + + +# name changer + + +@ultroid_cmd( + pattern="setname ?((.|//)*)", +) +async def _(ult): + ok = await eor(ult, "...") + names = ult.pattern_match.group(1) + first_name = names + last_name = "" + if "//" in names: + first_name, last_name = names.split("//", 1) + try: + await ultroid_bot( + functions.account.UpdateProfileRequest( + first_name=first_name, last_name=last_name + ) + ) + await ok.edit("Name changed to `{}`".format(names)) + except Exception as ex: + await ok.edit("Error occured.\n`{}`".format(str(ex))) + await asyncio.sleep(10) + await ok.delete() + + +# profile pic + + +@ultroid_cmd( + pattern="setpic$", +) +async def _(ult): + ok = await eor(ult, "...") + reply_message = await ult.get_reply_message() + await ok.edit("`Downloading that picture...`") + if not os.path.isdir(TMP_DOWNLOAD_DIRECTORY): + os.makedirs(TMP_DOWNLOAD_DIRECTORY) + photo = None + try: + photo = await ultroid_bot.download_media(reply_message, TMP_DOWNLOAD_DIRECTORY) + except Exception as ex: + await ok.edit("Error occured.\n`{}`".format(str(ex))) + else: + if photo: + await ok.edit("`Uploading it to my profile...`") + file = await ultroid_bot.upload_file(photo) + try: + await ultroid_bot(functions.photos.UploadProfilePhotoRequest(file)) + except Exception as ex: + await ok.edit("Error occured.\n`{}`".format(str(ex))) + else: + await ok.edit("`My profile picture has been changed !`") + await asyncio.sleep(10) + await ok.delete() + try: + os.remove(photo) + except Exception as ex: + LOGS.exception(ex) + + +# delete profile pic(s) + + +@ultroid_cmd( + pattern="delpfp ?(.*)", +) +async def remove_profilepic(delpfp): + ok = await eor(delpfp, "...") + group = delpfp.text[8:] + if group == "all": + lim = 0 + elif group.isdigit(): + lim = int(group) + else: + lim = 1 + pfplist = await ultroid_bot( + GetUserPhotosRequest(user_id=delpfp.from_id, offset=0, max_id=0, limit=lim) + ) + input_photos = [] + for sep in pfplist.photos: + input_photos.append( + InputPhoto( + id=sep.id, + access_hash=sep.access_hash, + file_reference=sep.file_reference, + ) + ) + await ultroid_bot(DeletePhotosRequest(id=input_photos)) + await ok.edit(f"`Successfully deleted {len(input_photos)} profile picture(s).`") + await asyncio.sleep(10) + await ok.delete() + + +@ultroid_cmd(pattern="gpoto ?(.*)") +async def gpoto(e): + ult = e.pattern_match.group(1) + try: + okla = await ultroid_bot.download_profile_photo( + ult, "profile.jpg", download_big=True + ) + await ultroid_bot.send_message(e.chat_id, file=okla) + os.remove(okla) + except Exception as e: + await eor(e, f"ERROR - {str(e)}") + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/redis.py b/plugins/redis.py new file mode 100644 index 0000000000..29718a5403 --- /dev/null +++ b/plugins/redis.py @@ -0,0 +1,115 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}setredis key | value` + Redis Set Value. + e.g : + `{i}setredis hi there` + `{i}setredis hi there | ultroid here` + +• `{i}getredis key` + Redis Get Value + +• `{i}delredis key` + Delete Key from Redis DB + +• `{i}renredis old keyname | new keyname` + Update Key Name + +• `{i}getkeys` + Get the list of keys stored in Redis +""" + +import re + +from . import * + + +@ultroid_cmd( + pattern="setredis ?(.*)", +) +async def _(ult): + ok = await eor(ult, "`...`") + try: + delim = " " if re.search("[|]", ult.pattern_match.group(1)) is None else " | " + data = ult.pattern_match.group(1).split(delim) + udB.set(data[0], data[1]) + redisdata = Redis(data[0]) + await ok.edit( + "Redis Key Value Pair Updated\nKey : `{}`\nValue : `{}`".format( + data[0], redisdata + ) + ) + except BaseException: + await ok.edit("`Something Went Wrong`") + + +@ultroid_cmd( + pattern="getredis ?(.*)", +) +async def _(ult): + ok = await eor(ult, "`Fetching data from Redis`") + val = ult.pattern_match.group(1) + if val == "": + return await ult.edit(f"Please use `{hndlr}getkeys `") + try: + value = Redis(val) + await ok.edit("Key: `{}`\nValue: `{}`".format(val, value)) + except BaseException: + await ok.edit("`Something Went Wrong`") + + +@ultroid_cmd( + pattern="delredis ?(.*)", +) +async def _(ult): + ok = await eor(ult, "`Deleting data from Redis ...`") + try: + key = ult.pattern_match.group(1) + udB.delete(key) + await ok.edit(f"`Successfully deleted key {key}`") + except BaseException: + await ok.edit("`Something Went Wrong`") + + +@ultroid_cmd( + pattern="renredis ?(.*)", +) +async def _(ult): + ok = await eor(ult, "`...`") + delim = " " if re.search("[|]", ult.pattern_match.group(1)) is None else " | " + data = ult.pattern_match.group(1).split(delim) + if Redis(data[0]): + try: + udB.rename(data[0], data[1]) + await ok.edit( + "Redis Key Rename Successful\nOld Key : `{}`\nNew Key : `{}`".format( + data[0], data[1] + ) + ) + except BaseException: + await ok.edit("Something went wrong ...") + else: + await ok.edit("Key not found") + + +@ultroid_cmd( + pattern="getkeys$", +) +async def _(ult): + ok = await eor(ult, "`Fetching Keys ...`") + keys = udB.keys() + msg = "" + for x in keys: + msg += "• `{}`".format(x) + "\n" + await ok.edit("**List of Redis Keys :**\n{}".format(msg)) + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/specialtools.py b/plugins/specialtools.py new file mode 100644 index 0000000000..92c13e716e --- /dev/null +++ b/plugins/specialtools.py @@ -0,0 +1,246 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}wspr ` + Send secret message.. + +• `{i}sticker ` + Search Stickers as Per ur Wish.. + +• `{i}getaudio ` + Download Audio To put in ur Desired Video/Gif. + +• `{i}addaudio ` + It will put the above audio to the replied video/gif. + +• `{i}dob ` + Put in dd/mm/yy Format only(eg .dob 01/01/1999). + +• `{i}wall ` + Search Hd Wallpaper as Per ur Wish.. +""" + +import os +from datetime import datetime as dt +from random import choice +from shutil import rmtree + +import moviepy.editor as m +import pytz +import requests +from bs4 import BeautifulSoup as b + +from . import * + + +@ultroid_cmd( + pattern="getaudio$", +) +async def daudtoid(event): + ureply = await event.get_reply_message() + if not (ureply and ("audio" in ureply.document.mime_type)): + await eor(event, "`Reply To Audio Only..`") + return + xx = await eor(event, "`processing...`") + d = os.path.join("resources/extras/", "ul.mp3") + await xx.edit("`Downloading... Large Files Takes Time..`") + await event.client.download_media(ureply, d) + await xx.edit("`Done.. Now reply to video In which u want to add that Audio`") + + +@ultroid_cmd( + pattern="addaudio$", +) +async def adaudroid(event): + ureply = await event.get_reply_message() + if not (ureply and ("video" in ureply.document.mime_type)): + await eor(event, "`Reply To Gif/Video In which u want to add audio.`") + return + xx = await eor(event, "`processing...`") + ultt = await ureply.download_media() + ls = os.listdir("resources/extras") + z = "ul.mp3" + x = "resources/extras/ul.mp3" + if z not in ls: + await xx.edit("`First reply an audio with .aw`") + return + video = m.VideoFileClip(ultt) + audio = m.AudioFileClip(x) + out = video.set_audio(audio) + out.write_videofile("ok.mp4", fps=30) + await event.client.send_file( + event.chat_id, + file="ok.mp4", + force_document=False, + reply_to=event.reply_to_msg_id, + ) + os.remove("ok.mp4") + os.remove(x) + os.remove(ultt) + await xx.delete() + + +@ultroid_cmd( + pattern=r"dob ?(.*)", +) +async def hbd(event): + if not event.pattern_match.group(1): + await eor(event, "`Put input in dd/mm/yyyy format`") + return + if event.reply_to_msg_id: + kk = await event.get_reply_message() + nam = await ultroid_bot.get_entity(kk.from_id) + name = nam.first_name + else: + a = await ultroid_bot.get_me() + name = a.first_name + zn = pytz.timezone("Asia/Kolkata") + abhi = dt.now(zn) + n = event.text + q = n[5:] + p = n[5:7] + r = n[8:10] + s = n[11:] + day = int(p) + month = r + paida = q + jn = dt.strptime(paida, "%d/%m/%Y") + jnm = zn.localize(jn) + zinda = abhi - jnm + barsh = (zinda.total_seconds()) / (365.242 * 24 * 3600) + saal = int(barsh) + mash = (barsh - saal) * 12 + mahina = int(mash) + divas = (mash - mahina) * (365.242 / 12) + din = int(divas) + samay = (divas - din) * 24 + ghanta = int(samay) + pehl = (samay - ghanta) * 60 + mi = int(pehl) + sec = (pehl - mi) * 60 + slive = int(sec) + y = int(s) + int(saal) + 1 + m = int(r) + brth = dt(y, m, day) + cm = dt(abhi.year, brth.month, brth.day) + ish = (cm - abhi.today()).days + 1 + dan = ish + if dan == 0: + hp = "`Happy BirthDay To U🎉🎊`" + elif dan < 0: + okk = 365 + ish + hp = f"{okk} Days Left 🥳" + elif dan > 0: + hp = f"{ish} Days Left 🥳" + if month == "12": + sign = "Sagittarius" if (day < 22) else "Capricorn" + elif month == "01": + sign = "Capricorn" if (day < 20) else "Aquarius" + elif month == "02": + sign = "Aquarius" if (day < 19) else "Pisces" + elif month == "03": + sign = "Pisces" if (day < 21) else "Aries" + elif month == "04": + sign = "Aries" if (day < 20) else "Taurus" + elif month == "05": + sign = "Taurus" if (day < 21) else "Gemini" + elif month == "06": + sign = "Gemini" if (day < 21) else "Cancer" + elif month == "07": + sign = "Cancer" if (day < 23) else "Leo" + elif month == "08": + sign = "Leo" if (day < 23) else "Virgo" + elif month == "09": + sign = "Virgo" if (day < 23) else "Libra" + elif month == "10": + sign = "Libra" if (day < 23) else "Scorpion" + elif month == "11": + sign = "Scorpio" if (day < 22) else "Sagittarius" + sign = f"{sign}" + params = (("sign", sign), ("today", day)) + response = requests.post("https://aztro.sameerkumar.website/", params=params) + json = response.json() + dd = json.get("current_date") + ds = json.get("description") + lt = json.get("lucky_time") + md = json.get("mood") + cl = json.get("color") + ln = json.get("lucky_number") + await event.delete() + await event.client.send_message( + event.chat_id, + f""" + Name -: {name} + +D.O.B -: {paida} + +Lived -: {saal}yr, {mahina}m, {din}d, {ghanta}hr, {mi}min, {slive}sec + +Birthday -: {hp} + +Zodiac -: {sign} + +**Horoscope On {dd} -** + +`{ds}` + + Lucky Time :- {lt} + Lucky Number :- {ln} + Lucky Color :- {cl} + Mood :- {md} + """, + reply_to=event.reply_to_msg_id, + ) + + +@ultroid_cmd(pattern="sticker ?(.*)") +async def _(event): + x = event.pattern_match.group(1) + if not x: + return await eor(event, "`Give something to search`") + uu = await eor(event, "`Processing...`") + z = requests.get("https://combot.org/telegram/stickers?q=" + x).text + xx = b(z, "lxml") + title = xx.find_all("div", "sticker-pack__title") + link = xx.find_all("a", {"class": "sticker-pack__btn"}) + if not link: + return await uu.edit("Found Nothing") + a = "SᴛɪᴄᴋEʀs Aᴡᴀɪʟᴀʙʟᴇ ~" + for xxx, yyy in zip(title, link): + v = xxx.get_text() + w = yyy["href"] + d = f"\n\n[{v}]({w})" + if d not in a: + a += d + await uu.edit(a) + + +@ultroid_cmd(pattern="wall ?(.*)") +async def wall(event): + inp = event.pattern_match.group(1) + if not inp: + return await eor(event, "`Give something to search") + nn = await eor(event, "`Processing Keep Patience...`") + query = f"hd {inp}" + gi = googleimagesdownload() + args = { + "keywords": query, + "limit": 10, + "format": "jpg", + "output_directory": "./resources/downloads/", + } + gi.download(args) + xx = choice(os.listdir(os.path.abspath(f"./resources/downloads/{query}/"))) + await event.client.send_file(event.chat_id, f"./resources/downloads/{query}/{xx}") + rmtree(f"./resources/downloads/{query}/") + await nn.delete() + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/stickertools.py b/plugins/stickertools.py new file mode 100644 index 0000000000..ec241535a5 --- /dev/null +++ b/plugins/stickertools.py @@ -0,0 +1,524 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}destroy ` + To destroy the sticker. + +• `{i}tiny ` + To create Tiny stickers. + +• `{i}convert ` + Reply to animated sticker. + +• `{i}kang ` + Kang the sticker (add to your pack). + +• `{i}round ` + To extract round sticker. + +• `{i}waifu ` + paste text on random stickers. + +""" + +import asyncio +import io +import os +import random +import re +import urllib.request +from os import remove + +import cv2 +import numpy as np +from PIL import Image, ImageDraw +from telethon.errors import ChatSendInlineForbiddenError, ChatSendStickersForbiddenError +from telethon.tl.types import ( + DocumentAttributeFilename, + DocumentAttributeSticker, + MessageMediaPhoto, +) + +from . import * + +EMOJI_PATTERN = re.compile( + "[" + "\U0001F1E0-\U0001F1FF" # flags (iOS) + "\U0001F300-\U0001F5FF" # symbols & pictographs + "\U0001F600-\U0001F64F" # emoticons + "\U0001F680-\U0001F6FF" # transport & map symbols + "\U0001F700-\U0001F77F" # alchemical symbols + "\U0001F780-\U0001F7FF" # Geometric Shapes Extended + "\U0001F800-\U0001F8FF" # Supplemental Arrows-C + "\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs + "\U0001FA00-\U0001FA6F" # Chess Symbols + "\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A + "\U00002702-\U000027B0" # Dingbats + "]+" +) + + +def deEmojify(inputString: str) -> str: + """Remove emojis and other non-safe characters from string""" + return re.sub(EMOJI_PATTERN, "", inputString) + + +@ultroid_cmd( + pattern="waifu ?(.*)", +) +async def waifu(animu): + xx = await eor(animu, "`Processing...`") + # """Creates random anime sticker!""" + text = animu.pattern_match.group(1) + if not text: + if animu.is_reply: + text = (await animu.get_reply_message()).message + else: + await xx.edit("`You haven't written any article, Waifu is going away.`") + return + animus = [0, 1, 3, 7, 9, 13, 22, 34, 35, 36, 37, 43, 44, 45, 52, 53, 55] + t1 = "#" + (str(random.choice(animus))) + animus2 = [0, 11, 12, 19, 20, 21, 22, 23, 28, 32, 33] + t2 = "$" + (str(random.choice(animus2))) + animus3 = [10, 14, 19] + t3 = "&" + (str(random.choice(animus3))) + animus4 = [0, 3, 9, 15] + t4 = "%" + (str(random.choice(animus4))) + t5 = "*" + (str(random.choice(animus4))) + # to increase probability of getting sticker from special category + finalcall = random.choice([t1, t2, t3, t4, t5, t1]) + try: + sticcers = await ultroid_bot.inline_query( + "stickerizerbot", f"{finalcall}{(deEmojify(text))}" + ) + await sticcers[0].click( + animu.chat_id, + reply_to=animu.reply_to_msg_id, + silent=True if animu.is_reply else False, + hide_via=True, + ) + await xx.delete() + except ChatSendInlineForbiddenError: + await xx.edit("`Boss ! I cant use inline things here...`") + except ChatSendStickersForbiddenError: + await xx.edit("Sorry boss, I can't send Sticker Here !!") + + +@ultroid_cmd( + pattern="convert ?(.*)", +) +async def uconverter(event): + xx = await eor(event, "`Processing...`") + a = await event.get_reply_message() + input = event.pattern_match.group(1) + b = await event.client.download_media(a, "resources/downloads/") + if "gif" in input: + cmd = ["lottie_convert.py", b, "something.gif"] + file = "something.gif" + elif "sticker" in input: + cmd = ["lottie_convert.py", b, "something.webp"] + file = "something.webp" + elif "img" in input: + cmd = ["lottie_convert.py", b, "something.png"] + file = "something.png" + else: + await xx.edit("**Please select from gif/sticker/img**") + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + os.remove(b) + await event.client.send_file(event.chat_id, file, force_document=False) + os.remove(file) + await xx.delete() + + +@ultroid_cmd( + pattern="kang", +) +async def hehe(args): + xx = await eor(args, "`Processing...`") + user = await ultroid_bot.get_me() + if not user.username: + user.username = user.first_name + message = await args.get_reply_message() + photo = None + emojibypass = False + is_anim = False + emoji = None + if message and message.media: + if isinstance(message.media, MessageMediaPhoto): + await xx.edit(f"`{random.choice(KANGING_STR)}`") + photo = io.BytesIO() + photo = await ultroid_bot.download_media(message.photo, photo) + elif "image" in message.media.document.mime_type.split("/"): + await xx.edit(f"`{random.choice(KANGING_STR)}`") + photo = io.BytesIO() + await ultroid_bot.download_file(message.media.document, photo) + if ( + DocumentAttributeFilename(file_name="sticker.webp") + in message.media.document.attributes + ): + emoji = message.media.document.attributes[1].alt + emojibypass = True + elif "video" in message.media.document.mime_type.split("/"): + await xx.edit(f"`{random.choice(KANGING_STR)}`") + xy = await message.download_media() + y = cv2.VideoCapture(xy) + heh, lol = y.read() + cv2.imwrite("ult.webp", lol) + photo = "ult.webp" + elif "tgsticker" in message.media.document.mime_type: + await xx.edit(f"`{random.choice(KANGING_STR)}`") + await ultroid_bot.download_file( + message.media.document, "AnimatedSticker.tgs" + ) + + attributes = message.media.document.attributes + for attribute in attributes: + if isinstance(attribute, DocumentAttributeSticker): + emoji = attribute.alt + + emojibypass = True + is_anim = True + photo = 1 + else: + await xx.edit("`Unsupported File!`") + return + else: + await xx.edit("`I can't kang that...`") + return + + if photo: + splat = args.text.split() + if not emojibypass: + emoji = "🔰" + pack = 1 + if len(splat) == 3: + pack = splat[2] # User sent ultroid_both + emoji = splat[1] + elif len(splat) == 2: + if splat[1].isnumeric(): + pack = int(splat[1]) + else: + emoji = splat[1] + + packname = f"ult_{user.id}_{pack}" + packnick = f"@{user.username}'s Pack {pack}" + cmd = "/newpack" + file = io.BytesIO() + + if not is_anim: + image = await resize_photo(photo) + file.name = "sticker.png" + image.save(file, "PNG") + else: + packname += "_anim" + packnick += " (Animated)" + cmd = "/newanimated" + + response = urllib.request.urlopen( + urllib.request.Request(f"http://t.me/addstickers/{packname}") + ) + htmlstr = response.read().decode("utf8").split("\n") + + if ( + " A Telegram user has created the Sticker Set." + not in htmlstr + ): + async with ultroid_bot.conversation("Stickers") as conv: + await conv.send_message("/addsticker") + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.send_message(packname) + x = await conv.get_response() + while "120" in x.text: + pack += 1 + packname = f"ult_{user.id}_{pack}" + packnick = f"@{user.username}'s Pack {pack}" + await xx.edit( + "`Switching to Pack " + + str(pack) + + " due to insufficient space`" + ) + await conv.send_message(packname) + x = await conv.get_response() + if x.text == "Invalid pack selected.": + await conv.send_message(cmd) + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.send_message(packnick) + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + if is_anim: + await conv.send_file("AnimatedSticker.tgs") + remove("AnimatedSticker.tgs") + else: + file.seek(0) + await conv.send_file(file, force_document=True) + await conv.get_response() + await conv.send_message(emoji) + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.get_response() + await conv.send_message("/publish") + if is_anim: + await conv.get_response() + await conv.send_message(f"<{packnick}>") + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.send_message("/skip") + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.get_response() + await conv.send_message(packname) + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await xx.edit( + f"`Sticker added in a Different Pack !\ + \nThis Pack is Newly created!\ + \nYour pack can be found [here](t.me/addstickers/{packname})", + parse_mode="md", + ) + return + if is_anim: + await conv.send_file("AnimatedSticker.tgs") + remove("AnimatedSticker.tgs") + else: + file.seek(0) + await conv.send_file(file, force_document=True) + rsp = await conv.get_response() + if "Sorry, the file type is invalid." in rsp.text: + await xx.edit( + "`Failed to add sticker, use` @Stickers `bot to add the sticker manually.`" + ) + return + await conv.send_message(emoji) + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.get_response() + await conv.send_message("/done") + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + else: + await xx.edit("`Brewing a new Pack...`") + async with ultroid_bot.conversation("Stickers") as conv: + await conv.send_message(cmd) + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.send_message(packnick) + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + if is_anim: + await conv.send_file("AnimatedSticker.tgs") + remove("AnimatedSticker.tgs") + else: + file.seek(0) + await conv.send_file(file, force_document=True) + rsp = await conv.get_response() + if "Sorry, the file type is invalid." in rsp.text: + await xx.edit( + "`Failed to add sticker, use` @Stickers `bot to add the sticker manually.`" + ) + return + await conv.send_message(emoji) + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.get_response() + await conv.send_message("/publish") + if is_anim: + await conv.get_response() + await conv.send_message(f"<{packnick}>") + + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.send_message("/skip") + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.get_response() + await conv.send_message(packname) + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await conv.get_response() + await ultroid_bot.send_read_acknowledge(conv.chat_id) + await xx.edit( + f"`Kanged!`\ + \n`Emoji` - {emoji}\ + \n`Sticker Pack` [here](t.me/addstickers/{packname})", + parse_mode="md", + ) + try: + os.remove(photo) + except BaseException: + pass + + +@ultroid_cmd( + pattern="round$", +) +async def ultdround(event): + ureply = await event.get_reply_message() + xx = await eor(event, "`Processing...`") + if not (ureply and (ureply.media)): + await xx.edit("`Reply to any media`") + return + ultt = await ureply.download_media() + if ultt.endswith(".tgs"): + await xx.edit("`Ooo Animated Sticker 👀...`") + cmd = ["lottie_convert.py", ultt, "ult.png"] + file = "ult.png" + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + elif ultt.endswith((".gif", ".mp4", ".mkv")): + await xx.edit("`Processing...`") + img = cv2.VideoCapture(ultt) + heh, lol = img.read() + cv2.imwrite("ult.png", lol) + file = "ult.png" + else: + file = ultt + img = Image.open(file).convert("RGB") + npImage = np.array(img) + h, w = img.size + alpha = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(alpha) + draw.pieslice([0, 0, h, w], 0, 360, fill=255) + npAlpha = np.array(alpha) + npImage = np.dstack((npImage, npAlpha)) + Image.fromarray(npImage).save("ult.webp") + await event.client.send_file( + event.chat_id, "ult.webp", force_document=False, reply_to=event.reply_to_msg_id + ) + await xx.delete() + os.remove(file) + os.remove("ult.webp") + os.remove(ultt) + + +@ultroid_cmd( + pattern="destroy$", +) +async def ultdestroy(event): + ult = await event.get_reply_message() + if not (ult and ("tgsticker" in ult.media.document.mime_type)): + await eor(event, "`Reply to Animated Sticker Only...`") + return + roid = await event.client.download_media(ult, "ultroid.tgs") + xx = await eor(event, "`Processing...`") + os.system("lottie_convert.py ultroid.tgs json.json") + json = open("json.json", "r") + jsn = json.read() + json.close() + jsn = ( + jsn.replace("[100]", "[200]") + .replace("[10]", "[40]") + .replace("[-1]", "[-10]") + .replace("[0]", "[15]") + .replace("[1]", "[20]") + .replace("[2]", "[17]") + .replace("[3]", "[40]") + .replace("[4]", "[37]") + .replace("[5]", "[60]") + .replace("[6]", "[70]") + .replace("[7]", "[40]") + .replace("[8]", "[37]") + .replace("[9]", "[110]") + ) + open("json.json", "w").write(jsn) + os.system("lottie_convert.py json.json ultroid.tgs") + await event.client.send_file( + event.chat_id, + file="ultroid.tgs", + force_document=False, + reply_to=event.reply_to_msg_id, + ) + await xx.delete() + os.remove("ultroid.tgs") + os.remove("json.json") + os.remove(roid) + + +@ultroid_cmd( + pattern="tiny$", +) +async def ultiny(event): + reply = await event.get_reply_message() + if not (reply and (reply.media)): + await eor(event, "`Reply To Media`") + return + xx = await eor(event, "`processing...`") + ik = await ultroid_bot.download_media(reply) + im1 = Image.open("resources/extras/ultroid_blank.png") + if ik.endswith(".tgs"): + await event.client.download_media(reply, "ult.tgs") + os.system("lottie_convert.py ult.tgs json.json") + json = open("json.json", "r") + jsn = json.read() + json.close() + jsn = jsn.replace("512", "2000") + open("json.json", "w").write(jsn) + os.system("lottie_convert.py json.json ult.tgs") + file = "ult.tgs" + os.remove("json.json") + elif ik.endswith((".gif", ".mp4")): + iik = cv2.VideoCapture(ik) + dani, busy = iik.read() + cv2.imwrite("i.png", busy) + fil = "i.png" + im = Image.open(fil) + z, d = im.size + if z == d: + xxx, yyy = 200, 200 + else: + t = z + d + a = z / t + b = d / t + aa = (a * 100) - 50 + bb = (b * 100) - 50 + xxx = 200 + 5 * aa + yyy = 200 + 5 * bb + k = im.resize((int(xxx), int(yyy))) + k.save("k.png", format="PNG", optimize=True) + im2 = Image.open("k.png") + back_im = im1.copy() + back_im.paste(im2, (150, 0)) + back_im.save("o.webp", "WEBP", quality=95) + file = "o.webp" + os.remove(fil) + os.remove("k.png") + else: + im = Image.open(ik) + z, d = im.size + if z == d: + xxx, yyy = 200, 200 + else: + t = z + d + a = z / t + b = d / t + aa = (a * 100) - 50 + bb = (b * 100) - 50 + xxx = 200 + 5 * aa + yyy = 200 + 5 * bb + k = im.resize((int(xxx), int(yyy))) + k.save("k.png", format="PNG", optimize=True) + im2 = Image.open("k.png") + back_im = im1.copy() + back_im.paste(im2, (150, 0)) + back_im.save("o.webp", "WEBP", quality=95) + file = "o.webp" + os.remove("k.png") + await event.client.send_file(event.chat_id, file, reply_to=event.reply_to_msg_id) + await xx.delete() + os.remove(file) + os.remove(ik) + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/sudo.py b/plugins/sudo.py new file mode 100644 index 0000000000..d5c1a84c6d --- /dev/null +++ b/plugins/sudo.py @@ -0,0 +1,194 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}addsudo` + Add Sudo Users by replying to user or using separated userid(s) + +• `{i}delsudo` + Remove Sudo Users by replying to user or using separated userid(s) + +• `{i}listsudo` + List all sudo users. +""" + +import re + +from telethon.tl.functions.users import GetFullUserRequest + +from . import * + + +@ultroid_cmd( + pattern="addsudo ?(.*)", +) +async def _(ult): + ok = await eor(ult, "`Updating SUDO Users List ...`") + + if ult.reply_to_msg_id: + replied_to = await ult.get_reply_message() + id = replied_to.sender.id + user = await ult.client(GetFullUserRequest(int(id))) + sed.append(id) + if id == ultroid_bot.me.id: + return await ok.edit("You cant add yourself as Sudo User...") + elif is_sudo(id): + return await ok.edit( + f"[{user.user.first_name}](tg://user?id={id}) `is already a SUDO User ...`" + ) + elif add_sudo(id): + return await ok.edit( + f"**Added [{user.user.first_name}](tg://user?id={id}) as SUDO User**" + ) + else: + return await ok.edit("`SEEMS LIKE THIS FUNCTION CHOOSE TO BROKE ITSELF`") + + args = ult.pattern_match.group(1).strip() + + if re.search(r"[\s]", args) is not None: + args = args.split(" ") + msg = "" + sudos = get_sudos() + for item in args: + user = "" + try: + user = await ult.client(GetFullUserRequest(int(item))) + except BaseException: + pass + if not hasattr(user, "user"): + msg += f"• `{item}` __Invalid UserID__\n" + elif item in sudos: + msg += f"• [{user.user.first_name}](tg://user?id={item}) __Already a SUDO__\n" + elif add_sudo(item.strip()): + msg += ( + f"• [{user.user.first_name}](tg://user?id={item}) __Added SUDO__\n" + ) + else: + msg += f"• `{item}` __Failed to Add SUDO__\n" + return await ok.edit(f"**Adding Sudo Users :**\n{msg}") + + id = args.strip() + user = "" + + try: + user = await ult.client(GetFullUserRequest(int(i))) + except BaseException: + pass + + if not id.isdigit(): + return await ok.edit("`Integer(s) Expected`") + elif not hasattr(user, "user"): + return await ok.edit("`Invalid UserID`") + elif is_sudo(id): + return await ok.edit( + f"[{user.user.first_name}](tg://user?id={id}) `is already a SUDO User ...`" + ) + elif add_sudo(id): + return await ok.edit( + f"**Added [{user.user.first_name}](tg://user?id={id}) as SUDO User**" + ) + else: + return await ok.edit(f"**Failed to add `{id}` as SUDO User ... **") + + +@ultroid_cmd( + pattern="delsudo ?(.*)", +) +async def _(ult): + ok = await eor(ult, "`Updating SUDO Users List ...`") + + if ult.reply_to_msg_id: + replied_to = await ult.get_reply_message() + id = replied_to.sender.id + user = await ult.client(GetFullUserRequest(int(id))) + sed.remove(id) + if not is_sudo(id): + return await ok.edit( + f"[{user.user.first_name}](tg://user?id={id}) `wasn't a SUDO User ...`" + ) + elif del_sudo(id): + return await ok.edit( + f"**Removed [{user.user.first_name}](tg://user?id={id}) from SUDO User(s)**" + ) + else: + return await ok.edit("`SEEMS LIKE THIS FUNCTION CHOOSE TO BROKE ITSELF`") + + args = ult.pattern_match.group(1) + + if re.search(r"[\s]", args) is not None: + args = args.split(" ") + msg = "" + sudos = get_sudos() + for item in args: + user = "" + try: + user = await ult.client(GetFullUserRequest(int(item))) + except BaseException: + pass + if not hasattr(user, "user"): + msg += f"• `{item}` __Invalid UserID__\n" + elif item in sudos and del_sudo(item): + msg += ( + f"• [{user.user.first_name}](tg://user?id={id}) __Removed SUDO__\n" + ) + elif item not in sudos: + msg += ( + f"• [{user.user.first_name}](tg://user?id={id}) __Wasn't a SUDO__\n" + ) + else: + msg += f"• `{item}` __Failed to Remove SUDO__\n" + return await ok.edit(msg) + + id = args.strip() + user = "" + + try: + user = await ult.client(GetFullUserRequest(int(i))) + except BaseException: + pass + + if not id.isdigit(): + return await ok.edit("`Integer(s) Expected`") + elif not hasattr(user, "user"): + return await ok.edit("`Invalid UserID`") + elif not is_sudo(id): + return await ok.edit( + f"[{user.user.first_name}](tg://user?id={id}) wasn't a SUDO user ..." + ) + elif del_sudo(id): + return await ok.edit( + f"**Removed [{user.user.first_name}](tg://user?id={id}) from SUDO User**" + ) + else: + return await ok.edit(f"**Failed to Remove `{id}` as SUDO User ... **") + + +@ultroid_cmd( + pattern="listsudo$", +) +async def _(ult): + ok = await eor(ult, "`...") + sudos = get_sudos() + if "" in sudos: + return await ok.edit("`No SUDO User was assigned ...`") + msg = "" + for i in sudos: + user = "" + try: + user = await ok.client(GetFullUserRequest(int(i.strip()))) + except BaseException: + pass + if hasattr(user, "user"): + msg += f"• [{user.user.first_name}](tg://user?id={i}) ( `{i}` )\n" + else: + msg += f"• `{i}` -> Invalid User\n" + return await ok.edit(f"**List of SUDO Users :**\n{msg}") + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/superfban.py b/plugins/superfban.py new file mode 100644 index 0000000000..5d5e29e9ed --- /dev/null +++ b/plugins/superfban.py @@ -0,0 +1,328 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}superfban ` + FBan the person across all feds in which you are admin. + +• `{i}superunfban ` + Un-FBan the person across all feds in which you are admin. + +Specify FBan Group and Feds to exclude in the assistant. +""" + +import asyncio +import os + +from . import * + + +@ultroid_cmd(pattern="superfban ?(.*)") +async def _(event): + msg = await eor(event, "Starting a Mass-FedBan...") + fedList = [] + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + if previous_message.media: + downloaded_file_name = await ultroid_bot.download_media( + previous_message, "fedlist" + ) + file = open(downloaded_file_name, encoding="utf8") + lines = file.readlines() + for line in lines: + try: + fedList.append(line[:36]) + except BaseException: + pass + arg = event.text.split(" ", maxsplit=2) + if len(arg) > 2: + FBAN = arg[1] + REASON = arg[2] + else: + FBAN = arg[1] + REASON = " #TBMassBanned " + else: + FBAN = previous_message.sender_id + try: + REASON = event.text.split(" ", maxsplit=1)[1] + except BaseException: + REASON = "" + if REASON.strip() == "": + REASON = " #TBMassBanned " + else: + arg = event.text.split(" ", maxsplit=2) + if len(arg) > 2: + try: + FBAN = arg[1] + REASON = arg[2] + except BaseException: + return await msg.edit("`No user designated!`") + else: + try: + FBAN = arg[1] + REASON = " #TBMassBanned " + except BaseException: + return await msg.edit("`No user designated!`") + try: + if str(FBAN) in DEVLIST: + await msg.edit("You can't ban my dev you noob!!") + return + elif FBAN.startswith("@"): + try: + x = await ultroid_bot(GetFullUserRequest(FBAN)) + uid = x.user.id + if str(uid) in DEVLIST: + await msg.edit("You can't ban my dev you noob!!") + return + except Exception as e: + print(str(e)) + return await msg.edit(str(e)) + except Exception as e: + print(str(e)) + return await msg.edit(str(e)) + if udB.get("FBAN_GROUP_ID"): + chat = int(udB.get("FBAN_GROUP_ID")) + else: + chat = await event.get_chat() + if not len(fedList): + for a in range(3): + async with ultroid_bot.conversation("@MissRose_bot") as bot_conv: + await bot_conv.send_message("/start") + await asyncio.sleep(3) + await bot_conv.send_message("/myfeds") + await asyncio.sleep(3) + try: + response = await bot_conv.get_response() + except asyncio.exceptions.TimeoutError: + return await msg.edit( + "`Seems like rose isn't responding, or, the plugin is misbehaving`" + ) + await asyncio.sleep(3) + if "make a file" in response.text or "Looks like" in response.text: + await response.click(0) + await asyncio.sleep(3) + fedfile = await bot_conv.get_response() + await asyncio.sleep(3) + if fedfile.media: + downloaded_file_name = await ultroid_bot.download_media( + fedfile, "fedlist" + ) + await asyncio.sleep(6) + file = open(downloaded_file_name, "r", errors="ignore") + lines = file.readlines() + for line in lines: + try: + fedList.append(line[:36]) + except BaseException: + pass + elif "You can only use fed commands once every 5 minutes" in ( + await bot_conv.get_edit + ): + await msg.edit("Try again after 5 mins.") + return + if len(fedList) == 0: + await msg.edit( + f"Unable to collect FedAdminList. Retrying ({a+1}/3)..." + ) + else: + break + else: + await msg.edit("Error") + In = False + tempFedId = "" + for x in response.text: + if x == "`": + if In: + In = False + fedList.append(tempFedId) + tempFedId = "" + else: + In = True + elif In: + tempFedId += x + if len(fedList) == 0: + await msg.edit("Unable to collect FedAdminList.") + return + await msg.edit(f"FBaning in {len(fedList)} feds.") + try: + await ultroid_bot.send_message(chat, f"/start") + except BaseException: + await msg.edit("Specified FBan Group ID is incorrect.") + return + await asyncio.sleep(3) + if udB.get("EXCLUDE_FED"): + excludeFed = udB.get("EXCLUDE_FED").split(" ") + for n in range(len(excludeFed)): + excludeFed[n] = excludeFed[n].strip() + exCount = 0 + for fed in fedList: + if udB.get("EXCLUDE_FED") and fed in excludeFed: + await ultroid_bot.send_message(chat, f"{fed} Excluded.") + exCount += 1 + continue + await ultroid_bot.send_message(chat, f"/joinfed {fed}") + await asyncio.sleep(3) + await ultroid_bot.send_message(chat, f"/fban {FBAN} {REASON}") + await asyncio.sleep(3) + try: + os.remove("fedlist") + except Exception as e: + print(f"Error in removing FedAdmin file.\n{str(e)}") + await msg.edit( + f"SuperFBan Completed.\nTotal Feds - {len(fedlist)}.\nExcluded - {exCount}.\n Affected {len(fedList) - exCount} feds.\n#TB" + ) + + +@ultroid_cmd(pattern="superunfban ?(.*)") +async def _(event): + msg = await eor(event, "Starting a Mass-UnFedBan...") + fedList = [] + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + if previous_message.media: + downloaded_file_name = await ultroid_bot.download_media( + previous_message, "fedlist" + ) + file = open(downloaded_file_name, encoding="utf8") + lines = file.readlines() + for line in lines: + try: + fedList.append(line[:36]) + except BaseException: + pass + arg = event.text.split(" ", maxsplit=2) + if len(arg) > 2: + FBAN = arg[1] + REASON = arg[2] # rose unbans now can have reasons + else: + FBAN = arg[1] + REASON = "" + else: + FBAN = previous_message.sender_id + try: + REASON = event.text.split(" ", maxsplit=1)[1] + except BaseException: + REASON = "" + if REASON.strip() == "": + REASON = "" + else: + arg = event.text.split(" ", maxsplit=2) + if len(arg) > 2: + try: + FBAN = arg[1] + REASON = arg[2] + except BaseException: + return await msg.edit("`No user designated!`") + else: + try: + FBAN = arg[1] + REASON = " #TBMassUnBanned " + except BaseException: + return await msg.edit("`No user designated!`") + try: + if str(FBAN) in DEVLIST: + await msg.edit("You can't ban my dev you noob!!") + return + except Exception as e: + print(str(e)) + return await msg.edit(str(e)) + if udB.get("FBAN_GROUP_ID"): + chat = int(udB.get("FBAN_GROUP_ID")) + else: + chat = await event.get_chat() + if not len(fedList): + for a in range(3): + async with ultroid_bot.conversation("@MissRose_bot") as bot_conv: + await bot_conv.send_message("/start") + await asyncio.sleep(3) + await bot_conv.send_message("/myfeds") + await asyncio.sleep(3) + try: + response = await bot_conv.get_response() + except asyncio.exceptions.TimeoutError: + return await msg.edit( + "`Seems like rose isn't responding, or, the plugin is misbehaving`" + ) + await asyncio.sleep(3) + if "make a file" in response.text or "Looks like" in response.text: + await response.click(0) + await asyncio.sleep(3) + fedfile = await bot_conv.get_response() + await asyncio.sleep(3) + if fedfile.media: + downloaded_file_name = await ultroid_bot.download_media( + fedfile, "fedlist" + ) + await asyncio.sleep(6) + file = open(downloaded_file_name, "r", errors="ignore") + lines = file.readlines() + for line in lines: + try: + fedList.append(line[:36]) + except BaseException: + pass + elif "You can only use fed commands once every 5 minutes" in ( + await bot_conv.get_edit + ): + await msg.edit("Try again after 5 mins.") + return + if len(fedList) == 0: + await msg.edit( + f"Unable to collect FedAdminList. Retrying ({a+1}/3)..." + ) + else: + break + else: + await msg.edit("Error") + In = False + tempFedId = "" + for x in response.text: + if x == "`": + if In: + In = False + fedList.append(tempFedId) + tempFedId = "" + else: + In = True + elif In: + tempFedId += x + if len(fedList) == 0: + await msg.edit("Unable to collect FedAdminList.") + return + await msg.edit(f"UnFBaning in {len(fedList)} feds.") + try: + await ultroid_bot.send_message(chat, f"/start") + except BaseException: + await msg.edit("Specified FBan Group ID is incorrect.") + return + await asyncio.sleep(3) + if udB.get("EXCLUDE_FED"): + excludeFed = udB.get("EXCLUDE_FED").split(" ") + for n in range(len(excludeFed)): + excludeFed[n] = excludeFed[n].strip() + exCount = 0 + for fed in fedList: + if udB.get("EXCLUDE_FED") and fed in excludeFed: + await ultroid_bot.send_message(chat, f"{fed} Excluded.") + exCount += 1 + continue + await ultroid_bot.send_message(chat, f"/joinfed {fed}") + await asyncio.sleep(3) + await ultroid_bot.send_message(chat, f"/unfban {FBAN} {REASON}") + await asyncio.sleep(3) + try: + os.remove("fedlist") + except Exception as e: + print(f"Error in removing FedAdmin file.\n{str(e)}") + await msg.edit( + f"SuperUnFBan Completed.\nTotal Feds - {len(fedlist)}.\nExcluded - {exCount}.\n Affected {len(fedList) - exCount} feds.\n#TB" + ) + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/tools.py b/plugins/tools.py new file mode 100644 index 0000000000..314e36cb67 --- /dev/null +++ b/plugins/tools.py @@ -0,0 +1,411 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}bash ` + Run linux commands on telegram. + +• `{i}eval ` + Evaluate python commands on telegram. + +• `{i}circle` + Reply to a audio song or gif to get video note. + +• `{i}bots` + Shows the number of bots in the current chat with their perma-link. + +• `{i}hl` + {i}hl hyperlinks the message by editing the message with a blank text. + +• `{i}id` + Reply a Sticker to Get Its Id + Reply a User to Get His Id + Without Replying You Will Get the Chat's Id + +• `{i}sg` + Reply The User to Get His Name History + +• `{i}tr ` + Reply to a message with `{i}tr ` + Use `{i}tr ` + To get translated message. +""" + +import asyncio +import io +import sys +import time +import traceback +from asyncio.exceptions import TimeoutError + +import cv2 +import emoji +from googletrans import Translator +from telethon.errors.rpcerrorlist import YouBlockedUserError +from telethon.tl.types import ChannelParticipantAdmin, ChannelParticipantsBots +from telethon.utils import pack_bot_file_id + +from . import * + + +@ultroid_cmd( + pattern="tr", +) +async def _(event): + input = event.text[4:6] + txt = event.text[7:] + xx = await eor(event, "`Translating...`") + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + text = previous_message.message + lan = input or "en" + elif input: + text = txt + lan = input or "en" + else: + return await eod(xx, f"`{hndlr}tr LanguageCode` as reply to a message", time=5) + text = emoji.demojize(text.strip()) + lan = lan.strip() + translator = Translator() + try: + tt = translator.translate(text, dest=lan) + output_str = f"**TRANSLATED** from {tt.src} to {lan}\n{tt.text}" + await eod(xx, output_str) + except Exception as exc: + await eod(xx, str(exc), time=10) + + +@ultroid_cmd( + pattern="id$", +) +async def _(event): + if event.reply_to_msg_id: + await event.get_input_chat() + r_msg = await event.get_reply_message() + if r_msg.media: + bot_api_file_id = pack_bot_file_id(r_msg.media) + await eor( + event, + "**Current Chat ID:** `{}`\n**From User ID:** `{}`\n**Bot API File ID:** `{}`".format( + str(event.chat_id), str(r_msg.sender.id), bot_api_file_id + ), + ) + else: + await eor( + event, + "**Chat ID:** `{}`\n**User ID:** `{}`".format( + str(event.chat_id), str(r_msg.sender.id) + ), + ) + else: + await eor(event, "**Current Chat ID:** `{}`".format(str(event.chat_id))) + + +@ultroid_cmd(pattern="bots ?(.*)") +async def _(ult): + mentions = "**Bots in this Chat**: \n" + input_str = ult.pattern_match.group(1) + to_write_chat = await ult.get_input_chat() + chat = None + if not input_str: + chat = to_write_chat + else: + mentions = "**Bots in **{}: \n".format(input_str) + try: + chat = await ultroid_bot.get_entity(input_str) + except Exception as e: + await eor(ult, str(e)) + return None + try: + async for x in ultroid_bot.iter_participants( + chat, filter=ChannelParticipantsBots + ): + if isinstance(x.participant, ChannelParticipantAdmin): + mentions += "\n ⚜️ [{}](tg://user?id={}) `{}`".format( + x.first_name, x.id, x.id + ) + else: + mentions += "\n [{}](tg://user?id={}) `{}`".format( + x.first_name, x.id, x.id + ) + except Exception as e: + mentions += " " + str(e) + "\n" + await eod(ult, mentions) + + +@ultroid_cmd(pattern="hl") +async def _(ult): + try: + input = ult.text.split(" ", maxsplit=1)[1] + except IndexError: + return await eod(ult, "`Input some link`", time=5) + await eod(ult, "[ㅤㅤㅤㅤㅤㅤㅤ](" + input + ")", link_preview=False) + + +@ultroid_cmd( + pattern="circle$", +) +async def _(e): + a = await e.get_reply_message() + if a is None: + return await eor(e, "Reply to a gif or audio") + if a.document and a.document.mime_type == "audio/mpeg": + z = await eor(e, "**Cʀᴇᴀᴛɪɴɢ Vɪᴅᴇᴏ Nᴏᴛᴇ**") + toime = time.time() + try: + bbbb = await a.download_media(thumb=-1) + im = cv2.imread(bbbb) + dsize = (320, 320) + output = cv2.resize(im, dsize, interpolation=cv2.INTER_AREA) + cv2.imwrite("img.png", output) + thumb = "img.png" + except TypeError: + thumb = "./resources/extras/thumb.jpg" + c = await a.download_media( + "resources/downloads/", + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress(d, t, z, toime, "**Dᴏᴡɴʟᴏᴀᴅɪɴɢ...**") + ), + ) + await z.edit("**Dᴏᴡɴʟᴏᴀᴅᴇᴅ...\nNᴏᴡ Cᴏɴᴠᴇʀᴛɪɴɢ...**") + cmd = [ + "ffmpeg", + "-i", + c, + "-acodec", + "libmp3lame", + "-ac", + "2", + "-ab", + "144k", + "-ar", + "44100", + "comp.mp3", + ] + proess = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await proess.communicate() + stderr.decode().strip() + stdout.decode().strip() + mcd = [ + "ffmpeg", + "-y", + "-i", + thumb, + "-i", + "comp.mp3", + "-c:a", + "copy", + "circle.mp4", + ] + process = await asyncio.create_subprocess_exec( + *mcd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + stderr.decode().strip() + stdout.decode().strip() + taime = time.time() + await e.client.send_file( + e.chat_id, + "circle.mp4", + thumb=thumb, + video_note=True, + reply_to=a, + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress(d, t, z, taime, "**Uᴘʟᴏᴀᴅɪɴɢ...**") + ), + ) + await z.delete() + os.system("rm resources/downloads/*") + os.system("rm circle.mp4 comp.mp3 img.png") + os.remove(bbbb) + elif a.document and a.document.mime_type == "video/mp4": + z = await eor(e, "**Cʀᴇᴀᴛɪɴɢ Vɪᴅᴇᴏ Nᴏᴛᴇ**") + c = await a.download_media("resources/downloads/") + await e.client.send_file(e.chat_id, c, video_note=True, reply_to=a) + await z.delete() + os.remove(c) + else: + return await eor(e, "**Reply to a gif or audio file only**") + + +@ultroid_cmd( + pattern="bash", +) +async def _(event): + if (Var.I_DEV if Var.I_DEV else Redis("I_DEV")) != "True": + await eor( + event, + "Developer Restricted!\nIf you know what this does, and want to proceed\n\n set var `I_DEV` as `True`\n\nThis Might Be Dangerous.", + ) + return + xx = await eor(event, "`Processing...`") + try: + cmd = event.text.split(" ", maxsplit=1)[1] + except IndexError: + return await eod(xx, "`No cmd given`", time=10) + reply_to_id = event.message.id + if event.reply_to_msg_id: + reply_to_id = event.reply_to_msg_id + time.time() + 100 + process = await asyncio.create_subprocess_shell( + cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + OUT = f"**☞ BASH\n\n• COMMAND:**\n`{cmd}` \n\n" + e = stderr.decode() + if e: + OUT += f"**• ERROR:** \n`{e}`\n" + o = stdout.decode() + if not o and not e: + o = "Success" + OUT += f"**• OUTPUT:**\n`{o}`" + else: + _o = o.split("\n") + o = "`\n".join(_o) + OUT += f"**• OUTPUT:**\n{o}" + if len(OUT) > 4096: + with io.BytesIO(str.encode(OUT)) as out_file: + out_file.name = "bash.txt" + await event.client.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + caption=f"`{cmd}`", + reply_to=reply_to_id, + ) + await xx.delete() + else: + await eod(xx, OUT) + + +@ultroid_cmd( + pattern="eval", +) +async def _(event): + if (Var.I_DEV if Var.I_DEV else Redis("I_DEV")) != "True": + await eor( + event, + "Developer Restricted!\nIf you know what this does, and want to proceed\n\n set var `I_DEV` as `True`\n\nThis Might Be Dangerous.", + ) + return + xx = await eor(event, "`Processing ...`") + try: + cmd = event.text.split(" ", maxsplit=1)[1] + except IndexError: + return await eod(xx, "`Give some python cmd`", time=5) + if event.reply_to_msg_id: + reply_to_id = event.reply_to_msg_id + old_stderr = sys.stderr + old_stdout = sys.stdout + redirected_output = sys.stdout = io.StringIO() + redirected_error = sys.stderr = io.StringIO() + stdout, stderr, exc = None, None, None + reply_to_id = event.message.id + try: + await aexec(cmd, event) + except Exception: + exc = traceback.format_exc() + stdout = redirected_output.getvalue() + stderr = redirected_error.getvalue() + sys.stdout = old_stdout + sys.stderr = old_stderr + evaluation = "" + if exc: + evaluation = exc + elif stderr: + evaluation = stderr + elif stdout: + evaluation = stdout + else: + evaluation = "Success" + final_output = ( + "__►__ **EVAL**\n```{}``` \n\n __►__ **OUTPUT**: \n```{}``` \n".format( + cmd, evaluation + ) + ) + if len(final_output) > 4096: + with io.BytesIO(str.encode(final_output)) as out_file: + out_file.name = "eval.txt" + await ultroid_bot.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + caption=f"```{cmd}```", + reply_to=reply_to_id, + ) + await xx.delete() + else: + await eod(xx, final_output) + + +async def aexec(code, event): + e = message = event + client = event.client + exec( + f"async def __aexec(e, client): " + + "\n message = event = e" + + "".join(f"\n {l}" for l in code.split("\n")) + ) + + return await locals()["__aexec"](e, e.client) + + +@ultroid_cmd( + pattern="sg(?: |$)(.*)", +) +async def lastname(steal): + if steal.fwd_from: + return + if not steal.reply_to_msg_id: + await steal.edit("Reply to any user message.") + return + message = await steal.get_reply_message() + chat = "@SangMataInfo_bot" + user_id = message.sender.id + id = f"/search_id {user_id}" + if message.sender.bot: + await steal.edit("Reply to actual users message.") + return + lol = await eor(steal, "Processingg !!!!!") + try: + async with ultroid_bot.conversation(chat) as conv: + try: + msg = await conv.send_message(id) + response = await conv.get_response() + respond = await conv.get_response() + responds = await conv.get_response() + except YouBlockedUserError: + await lol.edit("Please unblock @sangmatainfo_bot and try again") + return + if response.text.startswith("No records found"): + await lol.edit("No records found for this user") + await steal.client.delete_messages(conv.chat_id, [msg.id, response.id]) + return + else: + if response.text.startswith("🔗"): + await lol.edit(respond.message) + await lol.reply(responds.message) + elif respond.text.startswith("🔗"): + await lol.edit(response.message) + await lol.reply(responds.message) + else: + await lol.edit(respond.message) + await lol.reply(response.message) + await steal.client.delete_messages( + conv.chat_id, [msg.id, responds.id, respond.id, response.id] + ) + except TimeoutError: + return await lol.edit("Error: @SangMataInfo_bot is not responding!.") + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/updater.py b/plugins/updater.py new file mode 100644 index 0000000000..a05f0e185f --- /dev/null +++ b/plugins/updater.py @@ -0,0 +1,178 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +import asyncio +import sys +from os import environ, execle, path, remove + +from git import Repo +from git.exc import GitCommandError, InvalidGitRepositoryError, NoSuchPathError + +UPSTREAM_REPO_URL = "https://github.com/TeamUltroid/Ultroid" +requirements_path = path.join( + path.dirname(path.dirname(path.dirname(__file__))), "requirements.txt" +) + + +async def gen_chlog(repo, diff): + ch_log = "" + d_form = "On %d/%m/%y at %H:%M:%S" + for c in repo.iter_commits(diff): + ch_log += f"**#{c.count()}** : {c.committed_datetime.strftime(d_form)} : [{c.summary}]({UPSTREAM_REPO_URL.rstrip('/')}/commit/{c}) by `{c.author}`\n" + return ch_log + + +async def updateme_requirements(): + reqs = str(requirements_path) + try: + process = await asyncio.create_subprocess_shell( + " ".join([sys.executable, "-m", "pip", "install", "-r", reqs]), + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + await process.communicate() + return process.returncode + except Exception as e: + return repr(e) + + +@ultroid_cmd( + pattern="update ?(.*)", +) +async def upstream(ups): + pagal = await eor(ups, "`Checking for updates, please wait....`") + conf = ups.pattern_match.group(1) + off_repo = UPSTREAM_REPO_URL + try: + txt = "`Oops.. Updater cannot continue due to " + txt += "some problems occured`\n\n**LOGTRACE:**\n" + repo = Repo() + except NoSuchPathError as error: + await eod(pagal, f"{txt}\n`directory {error} is not found`", time=10) + repo.__del__() + return + except GitCommandError as error: + await eod(pagal, f"{txt}\n`Early failure! {error}`", time=10) + repo.__del__() + return + except InvalidGitRepositoryError as error: + if conf != "now": + await eod( + pagal, + f"**Unfortunately, the directory {error} does not seem to be a git repository.Or Maybe it just needs a sync verification with {GIT_REPO_NAME} But we can fix that by force updating the userbot using** `.update now.`", + time=30, + ) + return + repo = Repo.init() + origin = repo.create_remote("upstream", off_repo) + origin.fetch() + repo.create_head("main", origin.refs.main) + repo.heads.main.set_tracking_branch(origin.refs.main) + repo.heads.main.checkout(True) + ac_br = repo.active_branch.name + if ac_br != "main": + await eod( + pagal, + f"**[UPDATER]:**` You are on ({ac_br})\n Please change to main branch.`", + ) + repo.__del__() + return + try: + repo.create_remote("upstream", off_repo) + except BaseException: + pass + ups_rem = repo.remote("upstream") + ups_rem.fetch(ac_br) + changelog = await gen_chlog(repo, f"HEAD..upstream/{ac_br}") + if "now" not in conf: + if changelog: + changelog_str = f"**New UPDATE available for [[{ac_br}]]({UPSTREAM_REPO_URL}/tree/{ac_br}):\n\nCHANGELOG**\n\n{changelog}" + if len(changelog_str) > 4096: + await eor(pagal, "`Changelog is too big, view the file to see it.`") + file = open("output.txt", "w+") + file.write(changelog_str) + file.close() + await ups.client.send_file( + ups.chat_id, + "output.txt", + caption=f"Do `{hndlr}update now` to update.", + reply_to=ups.id, + ) + remove("output.txt") + else: + return await eod( + pagal, f"{changelog_str}\n\nDo `{hndlr}update now` to update." + ) + else: + await eod( + pagal, + f"\n`Your BOT is` **up-to-date** `with` **[[{ac_br}]]({UPSTREAM_REPO_URL}/tree/{ac_br})**\n", + time=10, + ) + repo.__del__() + return + if Var.HEROKU_API is not None: + import heroku3 + + heroku = heroku3.from_key(Var.HEROKU_API) + heroku_app = None + heroku_applications = heroku.apps() + if not Var.HEROKU_APP_NAME: + await eod( + pagal, + "`Please set up the HEROKU_APP_NAME variable to be able to update userbot.`", + time=10, + ) + repo.__del__() + return + for app in heroku_applications: + if app.name == Var.HEROKU_APP_NAME: + heroku_app = app + break + if heroku_app is None: + await eod( + pagal, + f"{txt}\n`Invalid Heroku credentials for updating userbot dyno.`", + time=10, + ) + repo.__del__() + return + await eor( + pagal, "`Userbot dyno build in progress, please wait for it to complete.`" + ) + ups_rem.fetch(ac_br) + repo.git.reset("--hard", "FETCH_HEAD") + heroku_git_url = heroku_app.git_url.replace( + "https://", "https://api:" + Var.HEROKU_API + "@" + ) + if "heroku" in repo.remotes: + remote = repo.remote("heroku") + remote.set_url(heroku_git_url) + else: + remote = repo.create_remote("heroku", heroku_git_url) + try: + remote.push(refspec=f"HEAD:refs/heads/{ac_br}", force=True) + except GitCommandError as error: + await eod(pagal, f"{txt}\n`Here is the error log:\n{error}`", time=10) + repo.__del__() + return + await eod(pagal, "`Successfully Updated!\nRestarting, please wait...`", time=60) + else: + # Classic Updater, pretty straightforward. + try: + ups_rem.pull(ac_br) + except GitCommandError: + repo.git.reset("--hard", "FETCH_HEAD") + await updateme_requirements() + await eod( + pagal, + "`Successfully Updated!\nBot is restarting... Wait for a second!`", + ) + # Spin a new instance of bot + args = [sys.executable, "./resources/startup/deploy.sh"] + execle(sys.executable, *args, environ) + return diff --git a/plugins/uploads_files.py b/plugins/uploads_files.py new file mode 100644 index 0000000000..0501e8fa08 --- /dev/null +++ b/plugins/uploads_files.py @@ -0,0 +1,175 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}ul ` + Upload file to telegram chat. + +• `{i}dl ` + Reply to file to download. + +• `{i}save ` + Reply to a text msg to save it in a file. + +• `{i}open` + Reply to a file to reveal it's text. +""" + +import asyncio +import os +import time +from datetime import datetime as dt + +from . import * + +opn = [] + + +@ultroid_cmd( + pattern="dl ?(.*)", +) +async def download(event): + xx = await eor(event, "`Processing...`") + kk = event.pattern_match.group(1) + s = dt.now() + k = time.time() + if event.reply_to_msg_id: + ok = await event.get_reply_message() + if not ok.media: + return await eod(xx, "`Reply The File/Media u Want to Download..`", time=5) + else: + if not kk: + d = "resources/downloads/" + o = await event.client.download_media( + ok, + d, + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress( + d, + t, + xx, + k, + "Downloading...", + ) + ), + ) + else: + d = f"resources/downloads/{kk}" + o = await event.client.download_media( + ok, + d, + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress( + d, + t, + xx, + k, + "Downloading...", + file_name=d, + ) + ), + ) + e = datetime.now() + t = convert((e - s).seconds) + await eod(xx, f"Download Successful..\nTo\n`{o}`\nin `{t}`") + + +@ultroid_cmd( + pattern="ul ?(.*)", +) +async def download(event): + xx = await eor(event, "`Processing...`") + kk = event.pattern_match.group(1) + s = dt.now() + tt = time.time() + if not kk: + return await eod(xx, "`Give a specific path to file`") + else: + try: + x = await event.client.send_file( + event.chat_id, + kk, + caption=kk, + progress_callback=lambda d, t: asyncio.get_event_loop().create_task( + progress( + d, + t, + xx, + tt, + "Uploading...", + file_name=kk, + ) + ), + ) + except ValueError as ve: + return await eod(xx, str(ve)) + e = datetime.now() + t = convert((e - s).seconds) + try: + await x.edit(f"`{kk}`\nTime Taken: `{t}`") + except BaseException: + pass + await eod(xx, f"Uploaded `{kk}` in `{t}`", time=5) + + +@ultroid_cmd( + pattern="save", +) +async def _(event): + input_str = event.text[6:] + xx = await eor(event, "`Processing...`") + if event.reply_to_msg_id: + a = await event.get_reply_message() + if not a.message: + return await xx.edit("`Reply to a message`") + else: + b = open(input_str, "w") + b.write(str(a.message)) + b.close() + await xx.edit(f"**Packing into** `{input_str}`") + await asyncio.sleep(2) + await xx.edit(f"**Uploading** `{input_str}`") + await asyncio.sleep(2) + await event.client.send_file(event.chat_id, input_str) + await xx.delete() + os.remove(input_str) + + +@ultroid_cmd( + pattern="open$", +) +async def _(event): + xx = await eor(event, "`Processing...`") + if event.reply_to_msg_id: + a = await event.get_reply_message() + if a.media: + b = await a.download_media() + c = open(b, "r") + d = c.read() + c.close() + n = 4096 + for bkl in range(0, len(d), n): + opn.append(d[bkl : bkl + n]) + for bc in opn: + await event.client.send_message( + event.chat_id, + f"```{bc}```", + reply_to=event.reply_to_msg_id, + ) + await event.delete() + opn.clear() + os.remove(b) + await xx.delete() + else: + return await eod(xx, "`Reply to a readable file`", time=10) + else: + return await eod(xx, "`Reply to a readable file`", time=10) + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/utilities.py b/plugins/utilities.py new file mode 100644 index 0000000000..b9fe0faadf --- /dev/null +++ b/plugins/utilities.py @@ -0,0 +1,583 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}kickme` + Leaves the group in which it is used. + +• `{i}calc ` + A simple calculator. + +• `{i}date` + Show Calender. + +• `{i}chatinfo` + Get full info about the group/chat. + +• `{i}listreserved` + List all usernames (channels/groups) you own. + +• `{i}stats` + See your profile stats. + +• `{i}paste` + Include long text / Reply to text file. + +• `{i}hastebin` + Include long text / Reply to text file. + +• `{i}info ` + Reply to someone's msg. + +• `{i}invite ` + Add user to the chat. + +• `{i}rmbg ` + Remove background from that picture. + +• `{i}telegraph ` + Upload media/text to telegraph. + +• `{i}json ` + Get the json encoding of the message. +""" +import asyncio +import calendar +import html +import io +import os +import sys +import time +import traceback +from datetime import datetime as dt + +import pytz +import requests +from telegraph import Telegraph +from telegraph import upload_file as uf +from telethon import functions +from telethon.errors.rpcerrorlist import BotInlineDisabledError, BotResponseTimeoutError +from telethon.events import NewMessage +from telethon.tl.custom import Dialog +from telethon.tl.functions.channels import LeaveChannelRequest +from telethon.tl.functions.photos import GetUserPhotosRequest +from telethon.tl.types import Channel, Chat, User +from telethon.utils import get_input_location + +# =================================================================# +from . import * + +TMP_DOWNLOAD_DIRECTORY = "resources/downloads/" + +# Telegraph Things +telegraph = Telegraph() +telegraph.create_account(short_name="Ultroid") +# ================================================================# + + +@ultroid_cmd( + pattern="kickme$", + groups_only=True, +) +async def leave(ult): + x = ultroid_bot.me + name = x.first_name + await eor(ult, f"`{name} has left this group, bye!!.`") + await ultroid_bot(LeaveChannelRequest(ult.chat_id)) + + +@ultroid_cmd( + pattern="date$", +) +async def date(event): + k = pytz.timezone("Asia/Kolkata") + m = dt.now(k).month + y = dt.now(k).year + d = dt.now(k).strftime("Date - %B %d, %Y\nTime- %H:%M:%S") + k = calendar.month(y, m) + ultroid = await eor(event, f"`{k}\n\n{d}`") + + +@ultroid_cmd( + pattern="calc", +) +async def _(event): + x = await eor(event, "...") + cmd = event.text.split(" ", maxsplit=1)[1] + event.message.id + if event.reply_to_msg_id: + event.reply_to_msg_id + wtf = f"print({cmd})" + old_stderr = sys.stderr + old_stdout = sys.stdout + redirected_output = sys.stdout = io.StringIO() + redirected_error = sys.stderr = io.StringIO() + stdout, stderr, exc = None, None, None + try: + await aexec(wtf, event) + except Exception: + exc = traceback.format_exc() + stdout = redirected_output.getvalue() + stderr = redirected_error.getvalue() + sys.stdout = old_stdout + sys.stderr = old_stderr + evaluation = "" + if exc: + evaluation = exc + elif stderr: + evaluation = stderr + elif stdout: + evaluation = stdout + else: + evaluation = "`Something went wrong`" + + final_output = """ +**EQUATION**: +`{}` +**SOLUTION**: +`{}` +""".format( + cmd, evaluation + ) + await x.edit(final_output) + + +async def aexec(code, event): + exec(f"async def __aexec(event): " + "".join(f"\n {l}" for l in code.split("\n"))) + return await locals()["__aexec"](event) + + +@ultroid_cmd( + pattern="chatinfo(?: |$)(.*)", +) +async def info(event): + ok = await eor(event, "`...`") + chat = await get_chatinfo(event) + caption = await fetch_info(chat, event) + try: + await ok.edit(caption, parse_mode="html") + except Exception as e: + print("Exception:", e) + await ok.edit(f"`An unexpected error has occurred. {e}`") + await asyncio.sleep(5) + await ok.delete() + return + + +@ultroid_cmd( + pattern="listreserved$", +) +async def _(event): + result = await ultroid_bot(functions.channels.GetAdminedPublicChannelsRequest()) + output_str = "" + r = result.chats + for channel_obj in r: + output_str += f"- {channel_obj.title} @{channel_obj.username} \n" + if not r: + await eor(event, "`Not username Reserved`") + else: + await eor(event, output_str) + + +@ultroid_cmd( + pattern="stats$", +) +async def stats( + event: NewMessage.Event, +) -> None: + ok = await eor(event, "`Collecting stats...`") + start_time = time.time() + private_chats = 0 + bots = 0 + groups = 0 + broadcast_channels = 0 + admin_in_groups = 0 + creator_in_groups = 0 + admin_in_broadcast_channels = 0 + creator_in_channels = 0 + unread_mentions = 0 + unread = 0 + dialog: Dialog + async for dialog in ultroid_bot.iter_dialogs(): + entity = dialog.entity + if isinstance(entity, Channel): + if entity.broadcast: + broadcast_channels += 1 + if entity.creator or entity.admin_rights: + admin_in_broadcast_channels += 1 + if entity.creator: + creator_in_channels += 1 + + elif entity.megagroup: + groups += 1 + if entity.creator or entity.admin_rights: + admin_in_groups += 1 + if entity.creator: + creator_in_groups += 1 + + elif isinstance(entity, User): + private_chats += 1 + if entity.bot: + bots += 1 + + elif isinstance(entity, Chat): + groups += 1 + if entity.creator or entity.admin_rights: + admin_in_groups += 1 + if entity.creator: + creator_in_groups += 1 + + unread_mentions += dialog.unread_mentions_count + unread += dialog.unread_count + stop_time = time.time() - start_time + + full_name = inline_mention(await ultroid_bot.get_me()) + response = f"🔸 **Stats for {full_name}** \n\n" + response += f"**Private Chats:** {private_chats} \n" + response += f"** •• **`Users: {private_chats - bots}` \n" + response += f"** •• **`Bots: {bots}` \n" + response += f"**Groups:** {groups} \n" + response += f"**Channels:** {broadcast_channels} \n" + response += f"**Admin in Groups:** {admin_in_groups} \n" + response += f"** •• **`Creator: {creator_in_groups}` \n" + response += f"** •• **`Admin Rights: {admin_in_groups - creator_in_groups}` \n" + response += f"**Admin in Channels:** {admin_in_broadcast_channels} \n" + response += f"** •• **`Creator: {creator_in_channels}` \n" + response += f"** •• **`Admin Rights: {admin_in_broadcast_channels - creator_in_channels}` \n" + response += f"**Unread:** {unread} \n" + response += f"**Unread Mentions:** {unread_mentions} \n\n" + response += f"**__It Took:__** {stop_time:.02f}s \n" + await ok.edit(response) + + +@ultroid_cmd( + pattern="paste( (.*)|$)", +) +async def _(event): + xx = await eor(event, "`...`") + input_str = "".join(event.text.split(maxsplit=1)[1:]) + if input_str: + message = input_str + downloaded_file_name = None + elif event.reply_to_msg_id: + previous_message = await event.get_reply_message() + if previous_message.media: + downloaded_file_name = await event.client.download_media( + previous_message, + "./resources/downloads", + ) + m_list = None + with open(downloaded_file_name, "rb") as fd: + m_list = fd.readlines() + message = "" + try: + for m in m_list: + message += m.decode("UTF-8") + except BaseException: + message = "`Include long text / Reply to text file`" + os.remove(downloaded_file_name) + else: + downloaded_file_name = None + message = previous_message.message + else: + downloaded_file_name = None + message = "`Include long text / Reply to text file`" + if downloaded_file_name and downloaded_file_name.endswith(".py"): + data = message + key = ( + requests.post("https://nekobin.com/api/documents", json={"content": data}) + .json() + .get("result") + .get("key") + ) + else: + data = message + key = ( + requests.post("https://nekobin.com/api/documents", json={"content": data}) + .json() + .get("result") + .get("key") + ) + q = f"paste-{key}" + try: + ok = await ultroid_bot.inline_query(Var.BOT_USERNAME, q) + await ok[0].click(event.chat_id, reply_to=event.reply_to_msg_id, hide_via=True) + await xx.delete() + except BotInlineDisabledError or BotResponseTimeoutError: # incase the bot doesnt respond + await xx.edit(reply_text) + + +@ultroid_cmd( + pattern="hastebin ?(.*)", +) +async def _(event): + input_str = event.pattern_match.group(1) + xx = await eor(event, "`Pasting...`") + message = "SYNTAX: `.paste `" + if input_str: + message = input_str + elif event.reply_to_msg_id: + previous_message = await event.get_reply_message() + if previous_message.media: + downloaded_file_name = await event.client.download_media( + previous_message, + "./resources/downloads", + ) + m_list = None + with open(downloaded_file_name, "rb") as fd: + m_list = fd.readlines() + message = "" + for m in m_list: + message += m.decode("UTF-8") + "\r\n" + os.remove(downloaded_file_name) + else: + message = previous_message.message + else: + message = "SYNTAX: `.hastebin `" + url = "https://hastebin.com/documents" + r = requests.post(url, data=message).json() + url = f"https://hastebin.com/{r['key']}" + await xx.edit("**Pasted to Hastebin** : [Link]({})".format(url)) + + +@ultroid_cmd( + pattern="info ?(.*)", +) +async def _(event): + xx = await eor(event, "`Processing...`") + replied_user, error_i_a = await get_full_user(event) + if replied_user is None: + await xx.edit("Please repl to a user.\nError - " + str(error_i_a)) + return False + replied_user_profile_photos = await event.client( + GetUserPhotosRequest( + user_id=replied_user.user.id, offset=42, max_id=0, limit=80 + ) + ) + replied_user_profile_photos_count = "NaN" + try: + replied_user_profile_photos_count = replied_user_profile_photos.count + except AttributeError: + pass + user_id = replied_user.user.id + first_name = html.escape(replied_user.user.first_name) + if first_name is not None: + first_name = first_name.replace("\u2060", "") + last_name = replied_user.user.last_name + last_name = ( + last_name.replace("\u2060", "") if last_name else ("Last Name not found") + ) + user_bio = replied_user.about + if user_bio is not None: + user_bio = html.escape(replied_user.about) + common_chats = replied_user.common_chats_count + try: + dc_id, location = get_input_location(replied_user.profile_photo) + except Exception as e: + dc_id = "Need a Profile Picture to check this" + str(e) + caption = """Exᴛʀᴀᴄᴛᴇᴅ Dᴀᴛᴀʙᴀsᴇ Fʀᴏᴍ Tᴇʟᴇɢʀᴀᴍ's Dᴀᴛᴀʙᴀsᴇ + ••Tᴇʟᴇɢʀᴀᴍ ID: {} + ••Pᴇʀᴍᴀɴᴇɴᴛ Lɪɴᴋ: Click Here + ••Fɪʀsᴛ Nᴀᴍᴇ: {} + ••Sᴇᴄᴏɴᴅ Nᴀᴍᴇ: {} + ••Bɪᴏ: {} + ••Dᴄ ID: {} + ••Nᴏ. Oғ PғPs : {} + ••Is Rᴇsᴛʀɪᴄᴛᴇᴅ: {} + ••Vᴇʀɪғɪᴇᴅ: {} + ••Is A Bᴏᴛ: {} + ••Gʀᴏᴜᴘs Iɴ Cᴏᴍᴍᴏɴ: {} + """.format( + user_id, + user_id, + first_name, + last_name, + user_bio, + dc_id, + replied_user_profile_photos_count, + replied_user.user.restricted, + replied_user.user.verified, + replied_user.user.bot, + common_chats, + ) + message_id_to_reply = event.message.reply_to_msg_id + if not message_id_to_reply: + message_id_to_reply = event.message.id + await event.client.send_message( + event.chat_id, + caption, + reply_to=message_id_to_reply, + parse_mode="HTML", + file=replied_user.profile_photo, + force_document=False, + silent=True, + ) + await xx.delete() + + +@ultroid_cmd( + pattern="invite ?(.*)", + groups_only=True, +) +async def _(ult): + xx = await eor(ult, "`Processing...`") + to_add_users = ult.pattern_match.group(1) + if not ult.is_channel and ult.is_group: + for user_id in to_add_users.split(" "): + try: + await ultroid_bot( + functions.messages.AddChatUserRequest( + chat_id=ult.chat_id, user_id=user_id, fwd_limit=1000000 + ) + ) + await xx.edit(f"Successfully invited `{user_id}` to `{ult.chat_id}`") + except Exception as e: + await xx.edit(str(e)) + else: + for user_id in to_add_users.split(" "): + try: + await ultroid_bot( + functions.channels.InviteToChannelRequest( + channel=ult.chat_id, users=[user_id] + ) + ) + await xx.edit(f"Successfully invited `{user_id}` to `{ult.chat_id}`") + except Exception as e: + await xx.edit(str(e)) + + +@ultroid_cmd( + pattern=r"rmbg ?(.*)", +) +async def rmbg(event): + RMBG_API = udB.get("RMBG_API") + xx = await eor(event, "`Processing...`") + if not RMBG_API: + return await xx.edit( + "Get your API key from [here](https://www.remove.bg/) for this plugin to work.", + ) + input_str = event.pattern_match.group(1) + message_id = event.message.id + if event.reply_to_msg_id: + message_id = event.reply_to_msg_id + reply_message = await event.get_reply_message() + try: + dl_file = await ultroid_bot.download_media( + reply_message, TMP_DOWNLOAD_DIRECTORY + ) + except Exception as e: + return await xx.edit("**ERROR:**\n`{}`".format(str(e))) + else: + await xx.edit("`Sending to remove.bg`") + output_file_name = ReTrieveFile(dl_file) + os.remove(dl_file) + elif input_str: + await xx.edit("`Sending to remove.bg`") + output_file_name = ReTrieveURL(input_str) + else: + await xx.edit( + f"Use `{Var.HNDLR}rmbg` as reply to a pic to remove its background." + ) + await asyncio.sleep(5) + await xx.delete() + return + contentType = output_file_name.headers.get("content-type") + if "image" in contentType: + with io.BytesIO(output_file_name.content) as remove_bg_image: + remove_bg_image.name = "rmbg-ult.png" + await ultroid_bot.send_file( + event.chat_id, + remove_bg_image, + force_document=True, + supports_streaming=False, + allow_cache=False, + reply_to=message_id, + ) + await xx.edit("`Done.`") + else: + await xx.edit( + "RemoveBG returned an error - \n`{}`".format( + output_file_name.content.decode("UTF-8") + ), + ) + + +@ultroid_cmd( + pattern="telegraph ?(.*)", +) +async def telegraphcmd(event): + input_str = event.pattern_match.group(1) + xx = await eor(event, "`Processing...`") + if event.reply_to_msg_id: + getmsg = await event.get_reply_message() + if getmsg.photo or getmsg.video or getmsg.gif: + getit = await ultroid_bot.download_media(getmsg) + try: + variable = uf(getit) + os.remove(getit) + nn = "https://telegra.ph" + variable[0] + amsg = f"Uploaded to [Telegraph]({nn}) !" + except Exception as e: + amsg = f"Error - {e}" + await xx.edit(amsg) + elif getmsg.document: + getit = await ultroid_bot.download_media(getmsg) + ab = open(getit, "r") + cd = ab.read() + ab.close() + if input_str: + tcom = input_str + else: + tcom = "Ultroid" + makeit = telegraph.create_page(title=tcom, content=[f"{cd}"]) + war = makeit["url"] + os.remove(getit) + await xx.edit(f"Pasted to Telegraph : [Telegraph]({war})") + elif getmsg.text: + if input_str: + tcom = input_str + else: + tcom = "Ultroid" + makeit = telegraph.create_page(title=tcom, content=[f"{getmsg.text}"]) + war = makeit["url"] + await xx.edit(f"Pasted to Telegraph : [Telegraph]({war})") + else: + await xx.edit("Reply to a Media or Text !") + else: + await xx.edit("Reply to a Message !") + + +@ultroid_cmd(pattern="json") +async def _(event): + the_real_message = None + reply_to_id = None + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + the_real_message = previous_message.stringify() + reply_to_id = event.reply_to_msg_id + else: + the_real_message = event.stringify() + reply_to_id = event.message.id + if len(the_real_message) > 4096: + with io.BytesIO(str.encode(the_real_message)) as out_file: + out_file.name = "json-ult.txt" + await borg.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + reply_to=reply_to_id, + ) + await event.delete() + else: + await eor(event, f"```{the_real_message}```") + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/plugins/words.py b/plugins/words.py new file mode 100644 index 0000000000..f7556cb24a --- /dev/null +++ b/plugins/words.py @@ -0,0 +1,145 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +""" +✘ Commands Available - + +• `{i}meaning ` + Get the meaning of the word. + +• `{i}synonym ` + Get all synonyms. + +• `{i}antonym ` + Get all antonyms. + +• `{i}ud ` + Fetch word defenition from urbandictionary. +""" + +import asyncurban +from PyDictionary import PyDictionary + +from . import * + +dictionary = PyDictionary() + + +@ultroid_cmd( + pattern="meaning", +) +async def mean(event): + evid = event.message.id + xx = await eor(event, "`Processing...`") + wrd = event.text.split(" ", maxsplit=1)[1] + ok = dictionary.meaning(wrd) + try: + p = ok["Noun"] + except BaseException: + return await xx.edit("Oops! No such word found!!") + x = f"**Word** - `{wrd}`\n\n**Meanings** - \n" + c = 1 + for i in p: + x += f"**{c}.** `{i}`\n" + c += 1 + if len(x) > 4096: + with io.BytesIO(str.encode(x)) as fle: + fle.name = f"{wrd}-meanings.txt" + await ultroid_bot.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + caption=f"Meanings of {wrd}", + reply_to=evid, + ) + await xx.delete() + else: + await xx.edit(x) + + +@ultroid_cmd( + pattern="synonym", +) +async def mean(event): + evid = event.message.id + xx = await eor(event, "`Processing...`") + wrd = event.text.split(" ", maxsplit=1)[1] + ok = dictionary.synonym(wrd) + x = f"**Word** - `{wrd}`\n\n**Synonyms** - \n" + c = 1 + try: + for i in ok: + x += f"**{c}.** `{i}`\n" + c += 1 + if len(x) > 4096: + with io.BytesIO(str.encode(x)) as fle: + fle.name = f"{wrd}-synonyms.txt" + await ultroid_bot.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + caption=f"Synonyms of {wrd}", + reply_to=evid, + ) + await xx.delete() + else: + await xx.edit(x) + except Exception as e: + await xx.edit(f"No synonym found!!\n{str(e)}") + + +@ultroid_cmd( + pattern="antonym", +) +async def mean(event): + evid = event.message.id + xx = await eor(event, "`Processing...`") + wrd = event.text.split(" ", maxsplit=1)[1] + ok = dictionary.antonym(wrd) + x = f"**Word** - `{wrd}`\n\n**Antonyms** - \n" + c = 1 + try: + for i in ok: + x += f"**{c}.** `{i}`\n" + c += 1 + if len(x) > 4096: + with io.BytesIO(str.encode(x)) as fle: + fle.name = f"{wrd}-antonyms.txt" + await ultroid_bot.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + caption=f"Antonyms of {wrd}", + reply_to=evid, + ) + await xx.delete() + else: + await xx.edit(x) + except Exception as e: + await xx.edit(f"No antonym found!!\n{str(e)}") + + +@ultroid_cmd(pattern="ud (.*)") +async def _(event): + xx = await eor(event, "`Processing...`") + word = event.pattern_match.group(1) + if word is None: + return await xx.edit("`No word given!`") + urban = asyncurban.UrbanDictionary() + try: + mean = await urban.get_word(word) + await xx.edit( + f"**Text**: `{mean.word}`\n\n**Meaning**: `{mean.definition}`\n\n**Example**: __{mean.example}__" + ) + except asyncurban.WordNotFoundError: + await xx.edit(f"**No result found for** `{word}`") + + +HELP.update({f"{__name__.split('.')[1]}": f"{__doc__.format(i=Var.HNDLR)}"}) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..4a4c0a4fc9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,31 @@ +py-Ultroid==2021.2.21 +asyncurban +carbonnow +cairosvg +emoji +GitPython +googletrans==3.1.0a0 +heroku3 +imutils +lottie +lxml +moviepy +numpy +opencv-python-headless +Pillow>=7.0.0 +play-scraper +PyDictionary +PyPDF2 +psutil +pytz +requests>=2.18.4 +scikit-image +search-engine-parser +speedtest-cli==2.1.2 +telegraph +validators +youtube_dl +youtube-search-python +google-api-python-client==1.7.11 +oauth2client==4.1.3 +httplib2==0.13.1 diff --git a/resources/downloads/.atoz b/resources/downloads/.atoz new file mode 100644 index 0000000000..9c558e357c --- /dev/null +++ b/resources/downloads/.atoz @@ -0,0 +1 @@ +. diff --git a/resources/extras/logo_rdm.png b/resources/extras/logo_rdm.png new file mode 100644 index 0000000000000000000000000000000000000000..44157c50e304d742cd7913431fdae3ab887f8644 GIT binary patch literal 14068 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

)j`cXH3B&_2)0r3LEE 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 aG^xtYSPW;6ypI^n=;42*e?I+Z>3;xq7=LmA literal 0 HcmV?d00001 diff --git a/resources/extras/ultroid.jpg b/resources/extras/ultroid.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c339c08229ac2a8275d079ebd1936fbdd864d89c GIT binary patch literal 52867 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 KtZz-LGymDrLoUVu literal 0 HcmV?d00001 diff --git a/resources/extras/ultroid_blank.png b/resources/extras/ultroid_blank.png new file mode 100644 index 0000000000000000000000000000000000000000..5972e18ec8004ce0dad6ac3ac33c894d79cb9570 GIT binary patch literal 889 zcmeAS@N?(olHy`uVBq!ia0y~yVEh7PpWt8vl5FBlk@9XwqeLn`LHy?T)MfP%n* z4VV6w+qFGt?0CcZ>GZ6b{0tSh>#Z3WEYe>v2qcW+(J&ZI11M==1EXseORwdED-pmf O#o+1c=d#Wzp$P!J=_!5y literal 0 HcmV?d00001 diff --git a/resources/fonts/2.ttf b/resources/fonts/2.ttf new file mode 100644 index 0000000000000000000000000000000000000000..23d266d3d2a580f00748427ab3b98287716d3136 GIT binary patch literal 21872 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 +# PLease read the GNU Affero General Public License in . + +clear +echo -e "\e[1m" +echo " _ _ _ _ _ _ " +echo " | | | | | | (_) | |" +echo " | | | | | |_ _ __ ___ _ __| |" +echo " | | | | | __| '__/ _ \| |/ _ |" +echo " | |__| | | |_| | | (_) | | (_| |" +echo " \____/|_|\__|_| \___/|_|\__,_|" +echo -e "\e[0m" +sec=5 +spinner=(⣻ ⢿ ⡿ ⣟ ⣯ ⣷) +while [ $sec -gt 0 ]; do + echo -ne "\e[33m ${spinner[sec]} Starting dependency installation in $sec seconds...\r" + sleep 1 + sec=$(($sec - 1)) +done +echo -e "\e[1;32mInstalling Dependencies ---------------------------\e[0m\n" # Don't Remove Dashes / Fix it +apt-get update +apt-get upgrade -y +pkg upgrade -y +pkg install python wget -y +wget https://raw.githubusercontent.com/TeamUltroid/ultroid/main/resources/session/ssgen.py +pip install telethon +clear +python3 ssgen.py diff --git a/resources/session/ssgen.py b/resources/session/ssgen.py new file mode 100644 index 0000000000..cd3b4d6cbc --- /dev/null +++ b/resources/session/ssgen.py @@ -0,0 +1,24 @@ +#!/bin/bash +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +from telethon.sessions import StringSession +from telethon.sync import TelegramClient + +print("Please ensure that you have your API ID and API HASH.") +print("") + +API_ID = int(input("Enter API ID: ")) +API_HASH = input("Enter API HASH: ") + +with TelegramClient(StringSession(), API_ID, API_HASH) as client: + ult = client.send_message("me", client.session.save()) + ult.reply( + "The above is the `SESSION` for your current session.\nVisit @TheUltroid") + print("") + print("String Session for the current login has been generated.") + print("Check your Telegram Saved messages for your SESSION.") diff --git a/resources/startup/deploy.sh b/resources/startup/deploy.sh new file mode 100644 index 0000000000..6847eb46d8 --- /dev/null +++ b/resources/startup/deploy.sh @@ -0,0 +1,50 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in +# . + +echo " + ╔╦╦╦══╦═╦═╦══╦══╗ + ║║║╠╗╔╣╬║║╠║║╩╗╗║ + ║║║╚╣║║╗╣║╠║║╦╩╝║ + ╚═╩═╩╝╚╩╩═╩══╩══╝ + + °•° Deployment Begins •°• +" +echo ' + •• Getting Packages and Installing +' + +export DEBIAN_FRONTEND=noninteractive +export TZ=Asia/Kolkata +ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +apt-get update +apt-get upgrade -y +apt-get install -y --no-install-recommends ffmpeg neofetch mediainfo megatools +apt-get autoremove --purge + +echo ' + •• Cloning Repository +' +git clone https://github.com/TeamUltroid/Ultroid.git /root/TeamUltroid/ + +echo ' + •• Getting Libraries and Installing +' +pip install --upgrade pip setuptools wheel +pip install -r /root/TeamUltroid/requirements.txt + +echo " + + ┏┳┓╋┏┓╋╋╋╋┏┓┏┓ + ┃┃┣┓┃┗┳┳┳━╋╋┛┃ + ┃┃┃┗┫┏┫┏┫╋┃┃╋┃ + ┗━┻━┻━┻┛┗━┻┻━┛ + + •°• Deployed Successfully °•° + •• Wait till python images are pushed + •• Give build logs in @UltroidSupport if build fails +" diff --git a/resources/startup/startup.sh b/resources/startup/startup.sh new file mode 100644 index 0000000000..5bd1de5938 --- /dev/null +++ b/resources/startup/startup.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in . + +echo " + + +-+ +-+ +-+ +-+ +-+ +-+ +-+ + |U| |L| |T| |R| |O| |I| |D| + +-+ +-+ +-+ +-+ +-+ +-+ +-+ + + + Visit @TheUltroid for updates!! + +" + +python3 -m pyUltroid diff --git a/sessiongen b/sessiongen new file mode 100644 index 0000000000..5fc90cbbac --- /dev/null +++ b/sessiongen @@ -0,0 +1,7 @@ +# Ultroid - UserBot +# Copyright (C) 2020 TeamUltroid +# +# This file is a part of < https://github.com/TeamUltroid/Ultroid/ > +# PLease read the GNU Affero General Public License in . + +bash resources/session/session.sh \ No newline at end of file